diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000000..5247b2f494 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,5 @@ +build --action_env=PYTHON_BIN_PATH=/usr/bin/python3 +build --action_env=BAZEL_CXXOPTS=-std=c++17 +build --cxxopt=-std=c++17 +build --copt=-Wno-sign-compare +build --copt=-Wno-comment diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..251278c4c2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement (CLA). You (or your employer) retain the copyright to your +contribution; this simply gives us permission to use and redistribute your +contributions as part of the project. Head over to + to see your current agreements on file or +to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code style + +When writing code contributions to the project, please make sure to follow the +style guides: +The [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) +and the +[Google Python Style Guide](https://google.github.io/styleguide/pyguide.html). + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows +[Google's Open Source Community Guidelines](https://opensource.google/conduct/). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..411a0a2cce --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# XLS: Accelerated HW Synthesis + +NOTE This is not an officially supported Google product. + +The XLS (Accelerator Synthesis) toolchain aims to enable the rapid development +of hardware IP via "software style" methodology. XLS is a High Level Synthesis +(HLS) toolchain which produces synthesizable designs from flexible, high-level +descriptions of functionality. diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000000..5794807078 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,7 @@ +workspace(name = "com_google_xls") + +load("//dependency_support:load_external.bzl", "load_external_repositories") +load_external_repositories() + +load("//dependency_support:initialize_external.bzl", "initialize_external_repositories") +initialize_external_repositories() diff --git a/dependency_support/BUILD.bazel b/dependency_support/BUILD.bazel new file mode 100644 index 0000000000..4ef7cb54f8 --- /dev/null +++ b/dependency_support/BUILD.bazel @@ -0,0 +1,15 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Needed to make this a package. diff --git a/dependency_support/automake_substitution.bzl b/dependency_support/automake_substitution.bzl new file mode 100644 index 0000000000..29d0145399 --- /dev/null +++ b/dependency_support/automake_substitution.bzl @@ -0,0 +1,33 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Provides helper that replaces @VARIABLE_NAME@ occurences with values, as +specified by a provided map.""" + +def automake_substitution(name, src, out, substitutions = {}): + """Replaces @VARIABLE_NAME@ occurences with values. + + Note: The current implementation does not allow slashes in variable + values.""" + + substitution_pipe = " ".join([ + "| sed 's/@%s@/%s/g'" % (variable_name, substitutions[variable_name]) + for variable_name in substitutions.keys() + ]) + native.genrule( + name = name, + srcs = [src], + outs = [out], + cmd = "cat $(location :%s) %s > $@" % (src, substitution_pipe), + ) diff --git a/dependency_support/com_icarus_iverilog/BUILD b/dependency_support/com_icarus_iverilog/BUILD new file mode 100644 index 0000000000..937bf34b2a --- /dev/null +++ b/dependency_support/com_icarus_iverilog/BUILD @@ -0,0 +1,23 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["restricted"]) # GPLv2 + +exports_files([ + "hello.v", + "hello_verilog_test.sh", + "hello_vpi.c", + "iverilog.sh", + "vvp.sh", +]) diff --git a/dependency_support/com_icarus_iverilog/build-plugins.bzl b/dependency_support/com_icarus_iverilog/build-plugins.bzl new file mode 100644 index 0000000000..302236adad --- /dev/null +++ b/dependency_support/com_icarus_iverilog/build-plugins.bzl @@ -0,0 +1,62 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""BUILD helpers for using iverilog. +""" + +def iverilog_compile(srcs, flags = ""): + """Compiles the first .v files given in srcs into a .vvp file. + Passes the flags to iverilog. + """ + vvp_file = srcs[0] + "vp" # Changes .v to .vvp + native.genrule( + name = "gen_" + vvp_file, + srcs = srcs, + outs = [vvp_file], + cmd = ( + "$(location @com_icarus_iverilog//:iverilog) " + + flags + " " + + "-o $@ " + + "$(location " + srcs[0] + ")" + ), + tools = ["@com_icarus_iverilog//:iverilog"], + ) + + # Creates a dummy test which will force the .vvp file production. + native.sh_test( + name = "force_on_test_build_" + vvp_file, + srcs = ["@com_google_xls//dependency_support/com_icarus_iverilog:dummy.sh"], + data = [vvp_file], + ) + +def vpi_binary(name, srcs, **kwargs): + """Creates a .vpi file with the given name from the given sources. + All the extra arguments are passed directly to cc_binary. + """ + so_name = name + ".so" + native.cc_binary( + name = so_name, + srcs = srcs, + linkshared = 1, + **kwargs + ) + + native.genrule( + name = "gen_" + name, + srcs = [so_name], + outs = [name], + cmd = "cp $< $@", + output_to_bindir = 1, + executable = 1, + ) diff --git a/dependency_support/com_icarus_iverilog/bundled.BUILD.bazel b/dependency_support/com_icarus_iverilog/bundled.BUILD.bazel new file mode 100644 index 0000000000..336856916b --- /dev/null +++ b/dependency_support/com_icarus_iverilog/bundled.BUILD.bazel @@ -0,0 +1,916 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Description: +# Icarus Verilog is a Verilog simulation and synthesis tool. +# Use :iverilog and :vvp targets in your genrules. + +load("@com_google_xls//dependency_support/com_icarus_iverilog:build-plugins.bzl", "vpi_binary", "iverilog_compile") +load("@com_google_xls//dependency_support:copy.bzl", "copy", "touch") +load("@com_google_xls//dependency_support:pseudo_configure.bzl", "pseudo_configure") +load("@com_google_xls//dependency_support/flex:flex.bzl", "genlex") +load("@com_google_xls//dependency_support/org_gnu_bison:bison.bzl", "genyacc") + +# The only two exported labels are iverilog and vvp. They are enough +# to run simple simulations. +package( + default_visibility = ["//visibility:private"], + features = [ + "-layering_check", + "-parse_headers", + ], +) + +licenses(["restricted"]) # GPLv2 + +exports_files([ + "LICENSE", + "build-plugins", +]) + +# This wrapper around iverilog compiler is to be used by +# simulations. A typical genrule will look similar to gen_hello.vvp +# below. +sh_binary( + name = "iverilog", + srcs = ["@com_google_xls//dependency_support/com_icarus_iverilog:iverilog.sh"], + data = [ + "iverilog-bin", + "ivl", + "ivlpp", + "vvp.conf", + "vvp.tgt", + ], + output_licenses = ["unencumbered"], + visibility = ["//visibility:public"], +) + +genrule( + name = "vvp_conf", + srcs = ["tgt-vvp/vvp.conf.in"], + outs = ["vvp.conf"], + cmd = "echo 'flag:VVP_EXECUTABLE=/unused' | cat $(location :tgt-vvp/vvp.conf.in) - > $@", +) + +# This wrapper around vvp simulator is to be used by simulations. A +# typical genrule will look similar to run_hello below. +sh_binary( + name = "vvp", + srcs = ["@com_google_xls//dependency_support/com_icarus_iverilog:vvp.sh"], + data = [ + "system.vpi", + "v2005_math.vpi", + "va_math.vpi", + "vhdl_table.vpi", + "vpi_debug.vpi", + "vvp-bin", + ], + output_licenses = ["unencumbered"], + visibility = ["//visibility:public"], +) + +# API for writing VPI extensions. +cc_library( + name = "vpi_user", + srcs = ["_pli_types.h"], + hdrs = ["vpi_user.h"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "ivl-misc", + srcs = [ + "libmisc/LineInfo.cc", + "libmisc/StringHeap.cc", + ], + hdrs = [ + "libmisc/LineInfo.h", + "libmisc/StringHeap.h", + ], +) + +# A Bazel bug requires full enumeration of symbols to retain. The list comes +# from ivl.def. +ivl_def = [ + "ivl_branch_island", + "ivl_branch_terminal", + "ivl_design_const", + "ivl_design_consts", + "ivl_design_discipline", + "ivl_design_disciplines", + "ivl_design_flag", + "ivl_design_process", + "ivl_design_root", + "ivl_design_roots", + "ivl_design_time_precision", + "ivl_const_bits", + "ivl_const_delay", + "ivl_const_real", + "ivl_const_signed", + "ivl_const_type", + "ivl_const_width", + "ivl_discipline_domain", + "ivl_discipline_flow", + "ivl_discipline_name", + "ivl_discipline_potential", + "ivl_event_any", + "ivl_event_basename", + "ivl_event_name", + "ivl_event_nany", + "ivl_event_neg", + "ivl_event_nneg", + "ivl_event_npos", + "ivl_event_pos", + "ivl_event_scope", + "ivl_expr_type", + "ivl_expr_bits", + "ivl_expr_branch", + "ivl_expr_def", + "ivl_expr_delay_val", + "ivl_expr_dvalue", + "ivl_expr_event", + "ivl_expr_file", + "ivl_expr_lineno", + "ivl_expr_name", + "ivl_expr_nature", + "ivl_expr_opcode", + "ivl_expr_oper1", + "ivl_expr_oper2", + "ivl_expr_oper3", + "ivl_expr_parameter", + "ivl_expr_parm", + "ivl_expr_parms", + "ivl_expr_repeat", + "ivl_expr_scope", + "ivl_expr_signal", + "ivl_expr_signed", + "ivl_expr_string", + "ivl_expr_uvalue", + "ivl_expr_value", + "ivl_expr_width", + "ivl_file_table_index", + "ivl_file_table_item", + "ivl_file_table_size", + "ivl_island_flag_set", + "ivl_island_flag_test", + "ivl_logic_attr", + "ivl_logic_attr_cnt", + "ivl_logic_attr_val", + "ivl_logic_basename", + "ivl_logic_delay", + "ivl_logic_drive0", + "ivl_logic_drive1", + "ivl_logic_name", + "ivl_logic_pin", + "ivl_logic_pins", + "ivl_logic_scope", + "ivl_logic_type", + "ivl_logic_udp", + "ivl_logic_width", + "ivl_lpm_array", + "ivl_lpm_aset_value", + "ivl_lpm_async_clr", + "ivl_lpm_async_set", + "ivl_lpm_base", + "ivl_lpm_basename", + "ivl_lpm_clk", + "ivl_lpm_data", + "ivl_lpm_datab", + "ivl_lpm_define", + "ivl_lpm_delay", + "ivl_lpm_enable", + "ivl_lpm_file", + "ivl_lpm_lineno", + "ivl_lpm_name", + "ivl_lpm_q", + "ivl_lpm_scope", + "ivl_lpm_select", + "ivl_lpm_selects", + "ivl_lpm_signed", + "ivl_lpm_size", + "ivl_lpm_sset_value", + "ivl_lpm_string", + "ivl_lpm_sync_clr", + "ivl_lpm_sync_set", + "ivl_lpm_trigger", + "ivl_lpm_type", + "ivl_lpm_width", + "ivl_lval_idx", + "ivl_lval_mux", + "ivl_lval_part_off", + "ivl_lval_sig", + "ivl_lval_width", + "ivl_nature_name", + "ivl_nexus_get_private", + "ivl_nexus_name", + "ivl_nexus_ptrs", + "ivl_nexus_ptr", + "ivl_nexus_set_private", + "ivl_nexus_ptr_branch", + "ivl_nexus_ptr_con", + "ivl_nexus_ptr_drive0", + "ivl_nexus_ptr_drive1", + "ivl_nexus_ptr_pin", + "ivl_nexus_ptr_lpm", + "ivl_nexus_ptr_log", + "ivl_nexus_ptr_sig", + "ivl_nexus_ptr_switch", + "ivl_parameter_basename", + "ivl_parameter_expr", + "ivl_parameter_file", + "ivl_parameter_lineno", + "ivl_path_condit", + "ivl_path_delay", + "ivl_path_is_condit", + "ivl_path_scope", + "ivl_path_source", + "ivl_path_source_negedge", + "ivl_path_source_posedge", + "ivl_scope_attr_cnt", + "ivl_scope_attr_val", + "ivl_scope_basename", + "ivl_scope_children", + "ivl_scope_def", + "ivl_scope_def_file", + "ivl_scope_def_lineno", + "ivl_scope_event", + "ivl_scope_events", + "ivl_scope_file", + "ivl_scope_is_auto", + "ivl_scope_is_cell", + "ivl_scope_lineno", + "ivl_scope_logs", + "ivl_scope_log", + "ivl_scope_lpms", + "ivl_scope_lpm", + "ivl_scope_name", + "ivl_scope_param", + "ivl_scope_params", + "ivl_scope_parent", + "ivl_scope_port", + "ivl_scope_ports", + "ivl_scope_sigs", + "ivl_scope_sig", + "ivl_scope_switch", + "ivl_scope_switches", + "ivl_scope_time_precision", + "ivl_scope_time_units", + "ivl_scope_type", + "ivl_scope_tname", + "ivl_signal_array_addr_swapped", + "ivl_signal_array_base", + "ivl_signal_array_count", + "ivl_signal_attr", + "ivl_signal_attr_cnt", + "ivl_signal_attr_val", + "ivl_signal_basename", + "ivl_signal_data_type", + "ivl_signal_dimensions", + "ivl_signal_discipline", + "ivl_signal_file", + "ivl_signal_integer", + "ivl_signal_lineno", + "ivl_signal_local", + "ivl_signal_lsb", + "ivl_signal_msb", + "ivl_signal_name", + "ivl_signal_nex", + "ivl_signal_npath", + "ivl_signal_path", + "ivl_signal_port", + "ivl_signal_signed", + "ivl_signal_type", + "ivl_signal_width", + "ivl_path_delay", + "ivl_path_source", + "ivl_process_analog", + "ivl_process_attr_cnt", + "ivl_process_attr_val", + "ivl_process_file", + "ivl_process_lineno", + "ivl_process_scope", + "ivl_process_stmt", + "ivl_process_type", + "ivl_statement_type", + "ivl_stmt_block_count", + "ivl_stmt_block_scope", + "ivl_stmt_block_stmt", + "ivl_stmt_call", + "ivl_stmt_case_count", + "ivl_stmt_case_expr", + "ivl_stmt_case_stmt", + "ivl_stmt_cond_expr", + "ivl_stmt_cond_false", + "ivl_stmt_cond_true", + "ivl_stmt_delay_expr", + "ivl_stmt_delay_val", + "ivl_stmt_events", + "ivl_stmt_file", + "ivl_stmt_lineno", + "ivl_stmt_lexp", + "ivl_stmt_lval", + "ivl_stmt_lvals", + "ivl_stmt_lwidth", + "ivl_stmt_name", + "ivl_stmt_nevent", + "ivl_stmt_parm", + "ivl_stmt_parm_count", + "ivl_stmt_rval", + "ivl_stmt_sub_stmt", + "ivl_switch_a", + "ivl_switch_b", + "ivl_switch_basename", + "ivl_switch_enable", + "ivl_switch_file", + "ivl_switch_island", + "ivl_switch_lineno", + "ivl_switch_offset", + "ivl_switch_part", + "ivl_switch_scope", + "ivl_switch_type", + "ivl_switch_width", + "ivl_udp_init", + "ivl_udp_name", + "ivl_udp_nin", + "ivl_udp_row", + "ivl_udp_rows", + "ivl_udp_sequ", +] + +cc_binary( + name = "ivl", + srcs = glob( + ["*.cc", "*.h"], + exclude = ["elab_anet.cc"], + ) + [ + "syn-rules.cc", + "lexor.cc", + "lexor_keyword.cc", + "parse.cc", + "parse.h", + "config.h", + ], + copts = [ + "-Wno-strict-aliasing", + "-Wno-unused-but-set-variable", + "-Wno-unused-variable", + ], + # Do not sort: dot last. + includes = [ + "libmisc", + ".", + ], + linkopts = [ + "-ldl", + "-Wl,-u," + ",-u,".join(ivl_def), + "-Wl,--export-dynamic", + "-Wl,-no-pie", + ], + deps = [ + "ivl-misc", + ":shared_headers", + ], +) + +genlex( + name = "lexor", + src = "lexor.lex", + out = "lexor.cc", +) + +genyacc( + name = "parse_y", + src = "parse.y", + header_out = "parse.h", + prefix = "VL", + source_out = "parse.cc", +) + +genyacc( + name = "syn-rules_y", + src = "syn-rules.y", + header_out = "syn-rules.h", + prefix = "syn_", + source_out = "syn-rules.cc", +) + +cc_library( + name = "shared_headers", + hdrs = [ + "config.h", + "ivl_alloc.h", + "ivl_target.h", + "ivl_target_priv.h", + "version_base.h", + "version_tag.h", + "sv_vpi_user.h", + ], + includes = [ + ".", + ], + deps = [ + ":vpi_user", + ], +) + +cc_binary( + name = "iverilog-bin", + srcs = [ + "driver/cflexor.c", + "driver/cfparse.c", + "driver/cfparse.h", + ] + glob([ + "driver/*.h", + "driver/*.c", + ]), + copts = [ + "-D_GNU_SOURCE", + "-std=c11", + "-fcommon", + "-Wno-format-truncation", + ], + # Do not sort: dot last. + includes = [ + "driver", + "libmisc", + ".", + ], + deps = [":shared_headers"], + visibility = ["//visibility:public"], +) + +genlex( + name = "cflexor", + src = "driver/cflexor.lex", + out = "driver/cflexor.c", +) + +genyacc( + name = "cfparse_y", + src = "driver/cfparse.y", + header_out = "driver/cfparse.h", + prefix = "cf", + source_out = "driver/cfparse.c", +) + +# A Bazel bug requires full enumeration of symbols to retain. The list comes +# from vvp.def. +vvp_def = [ + "vpi_chk_error", + "vpi_control", + "vpi_flush", + "vpi_fopen", + "vpi_free_object", + "vpi_get", + "vpi_get_delays", + "vpi_get_file", + "vpi_get_str", + "vpi_get_time", + "vpi_get_userdata", + "vpi_get_value", + "vpi_get_vlog_info", + "vpi_handle", + "vpi_handle_by_index", + "vpi_handle_by_name", + "vpi_iterate", + "vpi_mcd_close", + "vpi_mcd_flush", + "vpi_mcd_name", + "vpi_mcd_open", + "vpi_mcd_printf", + "vpi_mcd_vprintf", + "vpi_printf", + "vpi_put_delays", + "vpi_put_userdata", + "vpi_put_value", + "vpi_register_cb", + "vpi_register_systf", + "vpi_remove_cb", + "vpi_scan", + "vpi_sim_control", + "vpi_sim_vcontrol", + "vpi_vprintf", + "vpip_format_strength", + "vpip_set_return_value", + "vpip_calc_clog2", +] + +cc_binary( + name = "vvp-bin", + srcs = glob([ + "vvp/*.cc", + "vvp/*.h", + ]) + [ + "vvp_gen/config.h", + "vvp_gen/lexor.cc", + "vvp_gen/parse.h", + "vvp_gen/parse.cc", + "vvp_gen/tables.cc", + ], + copts = [ + "-O2", # Optimized binary regardless of configuration. + "-Wno-unused-variable", + "-Wno-implicit-fallthrough", + ], + # Do not sort: dot last. + includes = [ + "vvp_gen", + "vvp", + ".", + ], + linkopts = [ + "-ldl", + "-Wl,-u," + ",-u,".join(vvp_def), + "-Wl,--export-dynamic", + ], + deps = [ + ":shared_headers", + ":vpi_user", + "@dk_thrysoee_libedit//:pretend_to_be_gnu_readline_system", + "@net_invisible_island_ncurses//:ncurses", + ], + visibility = ["//visibility:public"], +) + +genyacc( + name = "vvp_parse_y", + src = "vvp/parse.y", + header_out = "vvp_gen/parse.h", + source_out = "vvp_gen/parse.cc", +) + +genlex( + name = "vvp_flexor", + src = "vvp/lexor.lex", + out = "vvp_gen/lexor.cc", +) + +cc_binary( + name = "draw_tt", + srcs = ["vvp/draw_tt.c"], +) + +genrule( + name = "gen_tables", + outs = ["vvp_gen/tables.cc"], + cmd = "$(location draw_tt) > $@", + tools = ["draw_tt"], +) + +cc_binary( + name = "ivlpp", + srcs = [ + "ivlpp_lex/lexor.c", + "ivlpp/globals.h", + "ivlpp/main.c", + ], + copts = ["-Wno-unused-variable"], + # Do not sort: dot last. + includes = [ + "ivlpp", + ".", + ], + deps = [ + ":shared_headers", + ], +) + +genlex( + name = "ivlpp_lexor", + src = "ivlpp/lexor.lex", + out = "ivlpp_lex/lexor.c", +) + +vpi_binary( + name = "system.vpi", + srcs = glob(["vpi/*.h"]) + [ + "config.h", + "vpi/fastlz.c", + "vpi/fstapi.c", + "vpi/lxt2_write.c", + "vpi/lxt_write.c", + "vpi/lz4.c", + "vpi/mt19937int.c", + "vpi/sdf_lexor.c", + "vpi/sdf_parse.c", + "vpi/sdf_parse.h", + "vpi/stringheap.c", + "vpi/sys_convert.c", + "vpi/sys_countdrivers.c", + "vpi/sys_darray.c", + "vpi/sys_deposit.c", + "vpi/sys_display.c", + "vpi/sys_fileio.c", + "vpi/sys_finish.c", + "vpi/sys_fst.c", + "vpi/sys_icarus.c", + "vpi/sys_lxt.c", + "vpi/sys_lxt2.c", + "vpi/sys_plusargs.c", + "vpi/sys_priv.c", + "vpi/sys_queue.c", + "vpi/sys_random.c", + "vpi/sys_random_mti.c", + "vpi/sys_readmem.c", + "vpi/sys_readmem_lex.c", + "vpi/sys_scanf.c", + "vpi/sys_sdf.c", + "vpi/sys_table.c", + "vpi/sys_time.c", + "vpi/sys_vcd.c", + "vpi/sys_vcdoff.c", + "vpi/table_mod.c", + "vpi/table_mod_lexor.c", + "vpi/table_mod_parse.c", + "vpi/table_mod_parse.h", + "vpi/vams_simparam.c", + "vpi/vcd_priv.c", + "vpi/vcd_priv2.cc", + "vpi/vpi_config.h", + ], + # Optimized binary regardless of configuration. + copts = [ + "$(STACK_FRAME_UNLIMITED)", + "-O2", + ], + linkopts = [ + "-lpthread", + ], + includes = [ + ".", + "vpi", + ], + deps = [ + ":shared_headers", + ":vpi_user", + "@org_sourceware_bzip2//:bzip2", + "@zlib//:zlib", + ], +) + +genyacc( + name = "table_mod_parse_y", + src = "vpi/table_mod_parse.y", + header_out = "vpi/table_mod_parse.h", + prefix = "tblmod", + source_out = "vpi/table_mod_parse.c", +) + +genlex( + name = "table_mod_lexor_lex", + src = "vpi/table_mod_lexor.lex", + out = "vpi/table_mod_lexor.c", +) + +genyacc( + name = "vpi_sdfparse_y", + src = "vpi/sdf_parse.y", + header_out = "vpi/sdf_parse.h", + prefix = "sdf", + source_out = "vpi/sdf_parse.c", +) + +genlex( + name = "vpi_sdf_lexor", + src = "vpi/sdf_lexor.lex", + out = "vpi/sdf_lexor.c", +) + +genlex( + name = "vpi_sys_readmem_lex", + src = "vpi/sys_readmem_lex.lex", + out = "vpi/sys_readmem_lex.c", +) + +vpi_binary( + name = "va_math.vpi", + srcs = [ + "vpi/va_math.c", + "vpi/vpi_config.h", + ], + copts = ["-O2"], # Optimized binary regardless of configuration. + includes = [ + ".", + "vpi", + ], + deps = [ + ":shared_headers", + ":vpi_user", + ], +) + +vpi_binary( + name = "v2005_math.vpi", + srcs = [ + "vpi/sys_clog2.c", + "vpi/v2005_math.c", + "vpi/vpi_config.h", + ], + copts = ["-O2"], # Optimized binary regardless of configuration. + includes = [ + ".", + "vpi", + ], + deps = [ + ":vpi_user", + ":shared_headers", + ], +) + +vpi_binary( + name = "vhdl_table.vpi", + srcs = [ + "vpi/vhdl_table.c", + "vpi/vpi_config.h", + ], + copts = ["-O2"], + includes = [ + ".", + "vpi", + ], + deps = [ + ":shared_headers", + ":vpi_user", + ], +) + +vpi_binary( + name = "vpi_debug.vpi", + srcs = [ + "vpi/vpi_debug.c", + ], + copts = ["-O2"], + includes = [ + ".", + "vpi", + ], + deps = [ + ":vpi_user", + ], +) + +vpi_binary( + name = "vvp.tgt", + srcs = [ + "tgt-vvp/draw_class.c", + "tgt-vvp/draw_delay.c", + "tgt-vvp/draw_enum.c", + "tgt-vvp/draw_mux.c", + "tgt-vvp/draw_net_input.c", + "tgt-vvp/draw_substitute.c", + "tgt-vvp/draw_switch.c", + "tgt-vvp/draw_ufunc.c", + "tgt-vvp/draw_vpi.c", + "tgt-vvp/eval_bool.c", + "tgt-vvp/eval_condit.c", + "tgt-vvp/eval_expr.c", + "tgt-vvp/eval_object.c", + "tgt-vvp/eval_real.c", + "tgt-vvp/eval_string.c", + "tgt-vvp/eval_vec4.c", + "tgt-vvp/modpath.c", + "tgt-vvp/stmt_assign.c", + "tgt-vvp/vector.c", + "tgt-vvp/vvp.c", + "tgt-vvp/vvp_config.h", + "tgt-vvp/vvp_priv.h", + "tgt-vvp/vvp_process.c", + "tgt-vvp/vvp_scope.c", + ], + copts = [ + "-Wno-implicit-function-declaration", + "-Wno-int-conversion", + "-Wno-unused-but-set-variable", + "-Wno-unused-variable", + "-std=c11", + ], + includes = [ + ".", + "tgt-vvp", + ], + deps = [ + ":shared_headers", + ], +) + +genrule( + name = "_pli_types_h", + srcs = ["_pli_types.h.in"], + outs = ["_pli_types.h"], + cmd = "cat $(location :_pli_types.h.in) | sed 's/# undef HAVE_INTTYPES_H/# define HAVE_INTTYPES_H 1/' > $@", +) + +genrule( + name = "lexor_keyword_cc", + srcs = ["lexor_keyword.gperf"], + tools = ["@org_gnu_gperf//:gperf"], + outs = ["lexor_keyword.cc"], + cmd = "$(location @org_gnu_gperf//:gperf) -o -i 7 -C -k 1-4,6,9,$$ -H keyword_hash -N check_identifier -t $(location :lexor_keyword.gperf) > $@", + message = "Generating perfect hash function from $(SRCS)", +) + +genrule( + name = "vhdlpp_lexor_keyword_cc", + srcs = ["vhdlpp/lexor_keyword.gperf"], + tools = ["@org_gnu_gperf//:gperf"], + outs = ["vhdlpp_lexor_keyword.cc"], + cmd = "$(location @org_gnu_gperf//:gperf) -o -i 7 --ignore-case -C -k 1-4,6,9,$$ -H keyword_hash -N check_identifier -t $(location :vhdlpp/lexor_keyword.gperf) > $@", + message = "Generating perfect hash function from $(SRCS)", +) + +# In the following genrules we do an extremely crude approximation of a +# configuration step -- workable now given the limited set of +# platforms/environments we intend to target. + +HAVE_CONFIG_SUFFIXES = 'TIMES|IOSFWD|GETOPT_H|INTTYPES_H|DLFCN_H|LIBREADLINE|LIBZ|LIBBZ2|LROUND|SYS_WAIT_H|ALLOCA_H|FSEEKO|LIBPTHREAD|REALPATH' +HAVE_CONFIG_RE = "HAVE_(%s)" % HAVE_CONFIG_SUFFIXES + +DEFS = ['HAVE_IOSFWD', 'HAVE_DLFCN_H', 'HAVE_GETOPT_H', 'HAVE_LIBREADLINE', 'HAVE_READLINE_READLINE_H', 'HAVE_LIBHISTORY', 'HAVE_READLINE_HISTORY_H', 'HAVE_INTTYPES_H', 'HAVE_LROUND', 'HAVE_LLROUND', 'HAVE_NAN', 'UINT64_T_AND_ULONG_SAME', 'HAVE_SYS_RESOURCE_H', 'LINUX'] + +pseudo_configure( + name = "tgt_vvp__vvp_config_h", + src = "tgt-vvp/vvp_config.h.in", + out = "tgt-vvp/vvp_config.h", + defs = ['HAVE_STDINT_H', 'HAVE_INTTYPES_H', '_LARGEFILE_SOURCE'], + mappings = {}, +) + +pseudo_configure( + name = "config_h", + src = "config.h.in", + out = "config.h", + defs = ['HAVE_TIMES', 'HAVE_IOSFWD', 'HAVE_GETOPT_H', 'HAVE_INTTYPES_H', 'HAVE_DLFCN_H', 'HAVE_LIBREADLINE', 'HAVE_LIBZ', 'HAVE_LIBBZ2', 'HAVE_LROUND', 'HAVE_SYS_WAIT_H', 'HAVE_ALLOCA_H', 'HAVE_FSEEKO', 'HAVE_LIBPTHREAD', 'HAVE_REALPATH'], + mappings = {}, +) + +genrule( + name = "vpi__vpi_config_h", + srcs = ["vpi/vpi_config.h.in"], + outs = ["vpi/vpi_config.h"], + cmd = "perl -p -e 's/# undef (\w+)/#define $$1 1/' $< > $@", + message = "Configuring vpi/vpi_config.h.in", +) + +pseudo_configure( + name = "vvp_gen__vvp_config_h", + src = "vvp/config.h.in", + out = "vvp_gen/config.h", + defs = DEFS, + mappings = {'SIZEOF_UNSIGNED_LONG_LONG': '8', 'SIZEOF_UNSIGNED_LONG': '8', 'SIZEOF_UNSIGNED': '4', 'USE_READLINE': '', 'USE_HISTORY': '', 'MODULE_DIR': '"."', '__STDC_FORMAT_MACROS': '', 'TIME_FMT_O': '"lo"', 'TIME_FMT_U': '"lu"', 'TIME_FMT_X': '"lx"', 'UL_AND_TIME64_SAME': '', 'i64round': 'lround', 'nan(x)': '(NAN)', 'INFINITY': 'HUGE_VAL', 'LU': '""', 'TU': '""'}, +) + +touch( + name = "version_tag_h", + out = "version_tag.h", + contents = dict(VERSION_TAG = '"v10_3"'), +) + +# Trivial integration tests to confirm iverilog is minimally functional. +copy( + name = "hello_v", + src = "@com_google_xls//dependency_support/com_icarus_iverilog:hello.v", + out = "hello.v", +) + +iverilog_compile( + srcs = ["hello.v"], +) + +vpi_binary( + name = "hello.vpi", + srcs = ["@com_google_xls//dependency_support/com_icarus_iverilog:hello_vpi.c"], + deps = [":vpi_user"], +) + +genrule( + name = "run_hello", + srcs = ["hello.vvp"], + outs = ["hello.out"], + cmd = ( + "$(location :vvp) " + + "-M$$(dirname $(location :hello.vpi)) " + + "-mhello $< > $@ " + ), + tools = [ + ":hello.vpi", + ":vvp", + ], +) + +sh_test( + name = "hello_verilog_test", + srcs = ["@com_google_xls//dependency_support/com_icarus_iverilog:hello_verilog_test.sh"], + data = [":hello.out"], + args = ["$(location :hello.out)"], +) diff --git a/dependency_support/com_icarus_iverilog/dummy.sh b/dependency_support/com_icarus_iverilog/dummy.sh new file mode 100644 index 0000000000..a9bf588e2f --- /dev/null +++ b/dependency_support/com_icarus_iverilog/dummy.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/dependency_support/com_icarus_iverilog/hello.v b/dependency_support/com_icarus_iverilog/hello.v new file mode 100644 index 0000000000..293fc49453 --- /dev/null +++ b/dependency_support/com_icarus_iverilog/hello.v @@ -0,0 +1,29 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Verilog test helper file. + +module hello; + +integer val; + +initial begin + val = 41; + $increment(val); + $display("$increment returns val=%d", val); + $finish(); +end + +endmodule diff --git a/dependency_support/com_icarus_iverilog/hello_verilog_test.sh b/dependency_support/com_icarus_iverilog/hello_verilog_test.sh new file mode 100755 index 0000000000..8f4cc8d816 --- /dev/null +++ b/dependency_support/com_icarus_iverilog/hello_verilog_test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +exec grep 42 $1 diff --git a/dependency_support/com_icarus_iverilog/hello_vpi.c b/dependency_support/com_icarus_iverilog/hello_vpi.c new file mode 100644 index 0000000000..5ac3765b51 --- /dev/null +++ b/dependency_support/com_icarus_iverilog/hello_vpi.c @@ -0,0 +1,60 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Based on http://en.wikipedia.org/wiki/Verilog_Procedural_Interface. +// +// Simple VPI plug-in to test the toolchain. + +#include "vpi_user.h" + +// Implements the increment system task +static PLI_INT32 increment(PLI_BYTE8 *userdata) { + // Obtains a handle to the argument list + vpiHandle systfref = vpi_handle(vpiSysTfCall, NULL); + vpiHandle args_iter = vpi_iterate(vpiArgument, systfref); + + // Grabs the value of the first argument + vpiHandle argh = vpi_scan(args_iter); + struct t_vpi_value argval; + argval.format = vpiIntVal; + vpi_get_value(argh, &argval); + + int value = argval.value.integer; + vpi_printf("Input %d\n", value); + + // Increments the value and puts it back as first argument + argval.value.integer = value + 1; + vpi_put_value(argh, &argval, NULL, vpiNoDelay); + + // Cleans up and returns. + vpi_free_object(args_iter); + return 0; +} + +// Registers the $increment task with the system. +static void registerIncrementTask() { + s_vpi_systf_data task; + task.type = vpiSysTask; + task.tfname = "$increment"; + task.calltf = increment; + task.compiletf = 0; + + vpi_register_systf(&task); +} + +// Registers the new system task here. +void (*vlog_startup_routines[]) () = { + registerIncrementTask, + 0 +}; diff --git a/dependency_support/com_icarus_iverilog/iverilog.sh b/dependency_support/com_icarus_iverilog/iverilog.sh new file mode 100755 index 0000000000..2365f98900 --- /dev/null +++ b/dependency_support/com_icarus_iverilog/iverilog.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Wrapper around iverilog binary. Adds the path to the dependencies to +# the command line of iverilog. + +set -eu + +dir=$(dirname $(find . -name iverilog-bin | head -n 1)) + +if [[ ! -d "$dir" ]]; then + echo "Unable to find dependencies (looking under $dir)." 1>&2 + exit 1 +fi + +exec "$dir/iverilog-bin" -B"$dir" -DIVERILOG "$@" diff --git a/dependency_support/com_icarus_iverilog/vvp.sh b/dependency_support/com_icarus_iverilog/vvp.sh new file mode 100755 index 0000000000..f1d3e3873d --- /dev/null +++ b/dependency_support/com_icarus_iverilog/vvp.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Wrapper around vvp binary. Adds the path to the dependencies to +# the command line of vvp. + +set -eu + +dir=$(dirname $(find . -name vvp-bin | head -n 1)) + +if [[ ! -d "$dir" ]]; then + echo "Unable to find dependencies (looking under $dir)." 1>&2 + exit 1 +fi + +exec "$dir/vvp-bin" -M"$dir" "$@" diff --git a/dependency_support/com_icarus_iverilog/workspace.bzl b/dependency_support/com_icarus_iverilog/workspace.bzl new file mode 100644 index 0000000000..78640956f4 --- /dev/null +++ b/dependency_support/com_icarus_iverilog/workspace.bzl @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the libedit library, used by iverilog (it poses as GNU readline).""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "com_icarus_iverilog", + urls = [ + "https://github.com/steveicarus/iverilog/archive/v10_3.tar.gz", + ], + strip_prefix = "iverilog-10_3", + sha256 = "4b884261645a73b37467242d6ae69264fdde2e7c4c15b245d902531efaaeb234", + build_file = Label("//dependency_support:com_icarus_iverilog/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/copy.bzl b/dependency_support/copy.bzl new file mode 100644 index 0000000000..7aea96774c --- /dev/null +++ b/dependency_support/copy.bzl @@ -0,0 +1,45 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Provides a utility macro that copies a file.""" + +def copy(name, src, out): + native.genrule( + name = name, + srcs = [src], + outs = [out], + cmd = "cp $(SRCS) $@", + message = "Copying $(SRCS)", + ) + +def touch(name, out, contents = None): + """Produces a genrule to creates a file, with optional #define contents. + + Args: + name: Name to use for the genrule. + out: Path for the output file. + contents: Optional mapping that will be materialized as + `#define $KEY $VALUE` in the output file. + """ + lines = [] + if contents: + for k, v in contents.items(): + lines.append("#define %s %s" % (k, v)) + contents = "\n".join(lines) + native.genrule( + name = name, + outs = [out], + cmd = "echo " + repr(contents) + " > $@", + message = "Touch $@", + ) diff --git a/dependency_support/dk_thrysoee_libedit/BUILD b/dependency_support/dk_thrysoee_libedit/BUILD new file mode 100644 index 0000000000..0d2a44b9df --- /dev/null +++ b/dependency_support/dk_thrysoee_libedit/BUILD @@ -0,0 +1,20 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["notice"]) # BSD + +exports_files([ + "readline_example.cc", + "readline_test.cc", +]) diff --git a/dependency_support/dk_thrysoee_libedit/build_defs.bzl b/dependency_support/dk_thrysoee_libedit/build_defs.bzl new file mode 100644 index 0000000000..a335f9407a --- /dev/null +++ b/dependency_support/dk_thrysoee_libedit/build_defs.bzl @@ -0,0 +1,46 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilities for building libedit.""" + +def _generated_headers(names): + """Transforms names into their generated equivalent.""" + return [":src/%s.h" % name for name in names] + +def _makelist(name, srcs, flag): + """Runs makelist over a set of inputs to generate a header file.""" + native.genrule( + name = "%s_makelist" % name, + srcs = srcs, + outs = ["src/%s.h" % name], + tools = ["src/makelist"], + cmd = "sh $(location src/makelist) %s $(SRCS) > $@" % flag, + ) + +# The base files for makelist calls. +_inputs = ["common", "emacs", "vi"] + +# The headers generated directly from inputs. +_input_headers = _generated_headers(_inputs) + +# The full set of headers generated, used for srcs. +makelist_headers = _input_headers + _generated_headers(["fcns", "func", "help"]) + +def makelist_genrules(): + """Runs all necessary makelist calls.""" + for name in _inputs: + _makelist(name, ["src/%s.c" % name], "-h") + _makelist("fcns", _input_headers, "-fh") + _makelist("func", _input_headers, "-fc") + _makelist("help", ["src/%s.c" % name for name in _inputs], "-bh") diff --git a/dependency_support/dk_thrysoee_libedit/bundled.BUILD.bazel b/dependency_support/dk_thrysoee_libedit/bundled.BUILD.bazel new file mode 100644 index 0000000000..ecdfa2d5e2 --- /dev/null +++ b/dependency_support/dk_thrysoee_libedit/bundled.BUILD.bazel @@ -0,0 +1,158 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Description: +# Line editor library. +# +# Use the "pretend_to_be_gnu_readline" libraries if your client wants to use +# readline-like APIs for libedit. Use "native" libraries if your client wants +# to use native libedit APIs. + +# Provides headers for edit_impl. +load( + "@com_google_xls//dependency_support/dk_thrysoee_libedit:build_defs.bzl", + "makelist_genrules", + "makelist_headers", +) +load("@com_google_xls//dependency_support:copy.bzl", "copy") +load("@com_google_xls//dependency_support:pseudo_configure.bzl", "pseudo_configure") + +licenses(["notice"]) # BSD + +exports_files(["LICENSE"]) + +GNU_READLINE_IMITATION_HEADERS = [ + "include/readline/history.h", + "include/readline/readline.h", +] + +# Provides the readline-like interface. +# #include +cc_library( + name = "pretend_to_be_gnu_readline_system", + hdrs = GNU_READLINE_IMITATION_HEADERS, + includes = ["include"], + visibility = ["//visibility:public"], + deps = [ + ":edit_impl", + ], +) + +# Provides the readline-like interface. +# #include +cc_library( + name = "pretend_to_be_gnu_readline_system_toplevel", + hdrs = GNU_READLINE_IMITATION_HEADERS, + includes = ["include/readline"], + visibility = ["//visibility:public"], + deps = [ + ":edit_impl", + ], +) + +# Provides the libedit interface. +# #include +# #include +cc_library( + name = "native_system", + hdrs = [ + "src/editline/readline.h", + "src/histedit.h", + ], + includes = ["src"], + visibility = ["//visibility:public"], + deps = [":edit_impl"], +) + +makelist_genrules() + +# The common target for libedit. Singular to avoid recompilation of files +# across the multiple #include approaches. +cc_library( + name = "edit_impl", + srcs = glob( + [ + "src/*.c", + "src/*.h", + ], + ) + [ + # Generated file is not found by glob. + "src/config.h", + ] + makelist_headers, + hdrs = [ + "src/editline/readline.h", + # Used as includes; otherwise, these cause compiler errors. + "src/history.c", + "src/tokenizer.c", + ], + copts = [ + "-Wno-implicit-function-declaration", # strlcpy is defined but not included + "-Wno-unused-result", + "-Wno-pointer-sign", + ], + defines = ["HAVE_GETPW_R_POSIX"], + features = ["-parse_headers"], + includes = ["src"], + deps = [ + "@net_invisible_island_ncurses//:ncurses", + ], +) + +# A tiny example binary. +cc_binary( + name = "readline_example", + srcs = ["@com_google_xls//dependency_support/dk_thrysoee_libedit:readline_example.cc"], + deps = [ + ":pretend_to_be_gnu_readline_system_toplevel", + ], +) + +# Simple test to verify compilation. +cc_test( + name = "readline_test", + size = "small", + srcs = ["@com_google_xls//dependency_support/dk_thrysoee_libedit:readline_test.cc"], + deps = [ + ":pretend_to_be_gnu_readline_system", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +forwarding_header_contents = """ +// libedit combines the functionality of GNU's readline.h and history.h in one +// header, so this file is just a stub. + +#ifndef DK_THRYSOEE_LIBEDIT_READLINE_H +#define DK_THRYSOEE_LIBEDIT_READLINE_H + +#include "editline/readline.h" + +#endif // DK_THRYSOEE_LIBEDIT_READLINE_H +""" +[genrule( + name = "%s_h" % name, + outs = ["include/readline/%s.h" % name], + cmd = "cat > $@ << 'BAZEL_EOF'\n" + forwarding_header_contents.replace('$', '$$') + "\nBAZEL_EOF", +) for name in ["history", "readline"]] + +pseudo_configure( + name = "config_h", + src = "config.h.in", + out = "src/config.h", + defs = ['HAVE_CURSES_H', 'HAVE_DIRENT_H', 'HAVE_DLFCN_H', 'HAVE_ENDPWENT', 'HAVE_FCNTL_H', 'HAVE_FORK', 'HAVE_GETLINE', 'HAVE_INTTYPES_H', 'HAVE_ISASCII', 'HAVE_LIBNCURSES', 'HAVE_LIMITS_H', 'HAVE_MALLOC_H', 'HAVE_MEMCHR', 'HAVE_MEMORY_H', 'HAVE_MEMSET', 'HAVE_NCURSES_H', 'HAVE_REGCOMP', 'HAVE_RE_COMP', 'HAVE_SECURE_GETENV', 'HAVE_STDINT_H', 'HAVE_STDLIB_H', 'HAVE_STRCASECMP', 'HAVE_STRCHR', 'HAVE_STRCSPN', 'HAVE_STRDUP', 'HAVE_STRERROR', 'HAVE_STRINGS_H', 'HAVE_STRING_H', 'HAVE_STRLCAT', 'HAVE_STRRCHR', 'HAVE_STRSTR', 'HAVE_STRTOL', 'HAVE_SYS_CDEFS_H', 'HAVE_SYS_IOCTL_H', 'HAVE_SYS_PARAM_H', 'HAVE_SYS_STAT_H', 'HAVE_SYS_TYPES_H', 'HAVE_SYS_WAIT_H', 'HAVE_TERMCAP_H', 'HAVE_TERM_H', 'HAVE_UNISTD_H', 'HAVE_U_INT32_T', 'HAVE_VFORK', 'HAVE_WCSDUP', 'HAVE_WORKING_FORK', 'HAVE_WORKING_VFORK', 'LSTAT_FOLLOWS_SLASHED_SYMLINK', 'STDC_HEADERS', '_ALL_SOURCE', '_GNU_SOURCE', '_POSIX_PTHREAD_SEMANTICS', '_TANDEM_SOURCE', '__EXTENSIONS__'], + mappings = {'HAVE_STRLCPY': '0', 'LT_OBJDIR': '".libs"', 'PACKAGE': '"libedit-20180525"', 'PACKAGE_BUGREPORT': '""', 'PACKAGE_NAME': '"libedit"', 'PACKAGE_STRING': '"libedit 3.1"', 'PACKAGE_TARNAME': '"libedit-20180525"', 'PACKAGE_URL': '""', 'PACKAGE_VERSION': '"3.1"', 'RETSIGTYPE': 'void', 'VERSION': '"3.1"', 'SCCSID': '', 'lint': ''}, +) + + diff --git a/dependency_support/dk_thrysoee_libedit/readline_example.cc b/dependency_support/dk_thrysoee_libedit/readline_example.cc new file mode 100644 index 0000000000..e0a6a8c92b --- /dev/null +++ b/dependency_support/dk_thrysoee_libedit/readline_example.cc @@ -0,0 +1,27 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +int main(int argc, char** argv) { + for (;;) { + char* result = readline("Test> "); + if (result == nullptr) break; + fprintf(stdout, "Result: %s\\n", result); + add_history(result); + } + return 0; +} diff --git a/dependency_support/dk_thrysoee_libedit/readline_test.cc b/dependency_support/dk_thrysoee_libedit/readline_test.cc new file mode 100644 index 0000000000..541a758bdf --- /dev/null +++ b/dependency_support/dk_thrysoee_libedit/readline_test.cc @@ -0,0 +1,99 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include +#include + +#include "absl/strings/substitute.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::Eq; +using ::testing::Ne; +using ::testing::StrEq; +using ::testing::Test; + +namespace { + +std::string HomeDirectory() { + const char* home = getenv("HOME"); + return home == nullptr ? "" : home; +} + +} // namespace + +class ReadlineTest : public Test { + protected: + void TestTilde(const char* input, const std::string& expected) { + auto actual = tilde_expand(const_cast(input)); + EXPECT_THAT(actual, Eq(expected)); + free(actual); + } +}; + +TEST_F(ReadlineTest, ReadLine) { + // Create a new input for the purpose of this test + int pipefds[2]; + ASSERT_THAT(pipe2(pipefds, 0), Eq(0)); + + FILE *in = fdopen(pipefds[0], "rb"); + ASSERT_THAT(in, Ne(nullptr)); + rl_instream = in; + + ASSERT_THAT(write(pipefds[1], "foo\\n", 4), Eq(4)); + ASSERT_THAT(close(pipefds[1]), Eq(0)); + + // Test 1: Read one line. + { + char* s = readline("test> "); + EXPECT_THAT(s, Ne(nullptr)); + EXPECT_THAT(s, StrEq("foo")); + free(s); + } + + // Test 2: Since we closed the stream after one line, no more lines are read. + { + char* s = readline("test> "); + EXPECT_THAT(s, Eq(nullptr)); + } + + ASSERT_THAT(fclose(in), Eq(0)); +} + +TEST_F(ReadlineTest, TildeMinimal) { + // tilde_expand always appends a /. + TestTilde("~", absl::Substitute("$0/", HomeDirectory())); +} + +TEST_F(ReadlineTest, TildeSlash) { + TestTilde("~/", absl::Substitute("$0/", HomeDirectory())); +} + +TEST_F(ReadlineTest, TildeSlashPath) { + TestTilde("~/foo/bar", + absl::Substitute("$0/foo/bar", HomeDirectory())); +} + +TEST_F(ReadlineTest, TildeUnchanged) { + TestTilde("foo/bar", "foo/bar"); +} + +TEST_F(ReadlineTest, TildeInvalid) { + auto val = "~invalid-user-that-does-not-exist/"; + TestTilde(val, val); +} diff --git a/dependency_support/dk_thrysoee_libedit/workspace.bzl b/dependency_support/dk_thrysoee_libedit/workspace.bzl new file mode 100644 index 0000000000..c87910240c --- /dev/null +++ b/dependency_support/dk_thrysoee_libedit/workspace.bzl @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the libedit library, used by iverilog (it poses as GNU readline).""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "dk_thrysoee_libedit", + urls = [ + "https://www.thrysoee.dk/editline/libedit-20191231-3.1.tar.gz", + ], + strip_prefix = "libedit-20191231-3.1", + sha256 = "dbb82cb7e116a5f8025d35ef5b4f7d4a3cdd0a3909a146a39112095a2d229071", + build_file = Label("//dependency_support:dk_thrysoee_libedit/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/edu_berkeley_abc/bundled.BUILD.bazel b/dependency_support/edu_berkeley_abc/bundled.BUILD.bazel new file mode 100644 index 0000000000..41d35cbcb9 --- /dev/null +++ b/dependency_support/edu_berkeley_abc/bundled.BUILD.bazel @@ -0,0 +1,1179 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ABC: System for Sequential Synthesis and Verification by Berkeley Logic +# Synthesis and Verification Group. + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +cc_binary( + name = "abc", + srcs = ["src/base/main/main.c"], + includes = ["src/"], + deps = [":abc-lib"], +) + +cc_library( + name = "abc-lib", + srcs = glob(["src/**/*.h"]) + [ + # this list is generated by calling "make cmake_info" and incorporating the files + # between the SEPARATOR_SRC blocks + "src/base/abc/abcAig.c", + "src/base/abc/abcBarBuf.c", + "src/base/abc/abcBlifMv.c", + "src/base/abc/abcCheck.c", + "src/base/abc/abcDfs.c", + "src/base/abc/abcFanio.c", + "src/base/abc/abcFanOrder.c", + "src/base/abc/abcFunc.c", + "src/base/abc/abcHie.c", + "src/base/abc/abcHieCec.c", + "src/base/abc/abcHieGia.c", + "src/base/abc/abcHieNew.c", + "src/base/abc/abcLatch.c", + "src/base/abc/abcLib.c", + "src/base/abc/abcMinBase.c", + "src/base/abc/abcNames.c", + "src/base/abc/abcNetlist.c", + "src/base/abc/abcNtk.c", + "src/base/abc/abcObj.c", + "src/base/abc/abcRefs.c", + "src/base/abc/abcShow.c", + "src/base/abc/abcSop.c", + "src/base/abc/abcUtil.c", + "src/base/abci/abc.c", + "src/base/abci/abcAttach.c", + "src/base/abci/abcAuto.c", + "src/base/abci/abcBalance.c", + "src/base/abci/abcBidec.c", + "src/base/abci/abcBm.c", + "src/base/abci/abcBmc.c", + "src/base/abci/abcCas.c", + "src/base/abci/abcCascade.c", + "src/base/abci/abcCollapse.c", + "src/base/abci/abcCut.c", + "src/base/abci/abcDar.c", + "src/base/abci/abcDebug.c", + "src/base/abci/abcDec.c", + "src/base/abci/abcDetect.c", + "src/base/abci/abcDress.c", + "src/base/abci/abcDress2.c", + "src/base/abci/abcDress3.c", + "src/base/abci/abcDsd.c", + "src/base/abci/abcEco.c", + "src/base/abci/abcExact.c", + "src/base/abci/abcExtract.c", + "src/base/abci/abcFraig.c", + "src/base/abci/abcFx.c", + "src/base/abci/abcFxu.c", + "src/base/abci/abcGen.c", + "src/base/abci/abcHaig.c", + "src/base/abci/abcIf.c", + "src/base/abci/abcIfif.c", + "src/base/abci/abcIfMux.c", + "src/base/abci/abcIvy.c", + "src/base/abci/abcLog.c", + "src/base/abci/abcLut.c", + "src/base/abci/abcLutmin.c", + "src/base/abci/abcMap.c", + "src/base/abci/abcMerge.c", + "src/base/abci/abcMfs.c", + "src/base/abci/abcMini.c", + "src/base/abci/abcMiter.c", + "src/base/abci/abcMulti.c", + "src/base/abci/abcNtbdd.c", + "src/base/abci/abcNpn.c", + "src/base/abci/abcNpnSave.c", + "src/base/abci/abcOdc.c", + "src/base/abci/abcOrder.c", + "src/base/abci/abcPart.c", + "src/base/abci/abcPrint.c", + "src/base/abci/abcProve.c", + "src/base/abci/abcQbf.c", + "src/base/abci/abcQuant.c", + "src/base/abci/abcRec3.c", + "src/base/abci/abcReconv.c", + "src/base/abci/abcReach.c", + "src/base/abci/abcRefactor.c", + "src/base/abci/abcRenode.c", + "src/base/abci/abcReorder.c", + "src/base/abci/abcRestruct.c", + "src/base/abci/abcResub.c", + "src/base/abci/abcRewrite.c", + "src/base/abci/abcRpo.c", + "src/base/abci/abcRr.c", + "src/base/abci/abcRunGen.c", + "src/base/abci/abcSat.c", + "src/base/abci/abcSaucy.c", + "src/base/abci/abcScorr.c", + "src/base/abci/abcSense.c", + "src/base/abci/abcSpeedup.c", + "src/base/abci/abcStrash.c", + "src/base/abci/abcSweep.c", + "src/base/abci/abcSymm.c", + "src/base/abci/abcTim.c", + "src/base/abci/abcTiming.c", + "src/base/abci/abcUnate.c", + "src/base/abci/abcUnreach.c", + "src/base/abci/abcVerify.c", + "src/base/abci/abcXsim.c", + "src/base/cmd/cmd.c", + "src/base/cmd/cmdAlias.c", + "src/base/cmd/cmdApi.c", + "src/base/cmd/cmdAuto.c", + "src/base/cmd/cmdFlag.c", + "src/base/cmd/cmdHist.c", + "src/base/cmd/cmdLoad.c", + "src/base/cmd/cmdPlugin.c", + "src/base/cmd/cmdStarter.c", + "src/base/cmd/cmdUtils.c", + "src/base/io/io.c", + "src/base/io/ioJson.c", + "src/base/io/ioReadAiger.c", + "src/base/io/ioReadBaf.c", + "src/base/io/ioReadBblif.c", + "src/base/io/ioReadBench.c", + "src/base/io/ioReadBlif.c", + "src/base/io/ioReadBlifAig.c", + "src/base/io/ioReadBlifMv.c", + "src/base/io/ioReadDsd.c", + "src/base/io/ioReadEdif.c", + "src/base/io/ioReadEqn.c", + "src/base/io/ioReadPla.c", + "src/base/io/ioReadPlaMo.c", + "src/base/io/ioReadVerilog.c", + "src/base/io/ioUtil.c", + "src/base/io/ioWriteAiger.c", + "src/base/io/ioWriteBaf.c", + "src/base/io/ioWriteBblif.c", + "src/base/io/ioWriteBench.c", + "src/base/io/ioWriteBlif.c", + "src/base/io/ioWriteBlifMv.c", + "src/base/io/ioWriteBook.c", + "src/base/io/ioWriteCnf.c", + "src/base/io/ioWriteDot.c", + "src/base/io/ioWriteEqn.c", + "src/base/io/ioWriteGml.c", + "src/base/io/ioWriteList.c", + "src/base/io/ioWritePla.c", + "src/base/io/ioWriteVerilog.c", + "src/base/io/ioWriteSmv.c", + # removed from the library (as done in CMakeLists.txt in the abc distribution) + # "src/base/main/main.c", + "src/base/main/mainFrame.c", + "src/base/main/mainInit.c", + "src/base/main/mainLib.c", + "src/base/main/mainReal.c", + "src/base/main/libSupport.c", + "src/base/main/mainUtils.c", + "src/base/exor/exor.c", + "src/base/exor/exorBits.c", + "src/base/exor/exorCubes.c", + "src/base/exor/exorLink.c", + "src/base/exor/exorList.c", + "src/base/exor/exorUtil.c", + "src/base/ver/verCore.c", + "src/base/ver/verFormula.c", + "src/base/ver/verParse.c", + "src/base/ver/verStream.c", + "src/base/wlc/wlcAbs.c", + "src/base/wlc/wlcAbs2.c", + "src/base/wlc/wlcAbc.c", + "src/base/wlc/wlcPth.c", + "src/base/wlc/wlcBlast.c", + "src/base/wlc/wlcCom.c", + "src/base/wlc/wlcGraft.c", + "src/base/wlc/wlcJson.c", + "src/base/wlc/wlcMem.c", + "src/base/wlc/wlcNdr.c", + "src/base/wlc/wlcNtk.c", + "src/base/wlc/wlcReadSmt.c", + "src/base/wlc/wlcReadVer.c", + "src/base/wlc/wlcSim.c", + "src/base/wlc/wlcShow.c", + "src/base/wlc/wlcStdin.c", + "src/base/wlc/wlcUif.c", + "src/base/wlc/wlcWin.c", + "src/base/wlc/wlcWriteVer.c", + "src/base/wln/wln.c", + "src/base/wln/wlnMem.c", + "src/base/wln/wlnNdr.c", + "src/base/wln/wlnNtk.c", + "src/base/wln/wlnObj.c", + "src/base/wln/wlnRetime.c", + "src/base/wln/wlnWlc.c", + "src/base/wln/wlnWriteVer.c", + "src/base/acb/acbAbc.c", + "src/base/acb/acbAig.c", + "src/base/acb/acbCom.c", + "src/base/acb/acbFunc.c", + "src/base/acb/acbMfs.c", + "src/base/acb/acbPush.c", + "src/base/acb/acbSets.c", + "src/base/acb/acbTest.c", + "src/base/acb/acbUtil.c", + "src/base/bac/bacBlast.c", + "src/base/bac/bacBac.c", + "src/base/bac/bacCom.c", + "src/base/bac/bacLib.c", + "src/base/bac/bacNtk.c", + "src/base/bac/bacPrsBuild.c", + "src/base/bac/bacPrsTrans.c", + "src/base/bac/bacPtr.c", + "src/base/bac/bacPtrAbc.c", + "src/base/bac/bacReadBlif.c", + "src/base/bac/bacReadSmt.c", + "src/base/bac/bacReadVer.c", + "src/base/bac/bacWriteBlif.c", + "src/base/bac/bacWriteSmt.c", + "src/base/bac/bacWriteVer.c", + "src/base/cba/cbaBlast.c", + "src/base/cba/cbaCba.c", + "src/base/cba/cbaCom.c", + "src/base/cba/cbaNtk.c", + "src/base/cba/cbaReadBlif.c", + "src/base/cba/cbaReadVer.c", + "src/base/cba/cbaWriteBlif.c", + "src/base/cba/cbaWriteVer.c", + "src/base/pla/plaCom.c", + "src/base/pla/plaHash.c", + "src/base/pla/plaMan.c", + "src/base/pla/plaMerge.c", + "src/base/pla/plaSimple.c", + "src/base/pla/plaRead.c", + "src/base/pla/plaWrite.c", + "src/base/test/test.c", + "src/map/mapper/mapper.c", + "src/map/mapper/mapperCanon.c", + "src/map/mapper/mapperCore.c", + "src/map/mapper/mapperCreate.c", + "src/map/mapper/mapperCut.c", + "src/map/mapper/mapperCutUtils.c", + "src/map/mapper/mapperLib.c", + "src/map/mapper/mapperMatch.c", + "src/map/mapper/mapperRefs.c", + "src/map/mapper/mapperSuper.c", + "src/map/mapper/mapperSwitch.c", + "src/map/mapper/mapperTable.c", + "src/map/mapper/mapperTime.c", + "src/map/mapper/mapperTree.c", + "src/map/mapper/mapperTruth.c", + "src/map/mapper/mapperUtils.c", + "src/map/mapper/mapperVec.c", + "src/map/mio/mio.c", + "src/map/mio/mioApi.c", + "src/map/mio/mioFunc.c", + "src/map/mio/mioParse.c", + "src/map/mio/mioRead.c", + "src/map/mio/mioSop.c", + "src/map/mio/mioUtils.c", + "src/map/super/super.c", + "src/map/super/superAnd.c", + "src/map/super/superGate.c", + "src/map/if/ifCom.c", + "src/map/if/ifCache.c", + "src/map/if/ifCore.c", + "src/map/if/ifCut.c", + "src/map/if/ifData2.c", + "src/map/if/ifDec07.c", + "src/map/if/ifDec08.c", + "src/map/if/ifDec10.c", + "src/map/if/ifDec16.c", + "src/map/if/ifDec75.c", + "src/map/if/ifDelay.c", + "src/map/if/ifDsd.c", + "src/map/if/ifLibBox.c", + "src/map/if/ifLibLut.c", + "src/map/if/ifMan.c", + "src/map/if/ifMap.c", + "src/map/if/ifMatch2.c", + "src/map/if/ifReduce.c", + "src/map/if/ifSat.c", + "src/map/if/ifSelect.c", + "src/map/if/ifSeq.c", + "src/map/if/ifTest.c", + "src/map/if/ifTime.c", + "src/map/if/ifTruth.c", + "src/map/if/ifTune.c", + "src/map/if/ifUtil.c", + "src/map/amap/amapCore.c", + "src/map/amap/amapGraph.c", + "src/map/amap/amapLib.c", + "src/map/amap/amapLiberty.c", + "src/map/amap/amapMan.c", + "src/map/amap/amapMatch.c", + "src/map/amap/amapMerge.c", + "src/map/amap/amapOutput.c", + "src/map/amap/amapParse.c", + "src/map/amap/amapPerm.c", + "src/map/amap/amapRead.c", + "src/map/amap/amapRule.c", + "src/map/amap/amapUniq.c", + "src/map/cov/covBuild.c", + "src/map/cov/covCore.c", + "src/map/cov/covMan.c", + "src/map/cov/covMinEsop.c", + "src/map/cov/covMinMan.c", + "src/map/cov/covMinSop.c", + "src/map/cov/covMinUtil.c", + "src/map/scl/scl.c", + "src/map/scl/sclBuffer.c", + "src/map/scl/sclBufSize.c", + "src/map/scl/sclDnsize.c", + "src/map/scl/sclLiberty.c", + "src/map/scl/sclLibScl.c", + "src/map/scl/sclLibUtil.c", + "src/map/scl/sclLoad.c", + "src/map/scl/sclSize.c", + "src/map/scl/sclUpsize.c", + "src/map/scl/sclUtil.c", + "src/map/mpm/mpmAbc.c", + "src/map/mpm/mpmCore.c", + "src/map/mpm/mpmDsd.c", + "src/map/mpm/mpmGates.c", + "src/map/mpm/mpmLib.c", + "src/map/mpm/mpmMan.c", + "src/map/mpm/mpmMap.c", + "src/map/mpm/mpmMig.c", + "src/map/mpm/mpmPre.c", + "src/map/mpm/mpmTruth.c", + "src/map/mpm/mpmUtil.c", + "src/misc/extra/extraUtilBitMatrix.c", + "src/misc/extra/extraUtilCanon.c", + "src/misc/extra/extraUtilCfs.c", + "src/misc/extra/extraUtilCube.c", + "src/misc/extra/extraUtilDsd.c", + "src/misc/extra/extraUtilEnum.c", + "src/misc/extra/extraUtilFile.c", + "src/misc/extra/extraUtilGen.c", + "src/misc/extra/extraUtilMacc.c", + "src/misc/extra/extraUtilMaj.c", + "src/misc/extra/extraUtilMemory.c", + "src/misc/extra/extraUtilMisc.c", + "src/misc/extra/extraUtilMult.c", + "src/misc/extra/extraUtilPath.c", + "src/misc/extra/extraUtilPerm.c", + "src/misc/extra/extraUtilProgress.c", + "src/misc/extra/extraUtilReader.c", + "src/misc/extra/extraUtilSupp.c", + "src/misc/extra/extraUtilTruth.c", + "src/misc/extra/extraUtilUtil.c", + "src/misc/mvc/mvcApi.c", + "src/misc/mvc/mvcCompare.c", + "src/misc/mvc/mvcContain.c", + "src/misc/mvc/mvcCover.c", + "src/misc/mvc/mvcCube.c", + "src/misc/mvc/mvcDivide.c", + "src/misc/mvc/mvcDivisor.c", + "src/misc/mvc/mvcList.c", + "src/misc/mvc/mvcLits.c", + "src/misc/mvc/mvcMan.c", + "src/misc/mvc/mvcOpAlg.c", + "src/misc/mvc/mvcOpBool.c", + "src/misc/mvc/mvcPrint.c", + "src/misc/mvc/mvcSort.c", + "src/misc/mvc/mvcUtils.c", + "src/misc/st/st.c", + "src/misc/st/stmm.c", + "src/misc/util/utilBridge.c", + "src/misc/util/utilCex.c", + "src/misc/util/utilColor.c", + "src/misc/util/utilFile.c", + "src/misc/util/utilIsop.c", + "src/misc/util/utilNam.c", + "src/misc/util/utilSignal.c", + "src/misc/util/utilSort.c", + "src/misc/nm/nmApi.c", + "src/misc/nm/nmTable.c", + "src/misc/tim/timBox.c", + "src/misc/tim/timDump.c", + "src/misc/tim/timMan.c", + "src/misc/tim/timTime.c", + "src/misc/tim/timTrav.c", + "src/misc/bzlib/blocksort.c", + "src/misc/bzlib/bzlib.c", + "src/misc/bzlib/compress.c", + "src/misc/bzlib/crctable.c", + "src/misc/bzlib/decompress.c", + "src/misc/bzlib/huffman.c", + "src/misc/bzlib/randtable.c", + "src/misc/zlib/adler32.c", + "src/misc/zlib/compress_.c", + "src/misc/zlib/crc32.c", + "src/misc/zlib/deflate.c", + "src/misc/zlib/gzclose.c", + "src/misc/zlib/gzlib.c", + "src/misc/zlib/gzread.c", + "src/misc/zlib/gzwrite.c", + "src/misc/zlib/infback.c", + "src/misc/zlib/inffast.c", + "src/misc/zlib/inflate.c", + "src/misc/zlib/inftrees.c", + "src/misc/zlib/trees.c", + "src/misc/zlib/uncompr.c", + "src/misc/zlib/zutil.c", + "src/misc/mem/mem.c", + "src/misc/bar/bar.c", + "src/misc/bbl/bblif.c", + "src/misc/parse/parseEqn.c", + "src/misc/parse/parseStack.c", + "src/opt/cut/cutApi.c", + "src/opt/cut/cutCut.c", + "src/opt/cut/cutMan.c", + "src/opt/cut/cutMerge.c", + "src/opt/cut/cutNode.c", + "src/opt/cut/cutOracle.c", + "src/opt/cut/cutPre22.c", + "src/opt/cut/cutSeq.c", + "src/opt/cut/cutTruth.c", + "src/opt/fxu/fxu.c", + "src/opt/fxu/fxuCreate.c", + "src/opt/fxu/fxuHeapD.c", + "src/opt/fxu/fxuHeapS.c", + "src/opt/fxu/fxuList.c", + "src/opt/fxu/fxuMatrix.c", + "src/opt/fxu/fxuPair.c", + "src/opt/fxu/fxuPrint.c", + "src/opt/fxu/fxuReduce.c", + "src/opt/fxu/fxuSelect.c", + "src/opt/fxu/fxuSingle.c", + "src/opt/fxu/fxuUpdate.c", + "src/opt/fxch/Fxch.c", + "src/opt/fxch/FxchDiv.c", + "src/opt/fxch/FxchMan.c", + "src/opt/fxch/FxchSCHashTable.c", + "src/opt/rwr/rwrDec.c", + "src/opt/rwr/rwrEva.c", + "src/opt/rwr/rwrExp.c", + "src/opt/rwr/rwrLib.c", + "src/opt/rwr/rwrMan.c", + "src/opt/rwr/rwrPrint.c", + "src/opt/rwr/rwrUtil.c", + "src/opt/mfs/mfsCore.c", + "src/opt/mfs/mfsDiv.c", + "src/opt/mfs/mfsInter.c", + "src/opt/mfs/mfsMan.c", + "src/opt/mfs/mfsResub.c", + "src/opt/mfs/mfsSat.c", + "src/opt/mfs/mfsStrash.c", + "src/opt/mfs/mfsWin.c", + "src/opt/sim/simMan.c", + "src/opt/sim/simSeq.c", + "src/opt/sim/simSupp.c", + "src/opt/sim/simSwitch.c", + "src/opt/sim/simSym.c", + "src/opt/sim/simSymSat.c", + "src/opt/sim/simSymSim.c", + "src/opt/sim/simSymStr.c", + "src/opt/sim/simUtils.c", + "src/opt/ret/retArea.c", + "src/opt/ret/retCore.c", + "src/opt/ret/retDelay.c", + "src/opt/ret/retFlow.c", + "src/opt/ret/retIncrem.c", + "src/opt/ret/retInit.c", + "src/opt/ret/retLvalue.c", + "src/opt/fret/fretMain.c", + "src/opt/fret/fretFlow.c", + "src/opt/fret/fretInit.c", + "src/opt/fret/fretTime.c", + "src/opt/res/resCore.c", + "src/opt/res/resDivs.c", + "src/opt/res/resFilter.c", + "src/opt/res/resSat.c", + "src/opt/res/resSim.c", + "src/opt/res/resStrash.c", + "src/opt/res/resWin.c", + "src/opt/lpk/lpkCore.c", + "src/opt/lpk/lpkAbcDec.c", + "src/opt/lpk/lpkAbcMux.c", + "src/opt/lpk/lpkAbcDsd.c", + "src/opt/lpk/lpkAbcUtil.c", + "src/opt/lpk/lpkCut.c", + "src/opt/lpk/lpkMan.c", + "src/opt/lpk/lpkMap.c", + "src/opt/lpk/lpkMulti.c", + "src/opt/lpk/lpkMux.c", + "src/opt/lpk/lpkSets.c", + "src/opt/nwk/nwkAig.c", + "src/opt/nwk/nwkCheck.c", + "src/opt/nwk/nwkBidec.c", + "src/opt/nwk/nwkDfs.c", + "src/opt/nwk/nwkFanio.c", + "src/opt/nwk/nwkFlow.c", + "src/opt/nwk/nwkMan.c", + "src/opt/nwk/nwkMap.c", + "src/opt/nwk/nwkMerge.c", + "src/opt/nwk/nwkObj.c", + "src/opt/nwk/nwkSpeedup.c", + "src/opt/nwk/nwkStrash.c", + "src/opt/nwk/nwkTiming.c", + "src/opt/nwk/nwkUtil.c", + "src/opt/rwt/rwtDec.c", + "src/opt/rwt/rwtMan.c", + "src/opt/rwt/rwtUtil.c", + "src/opt/cgt/cgtAig.c", + "src/opt/cgt/cgtCore.c", + "src/opt/cgt/cgtDecide.c", + "src/opt/cgt/cgtMan.c", + "src/opt/cgt/cgtSat.c", + "src/opt/csw/cswCore.c", + "src/opt/csw/cswCut.c", + "src/opt/csw/cswMan.c", + "src/opt/csw/cswTable.c", + "src/opt/dar/darBalance.c", + "src/opt/dar/darCore.c", + "src/opt/dar/darCut.c", + "src/opt/dar/darData.c", + "src/opt/dar/darLib.c", + "src/opt/dar/darMan.c", + "src/opt/dar/darPrec.c", + "src/opt/dar/darRefact.c", + "src/opt/dar/darScript.c", + "src/opt/dau/dauCanon.c", + "src/opt/dau/dauCore.c", + "src/opt/dau/dauCount.c", + "src/opt/dau/dauDivs.c", + "src/opt/dau/dauDsd.c", + "src/opt/dau/dauEnum.c", + "src/opt/dau/dauGia.c", + "src/opt/dau/dauMerge.c", + "src/opt/dau/dauNonDsd.c", + "src/opt/dau/dauNpn.c", + "src/opt/dau/dauNpn2.c", + "src/opt/dau/dauTree.c", + "src/opt/dsc/dsc.c", + "src/opt/sfm/sfmArea.c", + "src/opt/sfm/sfmCnf.c", + "src/opt/sfm/sfmCore.c", + "src/opt/sfm/sfmDec.c", + "src/opt/sfm/sfmLib.c", + "src/opt/sfm/sfmNtk.c", + "src/opt/sfm/sfmSat.c", + "src/opt/sfm/sfmTim.c", + "src/opt/sfm/sfmMit.c", + "src/opt/sfm/sfmWin.c", + "src/opt/sbd/sbd.c", + "src/opt/sbd/sbdCnf.c", + "src/opt/sbd/sbdCore.c", + "src/opt/sbd/sbdCut.c", + "src/opt/sbd/sbdCut2.c", + "src/opt/sbd/sbdLut.c", + "src/opt/sbd/sbdPath.c", + "src/opt/sbd/sbdSat.c", + "src/opt/sbd/sbdWin.c", + "src/sat/bsat/satMem.c", + "src/sat/bsat/satInter.c", + "src/sat/bsat/satInterA.c", + "src/sat/bsat/satInterB.c", + "src/sat/bsat/satInterP.c", + "src/sat/bsat/satProof.c", + "src/sat/bsat/satSolver.c", + "src/sat/bsat/satSolver2.c", + "src/sat/bsat/satSolver2i.c", + "src/sat/bsat/satSolver3.c", + "src/sat/bsat/satStore.c", + "src/sat/bsat/satTrace.c", + "src/sat/bsat/satTruth.c", + "src/sat/bsat/satUtil.c", + "src/sat/xsat/xsatSolver.c", + "src/sat/xsat/xsatSolverAPI.c", + "src/sat/xsat/xsatCnfReader.c", + "src/sat/satoko/solver.c", + "src/sat/satoko/solver_api.c", + "src/sat/satoko/cnf_reader.c", + "src/sat/csat/csat_apis.c", + "src/sat/msat/msatActivity.c", + "src/sat/msat/msatClause.c", + "src/sat/msat/msatClauseVec.c", + "src/sat/msat/msatMem.c", + "src/sat/msat/msatOrderH.c", + "src/sat/msat/msatQueue.c", + "src/sat/msat/msatRead.c", + "src/sat/msat/msatSolverApi.c", + "src/sat/msat/msatSolverCore.c", + "src/sat/msat/msatSolverIo.c", + "src/sat/msat/msatSolverSearch.c", + "src/sat/msat/msatSort.c", + "src/sat/msat/msatVec.c", + "src/sat/cnf/cnfCore.c", + "src/sat/cnf/cnfCut.c", + "src/sat/cnf/cnfData.c", + "src/sat/cnf/cnfFast.c", + "src/sat/cnf/cnfMan.c", + "src/sat/cnf/cnfMap.c", + "src/sat/cnf/cnfPost.c", + "src/sat/cnf/cnfUtil.c", + "src/sat/cnf/cnfWrite.c", + "src/sat/bmc/bmcBCore.c", + "src/sat/bmc/bmcBmc.c", + "src/sat/bmc/bmcBmc2.c", + "src/sat/bmc/bmcBmc3.c", + "src/sat/bmc/bmcBmcAnd.c", + "src/sat/bmc/bmcBmci.c", + "src/sat/bmc/bmcBmcG.c", + "src/sat/bmc/bmcBmcS.c", + "src/sat/bmc/bmcCexCare.c", + "src/sat/bmc/bmcCexCut.c", + "src/sat/bmc/bmcCexDepth.c", + "src/sat/bmc/bmcCexMin1.c", + "src/sat/bmc/bmcCexMin2.c", + "src/sat/bmc/bmcCexTools.c", + "src/sat/bmc/bmcChain.c", + "src/sat/bmc/bmcClp.c", + "src/sat/bmc/bmcEco.c", + "src/sat/bmc/bmcExpand.c", + "src/sat/bmc/bmcFault.c", + "src/sat/bmc/bmcFx.c", + "src/sat/bmc/bmcGen.c", + "src/sat/bmc/bmcICheck.c", + "src/sat/bmc/bmcInse.c", + "src/sat/bmc/bmcLoad.c", + "src/sat/bmc/bmcMaj.c", + "src/sat/bmc/bmcMaj2.c", + "src/sat/bmc/bmcMaj3.c", + "src/sat/bmc/bmcMaxi.c", + "src/sat/bmc/bmcMesh.c", + "src/sat/bmc/bmcMesh2.c", + "src/sat/bmc/bmcMulti.c", + "src/sat/bmc/bmcUnroll.c", + "src/sat/glucose/AbcGlucose.cpp", + "src/sat/glucose/AbcGlucoseCmd.cpp", + "src/sat/glucose/Glucose.cpp", + "src/sat/glucose/Options.cpp", + "src/sat/glucose/SimpSolver.cpp", + "src/sat/glucose/System.cpp", + "src/bool/bdc/bdcCore.c", + "src/bool/bdc/bdcDec.c", + "src/bool/bdc/bdcSpfd.c", + "src/bool/bdc/bdcTable.c", + "src/bool/dec/decAbc.c", + "src/bool/dec/decFactor.c", + "src/bool/dec/decMan.c", + "src/bool/dec/decPrint.c", + "src/bool/dec/decUtil.c", + "src/bool/kit/kitAig.c", + "src/bool/kit/kitBdd.c", + "src/bool/kit/kitCloud.c", + "src/bool/kit/cloud.c", + "src/bool/kit/kitDsd.c", + "src/bool/kit/kitFactor.c", + "src/bool/kit/kitGraph.c", + "src/bool/kit/kitHop.c", + "src/bool/kit/kitIsop.c", + "src/bool/kit/kitPla.c", + "src/bool/kit/kitSop.c", + "src/bool/kit/kitTruth.c", + "src/bool/lucky/lucky.c", + "src/bool/lucky/luckyFast16.c", + "src/bool/lucky/luckyFast6.c", + "src/bool/lucky/luckyRead.c", + "src/bool/lucky/luckySimple.c", + "src/bool/lucky/luckySwapIJ.c", + "src/bool/lucky/luckySwap.c", + "src/bool/rsb/rsbDec6.c", + "src/bool/rsb/rsbMan.c", + "src/bool/rpo/rpo.c", + "src/proof/pdr/pdrCnf.c", + "src/proof/pdr/pdrCore.c", + "src/proof/pdr/pdrIncr.c", + "src/proof/pdr/pdrInv.c", + "src/proof/pdr/pdrMan.c", + "src/proof/pdr/pdrSat.c", + "src/proof/pdr/pdrTsim.c", + "src/proof/pdr/pdrTsim2.c", + "src/proof/pdr/pdrTsim3.c", + "src/proof/pdr/pdrUtil.c", + "src/proof/abs/absDup.c", + "src/proof/abs/absGla.c", + "src/proof/abs/absGlaOld.c", + "src/proof/abs/absIter.c", + "src/proof/abs/absOldCex.c", + "src/proof/abs/absOldRef.c", + "src/proof/abs/absOldSat.c", + "src/proof/abs/absOldSim.c", + "src/proof/abs/absOut.c", + "src/proof/abs/absPth.c", + "src/proof/abs/absRef.c", + "src/proof/abs/absRefSelect.c", + "src/proof/abs/absRpm.c", + "src/proof/abs/absRpmOld.c", + "src/proof/abs/absVta.c", + "src/proof/abs/absUtil.c", + "src/proof/live/liveness.c", + "src/proof/live/liveness_sim.c", + "src/proof/live/ltl_parser.c", + "src/proof/live/kliveness.c", + "src/proof/live/monotone.c", + "src/proof/live/disjunctiveMonotone.c", + "src/proof/live/arenaViolation.c", + "src/proof/live/kLiveConstraints.c", + "src/proof/live/combination.c", + "src/proof/ssc/sscClass.c", + "src/proof/ssc/sscCore.c", + "src/proof/ssc/sscSat.c", + "src/proof/ssc/sscSim.c", + "src/proof/ssc/sscUtil.c", + "src/proof/int/intCheck.c", + "src/proof/int/intContain.c", + "src/proof/int/intCore.c", + "src/proof/int/intCtrex.c", + "src/proof/int/intDup.c", + "src/proof/int/intFrames.c", + "src/proof/int/intInter.c", + "src/proof/int/intM114.c", + "src/proof/int/intMan.c", + "src/proof/int/intUtil.c", + "src/proof/cec/cecCec.c", + "src/proof/cec/cecChoice.c", + "src/proof/cec/cecClass.c", + "src/proof/cec/cecCore.c", + "src/proof/cec/cecCorr.c", + "src/proof/cec/cecIso.c", + "src/proof/cec/cecMan.c", + "src/proof/cec/cecPat.c", + "src/proof/cec/cecSat.c", + "src/proof/cec/cecSatG.c", + "src/proof/cec/cecSeq.c", + "src/proof/cec/cecSolve.c", + "src/proof/cec/cecSplit.c", + "src/proof/cec/cecSynth.c", + "src/proof/cec/cecSweep.c", + "src/proof/acec/acecCl.c", + "src/proof/acec/acecCore.c", + "src/proof/acec/acecCo.c", + "src/proof/acec/acecBo.c", + "src/proof/acec/acecRe.c", + "src/proof/acec/acecPa.c", + "src/proof/acec/acecPo.c", + "src/proof/acec/acecPool.c", + "src/proof/acec/acecCover.c", + "src/proof/acec/acecFadds.c", + "src/proof/acec/acecMult.c", + "src/proof/acec/acecNorm.c", + "src/proof/acec/acecOrder.c", + "src/proof/acec/acecPolyn.c", + "src/proof/acec/acecSt.c", + "src/proof/acec/acecTree.c", + "src/proof/acec/acecUtil.c", + "src/proof/acec/acec2Mult.c", + "src/proof/acec/acecXor.c", + "src/proof/dch/dchAig.c", + "src/proof/dch/dchChoice.c", + "src/proof/dch/dchClass.c", + "src/proof/dch/dchCnf.c", + "src/proof/dch/dchCore.c", + "src/proof/dch/dchMan.c", + "src/proof/dch/dchSat.c", + "src/proof/dch/dchSim.c", + "src/proof/dch/dchSimSat.c", + "src/proof/dch/dchSweep.c", + "src/proof/fraig/fraigApi.c", + "src/proof/fraig/fraigCanon.c", + "src/proof/fraig/fraigFanout.c", + "src/proof/fraig/fraigFeed.c", + "src/proof/fraig/fraigMan.c", + "src/proof/fraig/fraigMem.c", + "src/proof/fraig/fraigNode.c", + "src/proof/fraig/fraigPrime.c", + "src/proof/fraig/fraigSat.c", + "src/proof/fraig/fraigTable.c", + "src/proof/fraig/fraigUtil.c", + "src/proof/fraig/fraigVec.c", + "src/proof/fra/fraBmc.c", + "src/proof/fra/fraCec.c", + "src/proof/fra/fraClass.c", + "src/proof/fra/fraClau.c", + "src/proof/fra/fraClaus.c", + "src/proof/fra/fraCnf.c", + "src/proof/fra/fraCore.c", + "src/proof/fra/fraHot.c", + "src/proof/fra/fraImp.c", + "src/proof/fra/fraInd.c", + "src/proof/fra/fraIndVer.c", + "src/proof/fra/fraLcr.c", + "src/proof/fra/fraMan.c", + "src/proof/fra/fraPart.c", + "src/proof/fra/fraSat.c", + "src/proof/fra/fraSec.c", + "src/proof/fra/fraSim.c", + "src/proof/ssw/sswAig.c", + "src/proof/ssw/sswBmc.c", + "src/proof/ssw/sswClass.c", + "src/proof/ssw/sswCnf.c", + "src/proof/ssw/sswConstr.c", + "src/proof/ssw/sswCore.c", + "src/proof/ssw/sswDyn.c", + "src/proof/ssw/sswFilter.c", + "src/proof/ssw/sswIslands.c", + "src/proof/ssw/sswLcorr.c", + "src/proof/ssw/sswMan.c", + "src/proof/ssw/sswPart.c", + "src/proof/ssw/sswPairs.c", + "src/proof/ssw/sswRarity.c", + "src/proof/ssw/sswSat.c", + "src/proof/ssw/sswSemi.c", + "src/proof/ssw/sswSim.c", + "src/proof/ssw/sswSimSat.c", + "src/proof/ssw/sswSweep.c", + "src/proof/ssw/sswUnique.c", + "src/aig/aig/aigCheck.c", + "src/aig/aig/aigCanon.c", + "src/aig/aig/aigCuts.c", + "src/aig/aig/aigDfs.c", + "src/aig/aig/aigDup.c", + "src/aig/aig/aigFanout.c", + "src/aig/aig/aigFrames.c", + "src/aig/aig/aigInter.c", + "src/aig/aig/aigJust.c", + "src/aig/aig/aigMan.c", + "src/aig/aig/aigMem.c", + "src/aig/aig/aigMffc.c", + "src/aig/aig/aigObj.c", + "src/aig/aig/aigOper.c", + "src/aig/aig/aigOrder.c", + "src/aig/aig/aigPack.c", + "src/aig/aig/aigPart.c", + "src/aig/aig/aigPartReg.c", + "src/aig/aig/aigPartSat.c", + "src/aig/aig/aigRepr.c", + "src/aig/aig/aigRet.c", + "src/aig/aig/aigRetF.c", + "src/aig/aig/aigScl.c", + "src/aig/aig/aigShow.c", + "src/aig/aig/aigSplit.c", + "src/aig/aig/aigTable.c", + "src/aig/aig/aigTiming.c", + "src/aig/aig/aigTruth.c", + "src/aig/aig/aigTsim.c", + "src/aig/aig/aigUtil.c", + "src/aig/aig/aigWin.c", + "src/aig/saig/saigCone.c", + "src/aig/saig/saigConstr.c", + "src/aig/saig/saigConstr2.c", + "src/aig/saig/saigDual.c", + "src/aig/saig/saigDup.c", + "src/aig/saig/saigInd.c", + "src/aig/saig/saigIoa.c", + "src/aig/saig/saigIso.c", + "src/aig/saig/saigIsoFast.c", + "src/aig/saig/saigIsoSlow.c", + "src/aig/saig/saigMiter.c", + "src/aig/saig/saigOutDec.c", + "src/aig/saig/saigPhase.c", + "src/aig/saig/saigRetFwd.c", + "src/aig/saig/saigRetMin.c", + "src/aig/saig/saigRetStep.c", + "src/aig/saig/saigScl.c", + "src/aig/saig/saigSimFast.c", + "src/aig/saig/saigSimMv.c", + "src/aig/saig/saigSimSeq.c", + "src/aig/saig/saigStrSim.c", + "src/aig/saig/saigSwitch.c", + "src/aig/saig/saigSynch.c", + "src/aig/saig/saigTempor.c", + "src/aig/saig/saigTrans.c", + "src/aig/saig/saigWnd.c", + "src/aig/gia/giaAig.c", + "src/aig/gia/giaAgi.c", + "src/aig/gia/giaAiger.c", + "src/aig/gia/giaAigerExt.c", + "src/aig/gia/giaBalAig.c", + "src/aig/gia/giaBalLut.c", + "src/aig/gia/giaBalMap.c", + "src/aig/gia/giaBidec.c", + "src/aig/gia/giaCCof.c", + "src/aig/gia/giaCex.c", + "src/aig/gia/giaClp.c", + "src/aig/gia/giaCof.c", + "src/aig/gia/giaCone.c", + "src/aig/gia/giaCSatOld.c", + "src/aig/gia/giaCSat.c", + "src/aig/gia/giaCSat2.c", + "src/aig/gia/giaCTas.c", + "src/aig/gia/giaCut.c", + "src/aig/gia/giaDeep.c", + "src/aig/gia/giaDfs.c", + "src/aig/gia/giaDup.c", + "src/aig/gia/giaEdge.c", + "src/aig/gia/giaEmbed.c", + "src/aig/gia/giaEnable.c", + "src/aig/gia/giaEquiv.c", + "src/aig/gia/giaEra.c", + "src/aig/gia/giaEra2.c", + "src/aig/gia/giaEsop.c", + "src/aig/gia/giaExist.c", + "src/aig/gia/giaFalse.c", + "src/aig/gia/giaFanout.c", + "src/aig/gia/giaForce.c", + "src/aig/gia/giaFrames.c", + "src/aig/gia/giaFront.c", + "src/aig/gia/giaFx.c", + "src/aig/gia/giaGen.c", + "src/aig/gia/giaGig.c", + "src/aig/gia/giaGlitch.c", + "src/aig/gia/giaHash.c", + "src/aig/gia/giaIf.c", + "src/aig/gia/giaIff.c", + "src/aig/gia/giaIiff.c", + "src/aig/gia/giaIso.c", + "src/aig/gia/giaIso2.c", + "src/aig/gia/giaIso3.c", + "src/aig/gia/giaJf.c", + "src/aig/gia/giaKf.c", + "src/aig/gia/giaLf.c", + "src/aig/gia/giaMf.c", + "src/aig/gia/giaMan.c", + "src/aig/gia/giaMem.c", + "src/aig/gia/giaMfs.c", + "src/aig/gia/giaMini.c", + "src/aig/gia/giaMuxes.c", + "src/aig/gia/giaNf.c", + "src/aig/gia/giaOf.c", + "src/aig/gia/giaPack.c", + "src/aig/gia/giaPat.c", + "src/aig/gia/giaPf.c", + "src/aig/gia/giaQbf.c", + "src/aig/gia/giaResub.c", + "src/aig/gia/giaRetime.c", + "src/aig/gia/giaRex.c", + "src/aig/gia/giaSatEdge.c", + "src/aig/gia/giaSatLE.c", + "src/aig/gia/giaSatLut.c", + "src/aig/gia/giaSatMap.c", + "src/aig/gia/giaSatoko.c", + "src/aig/gia/giaSat3.c", + "src/aig/gia/giaScl.c", + "src/aig/gia/giaScript.c", + "src/aig/gia/giaShow.c", + "src/aig/gia/giaShrink.c", + "src/aig/gia/giaShrink6.c", + "src/aig/gia/giaShrink7.c", + "src/aig/gia/giaSim.c", + "src/aig/gia/giaSim2.c", + "src/aig/gia/giaSim4.c", + "src/aig/gia/giaSim5.c", + "src/aig/gia/giaSimBase.c", + "src/aig/gia/giaSort.c", + "src/aig/gia/giaSpeedup.c", + "src/aig/gia/giaSplit.c", + "src/aig/gia/giaStg.c", + "src/aig/gia/giaStr.c", + "src/aig/gia/giaSupMin.c", + "src/aig/gia/giaSupp.c", + "src/aig/gia/giaSweep.c", + "src/aig/gia/giaSweeper.c", + "src/aig/gia/giaSwitch.c", + "src/aig/gia/giaTim.c", + "src/aig/gia/giaTis.c", + "src/aig/gia/giaTruth.c", + "src/aig/gia/giaTsim.c", + "src/aig/gia/giaUnate.c", + "src/aig/gia/giaUtil.c", + "src/aig/ioa/ioaReadAig.c", + "src/aig/ioa/ioaWriteAig.c", + "src/aig/ioa/ioaUtil.c", + "src/aig/ivy/ivyBalance.c", + "src/aig/ivy/ivyCanon.c", + "src/aig/ivy/ivyCheck.c", + "src/aig/ivy/ivyCut.c", + "src/aig/ivy/ivyCutTrav.c", + "src/aig/ivy/ivyDfs.c", + "src/aig/ivy/ivyDsd.c", + "src/aig/ivy/ivyFanout.c", + "src/aig/ivy/ivyFastMap.c", + "src/aig/ivy/ivyFraig.c", + "src/aig/ivy/ivyHaig.c", + "src/aig/ivy/ivyMan.c", + "src/aig/ivy/ivyMem.c", + "src/aig/ivy/ivyMulti.c", + "src/aig/ivy/ivyObj.c", + "src/aig/ivy/ivyOper.c", + "src/aig/ivy/ivyResyn.c", + "src/aig/ivy/ivyRwr.c", + "src/aig/ivy/ivySeq.c", + "src/aig/ivy/ivyShow.c", + "src/aig/ivy/ivyTable.c", + "src/aig/ivy/ivyUtil.c", + "src/aig/hop/hopBalance.c", + "src/aig/hop/hopCheck.c", + "src/aig/hop/hopDfs.c", + "src/aig/hop/hopMan.c", + "src/aig/hop/hopMem.c", + "src/aig/hop/hopObj.c", + "src/aig/hop/hopOper.c", + "src/aig/hop/hopTable.c", + "src/aig/hop/hopTruth.c", + "src/aig/hop/hopUtil.c", + "src/bdd/cudd/cuddAPI.c", + "src/bdd/cudd/cuddAddAbs.c", + "src/bdd/cudd/cuddAddApply.c", + "src/bdd/cudd/cuddAddFind.c", + "src/bdd/cudd/cuddAddInv.c", + "src/bdd/cudd/cuddAddIte.c", + "src/bdd/cudd/cuddAddNeg.c", + "src/bdd/cudd/cuddAddWalsh.c", + "src/bdd/cudd/cuddAndAbs.c", + "src/bdd/cudd/cuddAnneal.c", + "src/bdd/cudd/cuddApa.c", + "src/bdd/cudd/cuddApprox.c", + "src/bdd/cudd/cuddBddAbs.c", + "src/bdd/cudd/cuddBddCorr.c", + "src/bdd/cudd/cuddBddIte.c", + "src/bdd/cudd/cuddBridge.c", + "src/bdd/cudd/cuddCache.c", + "src/bdd/cudd/cuddCheck.c", + "src/bdd/cudd/cuddClip.c", + "src/bdd/cudd/cuddCof.c", + "src/bdd/cudd/cuddCompose.c", + "src/bdd/cudd/cuddDecomp.c", + "src/bdd/cudd/cuddEssent.c", + "src/bdd/cudd/cuddExact.c", + "src/bdd/cudd/cuddExport.c", + "src/bdd/cudd/cuddGenCof.c", + "src/bdd/cudd/cuddGenetic.c", + "src/bdd/cudd/cuddGroup.c", + "src/bdd/cudd/cuddHarwell.c", + "src/bdd/cudd/cuddInit.c", + "src/bdd/cudd/cuddInteract.c", + "src/bdd/cudd/cuddLCache.c", + "src/bdd/cudd/cuddLevelQ.c", + "src/bdd/cudd/cuddLinear.c", + "src/bdd/cudd/cuddLiteral.c", + "src/bdd/cudd/cuddMatMult.c", + "src/bdd/cudd/cuddPriority.c", + "src/bdd/cudd/cuddRead.c", + "src/bdd/cudd/cuddRef.c", + "src/bdd/cudd/cuddReorder.c", + "src/bdd/cudd/cuddSat.c", + "src/bdd/cudd/cuddSign.c", + "src/bdd/cudd/cuddSolve.c", + "src/bdd/cudd/cuddSplit.c", + "src/bdd/cudd/cuddSubsetHB.c", + "src/bdd/cudd/cuddSubsetSP.c", + "src/bdd/cudd/cuddSymmetry.c", + "src/bdd/cudd/cuddTable.c", + "src/bdd/cudd/cuddUtil.c", + "src/bdd/cudd/cuddWindow.c", + "src/bdd/cudd/cuddZddCount.c", + "src/bdd/cudd/cuddZddFuncs.c", + "src/bdd/cudd/cuddZddGroup.c", + "src/bdd/cudd/cuddZddIsop.c", + "src/bdd/cudd/cuddZddLin.c", + "src/bdd/cudd/cuddZddMisc.c", + "src/bdd/cudd/cuddZddPort.c", + "src/bdd/cudd/cuddZddReord.c", + "src/bdd/cudd/cuddZddSetop.c", + "src/bdd/cudd/cuddZddSymm.c", + "src/bdd/cudd/cuddZddUtil.c", + "src/bdd/extrab/extraBddAuto.c", + "src/bdd/extrab/extraBddCas.c", + "src/bdd/extrab/extraBddImage.c", + "src/bdd/extrab/extraBddKmap.c", + "src/bdd/extrab/extraBddMaxMin.c", + "src/bdd/extrab/extraBddMisc.c", + "src/bdd/extrab/extraBddSet.c", + "src/bdd/extrab/extraBddSymm.c", + "src/bdd/extrab/extraBddThresh.c", + "src/bdd/extrab/extraBddTime.c", + "src/bdd/extrab/extraBddUnate.c", + "src/bdd/dsd/dsdApi.c", + "src/bdd/dsd/dsdCheck.c", + "src/bdd/dsd/dsdLocal.c", + "src/bdd/dsd/dsdMan.c", + "src/bdd/dsd/dsdProc.c", + "src/bdd/dsd/dsdTree.c", + "src/bdd/epd/epd.c", + "src/bdd/mtr/mtrBasic.c", + "src/bdd/mtr/mtrGroup.c", + "src/bdd/reo/reoApi.c", + "src/bdd/reo/reoCore.c", + "src/bdd/reo/reoProfile.c", + "src/bdd/reo/reoShuffle.c", + "src/bdd/reo/reoSift.c", + "src/bdd/reo/reoSwap.c", + "src/bdd/reo/reoTransfer.c", + "src/bdd/reo/reoUnits.c", + "src/bdd/cas/casCore.c", + "src/bdd/cas/casDec.c", + "src/bdd/bbr/bbrCex.c", + "src/bdd/bbr/bbrImage.c", + "src/bdd/bbr/bbrNtbdd.c", + "src/bdd/bbr/bbrReach.c", + "src/bdd/llb/llb1Cluster.c", + "src/bdd/llb/llb1Constr.c", + "src/bdd/llb/llb1Core.c", + "src/bdd/llb/llb1Group.c", + "src/bdd/llb/llb1Hint.c", + "src/bdd/llb/llb1Man.c", + "src/bdd/llb/llb1Matrix.c", + "src/bdd/llb/llb1Pivot.c", + "src/bdd/llb/llb1Reach.c", + "src/bdd/llb/llb1Sched.c", + "src/bdd/llb/llb2Bad.c", + "src/bdd/llb/llb2Core.c", + "src/bdd/llb/llb2Driver.c", + "src/bdd/llb/llb2Dump.c", + "src/bdd/llb/llb2Flow.c", + "src/bdd/llb/llb2Image.c", + "src/bdd/llb/llb3Image.c", + "src/bdd/llb/llb3Nonlin.c", + "src/bdd/llb/llb4Cex.c", + "src/bdd/llb/llb4Image.c", + "src/bdd/llb/llb4Nonlin.c", + "src/bdd/llb/llb4Sweep.c", + + ], + hdrs = [ + "src/misc/util/abc_global.h", + ], + copts = [ + "-O2", + "-Wno-format-overflow", + "-Wno-nonnull", + "-Wno-sign-compare", + "-Wno-strict-aliasing", + "-Wno-unused-but-set-variable", + "-Wno-unused-function", + "-Wno-unused-variable", + "-Wno-write-strings", + "-Wno-sometimes-uninitialized", # This is possibly concerning. + "-Wno-unknown-warning-option", # Clang and GCC warn differently. + ], + defines = [ + "LIN64", + "SIZEOF_VOID_P=8", + "SIZEOF_LONG=8", + "SIZEOF_INT=4", + "ABC_USE_CUDD=1", + "ABC_USE_PTHREADS", + "ABC_USE_READLINE", + "_DEFAULT_SOURCE", + ], + includes = ["src/"], + linkopts = ["-ldl", "-lpthread"], + linkstatic = True, + textual_hdrs = glob( + [ + "src/base/abci/abciUnfold2.c", + "src/base/abci/abcDarUnfold2.c", + "src/aig/saig/saigUnfold2.c", + ], + ), + deps = [ + "@dk_thrysoee_libedit//:pretend_to_be_gnu_readline_system", + ], +) diff --git a/dependency_support/edu_berkeley_abc/workspace.bzl b/dependency_support/edu_berkeley_abc/workspace.bzl new file mode 100644 index 0000000000..0f169ece13 --- /dev/null +++ b/dependency_support/edu_berkeley_abc/workspace.bzl @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the ABC system for sequential synthesis and verification, used by yosys.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "edu_berkeley_abc", + urls = [ + "https://github.com/berkeley-abc/abc/archive/a918e2dab1f951eb7e869f07b57f648b9a583561.zip", + ], + strip_prefix = "abc-a918e2dab1f951eb7e869f07b57f648b9a583561", + sha256 = "e2cb19f5c6a41cd059d749beb066afdc7759a2c6da822a975a73cfcd014ea3e6", + build_file = Label("//dependency_support:edu_berkeley_abc/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/flex/BUILD b/dependency_support/flex/BUILD new file mode 100644 index 0000000000..a8547aa717 --- /dev/null +++ b/dependency_support/flex/BUILD @@ -0,0 +1,19 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["notice"]) # BSD + +exports_files([ + "wc_test.sh", +]) diff --git a/dependency_support/flex/bundled.BUILD.bazel b/dependency_support/flex/bundled.BUILD.bazel new file mode 100644 index 0000000000..ccdd528636 --- /dev/null +++ b/dependency_support/flex/bundled.BUILD.bazel @@ -0,0 +1,159 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# !!! DO NOT DEPEND ON THIS PACKAGE DIRECTLY !!! +# This package exists only to support the genlex rule, and all users should use +# that if possible rather than using the binary directly through a genrule. + +package(default_visibility = ["//visibility:private"]) + +load("@com_google_xls//dependency_support:copy.bzl", "copy") +load("@com_google_xls//dependency_support/flex:flex.bzl", "genlex") +load("@com_google_xls//dependency_support:pseudo_configure.bzl", "pseudo_configure") + +licenses(["notice"]) # BSD + +exports_files(["LICENSE"]) + +VERSION = "2.6.4" + +# Generate the skel.c file. The generated file is included in the Flex +# distribution, but we have local patches that require regeneration. This +# follows the command at line 94 of src/Makefile.am. +genrule( + name = "skel_c", + srcs = [ + "src/flex.skl", + "src/mkskel.sh", + "src/flexint.h", + "src/tables_shared.h", + "src/tables_shared.c", + ], + outs = ["src/skel.c"], + cmd = ("$(location src/mkskel.sh) `dirname $(location src/flex.skl)` " + + "$(location @org_gnu_m4//:m4) " + VERSION + " > $@"), + tools = [ + "@org_gnu_m4//:m4", + ], +) + +# Generate the stage1scan.c file. This follows the non-ENABLE_BOOTSTRAP command +# at line 102 of src/Makefile.am. +genrule( + name = "stage1scan", + srcs = ["src/scan.c"], + outs = ["src/stage1scan.c"], + cmd = """sed 's|^\\(#line .*\\)"'$$(basename $< | sed 's|[][\\\\.*]|\\\\&|g')'"|\\1"$@"|g' $< > $@""", +) + +# These need to be a separate rule, as cc_binary does not support textual_hdrs, +# and many of these headers are not standalone. +cc_library( + name = "flex_internal_headers", + textual_hdrs = [ + "src/config.h", + "src/flexdef.h", + "src/flexint.h", + "src/gettext.h", + "src/options.h", + "src/parse.h", + "src/scanopt.h", + "src/tables.h", + "src/tables_shared.h", + "src/version.h", + ], +) + +# Allows users to '#include ' correctly in their own code. +cc_library( + name = "FlexLexer", + hdrs = ["src/FlexLexer.h"], + includes = ["src"], + visibility = ["//visibility:public"], +) + +exports_files( + ["src/FlexLexer.h"], + visibility = ["//dependency_support/flex/v2_5_35:__pkg__"], +) + +# The "flex" binary. This should only be used through genlex rules, not with +# genrules. +cc_binary( + name = "flex", + srcs = [ + "src/buf.c", + "src/ccl.c", + "src/dfa.c", + "src/ecs.c", + "src/filter.c", + "src/gen.c", + "src/main.c", + "src/misc.c", + "src/nfa.c", + "src/options.c", + "src/parse.c", + "src/regex.c", + "src/scanflags.c", + "src/scanopt.c", + "src/skel.c", + "src/stage1scan.c", + "src/sym.c", + "src/tables.c", + "src/tables_shared.c", + "src/tblcmp.c", + "src/yylex.c", + ], + includes = ["src"], + copts = [ + "-DHAVE_CONFIG_H", + "-DLOCALEDIR='\"" + "this_LOCALEDIR_does_not_exist" + "\"'", + "-Wno-format-truncation", + "-Wno-misleading-indentation", + "-Wno-pointer-sign", + "-Wno-stringop-truncation", + ], + visibility = ["//visibility:public"], + deps = [ + ":flex_internal_headers", + ], +) + +pseudo_configure( + name = "config_h", + src = "src/config.h.in", + out = "src/config.h", + defs = ['ENABLE_NLS', 'HAVE_ALLOCA', 'HAVE_ALLOCA_H', 'HAVE_DCGETTEXT', 'HAVE_DLFCN_H', 'HAVE_DUP2', 'HAVE_FORK', 'HAVE_GETTEXT', 'HAVE_INTTYPES_H', 'HAVE_LIBINTL_H', 'HAVE_LIBM', 'HAVE_LIMITS_H', 'HAVE_LOCALE_H', 'HAVE_MALLOC', 'HAVE_MALLOC_H', 'HAVE_MEMORY_H', 'HAVE_MEMSET', 'HAVE_NETINET_IN_H', 'HAVE_POW', 'HAVE_PTHREAD_H', 'HAVE_REALLOC', 'HAVE_REGCOMP', 'HAVE_REGEX_H', 'HAVE_SETLOCALE', 'HAVE_STDBOOL_H', 'HAVE_STDINT_H', 'HAVE_STDLIB_H', 'HAVE_STRCASECMP', 'HAVE_STRCHR', 'HAVE_STRDUP', 'HAVE_STRINGS_H', 'HAVE_STRING_H', 'HAVE_STRTOL', 'HAVE_SYS_STAT_H', 'HAVE_SYS_TYPES_H', 'HAVE_SYS_WAIT_H', 'HAVE_UNISTD_H', 'HAVE_VFORK', 'HAVE_WORKING_FORK', 'HAVE_WORKING_VFORK', 'HAVE__BOOL', 'STDC_HEADERS'], + mappings = {'LT_OBJDIR': '".libs"', 'M4': '"M4_environment_variable_must_be_set"', 'PACKAGE': '"flex"', 'PACKAGE_BUGREPORT': '"NA"', 'PACKAGE_NAME': '"the fast lexical analyser generator"', 'PACKAGE_STRING': '"the fast lexical analyser generator 2.6.4"', 'PACKAGE_TARNAME': '"flex"', 'PACKAGE_URL': '""', 'PACKAGE_VERSION': '"2.6.4"', 'VERSION': '"2.6.4"'}, +) + +genlex( + name = "wc_l", + src = "examples/fastwc/wc4.l", + out = "wc.c", +) + +cc_binary( + name = "wc", + srcs = ["wc.c"], +) + +sh_test( + name = "wc_test", + srcs = ["@com_google_xls//dependency_support/flex:wc_test.sh"], + args = ["$(location :wc)"], + data = [ + ":wc", + ], +) diff --git a/dependency_support/flex/flex.bzl b/dependency_support/flex/flex.bzl new file mode 100644 index 0000000000..311cdb9955 --- /dev/null +++ b/dependency_support/flex/flex.bzl @@ -0,0 +1,135 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Build rule for generating C or C++ sources with Flex. + +IMPORTANT: we _strongly recommend_ that you include a unique and project- +specific `%option prefix="myproject"` directive in your scanner spec to avoid +very hard-to-debug symbol name conflict problems if two scanners are linked +into the same dynamically-linked executable. Consider using ANTLR for new +projects. + +By default, flex includes the definition of a static function `yyunput` in its +output. If you never use the lex `unput` function in your lex rules, however, +`yyunput` will never be called. This causes problems building the output file, +as llvm issues warnings about static functions that are never called. To avoid +this problem, use `%option nounput` in the declarations section if your lex +rules never use `unput`. + +Note that if you use the c++ mode of flex, you will need to include the +boilerplate header `FlexLexer.h` file in any `cc_library` which includes the +generated flex scanner directly. This is typically done by +`#include ` with a declared BUILD dependency on +`@flex//:FlexLexer`. + +Flex invokes m4 behind the scenes to generate the output scanner. As such, +all genlex rules have an implicit dependency on `@org_gnu_m4//:m4`. Note +also that certain M4 control sequences (notably exactly the strings `"[["` and +`"]]"`) are not correctly handled by flex as a result. + +Examples +-------- + +This is a simple example. +``` +genlex( + name = "html_lex_lex", + src = "html.lex", + out = "html_lexer.c", +) +``` + +This example uses a `.tab.hh` file. +``` +genlex( + name = "rules_l", + src = "rules.lex", + includes = [ + "rules.tab.hh", + ], + out = "rules.yy.cc", +) +``` +""" + +def _genlex_impl(ctx): + """Implementation for genlex rule.""" + + # Compute the prefix, if not specified. + if ctx.attr.prefix: + prefix = ctx.attr.prefix + else: + prefix = ctx.file.src.basename.partition(".")[0] + + # Construct the arguments. + args = ctx.actions.args() + args.add("-o", ctx.outputs.out) + outputs = [ctx.outputs.out] + if ctx.outputs.header_out: + args.add("--header-file=%s" % ctx.outputs.header_out.path) + outputs.append(ctx.outputs.header_out) + args.add("-P", prefix) + args.add_all(ctx.attr.lexopts) + args.add(ctx.file.src) + + ctx.actions.run( + executable = ctx.executable._flex, + env = { + "M4": ctx.executable._m4.path, + }, + arguments = [args], + inputs = ctx.files.src + ctx.files.includes, + tools = [ctx.executable._m4], + outputs = outputs, + mnemonic = "Flex", + progress_message = "Generating %s from %s" % ( + ctx.outputs.out.short_path, + ctx.file.src.short_path, + ), + ) + +genlex = rule( + implementation = _genlex_impl, + doc = "Generate C/C++-language sources from a lex file using Flex.", + attrs = { + "src": attr.label( + mandatory = True, + allow_single_file = [".l", ".ll", ".lex", ".lpp"], + doc = "The .lex source file for this rule", + ), + "includes": attr.label_list( + allow_files = True, + doc = "A list of headers that are included by the .lex file", + ), + "out": attr.output(mandatory = True, doc = "The generated source file"), + "header_out": attr.output(mandatory = False, doc = "The generated header file"), + "prefix": attr.string( + doc = "External symbol prefix for Flex. This string is " + + "passed to flex as the -P option, causing the resulting C " + + "file to define external functions named 'prefix'text, " + + "'prefix'in, etc. The default is the basename of the source" + + "file without the .lex extension.", + ), + "lexopts": attr.string_list( + doc = "A list of options to be added to the flex command line.", + ), + "_flex": attr.label( + default = "@flex//:flex", + executable = True, + cfg = "host", + ), + "_m4": attr.label(default = "@org_gnu_m4//:m4", executable = True, cfg = "host"), + }, + output_to_genfiles = True, +) diff --git a/dependency_support/flex/wc_test.sh b/dependency_support/flex/wc_test.sh new file mode 100755 index 0000000000..c321b6eb2b --- /dev/null +++ b/dependency_support/flex/wc_test.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +RESULT=$(echo "ab cde f" | $1) + +if [ "$RESULT" == " 1 3 9" ]; then + echo "Success" +else + >&2 echo "Encountered unexpected result $RESULT" + exit 1 +fi diff --git a/dependency_support/flex/workspace.bzl b/dependency_support/flex/workspace.bzl new file mode 100644 index 0000000000..c5f58b397d --- /dev/null +++ b/dependency_support/flex/workspace.bzl @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the Flex lexer generator, used by iverilog.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "flex", + urls = [ + "https://github.com/westes/flex/files/981163/flex-2.6.4.tar.gz", + ], + strip_prefix = "flex-2.6.4", + sha256 = "e87aae032bf07c26f85ac0ed3250998c37621d95f8bd748b31f15b33c45ee995", + build_file = Label("//dependency_support:flex/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/initialize_external.bzl b/dependency_support/initialize_external.bzl new file mode 100644 index 0000000000..4cdae79ec5 --- /dev/null +++ b/dependency_support/initialize_external.bzl @@ -0,0 +1,25 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Provides helper that initializes external repositories with third-party code.""" + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") +load("@pybind11_bazel//:python_configure.bzl", "python_configure") + +def initialize_external_repositories(): + """Calls set-up methods for external repositories that require that.""" + bazel_skylib_workspace() + protobuf_deps() + python_configure(name = "local_config_python") diff --git a/dependency_support/llvm/BUILD b/dependency_support/llvm/BUILD new file mode 100644 index 0000000000..dc4ef66fb3 --- /dev/null +++ b/dependency_support/llvm/BUILD @@ -0,0 +1,25 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package( + licenses = ["notice"], # Apache 2.0 +) + +py_binary( + name = "expand_cmake_vars", + srcs = ["expand_cmake_vars.py"], + python_version = "PY3", + srcs_version = "PY3", + visibility = ["@llvm//:__subpackages__"], +) diff --git a/dependency_support/llvm/bundled.BUILD.bazel b/dependency_support/llvm/bundled.BUILD.bazel new file mode 100644 index 0000000000..83719a0779 --- /dev/null +++ b/dependency_support/llvm/bundled.BUILD.bazel @@ -0,0 +1,4592 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Bazel BUILD file for LLVM. +# +# This BUILD file is auto-generated; do not edit! + +licenses(["notice"]) + +exports_files(["LICENSE.TXT"]) + +load( + "@com_google_xls//dependency_support/llvm:llvm.bzl", + "cmake_var_string", + "expand_cmake_vars", + "gentbl", + "llvm_all_cmake_vars", + "llvm_copts", + "llvm_defines", + "llvm_linkopts", + "llvm_support_platform_specific_srcs_glob", +) +load( + "@com_google_xls//dependency_support/llvm:common.bzl", + "template_rule", +) + +package(default_visibility = ["//visibility:public"]) + +llvm_host_triple = "x86_64-unknown-linux_gnu" + +llvm_targets = [ + "AArch64", + "AMDGPU", + "ARM", + "NVPTX", + "PowerPC", + "X86", +] + +llvm_target_asm_parsers = llvm_targets + +llvm_target_asm_printers = llvm_targets + +llvm_target_disassemblers = llvm_targets + +# Performs CMake variable substitutions on configuration header files. +expand_cmake_vars( + name = "config_gen", + src = "include/llvm/Config/config.h.cmake", + cmake_vars = llvm_all_cmake_vars, + dst = "include/llvm/Config/config.h", +) + +expand_cmake_vars( + name = "llvm_config_gen", + src = "include/llvm/Config/llvm-config.h.cmake", + cmake_vars = llvm_all_cmake_vars, + dst = "include/llvm/Config/llvm-config.h", +) + +expand_cmake_vars( + name = "abi_breaking_gen", + src = "include/llvm/Config/abi-breaking.h.cmake", + cmake_vars = llvm_all_cmake_vars, + dst = "include/llvm/Config/abi-breaking.h", +) + +# Performs macro expansions on .def.in files +template_rule( + name = "targets_def_gen", + src = "include/llvm/Config/Targets.def.in", + out = "include/llvm/Config/Targets.def", + substitutions = { + "@LLVM_ENUM_TARGETS@": "\n".join( + ["LLVM_TARGET({})".format(t) for t in llvm_targets], + ), + }, +) + +template_rule( + name = "asm_parsers_def_gen", + src = "include/llvm/Config/AsmParsers.def.in", + out = "include/llvm/Config/AsmParsers.def", + substitutions = { + "@LLVM_ENUM_ASM_PARSERS@": "\n".join( + ["LLVM_ASM_PARSER({})".format(t) for t in llvm_target_asm_parsers], + ), + }, +) + +template_rule( + name = "asm_printers_def_gen", + src = "include/llvm/Config/AsmPrinters.def.in", + out = "include/llvm/Config/AsmPrinters.def", + substitutions = { + "@LLVM_ENUM_ASM_PRINTERS@": "\n".join( + ["LLVM_ASM_PRINTER({})".format(t) for t in llvm_target_asm_printers], + ), + }, +) + +template_rule( + name = "disassemblers_def_gen", + src = "include/llvm/Config/Disassemblers.def.in", + out = "include/llvm/Config/Disassemblers.def", + substitutions = { + "@LLVM_ENUM_DISASSEMBLERS@": "\n".join( + ["LLVM_DISASSEMBLER({})".format(t) for t in llvm_target_disassemblers], + ), + }, +) + +# A common library that all LLVM targets depend on. +# TODO(b/113996071): We need to glob all potentially #included files and stage +# them here because LLVM's build files are not strict headers clean, and remote +# build execution requires all inputs to be depended upon. +cc_library( + name = "config", + hdrs = glob([ + "**/*.h", + "**/*.def", + "**/*.inc.cpp", + ]) + [ + "include/llvm/Config/AsmParsers.def", + "include/llvm/Config/AsmPrinters.def", + "include/llvm/Config/Disassemblers.def", + "include/llvm/Config/Targets.def", + "include/llvm/Config/config.h", + "include/llvm/Config/llvm-config.h", + "include/llvm/Config/abi-breaking.h", + ], + defines = llvm_defines, + includes = ["include"], +) + +# A creator of an empty file include/llvm/Support/VCSRevision.h. +# This is usually populated by the upstream build infrastructure, but in this +# case we leave it blank. See upstream revision r300160. +genrule( + name = "vcs_revision_gen", + srcs = [], + outs = ["include/llvm/Support/VCSRevision.h"], + cmd = "echo '' > \"$@\"", +) + +# Rules that apply the LLVM tblgen tool. +gentbl( + name = "attributes_gen", + tbl_outs = [("-gen-attrs", "include/llvm/IR/Attributes.inc")], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Attributes.td", + td_srcs = ["include/llvm/IR/Attributes.td"], +) + +gentbl( + name = "instcombine_transforms_gen", + tbl_outs = [( + "-gen-searchable-tables", + "lib/Transforms/InstCombine/InstCombineTables.inc", + )], + tblgen = ":llvm-tblgen", + td_file = "lib/Transforms/InstCombine/InstCombineTables.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]) + ["include/llvm/TableGen/SearchableTable.td"], +) + +gentbl( + name = "intrinsic_enums_gen", + tbl_outs = [("-gen-intrinsic-enums", "include/llvm/IR/IntrinsicEnums.inc")], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "aarch64_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=aarch64", + "include/llvm/IR/IntrinsicsAArch64.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "amdgcn_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=amdgcn", + "include/llvm/IR/IntrinsicsAMDGPU.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "arm_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=arm", + "include/llvm/IR/IntrinsicsARM.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "bpf_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=bpf", + "include/llvm/IR/IntrinsicsBPF.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "hexagon_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=hexagon", + "include/llvm/IR/IntrinsicsHexagon.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "mips_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=mips", + "include/llvm/IR/IntrinsicsMips.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "nvvm_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=nvvm", + "include/llvm/IR/IntrinsicsNVPTX.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "ppc_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=ppc", + "include/llvm/IR/IntrinsicsPowerPC.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "r600_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=r600", + "include/llvm/IR/IntrinsicsR600.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "riscv_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=riscv", + "include/llvm/IR/IntrinsicsRISCV.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "s390_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=s390", + "include/llvm/IR/IntrinsicsS390.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "wasm_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=wasm", + "include/llvm/IR/IntrinsicsWebAssembly.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "x86_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=x86", + "include/llvm/IR/IntrinsicsX86.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "xcore_enums_gen", + tbl_outs = [( + "-gen-intrinsic-enums -intrinsic-prefix=xcore", + "include/llvm/IR/IntrinsicsXCore.h", + )], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +gentbl( + name = "intrinsics_impl_gen", + tbl_outs = [("-gen-intrinsic-impl", "include/llvm/IR/IntrinsicImpl.inc")], + tblgen = ":llvm-tblgen", + td_file = "include/llvm/IR/Intrinsics.td", + td_srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + ]), +) + +cc_library( + name = "utils_tablegen", + srcs = glob([ + "utils/TableGen/GlobalISel/*.cpp", + ]), + hdrs = glob([ + "utils/TableGen/GlobalISel/*.h", + ]), + deps = [ + ":tablegen", + ], +) + +# Binary targets used by Tensorflow. +cc_binary( + name = "llvm-tblgen", + srcs = glob([ + "utils/TableGen/*.cpp", + "utils/TableGen/*.h", + ]), + copts = llvm_copts, + linkopts = llvm_linkopts, + stamp = 0, + deps = [ + ":config", + ":support", + ":tablegen", + ":utils_tablegen", + ], +) + +cc_binary( + name = "FileCheck", + testonly = 1, + srcs = glob([ + "utils/FileCheck/*.cpp", + "utils/FileCheck/*.h", + ]), + copts = llvm_copts, + linkopts = llvm_linkopts, + stamp = 0, + deps = [":support"], +) + +llvm_target_list = [ + { + "name": "AArch64", + "lower_name": "aarch64", + "short_name": "AArch64", + "tbl_outs": [ + ("-gen-register-bank", "lib/Target/AArch64/AArch64GenRegisterBank.inc"), + ("-gen-register-info", "lib/Target/AArch64/AArch64GenRegisterInfo.inc"), + ("-gen-instr-info", "lib/Target/AArch64/AArch64GenInstrInfo.inc"), + ("-gen-emitter", "lib/Target/AArch64/AArch64GenMCCodeEmitter.inc"), + ("-gen-pseudo-lowering", "lib/Target/AArch64/AArch64GenMCPseudoLowering.inc"), + ("-gen-asm-writer", "lib/Target/AArch64/AArch64GenAsmWriter.inc"), + ("-gen-asm-writer -asmwriternum=1", "lib/Target/AArch64/AArch64GenAsmWriter1.inc"), + ("-gen-asm-matcher", "lib/Target/AArch64/AArch64GenAsmMatcher.inc"), + ("-gen-dag-isel", "lib/Target/AArch64/AArch64GenDAGISel.inc"), + ("-gen-fast-isel", "lib/Target/AArch64/AArch64GenFastISel.inc"), + ("-gen-global-isel", "lib/Target/AArch64/AArch64GenGlobalISel.inc"), + ("-gen-global-isel-combiner -combiners=AArch64PreLegalizerCombinerHelper", "lib/Target/AArch64/AArch64GenGICombiner.inc"), + ("-gen-callingconv", "lib/Target/AArch64/AArch64GenCallingConv.inc"), + ("-gen-subtarget", "lib/Target/AArch64/AArch64GenSubtargetInfo.inc"), + ("-gen-disassembler", "lib/Target/AArch64/AArch64GenDisassemblerTables.inc"), + ("-gen-searchable-tables", "lib/Target/AArch64/AArch64GenSystemOperands.inc"), + ], + }, + { + "name": "AMDGPU", + "lower_name": "amdgpu", + "short_name": "AMDGPU", + "tbl_outs": [ + ("-gen-register-bank", "lib/Target/AMDGPU/AMDGPUGenRegisterBank.inc"), + ("-gen-register-info", "lib/Target/AMDGPU/AMDGPUGenRegisterInfo.inc"), + ("-gen-instr-info", "lib/Target/AMDGPU/AMDGPUGenInstrInfo.inc"), + ("-gen-dag-isel", "lib/Target/AMDGPU/AMDGPUGenDAGISel.inc"), + ("-gen-callingconv", "lib/Target/AMDGPU/AMDGPUGenCallingConv.inc"), + ("-gen-subtarget", "lib/Target/AMDGPU/AMDGPUGenSubtargetInfo.inc"), + ("-gen-emitter", "lib/Target/AMDGPU/AMDGPUGenMCCodeEmitter.inc"), + ("-gen-dfa-packetizer", "lib/Target/AMDGPU/AMDGPUGenDFAPacketizer.inc"), + ("-gen-asm-writer", "lib/Target/AMDGPU/AMDGPUGenAsmWriter.inc"), + ("-gen-asm-matcher", "lib/Target/AMDGPU/AMDGPUGenAsmMatcher.inc"), + ("-gen-disassembler", "lib/Target/AMDGPU/AMDGPUGenDisassemblerTables.inc"), + ("-gen-pseudo-lowering", "lib/Target/AMDGPU/AMDGPUGenMCPseudoLowering.inc"), + ("-gen-searchable-tables", "lib/Target/AMDGPU/AMDGPUGenSearchableTables.inc"), + ], + "tbl_deps": [ + ":amdgpu_isel_target_gen", + ], + }, + { + "name": "AMDGPU", + "lower_name": "amdgpu_r600", + "short_name": "R600", + "tbl_outs": [ + ("-gen-asm-writer", "lib/Target/AMDGPU/R600GenAsmWriter.inc"), + ("-gen-callingconv", "lib/Target/AMDGPU/R600GenCallingConv.inc"), + ("-gen-dag-isel", "lib/Target/AMDGPU/R600GenDAGISel.inc"), + ("-gen-dfa-packetizer", "lib/Target/AMDGPU/R600GenDFAPacketizer.inc"), + ("-gen-instr-info", "lib/Target/AMDGPU/R600GenInstrInfo.inc"), + ("-gen-emitter", "lib/Target/AMDGPU/R600GenMCCodeEmitter.inc"), + ("-gen-register-info", "lib/Target/AMDGPU/R600GenRegisterInfo.inc"), + ("-gen-subtarget", "lib/Target/AMDGPU/R600GenSubtargetInfo.inc"), + ], + }, + { + "name": "ARM", + "lower_name": "arm", + "short_name": "ARM", + "tbl_outs": [ + ("-gen-register-bank", "lib/Target/ARM/ARMGenRegisterBank.inc"), + ("-gen-register-info", "lib/Target/ARM/ARMGenRegisterInfo.inc"), + ("-gen-searchable-tables", "lib/Target/ARM/ARMGenSystemRegister.inc"), + ("-gen-instr-info", "lib/Target/ARM/ARMGenInstrInfo.inc"), + ("-gen-emitter", "lib/Target/ARM/ARMGenMCCodeEmitter.inc"), + ("-gen-pseudo-lowering", "lib/Target/ARM/ARMGenMCPseudoLowering.inc"), + ("-gen-asm-writer", "lib/Target/ARM/ARMGenAsmWriter.inc"), + ("-gen-asm-matcher", "lib/Target/ARM/ARMGenAsmMatcher.inc"), + ("-gen-dag-isel", "lib/Target/ARM/ARMGenDAGISel.inc"), + ("-gen-fast-isel", "lib/Target/ARM/ARMGenFastISel.inc"), + ("-gen-global-isel", "lib/Target/ARM/ARMGenGlobalISel.inc"), + ("-gen-callingconv", "lib/Target/ARM/ARMGenCallingConv.inc"), + ("-gen-subtarget", "lib/Target/ARM/ARMGenSubtargetInfo.inc"), + ("-gen-disassembler", "lib/Target/ARM/ARMGenDisassemblerTables.inc"), + ], + }, + { + "name": "NVPTX", + "lower_name": "nvptx", + "short_name": "NVPTX", + "tbl_outs": [ + ("-gen-register-info", "lib/Target/NVPTX/NVPTXGenRegisterInfo.inc"), + ("-gen-instr-info", "lib/Target/NVPTX/NVPTXGenInstrInfo.inc"), + ("-gen-asm-writer", "lib/Target/NVPTX/NVPTXGenAsmWriter.inc"), + ("-gen-dag-isel", "lib/Target/NVPTX/NVPTXGenDAGISel.inc"), + ("-gen-subtarget", "lib/Target/NVPTX/NVPTXGenSubtargetInfo.inc"), + ], + }, + { + "name": "PowerPC", + "lower_name": "powerpc", + "short_name": "PPC", + "tbl_outs": [ + ("-gen-asm-writer", "lib/Target/PowerPC/PPCGenAsmWriter.inc"), + ("-gen-asm-matcher", "lib/Target/PowerPC/PPCGenAsmMatcher.inc"), + ("-gen-emitter", "lib/Target/PowerPC/PPCGenMCCodeEmitter.inc"), + ("-gen-register-info", "lib/Target/PowerPC/PPCGenRegisterInfo.inc"), + ("-gen-instr-info", "lib/Target/PowerPC/PPCGenInstrInfo.inc"), + ("-gen-dag-isel", "lib/Target/PowerPC/PPCGenDAGISel.inc"), + ("-gen-fast-isel", "lib/Target/PowerPC/PPCGenFastISel.inc"), + ("-gen-callingconv", "lib/Target/PowerPC/PPCGenCallingConv.inc"), + ("-gen-subtarget", "lib/Target/PowerPC/PPCGenSubtargetInfo.inc"), + ("-gen-disassembler", "lib/Target/PowerPC/PPCGenDisassemblerTables.inc"), + ], + }, + { + "name": "X86", + "lower_name": "x86", + "short_name": "X86", + "tbl_outs": [ + ("-gen-register-bank", "lib/Target/X86/X86GenRegisterBank.inc"), + ("-gen-register-info", "lib/Target/X86/X86GenRegisterInfo.inc"), + ("-gen-disassembler", "lib/Target/X86/X86GenDisassemblerTables.inc"), + ("-gen-instr-info", "lib/Target/X86/X86GenInstrInfo.inc"), + ("-gen-asm-writer", "lib/Target/X86/X86GenAsmWriter.inc"), + ("-gen-asm-writer -asmwriternum=1", "lib/Target/X86/X86GenAsmWriter1.inc"), + ("-gen-asm-matcher", "lib/Target/X86/X86GenAsmMatcher.inc"), + ("-gen-dag-isel", "lib/Target/X86/X86GenDAGISel.inc"), + ("-gen-fast-isel", "lib/Target/X86/X86GenFastISel.inc"), + ("-gen-global-isel", "lib/Target/X86/X86GenGlobalISel.inc"), + ("-gen-callingconv", "lib/Target/X86/X86GenCallingConv.inc"), + ("-gen-subtarget", "lib/Target/X86/X86GenSubtargetInfo.inc"), + ("-gen-x86-EVEX2VEX-tables", "lib/Target/X86/X86GenEVEX2VEXTables.inc"), + ], + }, +] + +filegroup( + name = "common_target_td_sources", + srcs = glob([ + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + "include/llvm/TableGen/*.td", + "include/llvm/Target/*.td", + "include/llvm/Target/GlobalISel/*.td", + ]), +) + +gentbl( + name = "amdgpu_isel_target_gen", + tbl_outs = [ + ("-gen-global-isel", "lib/Target/AMDGPU/AMDGPUGenGlobalISel.inc"), + ("-gen-global-isel-combiner -combiners=AMDGPUPreLegalizerCombinerHelper", "lib/Target/AMDGPU/AMDGPUGenPreLegalizeGICombiner.inc"), + ("-gen-global-isel-combiner -combiners=AMDGPUPostLegalizerCombinerHelper", "lib/Target/AMDGPU/AMDGPUGenPostLegalizeGICombiner.inc"), + ], + tblgen = ":llvm-tblgen", + td_file = "lib/Target/AMDGPU/AMDGPUGISel.td", + td_srcs = [ + ":common_target_td_sources", + ] + glob([ + "lib/Target/AMDGPU/*.td", + ]), +) + +[ + gentbl( + name = target["lower_name"] + "_target_gen", + tbl_outs = target["tbl_outs"], + tblgen = ":llvm-tblgen", + td_file = ("lib/Target/" + target["name"] + "/" + target["short_name"] + + ".td"), + td_srcs = glob([ + "lib/Target/" + target["name"] + "/*.td", + "include/llvm/CodeGen/*.td", + "include/llvm/IR/Intrinsics*.td", + "include/llvm/TableGen/*.td", + "include/llvm/Target/*.td", + "include/llvm/Target/GlobalISel/*.td", + ]), + deps = target.get("tbl_deps", []), + ) + for target in llvm_target_list +] + +# This target is used to provide *.def files to x86_code_gen. +# Files with '.def' extension are not allowed in 'srcs' of 'cc_library' rule. +cc_library( + name = "x86_defs", + hdrs = glob([ + "lib/Target/X86/*.def", + ]), + visibility = ["//visibility:private"], +) + +# This filegroup provides the docker build script in LLVM repo +filegroup( + name = "docker", + srcs = glob([ + "utils/docker/build_docker_image.sh", + ]), + visibility = ["//visibility:public"], +) + +py_binary( + name = "lit", + srcs = ["utils/lit/lit.py"] + glob(["utils/lit/lit/**/*.py"]), +) + +cc_binary( + name = "count", + srcs = ["utils/count/count.c"], +) + +cc_binary( + name = "not", + srcs = ["utils/not/not.cpp"], + copts = llvm_copts, + linkopts = llvm_linkopts, + deps = [ + ":support", + ], +) + +cc_library( + name = "all_targets", + deps = [ + ":aarch64_code_gen", + ":amdgpu_code_gen", + ":arm_code_gen", + ":nvptx_code_gen", + ":powerpc_code_gen", + ":x86_code_gen", + ], +) + +cc_library( + name = "aarch64_asm_parser", + srcs = glob([ + "lib/Target/AArch64/AsmParser/*.c", + "lib/Target/AArch64/AsmParser/*.cpp", + "lib/Target/AArch64/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AArch64/AsmParser/*.h", + "include/llvm/Target/AArch64/AsmParser/*.def", + "include/llvm/Target/AArch64/AsmParser/*.inc", + "lib/Target/AArch64/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AArch64"], + deps = [ + ":aarch64_desc", + ":aarch64_info", + ":aarch64_utils", + ":config", + ":mc", + ":mc_parser", + ":support", + ], +) + +cc_library( + name = "aarch64_code_gen", + srcs = glob([ + "lib/Target/AArch64/*.c", + "lib/Target/AArch64/*.cpp", + "lib/Target/AArch64/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AArch64/*.h", + "include/llvm/Target/AArch64/*.def", + "include/llvm/Target/AArch64/*.inc", + "lib/Target/AArch64/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AArch64"], + deps = [ + ":aarch64_desc", + ":aarch64_info", + ":aarch64_utils", + ":analysis", + ":asm_printer", + ":cf_guard", + ":code_gen", + ":config", + ":core", + ":global_i_sel", + ":mc", + ":scalar", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "aarch64_desc", + srcs = glob([ + "lib/Target/AArch64/MCTargetDesc/*.c", + "lib/Target/AArch64/MCTargetDesc/*.cpp", + "lib/Target/AArch64/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AArch64/MCTargetDesc/*.h", + "include/llvm/Target/AArch64/MCTargetDesc/*.def", + "include/llvm/Target/AArch64/MCTargetDesc/*.inc", + "lib/Target/AArch64/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AArch64"], + deps = [ + ":aarch64_info", + ":aarch64_target_gen", + ":aarch64_utils", + ":attributes_gen", + ":binary_format", + ":config", + ":intrinsic_enums_gen", + ":intrinsics_impl_gen", + ":mc", + ":support", + ], +) + +cc_library( + name = "aarch64_disassembler", + srcs = glob([ + "lib/Target/AArch64/Disassembler/*.c", + "lib/Target/AArch64/Disassembler/*.cpp", + "lib/Target/AArch64/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AArch64/Disassembler/*.h", + "include/llvm/Target/AArch64/Disassembler/*.def", + "include/llvm/Target/AArch64/Disassembler/*.inc", + "lib/Target/AArch64/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AArch64"], + deps = [ + ":aarch64_desc", + ":aarch64_info", + ":aarch64_utils", + ":config", + ":mc", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "aarch64_info", + srcs = glob([ + "lib/Target/AArch64/TargetInfo/*.c", + "lib/Target/AArch64/TargetInfo/*.cpp", + "lib/Target/AArch64/TargetInfo/*.inc", + "lib/Target/AArch64/MCTargetDesc/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/AArch64/TargetInfo/*.h", + "include/llvm/Target/AArch64/TargetInfo/*.def", + "include/llvm/Target/AArch64/TargetInfo/*.inc", + "lib/Target/AArch64/*.def", + "lib/Target/AArch64/AArch64*.h", + "lib/Target/AArch64/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AArch64"], + deps = [ + ":code_gen", + ":config", + ":support", + ":target", + ], +) + +cc_library( + name = "aarch64_utils", + srcs = glob([ + "lib/Target/AArch64/Utils/*.c", + "lib/Target/AArch64/Utils/*.cpp", + "lib/Target/AArch64/Utils/*.inc", + "lib/Target/AArch64/MCTargetDesc/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/AArch64/Utils/*.h", + "include/llvm/Target/AArch64/Utils/*.def", + "include/llvm/Target/AArch64/Utils/*.inc", + "lib/Target/AArch64/Utils/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AArch64"], + deps = [ + ":aarch64_target_gen", + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "amdgpu_asm_parser", + srcs = glob([ + "lib/Target/AMDGPU/AsmParser/*.c", + "lib/Target/AMDGPU/AsmParser/*.cpp", + "lib/Target/AMDGPU/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AMDGPU/AsmParser/*.h", + "include/llvm/Target/AMDGPU/AsmParser/*.def", + "include/llvm/Target/AMDGPU/AsmParser/*.inc", + "lib/Target/AMDGPU/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AMDGPU"], + deps = [ + ":amdgpu_desc", + ":amdgpu_info", + ":amdgpu_utils", + ":config", + ":mc", + ":mc_parser", + ":support", + ], +) + +cc_library( + name = "amdgpu_code_gen", + srcs = glob([ + "lib/Target/AMDGPU/*.c", + "lib/Target/AMDGPU/*.cpp", + "lib/Target/AMDGPU/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AMDGPU/*.h", + "include/llvm/Target/AMDGPU/*.def", + "include/llvm/Target/AMDGPU/*.inc", + "lib/Target/AMDGPU/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AMDGPU"], + deps = [ + ":amdgpu_desc", + ":amdgpu_info", + ":amdgpu_utils", + ":analysis", + ":asm_printer", + ":binary_format", + ":code_gen", + ":config", + ":core", + ":global_i_sel", + ":ipo", + ":mc", + ":mir_parser", + ":scalar", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ":vectorize", + ], +) + +cc_library( + name = "amdgpu_desc", + srcs = glob([ + "lib/Target/AMDGPU/MCTargetDesc/*.c", + "lib/Target/AMDGPU/MCTargetDesc/*.cpp", + "lib/Target/AMDGPU/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AMDGPU/MCTargetDesc/*.h", + "include/llvm/Target/AMDGPU/MCTargetDesc/*.def", + "include/llvm/Target/AMDGPU/MCTargetDesc/*.inc", + "lib/Target/AMDGPU/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AMDGPU"], + deps = [ + ":amdgpu_info", + ":amdgpu_utils", + ":binary_format", + ":config", + ":core", + ":mc", + ":support", + ], +) + +cc_library( + name = "amdgpu_disassembler", + srcs = glob([ + "lib/Target/AMDGPU/Disassembler/*.c", + "lib/Target/AMDGPU/Disassembler/*.cpp", + "lib/Target/AMDGPU/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AMDGPU/Disassembler/*.h", + "include/llvm/Target/AMDGPU/Disassembler/*.def", + "include/llvm/Target/AMDGPU/Disassembler/*.inc", + "lib/Target/AMDGPU/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AMDGPU"], + deps = [ + ":amdgpu_desc", + ":amdgpu_info", + ":amdgpu_utils", + ":config", + ":mc", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "amdgpu_info", + srcs = glob([ + "lib/Target/AMDGPU/TargetInfo/*.c", + "lib/Target/AMDGPU/TargetInfo/*.cpp", + "lib/Target/AMDGPU/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AMDGPU/TargetInfo/*.h", + "include/llvm/Target/AMDGPU/TargetInfo/*.def", + "include/llvm/Target/AMDGPU/TargetInfo/*.inc", + "lib/Target/AMDGPU/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AMDGPU"], + deps = [ + ":amdgpu_r600_target_gen", + ":amdgpu_target_gen", + ":config", + ":core", + ":support", + ], +) + +cc_library( + name = "amdgpu_utils", + srcs = glob([ + "lib/Target/AMDGPU/Utils/*.c", + "lib/Target/AMDGPU/Utils/*.cpp", + "lib/Target/AMDGPU/Utils/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AMDGPU/Utils/*.h", + "include/llvm/Target/AMDGPU/Utils/*.def", + "include/llvm/Target/AMDGPU/Utils/*.inc", + "lib/Target/AMDGPU/Utils/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AMDGPU"], + deps = [ + ":amdgpu_r600_target_gen", + ":amdgpu_target_gen", + ":binary_format", + ":config", + ":core", + ":mc", + ":support", + ], +) + +cc_library( + name = "arc_code_gen", + srcs = glob([ + "lib/Target/ARC/*.c", + "lib/Target/ARC/*.cpp", + "lib/Target/ARC/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/ARC/*.h", + "include/llvm/Target/ARC/*.def", + "include/llvm/Target/ARC/*.inc", + "lib/Target/ARC/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARC"], + deps = [ + ":analysis", + ":arc_desc", + ":arc_info", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":mc", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "arc_desc", + srcs = glob([ + "lib/Target/ARC/MCTargetDesc/*.c", + "lib/Target/ARC/MCTargetDesc/*.cpp", + "lib/Target/ARC/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/ARC/MCTargetDesc/*.h", + "include/llvm/Target/ARC/MCTargetDesc/*.def", + "include/llvm/Target/ARC/MCTargetDesc/*.inc", + "lib/Target/ARC/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARC"], + deps = [ + ":arc_info", + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "arc_disassembler", + srcs = glob([ + "lib/Target/ARC/Disassembler/*.c", + "lib/Target/ARC/Disassembler/*.cpp", + "lib/Target/ARC/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/ARC/Disassembler/*.h", + "include/llvm/Target/ARC/Disassembler/*.def", + "include/llvm/Target/ARC/Disassembler/*.inc", + "lib/Target/ARC/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARC"], + deps = [ + ":arc_info", + ":config", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "arc_info", + srcs = glob([ + "lib/Target/ARC/TargetInfo/*.c", + "lib/Target/ARC/TargetInfo/*.cpp", + "lib/Target/ARC/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/ARC/TargetInfo/*.h", + "include/llvm/Target/ARC/TargetInfo/*.def", + "include/llvm/Target/ARC/TargetInfo/*.inc", + "lib/Target/ARC/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARC"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "arm_asm_parser", + srcs = glob([ + "lib/Target/ARM/AsmParser/*.c", + "lib/Target/ARM/AsmParser/*.cpp", + "lib/Target/ARM/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/ARM/AsmParser/*.h", + "include/llvm/Target/ARM/AsmParser/*.def", + "include/llvm/Target/ARM/AsmParser/*.inc", + "lib/Target/ARM/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARM"], + deps = [ + ":arm_desc", + ":arm_info", + ":arm_utils", + ":config", + ":mc", + ":mc_parser", + ":support", + ], +) + +cc_library( + name = "arm_code_gen", + srcs = glob([ + "lib/Target/ARM/*.c", + "lib/Target/ARM/*.cpp", + "lib/Target/ARM/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/ARM/*.h", + "include/llvm/Target/ARM/*.def", + "include/llvm/Target/ARM/*.inc", + "lib/Target/ARM/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARM"], + deps = [ + ":analysis", + ":arm_desc", + ":arm_info", + ":arm_utils", + ":asm_printer", + ":cf_guard", + ":code_gen", + ":config", + ":core", + ":global_i_sel", + ":mc", + ":scalar", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "arm_desc", + srcs = glob([ + "lib/Target/ARM/MCTargetDesc/*.c", + "lib/Target/ARM/MCTargetDesc/*.cpp", + "lib/Target/ARM/MCTargetDesc/*.inc", + "lib/Target/ARM/*.h", + "include/llvm/CodeGen/GlobalISel/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/ARM/MCTargetDesc/*.h", + "include/llvm/Target/ARM/MCTargetDesc/*.def", + "include/llvm/Target/ARM/MCTargetDesc/*.inc", + "lib/Target/ARM/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARM"], + deps = [ + ":arm_info", + ":arm_target_gen", + ":arm_utils", + ":attributes_gen", + ":binary_format", + ":config", + ":intrinsic_enums_gen", + ":intrinsics_impl_gen", + ":mc", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "arm_disassembler", + srcs = glob([ + "lib/Target/ARM/Disassembler/*.c", + "lib/Target/ARM/Disassembler/*.cpp", + "lib/Target/ARM/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/ARM/Disassembler/*.h", + "include/llvm/Target/ARM/Disassembler/*.def", + "include/llvm/Target/ARM/Disassembler/*.inc", + "lib/Target/ARM/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARM"], + deps = [ + ":arm_desc", + ":arm_info", + ":arm_utils", + ":config", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "arm_info", + srcs = glob([ + "lib/Target/ARM/TargetInfo/*.c", + "lib/Target/ARM/TargetInfo/*.cpp", + "lib/Target/ARM/TargetInfo/*.inc", + "lib/Target/ARM/MCTargetDesc/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/ARM/TargetInfo/*.h", + "include/llvm/Target/ARM/TargetInfo/*.def", + "include/llvm/Target/ARM/TargetInfo/*.inc", + "lib/Target/ARM/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARM"], + deps = [ + ":arm_target_gen", + ":config", + ":support", + ":target", + ], +) + +cc_library( + name = "arm_utils", + srcs = glob([ + "lib/Target/ARM/Utils/*.c", + "lib/Target/ARM/Utils/*.cpp", + "lib/Target/ARM/Utils/*.inc", + "lib/Target/ARM/MCTargetDesc/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/ARM/Utils/*.h", + "include/llvm/Target/ARM/Utils/*.def", + "include/llvm/Target/ARM/Utils/*.inc", + "lib/Target/ARM/Utils/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/ARM"], + deps = [ + ":arm_target_gen", + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "avr_asm_parser", + srcs = glob([ + "lib/Target/AVR/AsmParser/*.c", + "lib/Target/AVR/AsmParser/*.cpp", + "lib/Target/AVR/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AVR/AsmParser/*.h", + "include/llvm/Target/AVR/AsmParser/*.def", + "include/llvm/Target/AVR/AsmParser/*.inc", + "lib/Target/AVR/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AVR"], + deps = [ + ":avr_desc", + ":avr_info", + ":config", + ":mc", + ":mc_parser", + ":support", + ], +) + +cc_library( + name = "avr_code_gen", + srcs = glob([ + "lib/Target/AVR/*.c", + "lib/Target/AVR/*.cpp", + "lib/Target/AVR/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AVR/*.h", + "include/llvm/Target/AVR/*.def", + "include/llvm/Target/AVR/*.inc", + "lib/Target/AVR/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AVR"], + deps = [ + ":asm_printer", + ":avr_desc", + ":avr_info", + ":code_gen", + ":config", + ":core", + ":mc", + ":selection_dag", + ":support", + ":target", + ], +) + +cc_library( + name = "avr_desc", + srcs = glob([ + "lib/Target/AVR/MCTargetDesc/*.c", + "lib/Target/AVR/MCTargetDesc/*.cpp", + "lib/Target/AVR/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AVR/MCTargetDesc/*.h", + "include/llvm/Target/AVR/MCTargetDesc/*.def", + "include/llvm/Target/AVR/MCTargetDesc/*.inc", + "lib/Target/AVR/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AVR"], + deps = [ + ":avr_info", + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "avr_disassembler", + srcs = glob([ + "lib/Target/AVR/Disassembler/*.c", + "lib/Target/AVR/Disassembler/*.cpp", + "lib/Target/AVR/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AVR/Disassembler/*.h", + "include/llvm/Target/AVR/Disassembler/*.def", + "include/llvm/Target/AVR/Disassembler/*.inc", + "lib/Target/AVR/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AVR"], + deps = [ + ":avr_info", + ":config", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "avr_info", + srcs = glob([ + "lib/Target/AVR/TargetInfo/*.c", + "lib/Target/AVR/TargetInfo/*.cpp", + "lib/Target/AVR/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/AVR/TargetInfo/*.h", + "include/llvm/Target/AVR/TargetInfo/*.def", + "include/llvm/Target/AVR/TargetInfo/*.inc", + "lib/Target/AVR/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/AVR"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "aggressive_inst_combine", + srcs = glob([ + "lib/Transforms/AggressiveInstCombine/*.c", + "lib/Transforms/AggressiveInstCombine/*.cpp", + "lib/Transforms/AggressiveInstCombine/*.inc", + "lib/Transforms/AggressiveInstCombine/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/AggressiveInstCombine/*.h", + "include/llvm/Transforms/AggressiveInstCombine/*.def", + "include/llvm/Transforms/AggressiveInstCombine/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":support", + ":transform_utils", + ], +) + +cc_library( + name = "analysis", + srcs = glob([ + "lib/Analysis/*.c", + "lib/Analysis/*.cpp", + "lib/Analysis/*.inc", + "include/llvm/Transforms/Utils/Local.h", + "include/llvm/Transforms/Scalar.h", + "lib/Analysis/*.h", + ]), + hdrs = glob([ + "include/llvm/Analysis/*.h", + "include/llvm/Analysis/*.def", + "include/llvm/Analysis/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":config", + ":core", + ":object", + ":profile_data", + ":support", + ], +) + +cc_library( + name = "asm_parser", + srcs = glob([ + "lib/AsmParser/*.c", + "lib/AsmParser/*.cpp", + "lib/AsmParser/*.inc", + "lib/AsmParser/*.h", + ]), + hdrs = glob([ + "include/llvm/AsmParser/*.h", + "include/llvm/AsmParser/*.def", + "include/llvm/AsmParser/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":config", + ":core", + ":support", + ], +) + +cc_library( + name = "asm_printer", + srcs = glob([ + "lib/CodeGen/AsmPrinter/*.c", + "lib/CodeGen/AsmPrinter/*.cpp", + "lib/CodeGen/AsmPrinter/*.inc", + "lib/CodeGen/AsmPrinter/*.h", + ]), + hdrs = glob([ + "include/llvm/CodeGen/AsmPrinter/*.h", + "include/llvm/CodeGen/AsmPrinter/*.def", + "include/llvm/CodeGen/AsmPrinter/*.inc", + "lib/CodeGen/AsmPrinter/*.def", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":binary_format", + ":code_gen", + ":config", + ":core", + ":debug_info_code_view", + ":debug_info_dwarf", + ":debug_info_msf", + ":mc", + ":mc_parser", + ":remarks", + ":support", + ":target", + ], +) + +cc_library( + name = "bpf_asm_parser", + srcs = glob([ + "lib/Target/BPF/AsmParser/*.c", + "lib/Target/BPF/AsmParser/*.cpp", + "lib/Target/BPF/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/BPF/AsmParser/*.h", + "include/llvm/Target/BPF/AsmParser/*.def", + "include/llvm/Target/BPF/AsmParser/*.inc", + "lib/Target/BPF/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/BPF"], + deps = [ + ":bpf_desc", + ":bpf_info", + ":config", + ":mc", + ":mc_parser", + ":support", + ], +) + +cc_library( + name = "bpf_code_gen", + srcs = glob([ + "lib/Target/BPF/*.c", + "lib/Target/BPF/*.cpp", + "lib/Target/BPF/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/BPF/*.h", + "include/llvm/Target/BPF/*.def", + "include/llvm/Target/BPF/*.inc", + "lib/Target/BPF/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/BPF"], + deps = [ + ":asm_printer", + ":bpf_desc", + ":bpf_info", + ":code_gen", + ":config", + ":core", + ":mc", + ":selection_dag", + ":support", + ":target", + ], +) + +cc_library( + name = "bpf_desc", + srcs = glob([ + "lib/Target/BPF/MCTargetDesc/*.c", + "lib/Target/BPF/MCTargetDesc/*.cpp", + "lib/Target/BPF/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/BPF/MCTargetDesc/*.h", + "include/llvm/Target/BPF/MCTargetDesc/*.def", + "include/llvm/Target/BPF/MCTargetDesc/*.inc", + "lib/Target/BPF/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/BPF"], + deps = [ + ":bpf_info", + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "bpf_disassembler", + srcs = glob([ + "lib/Target/BPF/Disassembler/*.c", + "lib/Target/BPF/Disassembler/*.cpp", + "lib/Target/BPF/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/BPF/Disassembler/*.h", + "include/llvm/Target/BPF/Disassembler/*.def", + "include/llvm/Target/BPF/Disassembler/*.inc", + "lib/Target/BPF/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/BPF"], + deps = [ + ":bpf_info", + ":config", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "bpf_info", + srcs = glob([ + "lib/Target/BPF/TargetInfo/*.c", + "lib/Target/BPF/TargetInfo/*.cpp", + "lib/Target/BPF/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/BPF/TargetInfo/*.h", + "include/llvm/Target/BPF/TargetInfo/*.def", + "include/llvm/Target/BPF/TargetInfo/*.inc", + "lib/Target/BPF/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/BPF"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "binary_format", + srcs = glob([ + "lib/BinaryFormat/*.c", + "lib/BinaryFormat/*.cpp", + "lib/BinaryFormat/*.inc", + "lib/BinaryFormat/*.h", + ]), + hdrs = glob([ + "include/llvm/BinaryFormat/*.h", + "include/llvm/BinaryFormat/*.def", + "include/llvm/BinaryFormat/*.inc", + "include/llvm/BinaryFormat/ELFRelocs/*.def", + "include/llvm/BinaryFormat/WasmRelocs/*.def", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "bit_reader", + srcs = glob([ + "lib/Bitcode/Reader/*.c", + "lib/Bitcode/Reader/*.cpp", + "lib/Bitcode/Reader/*.inc", + "lib/Bitcode/Reader/*.h", + ]), + hdrs = glob([ + "include/llvm/Bitcode/Reader/*.h", + "include/llvm/Bitcode/Reader/*.def", + "include/llvm/Bitcode/Reader/*.inc", + "include/llvm/Bitcode/BitstreamReader.h", + ]), + copts = llvm_copts, + deps = [ + ":bitstream_reader", + ":config", + ":core", + ":support", + ], +) + +cc_library( + name = "bit_writer", + srcs = glob([ + "lib/Bitcode/Writer/*.c", + "lib/Bitcode/Writer/*.cpp", + "lib/Bitcode/Writer/*.inc", + "lib/Bitcode/Writer/*.h", + ]), + hdrs = glob([ + "include/llvm/Bitcode/Writer/*.h", + "include/llvm/Bitcode/Writer/*.def", + "include/llvm/Bitcode/Writer/*.inc", + "include/llvm/Bitcode/BitcodeWriter.h", + "include/llvm/Bitcode/BitcodeWriterPass.h", + "include/llvm/Bitcode/BitstreamWriter.h", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":mc", + ":object", + ":support", + ], +) + +cc_library( + name = "bitstream_reader", + srcs = glob([ + "lib/Bitstream/Reader/*.c", + "lib/Bitstream/Reader/*.cpp", + "lib/Bitstream/Reader/*.inc", + "lib/Bitstream/Reader/*.h", + ]), + hdrs = glob([ + "include/llvm/Bitstream/Reader/*.h", + "include/llvm/Bitstream/Reader/*.def", + "include/llvm/Bitstream/Reader/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "cf_guard", + srcs = glob([ + "lib/Transforms/CFGuard/*.c", + "lib/Transforms/CFGuard/*.cpp", + "lib/Transforms/CFGuard/*.inc", + "lib/Transforms/CFGuard/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/CFGuard/*.h", + "include/llvm/Transforms/CFGuard/*.def", + "include/llvm/Transforms/CFGuard/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":support", + ], +) + +cc_library( + name = "code_gen", + srcs = glob([ + "lib/CodeGen/*.c", + "lib/CodeGen/*.cpp", + "lib/CodeGen/*.inc", + "lib/CodeGen/*.h", + ]), + hdrs = glob([ + "include/llvm/CodeGen/*.h", + "include/llvm/CodeGen/*.def", + "include/llvm/CodeGen/*.inc", + "include/llvm/CodeGen/**/*.h", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":bit_reader", + ":bit_writer", + ":config", + ":core", + ":instrumentation", + ":mc", + ":profile_data", + ":scalar", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "core", + srcs = glob([ + "lib/IR/*.c", + "lib/IR/*.cpp", + "lib/IR/*.inc", + "include/llvm/Analysis/*.h", + "include/llvm/Bitcode/BitcodeReader.h", + "include/llvm/Bitcode/BitCodes.h", + "include/llvm/Bitcode/LLVMBitCodes.h", + "include/llvm/CodeGen/MachineValueType.h", + "include/llvm/CodeGen/ValueTypes.h", + "lib/IR/*.h", + ]), + hdrs = glob([ + "include/llvm/IR/*.h", + "include/llvm/IR/*.def", + "include/llvm/IR/*.inc", + "include/llvm/*.h", + "include/llvm/Analysis/*.def", + ]), + copts = llvm_copts, + deps = [ + ":aarch64_enums_gen", + ":amdgcn_enums_gen", + ":arm_enums_gen", + ":attributes_gen", + ":binary_format", + ":bpf_enums_gen", + ":config", + ":hexagon_enums_gen", + ":intrinsic_enums_gen", + ":intrinsics_impl_gen", + ":mips_enums_gen", + ":nvvm_enums_gen", + ":ppc_enums_gen", + ":r600_enums_gen", + ":remarks", + ":riscv_enums_gen", + ":s390_enums_gen", + ":support", + ":wasm_enums_gen", + ":x86_enums_gen", + ":xcore_enums_gen", + ], +) + +cc_library( + name = "coroutines", + srcs = glob([ + "lib/Transforms/Coroutines/*.c", + "lib/Transforms/Coroutines/*.cpp", + "lib/Transforms/Coroutines/*.inc", + "lib/Transforms/Coroutines/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/Coroutines/*.h", + "include/llvm/Transforms/Coroutines/*.def", + "include/llvm/Transforms/Coroutines/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":ipo", + ":scalar", + ":support", + ":transform_utils", + ], +) + +cc_library( + name = "coverage", + srcs = glob([ + "lib/ProfileData/Coverage/*.c", + "lib/ProfileData/Coverage/*.cpp", + "lib/ProfileData/Coverage/*.inc", + "lib/ProfileData/Coverage/*.h", + ]), + hdrs = glob([ + "include/llvm/ProfileData/Coverage/*.h", + "include/llvm/ProfileData/Coverage/*.def", + "include/llvm/ProfileData/Coverage/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":object", + ":profile_data", + ":support", + ], +) + +cc_library( + name = "dwarf_linker", + srcs = glob([ + "lib/DWARFLinker/*.c", + "lib/DWARFLinker/*.cpp", + "lib/DWARFLinker/*.inc", + "lib/DWARFLinker/*.h", + ]), + hdrs = glob([ + "include/llvm/DWARFLinker/*.h", + "include/llvm/DWARFLinker/*.def", + "include/llvm/DWARFLinker/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":asm_printer", + ":code_gen", + ":config", + ":debug_info_dwarf", + ":mc", + ":object", + ":support", + ], +) + +cc_library( + name = "debug_info_code_view", + srcs = glob([ + "lib/DebugInfo/CodeView/*.c", + "lib/DebugInfo/CodeView/*.cpp", + "lib/DebugInfo/CodeView/*.inc", + "lib/DebugInfo/CodeView/*.h", + ]), + hdrs = glob([ + "include/llvm/DebugInfo/CodeView/*.h", + "include/llvm/DebugInfo/CodeView/*.def", + "include/llvm/DebugInfo/CodeView/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":config", + ":debug_info_msf", + ":support", + ], +) + +cc_library( + name = "debug_info_dwarf", + srcs = glob([ + "lib/DebugInfo/DWARF/*.c", + "lib/DebugInfo/DWARF/*.cpp", + "lib/DebugInfo/DWARF/*.inc", + "lib/DebugInfo/DWARF/*.h", + ]), + hdrs = glob([ + "include/llvm/DebugInfo/DWARF/*.h", + "include/llvm/DebugInfo/DWARF/*.def", + "include/llvm/DebugInfo/DWARF/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":config", + ":mc", + ":object", + ":support", + ], +) + +cc_library( + name = "debug_info_gsym", + srcs = glob([ + "lib/DebugInfo/GSYM/*.c", + "lib/DebugInfo/GSYM/*.cpp", + "lib/DebugInfo/GSYM/*.inc", + "lib/DebugInfo/GSYM/*.h", + ]), + hdrs = glob([ + "include/llvm/DebugInfo/GSYM/*.h", + "include/llvm/DebugInfo/GSYM/*.def", + "include/llvm/DebugInfo/GSYM/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":debug_info_dwarf", + ":mc", + ":object", + ":support", + ], +) + +cc_library( + name = "debug_info_msf", + srcs = glob([ + "lib/DebugInfo/MSF/*.c", + "lib/DebugInfo/MSF/*.cpp", + "lib/DebugInfo/MSF/*.inc", + "lib/DebugInfo/MSF/*.h", + ]), + hdrs = glob([ + "include/llvm/DebugInfo/MSF/*.h", + "include/llvm/DebugInfo/MSF/*.def", + "include/llvm/DebugInfo/MSF/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "debug_info_pdb", + srcs = glob([ + "lib/DebugInfo/PDB/*.c", + "lib/DebugInfo/PDB/*.cpp", + "lib/DebugInfo/PDB/*.inc", + "lib/DebugInfo/PDB/*.h", + ]), + hdrs = glob([ + "include/llvm/DebugInfo/PDB/*.h", + "include/llvm/DebugInfo/PDB/*.def", + "include/llvm/DebugInfo/PDB/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":config", + ":debug_info_code_view", + ":debug_info_msf", + ":object", + ":support", + ], +) + +cc_library( + name = "demangle", + srcs = glob([ + "lib/Demangle/*.c", + "lib/Demangle/*.cpp", + "lib/Demangle/*.inc", + "lib/Demangle/*.h", + ]), + hdrs = glob([ + "include/llvm/Demangle/*.h", + "include/llvm/Demangle/*.def", + "include/llvm/Demangle/*.inc", + ]), + copts = llvm_copts, + deps = [":config"], +) + +cc_library( + name = "dlltool_driver", + srcs = glob([ + "lib/ToolDrivers/llvm-dlltool/*.c", + "lib/ToolDrivers/llvm-dlltool/*.cpp", + "lib/ToolDrivers/llvm-dlltool/*.inc", + "lib/ToolDrivers/llvm-dlltool/*.h", + ]), + hdrs = glob([ + "include/llvm/ToolDrivers/llvm-dlltool/*.h", + "include/llvm/ToolDrivers/llvm-dlltool/*.def", + "include/llvm/ToolDrivers/llvm-dlltool/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":object", + ":option", + ":support", + ], +) + +cc_library( + name = "execution_engine", + srcs = glob([ + "lib/ExecutionEngine/*.c", + "lib/ExecutionEngine/*.cpp", + "lib/ExecutionEngine/*.inc", + "lib/ExecutionEngine/*.h", + ]), + hdrs = glob([ + "include/llvm/ExecutionEngine/*.h", + "include/llvm/ExecutionEngine/*.def", + "include/llvm/ExecutionEngine/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":mc", + ":object", + ":runtime_dyld", + ":support", + ":target", + ], +) + +cc_library( + name = "extensions", + srcs = glob([ + "lib/Extensions/*.c", + "lib/Extensions/*.cpp", + "lib/Extensions/*.inc", + "lib/Extensions/*.h", + ]), + hdrs = glob([ + "include/llvm/Extensions/*.h", + "include/llvm/Extensions/*.def", + "include/llvm/Extensions/*.inc", + ]), + copts = llvm_copts, + deps = [":config"], +) + +cc_library( + name = "frontend_open_mp", + srcs = glob([ + "lib/Frontend/OpenMP/*.c", + "lib/Frontend/OpenMP/*.cpp", + "lib/Frontend/OpenMP/*.inc", + "lib/Frontend/OpenMP/*.h", + ]), + hdrs = glob([ + "include/llvm/Frontend/OpenMP/*.h", + "include/llvm/Frontend/OpenMP/*.def", + "include/llvm/Frontend/OpenMP/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":support", + ":transform_utils", + ], +) + +cc_library( + name = "fuzz_mutate", + srcs = glob([ + "lib/FuzzMutate/*.c", + "lib/FuzzMutate/*.cpp", + "lib/FuzzMutate/*.inc", + "lib/FuzzMutate/*.h", + ]), + hdrs = glob([ + "include/llvm/FuzzMutate/*.h", + "include/llvm/FuzzMutate/*.def", + "include/llvm/FuzzMutate/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":bit_reader", + ":bit_writer", + ":config", + ":core", + ":scalar", + ":support", + ":target", + ], +) + +cc_library( + name = "global_i_sel", + srcs = glob([ + "lib/CodeGen/GlobalISel/*.c", + "lib/CodeGen/GlobalISel/*.cpp", + "lib/CodeGen/GlobalISel/*.inc", + "lib/CodeGen/GlobalISel/*.h", + ]), + hdrs = glob([ + "include/llvm/CodeGen/GlobalISel/*.h", + "include/llvm/CodeGen/GlobalISel/*.def", + "include/llvm/CodeGen/GlobalISel/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":code_gen", + ":config", + ":core", + ":mc", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "hexagon_asm_parser", + srcs = glob([ + "lib/Target/Hexagon/AsmParser/*.c", + "lib/Target/Hexagon/AsmParser/*.cpp", + "lib/Target/Hexagon/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Hexagon/AsmParser/*.h", + "include/llvm/Target/Hexagon/AsmParser/*.def", + "include/llvm/Target/Hexagon/AsmParser/*.inc", + "lib/Target/Hexagon/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Hexagon"], + deps = [ + ":config", + ":hexagon_desc", + ":hexagon_info", + ":mc", + ":mc_parser", + ":support", + ], +) + +cc_library( + name = "hexagon_code_gen", + srcs = glob([ + "lib/Target/Hexagon/*.c", + "lib/Target/Hexagon/*.cpp", + "lib/Target/Hexagon/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Hexagon/*.h", + "include/llvm/Target/Hexagon/*.def", + "include/llvm/Target/Hexagon/*.inc", + "lib/Target/Hexagon/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Hexagon"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":hexagon_asm_parser", + ":hexagon_desc", + ":hexagon_info", + ":ipo", + ":mc", + ":scalar", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "hexagon_desc", + srcs = glob([ + "lib/Target/Hexagon/MCTargetDesc/*.c", + "lib/Target/Hexagon/MCTargetDesc/*.cpp", + "lib/Target/Hexagon/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Hexagon/MCTargetDesc/*.h", + "include/llvm/Target/Hexagon/MCTargetDesc/*.def", + "include/llvm/Target/Hexagon/MCTargetDesc/*.inc", + "lib/Target/Hexagon/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Hexagon"], + deps = [ + ":config", + ":hexagon_info", + ":mc", + ":support", + ], +) + +cc_library( + name = "hexagon_disassembler", + srcs = glob([ + "lib/Target/Hexagon/Disassembler/*.c", + "lib/Target/Hexagon/Disassembler/*.cpp", + "lib/Target/Hexagon/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Hexagon/Disassembler/*.h", + "include/llvm/Target/Hexagon/Disassembler/*.def", + "include/llvm/Target/Hexagon/Disassembler/*.inc", + "lib/Target/Hexagon/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Hexagon"], + deps = [ + ":config", + ":hexagon_desc", + ":hexagon_info", + ":mc", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "hexagon_info", + srcs = glob([ + "lib/Target/Hexagon/TargetInfo/*.c", + "lib/Target/Hexagon/TargetInfo/*.cpp", + "lib/Target/Hexagon/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Hexagon/TargetInfo/*.h", + "include/llvm/Target/Hexagon/TargetInfo/*.def", + "include/llvm/Target/Hexagon/TargetInfo/*.inc", + "lib/Target/Hexagon/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Hexagon"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "ipo", + srcs = glob([ + "lib/Transforms/IPO/*.c", + "lib/Transforms/IPO/*.cpp", + "lib/Transforms/IPO/*.inc", + "include/llvm/Transforms/SampleProfile.h", + "include/llvm-c/Transforms/IPO.h", + "include/llvm-c/Transforms/PassManagerBuilder.h", + "lib/Transforms/IPO/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/IPO/*.h", + "include/llvm/Transforms/IPO/*.def", + "include/llvm/Transforms/IPO/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":aggressive_inst_combine", + ":analysis", + ":bit_reader", + ":bit_writer", + ":config", + ":core", + ":frontend_open_mp", + ":inst_combine", + ":instrumentation", + ":ir_reader", + ":linker", + ":object", + ":profile_data", + ":scalar", + ":support", + ":transform_utils", + ":vectorize", + ], +) + +cc_library( + name = "ir_reader", + srcs = glob([ + "lib/IRReader/*.c", + "lib/IRReader/*.cpp", + "lib/IRReader/*.inc", + "lib/IRReader/*.h", + ]), + hdrs = glob([ + "include/llvm/IRReader/*.h", + "include/llvm/IRReader/*.def", + "include/llvm/IRReader/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":asm_parser", + ":bit_reader", + ":config", + ":core", + ":support", + ], +) + +cc_library( + name = "inst_combine", + srcs = glob([ + "lib/Transforms/InstCombine/*.c", + "lib/Transforms/InstCombine/*.cpp", + "lib/Transforms/InstCombine/*.inc", + "lib/Transforms/InstCombine/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/InstCombine/*.h", + "include/llvm/Transforms/InstCombine/*.def", + "include/llvm/Transforms/InstCombine/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":instcombine_transforms_gen", + ":support", + ":transform_utils", + ], +) + +cc_library( + name = "instrumentation", + srcs = glob([ + "lib/Transforms/Instrumentation/*.c", + "lib/Transforms/Instrumentation/*.cpp", + "lib/Transforms/Instrumentation/*.inc", + "lib/Transforms/Instrumentation/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/Instrumentation/*.h", + "include/llvm/Transforms/Instrumentation/*.def", + "include/llvm/Transforms/Instrumentation/*.inc", + "include/llvm/Transforms/GCOVProfiler.h", + "include/llvm/Transforms/Instrumentation.h", + "include/llvm/Transforms/InstrProfiling.h", + "include/llvm/Transforms/PGOInstrumentation.h", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":mc", + ":profile_data", + ":support", + ":transform_utils", + ], +) + +cc_library( + name = "interpreter", + srcs = glob([ + "lib/ExecutionEngine/Interpreter/*.c", + "lib/ExecutionEngine/Interpreter/*.cpp", + "lib/ExecutionEngine/Interpreter/*.inc", + "lib/ExecutionEngine/Interpreter/*.h", + ]), + hdrs = glob([ + "include/llvm/ExecutionEngine/Interpreter/*.h", + "include/llvm/ExecutionEngine/Interpreter/*.def", + "include/llvm/ExecutionEngine/Interpreter/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":code_gen", + ":config", + ":core", + ":execution_engine", + ":support", + ], +) + +cc_library( + name = "jit_link", + srcs = glob([ + "lib/ExecutionEngine/JITLink/*.c", + "lib/ExecutionEngine/JITLink/*.cpp", + "lib/ExecutionEngine/JITLink/*.inc", + "lib/ExecutionEngine/JITLink/*.h", + ]), + hdrs = glob([ + "include/llvm/ExecutionEngine/JITLink/*.h", + "include/llvm/ExecutionEngine/JITLink/*.def", + "include/llvm/ExecutionEngine/JITLink/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":config", + ":object", + ":support", + ], +) + +cc_library( + name = "lto", + srcs = glob([ + "lib/LTO/*.c", + "lib/LTO/*.cpp", + "lib/LTO/*.inc", + "lib/LTO/*.h", + ]), + hdrs = glob([ + "include/llvm/LTO/*.h", + "include/llvm/LTO/*.def", + "include/llvm/LTO/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":aggressive_inst_combine", + ":analysis", + ":binary_format", + ":bit_reader", + ":bit_writer", + ":code_gen", + ":config", + ":core", + ":extensions", + ":inst_combine", + ":ipo", + ":linker", + ":mc", + ":objc_arc", + ":object", + ":passes", + ":remarks", + ":scalar", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "lanai_asm_parser", + srcs = glob([ + "lib/Target/Lanai/AsmParser/*.c", + "lib/Target/Lanai/AsmParser/*.cpp", + "lib/Target/Lanai/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Lanai/AsmParser/*.h", + "include/llvm/Target/Lanai/AsmParser/*.def", + "include/llvm/Target/Lanai/AsmParser/*.inc", + "lib/Target/Lanai/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Lanai"], + deps = [ + ":config", + ":lanai_desc", + ":lanai_info", + ":mc", + ":mc_parser", + ":support", + ], +) + +cc_library( + name = "lanai_code_gen", + srcs = glob([ + "lib/Target/Lanai/*.c", + "lib/Target/Lanai/*.cpp", + "lib/Target/Lanai/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Lanai/*.h", + "include/llvm/Target/Lanai/*.def", + "include/llvm/Target/Lanai/*.inc", + "lib/Target/Lanai/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Lanai"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":lanai_asm_parser", + ":lanai_desc", + ":lanai_info", + ":mc", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "lanai_desc", + srcs = glob([ + "lib/Target/Lanai/MCTargetDesc/*.c", + "lib/Target/Lanai/MCTargetDesc/*.cpp", + "lib/Target/Lanai/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Lanai/MCTargetDesc/*.h", + "include/llvm/Target/Lanai/MCTargetDesc/*.def", + "include/llvm/Target/Lanai/MCTargetDesc/*.inc", + "lib/Target/Lanai/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Lanai"], + deps = [ + ":config", + ":lanai_info", + ":mc", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "lanai_disassembler", + srcs = glob([ + "lib/Target/Lanai/Disassembler/*.c", + "lib/Target/Lanai/Disassembler/*.cpp", + "lib/Target/Lanai/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Lanai/Disassembler/*.h", + "include/llvm/Target/Lanai/Disassembler/*.def", + "include/llvm/Target/Lanai/Disassembler/*.inc", + "lib/Target/Lanai/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Lanai"], + deps = [ + ":config", + ":lanai_desc", + ":lanai_info", + ":mc", + ":mc_disassembler", + ":support", + ], +) + +cc_library( + name = "lanai_info", + srcs = glob([ + "lib/Target/Lanai/TargetInfo/*.c", + "lib/Target/Lanai/TargetInfo/*.cpp", + "lib/Target/Lanai/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Lanai/TargetInfo/*.h", + "include/llvm/Target/Lanai/TargetInfo/*.def", + "include/llvm/Target/Lanai/TargetInfo/*.inc", + "lib/Target/Lanai/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Lanai"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "lib_driver", + srcs = glob([ + "lib/ToolDrivers/llvm-lib/*.c", + "lib/ToolDrivers/llvm-lib/*.cpp", + "lib/ToolDrivers/llvm-lib/*.inc", + "lib/ToolDrivers/llvm-lib/*.h", + ]), + hdrs = glob([ + "include/llvm/ToolDrivers/llvm-lib/*.h", + "include/llvm/ToolDrivers/llvm-lib/*.def", + "include/llvm/ToolDrivers/llvm-lib/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":bit_reader", + ":config", + ":object", + ":option", + ":support", + ], +) + +cc_library( + name = "line_editor", + srcs = glob([ + "lib/LineEditor/*.c", + "lib/LineEditor/*.cpp", + "lib/LineEditor/*.inc", + "lib/LineEditor/*.h", + ]), + hdrs = glob([ + "include/llvm/LineEditor/*.h", + "include/llvm/LineEditor/*.def", + "include/llvm/LineEditor/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "linker", + srcs = glob([ + "lib/Linker/*.c", + "lib/Linker/*.cpp", + "lib/Linker/*.inc", + "lib/Linker/*.h", + ]), + hdrs = glob([ + "include/llvm/Linker/*.h", + "include/llvm/Linker/*.def", + "include/llvm/Linker/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":support", + ":transform_utils", + ], +) + +cc_library( + name = "mc", + srcs = glob([ + "lib/MC/*.c", + "lib/MC/*.cpp", + "lib/MC/*.inc", + "lib/MC/*.h", + ]), + hdrs = glob([ + "include/llvm/MC/*.h", + "include/llvm/MC/*.def", + "include/llvm/MC/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":config", + ":debug_info_code_view", + ":support", + ], +) + +cc_library( + name = "mca", + srcs = glob([ + "lib/MCA/*.c", + "lib/MCA/*.cpp", + "lib/MCA/*.inc", + "lib/MCA/*.h", + ]), + hdrs = glob([ + "include/llvm/MCA/*.h", + "include/llvm/MCA/*.def", + "include/llvm/MCA/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "mc_disassembler", + srcs = glob([ + "lib/MC/MCDisassembler/*.c", + "lib/MC/MCDisassembler/*.cpp", + "lib/MC/MCDisassembler/*.inc", + "lib/MC/MCDisassembler/*.h", + ]), + hdrs = glob([ + "include/llvm/MC/MCDisassembler/*.h", + "include/llvm/MC/MCDisassembler/*.def", + "include/llvm/MC/MCDisassembler/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "mcjit", + srcs = glob([ + "lib/ExecutionEngine/MCJIT/*.c", + "lib/ExecutionEngine/MCJIT/*.cpp", + "lib/ExecutionEngine/MCJIT/*.inc", + "lib/ExecutionEngine/MCJIT/*.h", + ]), + hdrs = glob([ + "include/llvm/ExecutionEngine/MCJIT/*.h", + "include/llvm/ExecutionEngine/MCJIT/*.def", + "include/llvm/ExecutionEngine/MCJIT/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":execution_engine", + ":object", + ":runtime_dyld", + ":support", + ":target", + ], +) + +cc_library( + name = "mc_parser", + srcs = glob([ + "lib/MC/MCParser/*.c", + "lib/MC/MCParser/*.cpp", + "lib/MC/MCParser/*.inc", + "lib/MC/MCParser/*.h", + ]), + hdrs = glob([ + "include/llvm/MC/MCParser/*.h", + "include/llvm/MC/MCParser/*.def", + "include/llvm/MC/MCParser/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "mir_parser", + srcs = glob([ + "lib/CodeGen/MIRParser/*.c", + "lib/CodeGen/MIRParser/*.cpp", + "lib/CodeGen/MIRParser/*.inc", + "lib/CodeGen/MIRParser/*.h", + ]), + hdrs = glob([ + "include/llvm/CodeGen/MIRParser/*.h", + "include/llvm/CodeGen/MIRParser/*.def", + "include/llvm/CodeGen/MIRParser/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":asm_parser", + ":binary_format", + ":code_gen", + ":config", + ":core", + ":mc", + ":support", + ":target", + ], +) + +cc_library( + name = "msp430_asm_parser", + srcs = glob([ + "lib/Target/MSP430/AsmParser/*.c", + "lib/Target/MSP430/AsmParser/*.cpp", + "lib/Target/MSP430/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/MSP430/AsmParser/*.h", + "include/llvm/Target/MSP430/AsmParser/*.def", + "include/llvm/Target/MSP430/AsmParser/*.inc", + "lib/Target/MSP430/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/MSP430"], + deps = [ + ":config", + ":mc", + ":mc_parser", + ":msp430_desc", + ":msp430_info", + ":support", + ], +) + +cc_library( + name = "msp430_code_gen", + srcs = glob([ + "lib/Target/MSP430/*.c", + "lib/Target/MSP430/*.cpp", + "lib/Target/MSP430/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/MSP430/*.h", + "include/llvm/Target/MSP430/*.def", + "include/llvm/Target/MSP430/*.inc", + "lib/Target/MSP430/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/MSP430"], + deps = [ + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":mc", + ":msp430_desc", + ":msp430_info", + ":selection_dag", + ":support", + ":target", + ], +) + +cc_library( + name = "msp430_desc", + srcs = glob([ + "lib/Target/MSP430/MCTargetDesc/*.c", + "lib/Target/MSP430/MCTargetDesc/*.cpp", + "lib/Target/MSP430/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/MSP430/MCTargetDesc/*.h", + "include/llvm/Target/MSP430/MCTargetDesc/*.def", + "include/llvm/Target/MSP430/MCTargetDesc/*.inc", + "lib/Target/MSP430/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/MSP430"], + deps = [ + ":config", + ":mc", + ":msp430_info", + ":support", + ], +) + +cc_library( + name = "msp430_disassembler", + srcs = glob([ + "lib/Target/MSP430/Disassembler/*.c", + "lib/Target/MSP430/Disassembler/*.cpp", + "lib/Target/MSP430/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/MSP430/Disassembler/*.h", + "include/llvm/Target/MSP430/Disassembler/*.def", + "include/llvm/Target/MSP430/Disassembler/*.inc", + "lib/Target/MSP430/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/MSP430"], + deps = [ + ":config", + ":mc_disassembler", + ":msp430_info", + ":support", + ], +) + +cc_library( + name = "msp430_info", + srcs = glob([ + "lib/Target/MSP430/TargetInfo/*.c", + "lib/Target/MSP430/TargetInfo/*.cpp", + "lib/Target/MSP430/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/MSP430/TargetInfo/*.h", + "include/llvm/Target/MSP430/TargetInfo/*.def", + "include/llvm/Target/MSP430/TargetInfo/*.inc", + "lib/Target/MSP430/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/MSP430"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "mips_asm_parser", + srcs = glob([ + "lib/Target/Mips/AsmParser/*.c", + "lib/Target/Mips/AsmParser/*.cpp", + "lib/Target/Mips/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Mips/AsmParser/*.h", + "include/llvm/Target/Mips/AsmParser/*.def", + "include/llvm/Target/Mips/AsmParser/*.inc", + "lib/Target/Mips/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Mips"], + deps = [ + ":config", + ":mc", + ":mc_parser", + ":mips_desc", + ":mips_info", + ":support", + ], +) + +cc_library( + name = "mips_code_gen", + srcs = glob([ + "lib/Target/Mips/*.c", + "lib/Target/Mips/*.cpp", + "lib/Target/Mips/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Mips/*.h", + "include/llvm/Target/Mips/*.def", + "include/llvm/Target/Mips/*.inc", + "lib/Target/Mips/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Mips"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":global_i_sel", + ":mc", + ":mips_desc", + ":mips_info", + ":selection_dag", + ":support", + ":target", + ], +) + +cc_library( + name = "mips_desc", + srcs = glob([ + "lib/Target/Mips/MCTargetDesc/*.c", + "lib/Target/Mips/MCTargetDesc/*.cpp", + "lib/Target/Mips/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Mips/MCTargetDesc/*.h", + "include/llvm/Target/Mips/MCTargetDesc/*.def", + "include/llvm/Target/Mips/MCTargetDesc/*.inc", + "lib/Target/Mips/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Mips"], + deps = [ + ":config", + ":mc", + ":mips_info", + ":support", + ], +) + +cc_library( + name = "mips_disassembler", + srcs = glob([ + "lib/Target/Mips/Disassembler/*.c", + "lib/Target/Mips/Disassembler/*.cpp", + "lib/Target/Mips/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Mips/Disassembler/*.h", + "include/llvm/Target/Mips/Disassembler/*.def", + "include/llvm/Target/Mips/Disassembler/*.inc", + "lib/Target/Mips/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Mips"], + deps = [ + ":config", + ":mc_disassembler", + ":mips_info", + ":support", + ], +) + +cc_library( + name = "mips_info", + srcs = glob([ + "lib/Target/Mips/TargetInfo/*.c", + "lib/Target/Mips/TargetInfo/*.cpp", + "lib/Target/Mips/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Mips/TargetInfo/*.h", + "include/llvm/Target/Mips/TargetInfo/*.def", + "include/llvm/Target/Mips/TargetInfo/*.inc", + "lib/Target/Mips/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Mips"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "nvptx_code_gen", + srcs = glob([ + "lib/Target/NVPTX/*.c", + "lib/Target/NVPTX/*.cpp", + "lib/Target/NVPTX/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/NVPTX/*.h", + "include/llvm/Target/NVPTX/*.def", + "include/llvm/Target/NVPTX/*.inc", + "lib/Target/NVPTX/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/NVPTX"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":ipo", + ":mc", + ":nvptx_desc", + ":nvptx_info", + ":scalar", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ":vectorize", + ], +) + +cc_library( + name = "nvptx_desc", + srcs = glob([ + "lib/Target/NVPTX/MCTargetDesc/*.c", + "lib/Target/NVPTX/MCTargetDesc/*.cpp", + "lib/Target/NVPTX/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/NVPTX/MCTargetDesc/*.h", + "include/llvm/Target/NVPTX/MCTargetDesc/*.def", + "include/llvm/Target/NVPTX/MCTargetDesc/*.inc", + "lib/Target/NVPTX/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/NVPTX"], + deps = [ + "nvptx_target_gen", + ":config", + ":mc", + ":nvptx_info", + ":support", + ], +) + +cc_library( + name = "nvptx_info", + srcs = glob([ + "lib/Target/NVPTX/TargetInfo/*.c", + "lib/Target/NVPTX/TargetInfo/*.cpp", + "lib/Target/NVPTX/TargetInfo/*.inc", + "lib/Target/NVPTX/MCTargetDesc/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/NVPTX/TargetInfo/*.h", + "include/llvm/Target/NVPTX/TargetInfo/*.def", + "include/llvm/Target/NVPTX/TargetInfo/*.inc", + "lib/Target/NVPTX/NVPTX.h", + "lib/Target/NVPTX/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/NVPTX"], + deps = [ + "nvptx_target_gen", + ":attributes_gen", + ":config", + ":core", + ":support", + ":target", + ], +) + +cc_library( + name = "objc_arc", + srcs = glob([ + "lib/Transforms/ObjCARC/*.c", + "lib/Transforms/ObjCARC/*.cpp", + "lib/Transforms/ObjCARC/*.inc", + "include/llvm/Transforms/ObjCARC.h", + "lib/Transforms/ObjCARC/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/ObjCARC/*.h", + "include/llvm/Transforms/ObjCARC/*.def", + "include/llvm/Transforms/ObjCARC/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":support", + ":transform_utils", + ], +) + +cc_library( + name = "object", + srcs = glob([ + "lib/Object/*.c", + "lib/Object/*.cpp", + "lib/Object/*.inc", + "lib/Object/*.h", + ]), + hdrs = glob([ + "include/llvm/Object/*.h", + "include/llvm/Object/*.def", + "include/llvm/Object/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":binary_format", + ":bit_reader", + ":config", + ":core", + ":mc", + ":mc_parser", + ":support", + ":text_api", + ], +) + +cc_library( + name = "object_yaml", + srcs = glob([ + "lib/ObjectYAML/*.c", + "lib/ObjectYAML/*.cpp", + "lib/ObjectYAML/*.inc", + "lib/ObjectYAML/*.h", + ]), + hdrs = glob([ + "include/llvm/ObjectYAML/*.h", + "include/llvm/ObjectYAML/*.def", + "include/llvm/ObjectYAML/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":debug_info_code_view", + ":mc", + ":object", + ":support", + ], +) + +cc_library( + name = "option", + srcs = glob([ + "lib/Option/*.c", + "lib/Option/*.cpp", + "lib/Option/*.inc", + "lib/Option/*.h", + ]), + hdrs = glob([ + "include/llvm/Option/*.h", + "include/llvm/Option/*.def", + "include/llvm/Option/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "orc_error", + srcs = glob([ + "lib/ExecutionEngine/OrcError/*.c", + "lib/ExecutionEngine/OrcError/*.cpp", + "lib/ExecutionEngine/OrcError/*.inc", + "lib/ExecutionEngine/OrcError/*.h", + ]), + hdrs = glob([ + "include/llvm/ExecutionEngine/OrcError/*.h", + "include/llvm/ExecutionEngine/OrcError/*.def", + "include/llvm/ExecutionEngine/OrcError/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "orc_jit", + srcs = glob([ + "lib/ExecutionEngine/Orc/*.c", + "lib/ExecutionEngine/Orc/*.cpp", + "lib/ExecutionEngine/Orc/*.inc", + "lib/ExecutionEngine/Orc/*.h", + ]), + hdrs = glob([ + "include/llvm/ExecutionEngine/Orc/*.h", + "include/llvm/ExecutionEngine/Orc/*.def", + "include/llvm/ExecutionEngine/Orc/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":execution_engine", + ":jit_link", + ":mc", + ":object", + ":orc_error", + ":passes", + ":runtime_dyld", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "passes", + srcs = glob([ + "lib/Passes/*.c", + "lib/Passes/*.cpp", + "lib/Passes/*.inc", + "lib/Passes/*.h", + ]), + hdrs = glob([ + "include/llvm/Passes/*.h", + "include/llvm/Passes/*.def", + "include/llvm/Passes/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":aggressive_inst_combine", + ":analysis", + ":code_gen", + ":config", + ":core", + ":coroutines", + ":inst_combine", + ":instrumentation", + ":ipo", + ":scalar", + ":support", + ":target", + ":transform_utils", + ":vectorize", + ], +) + +cc_library( + name = "powerpc_asm_parser", + srcs = glob([ + "lib/Target/PowerPC/AsmParser/*.c", + "lib/Target/PowerPC/AsmParser/*.cpp", + "lib/Target/PowerPC/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/PowerPC/AsmParser/*.h", + "include/llvm/Target/PowerPC/AsmParser/*.def", + "include/llvm/Target/PowerPC/AsmParser/*.inc", + "lib/Target/PowerPC/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/PowerPC"], + deps = [ + ":config", + ":mc", + ":mc_parser", + ":powerpc_desc", + ":powerpc_info", + ":support", + ], +) + +cc_library( + name = "powerpc_code_gen", + srcs = glob([ + "lib/Target/PowerPC/*.c", + "lib/Target/PowerPC/*.cpp", + "lib/Target/PowerPC/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/PowerPC/*.h", + "include/llvm/Target/PowerPC/*.def", + "include/llvm/Target/PowerPC/*.inc", + "lib/Target/PowerPC/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/PowerPC"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":mc", + ":powerpc_desc", + ":powerpc_info", + ":scalar", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "powerpc_desc", + srcs = glob([ + "lib/Target/PowerPC/MCTargetDesc/*.c", + "lib/Target/PowerPC/MCTargetDesc/*.cpp", + "lib/Target/PowerPC/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/PowerPC/MCTargetDesc/*.h", + "include/llvm/Target/PowerPC/MCTargetDesc/*.def", + "include/llvm/Target/PowerPC/MCTargetDesc/*.inc", + "lib/Target/PowerPC/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/PowerPC"], + deps = [ + ":attributes_gen", + ":binary_format", + ":config", + ":intrinsic_enums_gen", + ":intrinsics_impl_gen", + ":mc", + ":powerpc_info", + ":powerpc_target_gen", + ":support", + ], +) + +cc_library( + name = "powerpc_disassembler", + srcs = glob([ + "lib/Target/PowerPC/Disassembler/*.c", + "lib/Target/PowerPC/Disassembler/*.cpp", + "lib/Target/PowerPC/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/PowerPC/Disassembler/*.h", + "include/llvm/Target/PowerPC/Disassembler/*.def", + "include/llvm/Target/PowerPC/Disassembler/*.inc", + "lib/Target/PowerPC/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/PowerPC"], + deps = [ + ":config", + ":mc_disassembler", + ":powerpc_info", + ":support", + ], +) + +cc_library( + name = "powerpc_info", + srcs = glob([ + "lib/Target/PowerPC/TargetInfo/*.c", + "lib/Target/PowerPC/TargetInfo/*.cpp", + "lib/Target/PowerPC/TargetInfo/*.inc", + "lib/Target/PowerPC/MCTargetDesc/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/PowerPC/TargetInfo/*.h", + "include/llvm/Target/PowerPC/TargetInfo/*.def", + "include/llvm/Target/PowerPC/TargetInfo/*.inc", + "lib/Target/PowerPC/PPC*.h", + "lib/Target/PowerPC/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/PowerPC"], + deps = [ + ":attributes_gen", + ":config", + ":core", + ":powerpc_target_gen", + ":support", + ":target", + ], +) + +cc_library( + name = "profile_data", + srcs = glob([ + "lib/ProfileData/*.c", + "lib/ProfileData/*.cpp", + "lib/ProfileData/*.inc", + "lib/ProfileData/*.h", + ]), + hdrs = glob([ + "include/llvm/ProfileData/*.h", + "include/llvm/ProfileData/*.def", + "include/llvm/ProfileData/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":support", + ], +) + +cc_library( + name = "riscv_asm_parser", + srcs = glob([ + "lib/Target/RISCV/AsmParser/*.c", + "lib/Target/RISCV/AsmParser/*.cpp", + "lib/Target/RISCV/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/RISCV/AsmParser/*.h", + "include/llvm/Target/RISCV/AsmParser/*.def", + "include/llvm/Target/RISCV/AsmParser/*.inc", + "lib/Target/RISCV/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/RISCV"], + deps = [ + ":config", + ":mc", + ":mc_parser", + ":riscv_desc", + ":riscv_info", + ":riscv_utils", + ":support", + ], +) + +cc_library( + name = "riscv_code_gen", + srcs = glob([ + "lib/Target/RISCV/*.c", + "lib/Target/RISCV/*.cpp", + "lib/Target/RISCV/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/RISCV/*.h", + "include/llvm/Target/RISCV/*.def", + "include/llvm/Target/RISCV/*.inc", + "lib/Target/RISCV/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/RISCV"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":global_i_sel", + ":mc", + ":riscv_desc", + ":riscv_info", + ":riscv_utils", + ":selection_dag", + ":support", + ":target", + ], +) + +cc_library( + name = "riscv_desc", + srcs = glob([ + "lib/Target/RISCV/MCTargetDesc/*.c", + "lib/Target/RISCV/MCTargetDesc/*.cpp", + "lib/Target/RISCV/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/RISCV/MCTargetDesc/*.h", + "include/llvm/Target/RISCV/MCTargetDesc/*.def", + "include/llvm/Target/RISCV/MCTargetDesc/*.inc", + "lib/Target/RISCV/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/RISCV"], + deps = [ + ":config", + ":mc", + ":riscv_info", + ":riscv_utils", + ":support", + ], +) + +cc_library( + name = "riscv_disassembler", + srcs = glob([ + "lib/Target/RISCV/Disassembler/*.c", + "lib/Target/RISCV/Disassembler/*.cpp", + "lib/Target/RISCV/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/RISCV/Disassembler/*.h", + "include/llvm/Target/RISCV/Disassembler/*.def", + "include/llvm/Target/RISCV/Disassembler/*.inc", + "lib/Target/RISCV/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/RISCV"], + deps = [ + ":config", + ":mc_disassembler", + ":riscv_info", + ":support", + ], +) + +cc_library( + name = "riscv_info", + srcs = glob([ + "lib/Target/RISCV/TargetInfo/*.c", + "lib/Target/RISCV/TargetInfo/*.cpp", + "lib/Target/RISCV/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/RISCV/TargetInfo/*.h", + "include/llvm/Target/RISCV/TargetInfo/*.def", + "include/llvm/Target/RISCV/TargetInfo/*.inc", + "lib/Target/RISCV/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/RISCV"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "riscv_utils", + srcs = glob([ + "lib/Target/RISCV/Utils/*.c", + "lib/Target/RISCV/Utils/*.cpp", + "lib/Target/RISCV/Utils/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/RISCV/Utils/*.h", + "include/llvm/Target/RISCV/Utils/*.def", + "include/llvm/Target/RISCV/Utils/*.inc", + "lib/Target/RISCV/Utils/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/RISCV"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "remarks", + srcs = glob([ + "lib/Remarks/*.c", + "lib/Remarks/*.cpp", + "lib/Remarks/*.inc", + "lib/Remarks/*.h", + ]), + hdrs = glob([ + "include/llvm/Remarks/*.h", + "include/llvm/Remarks/*.def", + "include/llvm/Remarks/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":bitstream_reader", + ":config", + ":support", + ], +) + +cc_library( + name = "runtime_dyld", + srcs = glob([ + "lib/ExecutionEngine/RuntimeDyld/*.c", + "lib/ExecutionEngine/RuntimeDyld/*.cpp", + "lib/ExecutionEngine/RuntimeDyld/*.inc", + "include/llvm/ExecutionEngine/JITSymbol.h", + "include/llvm/ExecutionEngine/RTDyldMemoryManager.h", + "lib/ExecutionEngine/RuntimeDyld/*.h", + "lib/ExecutionEngine/RuntimeDyld/Targets/*.h", + "lib/ExecutionEngine/RuntimeDyld/Targets/*.cpp", + "lib/ExecutionEngine/RuntimeDyld/*.h", + ]), + hdrs = glob([ + "include/llvm/ExecutionEngine/RuntimeDyld/*.h", + "include/llvm/ExecutionEngine/RuntimeDyld/*.def", + "include/llvm/ExecutionEngine/RuntimeDyld/*.inc", + "include/llvm/DebugInfo/DIContext.h", + "include/llvm/ExecutionEngine/RTDyldMemoryManager.h", + "include/llvm/ExecutionEngine/RuntimeDyld*.h", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":mc", + ":mc_disassembler", + ":object", + ":support", + ], +) + +cc_library( + name = "scalar", + srcs = glob([ + "lib/Transforms/Scalar/*.c", + "lib/Transforms/Scalar/*.cpp", + "lib/Transforms/Scalar/*.inc", + "include/llvm-c/Transforms/Scalar.h", + "include/llvm/Transforms/Scalar.h", + "include/llvm/Target/TargetMachine.h", + "lib/Transforms/Scalar/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/Scalar/*.h", + "include/llvm/Transforms/Scalar/*.def", + "include/llvm/Transforms/Scalar/*.inc", + "include/llvm/Transforms/IPO.h", + "include/llvm/Transforms/IPO/SCCP.h", + ]), + copts = llvm_copts, + deps = [ + ":aggressive_inst_combine", + ":analysis", + ":config", + ":core", + ":inst_combine", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "selection_dag", + srcs = glob([ + "lib/CodeGen/SelectionDAG/*.c", + "lib/CodeGen/SelectionDAG/*.cpp", + "lib/CodeGen/SelectionDAG/*.inc", + "lib/CodeGen/SelectionDAG/*.h", + ]), + hdrs = glob([ + "include/llvm/CodeGen/SelectionDAG/*.h", + "include/llvm/CodeGen/SelectionDAG/*.def", + "include/llvm/CodeGen/SelectionDAG/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":code_gen", + ":config", + ":core", + ":mc", + ":support", + ":target", + ":transform_utils", + ], +) + +cc_library( + name = "sparc_asm_parser", + srcs = glob([ + "lib/Target/Sparc/AsmParser/*.c", + "lib/Target/Sparc/AsmParser/*.cpp", + "lib/Target/Sparc/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Sparc/AsmParser/*.h", + "include/llvm/Target/Sparc/AsmParser/*.def", + "include/llvm/Target/Sparc/AsmParser/*.inc", + "lib/Target/Sparc/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Sparc"], + deps = [ + ":config", + ":mc", + ":mc_parser", + ":sparc_desc", + ":sparc_info", + ":support", + ], +) + +cc_library( + name = "sparc_code_gen", + srcs = glob([ + "lib/Target/Sparc/*.c", + "lib/Target/Sparc/*.cpp", + "lib/Target/Sparc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Sparc/*.h", + "include/llvm/Target/Sparc/*.def", + "include/llvm/Target/Sparc/*.inc", + "lib/Target/Sparc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Sparc"], + deps = [ + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":mc", + ":selection_dag", + ":sparc_desc", + ":sparc_info", + ":support", + ":target", + ], +) + +cc_library( + name = "sparc_desc", + srcs = glob([ + "lib/Target/Sparc/MCTargetDesc/*.c", + "lib/Target/Sparc/MCTargetDesc/*.cpp", + "lib/Target/Sparc/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Sparc/MCTargetDesc/*.h", + "include/llvm/Target/Sparc/MCTargetDesc/*.def", + "include/llvm/Target/Sparc/MCTargetDesc/*.inc", + "lib/Target/Sparc/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Sparc"], + deps = [ + ":config", + ":mc", + ":sparc_info", + ":support", + ], +) + +cc_library( + name = "sparc_disassembler", + srcs = glob([ + "lib/Target/Sparc/Disassembler/*.c", + "lib/Target/Sparc/Disassembler/*.cpp", + "lib/Target/Sparc/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Sparc/Disassembler/*.h", + "include/llvm/Target/Sparc/Disassembler/*.def", + "include/llvm/Target/Sparc/Disassembler/*.inc", + "lib/Target/Sparc/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Sparc"], + deps = [ + ":config", + ":mc_disassembler", + ":sparc_info", + ":support", + ], +) + +cc_library( + name = "sparc_info", + srcs = glob([ + "lib/Target/Sparc/TargetInfo/*.c", + "lib/Target/Sparc/TargetInfo/*.cpp", + "lib/Target/Sparc/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/Sparc/TargetInfo/*.h", + "include/llvm/Target/Sparc/TargetInfo/*.def", + "include/llvm/Target/Sparc/TargetInfo/*.inc", + "lib/Target/Sparc/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/Sparc"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "support", + srcs = glob([ + "lib/Support/*.c", + "lib/Support/*.cpp", + "lib/Support/*.inc", + "include/llvm-c/*.h", + "include/llvm/CodeGen/MachineValueType.h", + "include/llvm/BinaryFormat/COFF.h", + "include/llvm/BinaryFormat/MachO.h", + "lib/Support/*.h", + ]) + llvm_support_platform_specific_srcs_glob(), + hdrs = glob([ + "include/llvm/Support/*.h", + "include/llvm/Support/*.def", + "include/llvm/Support/*.inc", + "include/llvm/ADT/*.h", + "include/llvm/Support/ELFRelocs/*.def", + "include/llvm/Support/WasmRelocs/*.def", + ]) + [ + "include/llvm/BinaryFormat/MachO.def", + "include/llvm/Support/VCSRevision.h", + ], + copts = llvm_copts, + linkopts = llvm_linkopts, + deps = [ + ":config", + ":demangle", + "@zlib", + ], +) + +cc_library( + name = "symbolize", + srcs = glob([ + "lib/DebugInfo/Symbolize/*.c", + "lib/DebugInfo/Symbolize/*.cpp", + "lib/DebugInfo/Symbolize/*.inc", + "lib/DebugInfo/Symbolize/*.h", + ]), + hdrs = glob([ + "include/llvm/DebugInfo/Symbolize/*.h", + "include/llvm/DebugInfo/Symbolize/*.def", + "include/llvm/DebugInfo/Symbolize/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":debug_info_dwarf", + ":debug_info_pdb", + ":demangle", + ":object", + ":support", + ], +) + +cc_library( + name = "system_z_asm_parser", + srcs = glob([ + "lib/Target/SystemZ/AsmParser/*.c", + "lib/Target/SystemZ/AsmParser/*.cpp", + "lib/Target/SystemZ/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/SystemZ/AsmParser/*.h", + "include/llvm/Target/SystemZ/AsmParser/*.def", + "include/llvm/Target/SystemZ/AsmParser/*.inc", + "lib/Target/SystemZ/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/SystemZ"], + deps = [ + ":config", + ":mc", + ":mc_parser", + ":support", + ":system_z_desc", + ":system_z_info", + ], +) + +cc_library( + name = "system_z_code_gen", + srcs = glob([ + "lib/Target/SystemZ/*.c", + "lib/Target/SystemZ/*.cpp", + "lib/Target/SystemZ/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/SystemZ/*.h", + "include/llvm/Target/SystemZ/*.def", + "include/llvm/Target/SystemZ/*.inc", + "lib/Target/SystemZ/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/SystemZ"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":mc", + ":scalar", + ":selection_dag", + ":support", + ":system_z_desc", + ":system_z_info", + ":target", + ], +) + +cc_library( + name = "system_z_desc", + srcs = glob([ + "lib/Target/SystemZ/MCTargetDesc/*.c", + "lib/Target/SystemZ/MCTargetDesc/*.cpp", + "lib/Target/SystemZ/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/SystemZ/MCTargetDesc/*.h", + "include/llvm/Target/SystemZ/MCTargetDesc/*.def", + "include/llvm/Target/SystemZ/MCTargetDesc/*.inc", + "lib/Target/SystemZ/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/SystemZ"], + deps = [ + ":config", + ":mc", + ":support", + ":system_z_info", + ], +) + +cc_library( + name = "system_z_disassembler", + srcs = glob([ + "lib/Target/SystemZ/Disassembler/*.c", + "lib/Target/SystemZ/Disassembler/*.cpp", + "lib/Target/SystemZ/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/SystemZ/Disassembler/*.h", + "include/llvm/Target/SystemZ/Disassembler/*.def", + "include/llvm/Target/SystemZ/Disassembler/*.inc", + "lib/Target/SystemZ/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/SystemZ"], + deps = [ + ":config", + ":mc", + ":mc_disassembler", + ":support", + ":system_z_desc", + ":system_z_info", + ], +) + +cc_library( + name = "system_z_info", + srcs = glob([ + "lib/Target/SystemZ/TargetInfo/*.c", + "lib/Target/SystemZ/TargetInfo/*.cpp", + "lib/Target/SystemZ/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/SystemZ/TargetInfo/*.h", + "include/llvm/Target/SystemZ/TargetInfo/*.def", + "include/llvm/Target/SystemZ/TargetInfo/*.inc", + "lib/Target/SystemZ/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/SystemZ"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "tablegen", + srcs = glob([ + "lib/TableGen/*.c", + "lib/TableGen/*.cpp", + "lib/TableGen/*.inc", + "include/llvm/CodeGen/*.h", + "lib/TableGen/*.h", + ]), + hdrs = glob([ + "include/llvm/TableGen/*.h", + "include/llvm/TableGen/*.def", + "include/llvm/TableGen/*.inc", + "include/llvm/Target/*.def", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":mc", + ":support", + ], +) + +cc_library( + name = "target", + srcs = glob([ + "lib/Target/*.c", + "lib/Target/*.cpp", + "lib/Target/*.inc", + "include/llvm/CodeGen/*.h", + "include/llvm-c/Initialization.h", + "include/llvm-c/Target.h", + "lib/Target/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/*.h", + "include/llvm/Target/*.def", + "include/llvm/Target/*.inc", + "include/llvm/CodeGen/*.def", + "include/llvm/CodeGen/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":mc", + ":support", + ], +) + +cc_library( + name = "testing_support", + srcs = glob([ + "lib/Testing/Support/*.c", + "lib/Testing/Support/*.cpp", + "lib/Testing/Support/*.inc", + "lib/Testing/Support/*.h", + ]), + hdrs = glob([ + "include/llvm/Testing/Support/*.h", + "include/llvm/Testing/Support/*.def", + "include/llvm/Testing/Support/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "text_api", + srcs = glob([ + "lib/TextAPI/*.c", + "lib/TextAPI/*.cpp", + "lib/TextAPI/*.inc", + "lib/TextAPI/ELF/*.cpp", + "lib/TextAPI/MachO/*.cpp", + "lib/TextAPI/MachO/*.h", + "lib/TextAPI/*.h", + ]), + hdrs = glob([ + "include/llvm/TextAPI/*.h", + "include/llvm/TextAPI/*.def", + "include/llvm/TextAPI/*.inc", + ]) + [ + "include/llvm/TextAPI/ELF/TBEHandler.h", + "include/llvm/TextAPI/ELF/ELFStub.h", + "include/llvm/TextAPI/MachO/Architecture.def", + "include/llvm/TextAPI/MachO/PackedVersion.h", + "include/llvm/TextAPI/MachO/InterfaceFile.h", + "include/llvm/TextAPI/MachO/Symbol.h", + "include/llvm/TextAPI/MachO/ArchitectureSet.h", + "include/llvm/TextAPI/MachO/TextAPIWriter.h", + "include/llvm/TextAPI/MachO/TextAPIReader.h", + "include/llvm/TextAPI/MachO/Architecture.h", + ], + copts = llvm_copts, + deps = [ + ":binary_format", + ":config", + ":support", + ], +) + +cc_library( + name = "transform_utils", + srcs = glob([ + "lib/Transforms/Utils/*.c", + "lib/Transforms/Utils/*.cpp", + "lib/Transforms/Utils/*.inc", + "include/llvm/Transforms/IPO.h", + "include/llvm/Transforms/Scalar.h", + "lib/Transforms/Utils/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/Utils/*.h", + "include/llvm/Transforms/Utils/*.def", + "include/llvm/Transforms/Utils/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":support", + ], +) + +cc_library( + name = "ve_code_gen", + srcs = glob([ + "lib/Target/VE/*.c", + "lib/Target/VE/*.cpp", + "lib/Target/VE/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/VE/*.h", + "include/llvm/Target/VE/*.def", + "include/llvm/Target/VE/*.inc", + "lib/Target/VE/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/VE"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":mc", + ":selection_dag", + ":support", + ":target", + ":ve_desc", + ":ve_info", + ], +) + +cc_library( + name = "ve_desc", + srcs = glob([ + "lib/Target/VE/MCTargetDesc/*.c", + "lib/Target/VE/MCTargetDesc/*.cpp", + "lib/Target/VE/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/VE/MCTargetDesc/*.h", + "include/llvm/Target/VE/MCTargetDesc/*.def", + "include/llvm/Target/VE/MCTargetDesc/*.inc", + "lib/Target/VE/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/VE"], + deps = [ + ":config", + ":mc", + ":support", + ":ve_info", + ], +) + +cc_library( + name = "ve_info", + srcs = glob([ + "lib/Target/VE/TargetInfo/*.c", + "lib/Target/VE/TargetInfo/*.cpp", + "lib/Target/VE/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/VE/TargetInfo/*.h", + "include/llvm/Target/VE/TargetInfo/*.def", + "include/llvm/Target/VE/TargetInfo/*.inc", + "lib/Target/VE/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/VE"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "vectorize", + srcs = glob([ + "lib/Transforms/Vectorize/*.c", + "lib/Transforms/Vectorize/*.cpp", + "lib/Transforms/Vectorize/*.inc", + "include/llvm-c/Transforms/Vectorize.h", + "lib/Transforms/Vectorize/*.h", + ]), + hdrs = glob([ + "include/llvm/Transforms/Vectorize/*.h", + "include/llvm/Transforms/Vectorize/*.def", + "include/llvm/Transforms/Vectorize/*.inc", + "include/llvm/Transforms/Vectorize.h", + ]), + copts = llvm_copts, + deps = [ + ":analysis", + ":config", + ":core", + ":scalar", + ":support", + ":transform_utils", + ], +) + +cc_library( + name = "web_assembly_asm_parser", + srcs = glob([ + "lib/Target/WebAssembly/AsmParser/*.c", + "lib/Target/WebAssembly/AsmParser/*.cpp", + "lib/Target/WebAssembly/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/WebAssembly/AsmParser/*.h", + "include/llvm/Target/WebAssembly/AsmParser/*.def", + "include/llvm/Target/WebAssembly/AsmParser/*.inc", + "lib/Target/WebAssembly/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/WebAssembly"], + deps = [ + ":config", + ":mc", + ":mc_parser", + ":support", + ":web_assembly_info", + ], +) + +cc_library( + name = "web_assembly_code_gen", + srcs = glob([ + "lib/Target/WebAssembly/*.c", + "lib/Target/WebAssembly/*.cpp", + "lib/Target/WebAssembly/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/WebAssembly/*.h", + "include/llvm/Target/WebAssembly/*.def", + "include/llvm/Target/WebAssembly/*.inc", + "lib/Target/WebAssembly/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/WebAssembly"], + deps = [ + ":analysis", + ":asm_printer", + ":binary_format", + ":code_gen", + ":config", + ":core", + ":mc", + ":scalar", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ":web_assembly_desc", + ":web_assembly_info", + ], +) + +cc_library( + name = "web_assembly_desc", + srcs = glob([ + "lib/Target/WebAssembly/MCTargetDesc/*.c", + "lib/Target/WebAssembly/MCTargetDesc/*.cpp", + "lib/Target/WebAssembly/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/WebAssembly/MCTargetDesc/*.h", + "include/llvm/Target/WebAssembly/MCTargetDesc/*.def", + "include/llvm/Target/WebAssembly/MCTargetDesc/*.inc", + "lib/Target/WebAssembly/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/WebAssembly"], + deps = [ + ":config", + ":mc", + ":support", + ":web_assembly_info", + ], +) + +cc_library( + name = "web_assembly_disassembler", + srcs = glob([ + "lib/Target/WebAssembly/Disassembler/*.c", + "lib/Target/WebAssembly/Disassembler/*.cpp", + "lib/Target/WebAssembly/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/WebAssembly/Disassembler/*.h", + "include/llvm/Target/WebAssembly/Disassembler/*.def", + "include/llvm/Target/WebAssembly/Disassembler/*.inc", + "lib/Target/WebAssembly/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/WebAssembly"], + deps = [ + ":config", + ":mc", + ":mc_disassembler", + ":support", + ":web_assembly_desc", + ":web_assembly_info", + ], +) + +cc_library( + name = "web_assembly_info", + srcs = glob([ + "lib/Target/WebAssembly/TargetInfo/*.c", + "lib/Target/WebAssembly/TargetInfo/*.cpp", + "lib/Target/WebAssembly/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/WebAssembly/TargetInfo/*.h", + "include/llvm/Target/WebAssembly/TargetInfo/*.def", + "include/llvm/Target/WebAssembly/TargetInfo/*.inc", + "lib/Target/WebAssembly/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/WebAssembly"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "windows_manifest", + srcs = glob([ + "lib/WindowsManifest/*.c", + "lib/WindowsManifest/*.cpp", + "lib/WindowsManifest/*.inc", + "lib/WindowsManifest/*.h", + ]), + hdrs = glob([ + "include/llvm/WindowsManifest/*.h", + "include/llvm/WindowsManifest/*.def", + "include/llvm/WindowsManifest/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "x86_asm_parser", + srcs = glob([ + "lib/Target/X86/AsmParser/*.c", + "lib/Target/X86/AsmParser/*.cpp", + "lib/Target/X86/AsmParser/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/X86/AsmParser/*.h", + "include/llvm/Target/X86/AsmParser/*.def", + "include/llvm/Target/X86/AsmParser/*.inc", + "lib/Target/X86/AsmParser/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/X86"], + deps = [ + ":config", + ":mc", + ":mc_parser", + ":support", + ":x86_desc", + ":x86_info", + ], +) + +cc_library( + name = "x86_code_gen", + srcs = glob([ + "lib/Target/X86/*.c", + "lib/Target/X86/*.cpp", + "lib/Target/X86/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/X86/*.h", + "include/llvm/Target/X86/*.def", + "include/llvm/Target/X86/*.inc", + "lib/Target/X86/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/X86"], + deps = [ + ":analysis", + ":asm_printer", + ":cf_guard", + ":code_gen", + ":config", + ":core", + ":global_i_sel", + ":mc", + ":profile_data", + ":selection_dag", + ":support", + ":target", + ":x86_defs", + ":x86_desc", + ":x86_info", + ], +) + +cc_library( + name = "x86_desc", + srcs = glob([ + "lib/Target/X86/MCTargetDesc/*.c", + "lib/Target/X86/MCTargetDesc/*.cpp", + "lib/Target/X86/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/X86/MCTargetDesc/*.h", + "include/llvm/Target/X86/MCTargetDesc/*.def", + "include/llvm/Target/X86/MCTargetDesc/*.inc", + "lib/Target/X86/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/X86"], + deps = [ + ":binary_format", + ":config", + ":mc", + ":mc_disassembler", + ":support", + ":x86_info", + ], +) + +cc_library( + name = "x86_disassembler", + srcs = glob([ + "lib/Target/X86/Disassembler/*.c", + "lib/Target/X86/Disassembler/*.cpp", + "lib/Target/X86/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/X86/Disassembler/*.h", + "include/llvm/Target/X86/Disassembler/*.def", + "include/llvm/Target/X86/Disassembler/*.inc", + "lib/Target/X86/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/X86"], + deps = [ + ":config", + ":mc_disassembler", + ":support", + ":x86_info", + ], +) + +cc_library( + name = "x86_info", + srcs = glob([ + "lib/Target/X86/TargetInfo/*.c", + "lib/Target/X86/TargetInfo/*.cpp", + "lib/Target/X86/TargetInfo/*.inc", + "lib/Target/X86/MCTargetDesc/*.h", + ]), + hdrs = glob([ + "include/llvm/Target/X86/TargetInfo/*.h", + "include/llvm/Target/X86/TargetInfo/*.def", + "include/llvm/Target/X86/TargetInfo/*.inc", + "lib/Target/X86/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/X86"], + deps = [ + ":config", + ":mc", + ":support", + ":x86_target_gen", + ], +) + +cc_library( + name = "x_core_code_gen", + srcs = glob([ + "lib/Target/XCore/*.c", + "lib/Target/XCore/*.cpp", + "lib/Target/XCore/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/XCore/*.h", + "include/llvm/Target/XCore/*.def", + "include/llvm/Target/XCore/*.inc", + "lib/Target/XCore/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/XCore"], + deps = [ + ":analysis", + ":asm_printer", + ":code_gen", + ":config", + ":core", + ":mc", + ":selection_dag", + ":support", + ":target", + ":transform_utils", + ":x_core_desc", + ":x_core_info", + ], +) + +cc_library( + name = "x_core_desc", + srcs = glob([ + "lib/Target/XCore/MCTargetDesc/*.c", + "lib/Target/XCore/MCTargetDesc/*.cpp", + "lib/Target/XCore/MCTargetDesc/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/XCore/MCTargetDesc/*.h", + "include/llvm/Target/XCore/MCTargetDesc/*.def", + "include/llvm/Target/XCore/MCTargetDesc/*.inc", + "lib/Target/XCore/MCTargetDesc/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/XCore"], + deps = [ + ":config", + ":mc", + ":support", + ":x_core_info", + ], +) + +cc_library( + name = "x_core_disassembler", + srcs = glob([ + "lib/Target/XCore/Disassembler/*.c", + "lib/Target/XCore/Disassembler/*.cpp", + "lib/Target/XCore/Disassembler/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/XCore/Disassembler/*.h", + "include/llvm/Target/XCore/Disassembler/*.def", + "include/llvm/Target/XCore/Disassembler/*.inc", + "lib/Target/XCore/Disassembler/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/XCore"], + deps = [ + ":config", + ":mc_disassembler", + ":support", + ":x_core_info", + ], +) + +cc_library( + name = "x_core_info", + srcs = glob([ + "lib/Target/XCore/TargetInfo/*.c", + "lib/Target/XCore/TargetInfo/*.cpp", + "lib/Target/XCore/TargetInfo/*.inc", + ]), + hdrs = glob([ + "include/llvm/Target/XCore/TargetInfo/*.h", + "include/llvm/Target/XCore/TargetInfo/*.def", + "include/llvm/Target/XCore/TargetInfo/*.inc", + "lib/Target/XCore/TargetInfo/*.h", + ]), + copts = llvm_copts + ["-Iexternal/llvm/lib/Target/XCore"], + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "x_ray", + srcs = glob([ + "lib/XRay/*.c", + "lib/XRay/*.cpp", + "lib/XRay/*.inc", + "lib/XRay/*.h", + ]), + hdrs = glob([ + "include/llvm/XRay/*.h", + "include/llvm/XRay/*.def", + "include/llvm/XRay/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":object", + ":support", + ], +) + +cc_library( + name = "gtest", + srcs = glob([ + "utils/unittest/*.c", + "utils/unittest/*.cpp", + "utils/unittest/*.inc", + "utils/unittest/*.h", + ]), + hdrs = glob([ + "utils/unittest/*.h", + "utils/unittest/*.def", + "utils/unittest/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":support", + ], +) + +cc_library( + name = "gtest_main", + srcs = glob([ + "utils/unittest/*.c", + "utils/unittest/*.cpp", + "utils/unittest/*.inc", + "utils/unittest/*.h", + ]), + hdrs = glob([ + "utils/unittest/*.h", + "utils/unittest/*.def", + "utils/unittest/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":gtest", + ], +) diff --git a/dependency_support/llvm/common.bzl b/dependency_support/llvm/common.bzl new file mode 100644 index 0000000000..eacf7e0b5e --- /dev/null +++ b/dependency_support/llvm/common.bzl @@ -0,0 +1,56 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Rule for simple expansion of template files. This performs a simple +# search over the template file for the keys in substitutions, +# and replaces them with the corresponding values. +# +# Typical usage: +# load("/tools/build_rules/template_rule", "expand_header_template") +# template_rule( +# name = "ExpandMyTemplate", +# src = "my.template", +# out = "my.txt", +# substitutions = { +# "$VAR1": "foo", +# "$VAR2": "bar", +# } +# ) +# +# Args: +# name: The name of the rule. +# template: The template file to expand +# out: The destination of the expanded file +# substitutions: A dictionary mapping strings to their substitutions + +def template_rule_impl(ctx): + ctx.actions.expand_template( + template = ctx.file.src, + output = ctx.outputs.out, + substitutions = ctx.attr.substitutions, + ) + +template_rule = rule( + attrs = { + "src": attr.label( + mandatory = True, + allow_single_file = True, + ), + "substitutions": attr.string_dict(mandatory = True), + "out": attr.output(mandatory = True), + }, + # output_to_genfiles is required for header files. + output_to_genfiles = True, + implementation = template_rule_impl, +) diff --git a/dependency_support/llvm/expand_cmake_vars.py b/dependency_support/llvm/expand_cmake_vars.py new file mode 100644 index 0000000000..b35aac1250 --- /dev/null +++ b/dependency_support/llvm/expand_cmake_vars.py @@ -0,0 +1,89 @@ +# Copyright 2016 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Expands CMake variables in a text file.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import re +import sys + +_CMAKE_DEFINE_REGEX = re.compile(r"\s*#cmakedefine\s+([A-Za-z_0-9]*)(\s.*)?$") +_CMAKE_DEFINE01_REGEX = re.compile(r"\s*#cmakedefine01\s+([A-Za-z_0-9]*)") +_CMAKE_VAR_REGEX = re.compile(r"\${([A-Za-z_0-9]*)}") + + +def _parse_args(argv): + """Parses arguments with the form KEY=VALUE into a dictionary.""" + result = {} + for arg in argv: + k, v = arg.split("=") + result[k] = v + return result + + +def _expand_variables(input_str, cmake_vars): + """Expands ${VARIABLE}s in 'input_str', using dictionary 'cmake_vars'. + + Args: + input_str: the string containing ${VARIABLE} expressions to expand. + cmake_vars: a dictionary mapping variable names to their values. + + Returns: + The expanded string. + """ + + def replace(match): + if match.group(1) in cmake_vars: + return cmake_vars[match.group(1)] + return "" + + return _CMAKE_VAR_REGEX.sub(replace, input_str) + + +def _expand_cmakedefines(line, cmake_vars): + """Expands #cmakedefine declarations, using a dictionary 'cmake_vars'.""" + + # Handles #cmakedefine lines + match = _CMAKE_DEFINE_REGEX.match(line) + if match: + name = match.group(1) + suffix = match.group(2) or "" + if name in cmake_vars: + return "#define {}{}\n".format(name, + _expand_variables(suffix, cmake_vars)) + else: + return "/* #undef {} */\n".format(name) + + # Handles #cmakedefine01 lines + match = _CMAKE_DEFINE01_REGEX.match(line) + if match: + name = match.group(1) + value = cmake_vars.get(name, "0") + return "#define {} {}\n".format(name, value) + + # Otherwise return the line unchanged. + return _expand_variables(line, cmake_vars) + + +def main(): + cmake_vars = _parse_args(sys.argv[1:]) + for line in sys.stdin: + sys.stdout.write(_expand_cmakedefines(line, cmake_vars)) + + +if __name__ == "__main__": + main() diff --git a/dependency_support/llvm/llvm.bzl b/dependency_support/llvm/llvm.bzl new file mode 100644 index 0000000000..1b495f7635 --- /dev/null +++ b/dependency_support/llvm/llvm.bzl @@ -0,0 +1,355 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This file contains BUILD extensions for generating source code from LLVM's table definition files using the TableGen tool. + +See http://llvm.org/cmds/tblgen.html for more information on the TableGen +tool. +TODO: Currently this expresses include-based dependencies as +"sources", and has no transitive understanding due to these files not being +correctly understood by the build system. +""" + +def _dict_add(*dictionaries): + """Returns a new `dict` that has all the entries of the given dictionaries. + + If the same key is present in more than one of the input dictionaries, the + last of them in the argument list overrides any earlier ones. + + This function is designed to take zero or one arguments as well as multiple + dictionaries, so that it follows arithmetic identities and callers can avoid + special cases for their inputs: the sum of zero dictionaries is the empty + dictionary, and the sum of a single dictionary is a copy of itself. + + Re-implemented here to avoid adding a dependency on skylib. + + Args: + *dictionaries: Zero or more dictionaries to be added. + + Returns: + A new `dict` that has all the entries of the given dictionaries. + """ + result = {} + for d in dictionaries: + result.update(d) + return result + +def gentbl(name, tblgen, td_file, td_srcs, tbl_outs, library = True, **kwargs): + """gentbl() generates tabular code from a table definition file. + + Args: + name: The name of the build rule for use in dependencies. + tblgen: The binary used to produce the output. + td_file: The primary table definitions file. + td_srcs: A list of table definition files included transitively. + tbl_outs: A list of tuples (opts, out), where each opts is a string of + options passed to tblgen, and the out is the corresponding output file + produced. + library: Whether to bundle the generated files into a library. + **kwargs: Keyword arguments to pass to subsidiary cc_library() rule. + """ + if td_file not in td_srcs: + td_srcs += [td_file] + includes = [] + for (opts, out) in tbl_outs: + outdir = out[:out.rindex("/")] + if outdir not in includes: + includes.append(outdir) + rule_suffix = "_".join(opts.replace("-", "_").replace("=", "_").split(" ")) + native.genrule( + name = "%s_%s_genrule" % (name, rule_suffix), + srcs = td_srcs, + outs = [out], + tools = [tblgen], + message = "Generating code from table: %s" % td_file, + cmd = (("$(location %s) " + "-I external/llvm/include " + + "-I $$(dirname $(location %s)) " + ("%s $(location %s) --long-string-literals=0 " + + "-o $@")) % ( + tblgen, + td_file, + opts, + td_file, + )), + ) + + # For now, all generated files can be assumed to comprise public interfaces. + # If this is not true, you should specify library = False + # and list the generated '.inc' files in "srcs". + if library: + native.cc_library( + name = name, + textual_hdrs = [f for (_, f) in tbl_outs], + includes = includes, + **kwargs + ) + +def llvm_target_cmake_vars(native_arch, target_triple): + return { + "LLVM_HOST_TRIPLE": target_triple, + "LLVM_DEFAULT_TARGET_TRIPLE": target_triple, + "LLVM_NATIVE_ARCH": native_arch, + } + +def _quote(s): + """Quotes the given string for use in a shell command. + + This function double-quotes the given string (in case it contains spaces or + other special characters) and escapes any special characters (dollar signs, + double-quotes, and backslashes) that may be present. + + Args: + s: The string to quote. + + Returns: + An escaped and quoted version of the string that can be passed to a shell + command. + """ + return ('"' + + s.replace("\\", "\\\\").replace("$", "\\$").replace('"', "\\\"") + + '"') + +def cmake_var_string(cmake_vars): + """Converts a dictionary to an input suitable for expand_cmake_vars. + + Ideally we would jist stringify in the expand_cmake_vars() rule, but select() + interacts badly with genrules. + + TODO: replace the genrule() with native rule and delete this rule. + + Args: + cmake_vars: a dictionary with string keys and values that are convertable to + strings. + + Returns: + cmake_vars in a form suitable for passing to expand_cmake_vars. + """ + return " ".join([ + _quote("{}={}".format(k, str(v))) + for (k, v) in cmake_vars.items() + ]) + +def expand_cmake_vars(name, src, dst, cmake_vars): + """Expands #cmakedefine, #cmakedefine01, and CMake variables in a text file. + + Args: + name: the name of the rule + src: the input of the rule + dst: the output of the rule + cmake_vars: a string containing the CMake variables, as generated by + cmake_var_string. + """ + expand_cmake_vars_tool = "@com_google_xls//dependency_support/llvm:expand_cmake_vars" + native.genrule( + name = name, + srcs = [src], + tools = [expand_cmake_vars_tool], + outs = [dst], + cmd = ("$(location {}) ".format(expand_cmake_vars_tool) + cmake_vars + + "< $< > $@"), + ) + +# TODO: the set of CMake variables was hardcoded for expediency. +# However, we should really detect many of these via configure-time tests. + +# The set of CMake variables common to all targets. +cmake_vars = { + # LLVM features + "ENABLE_BACKTRACES": 1, + "LLVM_BINDIR": "/dev/null", + "LLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING": 0, + "LLVM_ENABLE_ABI_BREAKING_CHECKS": 0, + "LLVM_ENABLE_THREADS": 1, + "LLVM_ENABLE_ZLIB": 1, + "LLVM_HAS_ATOMICS": 1, + "LLVM_INCLUDEDIR": "/dev/null", + "LLVM_INFODIR": "/dev/null", + "LLVM_MANDIR": "/dev/null", + "LLVM_NATIVE_TARGET": 1, + "LLVM_NATIVE_TARGETINFO": 1, + "LLVM_NATIVE_TARGETMC": 1, + "LLVM_NATIVE_ASMPRINTER": 1, + "LLVM_NATIVE_ASMPARSER": 1, + "LLVM_NATIVE_DISASSEMBLER": 1, + "LLVM_PREFIX": "/dev/null", + "LLVM_VERSION_MAJOR": 0, + "LLVM_VERSION_MINOR": 0, + "LLVM_VERSION_PATCH": 0, + "PACKAGE_NAME": "llvm", + "PACKAGE_STRING": "llvm tensorflow-trunk", + "PACKAGE_VERSION": "tensorflow-trunk", + "RETSIGTYPE": "void", +} + +# The set of CMake variables common to POSIX targets. +posix_cmake_vars = { + # Headers + "HAVE_DIRENT_H": 1, + "HAVE_DLFCN_H": 1, + "HAVE_ERRNO_H": 1, + "HAVE_EXECINFO_H": 1, + "HAVE_FCNTL_H": 1, + "HAVE_INTTYPES_H": 1, + "HAVE_PTHREAD_H": 1, + "HAVE_SIGNAL_H": 1, + "HAVE_STDINT_H": 1, + "HAVE_SYS_IOCTL_H": 1, + "HAVE_SYS_MMAN_H": 1, + "HAVE_SYS_PARAM_H": 1, + "HAVE_SYS_RESOURCE_H": 1, + "HAVE_SYS_STAT_H": 1, + "HAVE_SYS_TIME_H": 1, + "HAVE_SYS_TYPES_H": 1, + "HAVE_TERMIOS_H": 1, + "HAVE_UNISTD_H": 1, + "HAVE_ZLIB_H": 1, + + # Features + "HAVE_BACKTRACE": 1, + "BACKTRACE_HEADER": "execinfo.h", + "HAVE_DLOPEN": 1, + "HAVE_FUTIMES": 1, + "HAVE_GETCWD": 1, + "HAVE_GETPAGESIZE": 1, + "HAVE_GETRLIMIT": 1, + "HAVE_GETRUSAGE": 1, + "HAVE_GETTIMEOFDAY": 1, + "HAVE_INT64_T": 1, + "HAVE_ISATTY": 1, + "HAVE_LIBEDIT": 1, + "HAVE_LIBPTHREAD": 1, + "HAVE_LIBZ": 1, + "HAVE_MKDTEMP": 1, + "HAVE_MKSTEMP": 1, + "HAVE_MKTEMP": 1, + "HAVE_PREAD": 1, + "HAVE_PTHREAD_GETSPECIFIC": 1, + "HAVE_PTHREAD_MUTEX_LOCK": 1, + "HAVE_PTHREAD_RWLOCK_INIT": 1, + "HAVE_REALPATH": 1, + "HAVE_SBRK": 1, + "HAVE_SETENV": 1, + "HAVE_SETRLIMIT": 1, + "HAVE_SIGALTSTACK": 1, + "HAVE_STRERROR": 1, + "HAVE_STRERROR_R": 1, + "HAVE_STRTOLL": 1, + "HAVE_SYSCONF": 1, + "HAVE_UINT64_T": 1, + "HAVE__UNWIND_BACKTRACE": 1, + + # LLVM features + "LLVM_ON_UNIX": 1, + "LTDL_SHLIB_EXT": ".so", +} + +# CMake variables specific to the Linux platform +linux_cmake_vars = { + "HAVE_MALLOC_H": 1, + "HAVE_LINK_H": 1, + "HAVE_MALLINFO": 1, + "HAVE_FUTIMENS": 1, +} + +# CMake variables specific to the FreeBSD platform +freebsd_cmake_vars = { + "HAVE_MALLOC_H": 1, + "HAVE_LINK_H": 1, +} + +# CMake variables specific to the Darwin (Mac OS X) platform. +darwin_cmake_vars = { + "HAVE_MALLOC_MALLOC_H": 1, + "HAVE_MALLOC_ZONE_STATISTICS": 1, +} + +# CMake variables specific to the Windows platform. +win32_cmake_vars = { + # Headers + "HAVE_ERRNO_H": 1, + "HAVE_EXECINFO_H": 1, + "HAVE_FCNTL_H": 1, + "HAVE_FENV_H": 1, + "HAVE_INTTYPES_H": 1, + "HAVE_MALLOC_H": 1, + "HAVE_SIGNAL_H": 1, + "HAVE_STDINT_H": 1, + "HAVE_SYS_STAT_H": 1, + "HAVE_SYS_TYPES_H": 1, + "HAVE_ZLIB_H": 1, + + # Features + "BACKTRACE_HEADER": "execinfo.h", + "HAVE_GETCWD": 1, + "HAVE_INT64_T": 1, + "HAVE_STRERROR": 1, + "HAVE_STRTOLL": 1, + "HAVE_SYSCONF": 1, + "HAVE_UINT64_T": 1, + "HAVE__CHSIZE_S": 1, + "HAVE___CHKSTK": 1, + + # MSVC specific + "stricmp": "_stricmp", + "strdup": "_strdup", + + # LLVM features + "LTDL_SHLIB_EXT": ".dll", + + # ThreadPoolExecutor global destructor and thread handshaking do not work + # on this platform when used as a DLL. + # See: https://bugs.llvm.org/show_bug.cgi?id=44211 + "LLVM_ENABLE_THREADS": 0, +} + +# Select a set of CMake variables based on the platform. +# TODO: use a better method to select the right host triple, rather +# than hardcoding x86_64. +llvm_all_cmake_vars = select({ + "//conditions:default": cmake_var_string( + _dict_add( + cmake_vars, + llvm_target_cmake_vars("X86", "x86_64-unknown-linux_gnu"), + posix_cmake_vars, + linux_cmake_vars, + ), + ), +}) + +llvm_linkopts = select({ + "//conditions:default": ["-ldl", "-lm", "-lpthread"], +}) + +llvm_defines = select({ + "//conditions:default": [], +}) + [ + "LLVM_ENABLE_STATS", + "__STDC_LIMIT_MACROS", + "__STDC_CONSTANT_MACROS", + "__STDC_FORMAT_MACROS", + "LLVM_BUILD_GLOBAL_ISEL", +] + +llvm_copts = select({ + "//conditions:default": [], +}) + +# Platform specific sources for libSupport. + +def llvm_support_platform_specific_srcs_glob(): + return select({ + "//conditions:default": native.glob([ + "lib/Support/Unix/*.inc", + "lib/Support/Unix/*.h", + ]), + }) diff --git a/dependency_support/load_external.bzl b/dependency_support/load_external.bzl new file mode 100644 index 0000000000..cf90123b46 --- /dev/null +++ b/dependency_support/load_external.bzl @@ -0,0 +1,210 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Provides helper that loads external repositories with third-party code.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +load("//dependency_support:edu_berkeley_abc/workspace.bzl", repo_abc = "repo") +load("//dependency_support/org_gnu_bison:workspace.bzl", repo_bison = "repo") +load("//dependency_support:org_sourceware_bzip2/workspace.bzl", repo_bzip2 = "repo") +load("//dependency_support/flex:workspace.bzl", repo_flex = "repo") +load("//dependency_support/org_gnu_gperf:workspace.bzl", repo_gperf = "repo") +load("//dependency_support/com_icarus_iverilog:workspace.bzl", repo_iverilog = "repo") +load("//dependency_support/dk_thrysoee_libedit:workspace.bzl", repo_libedit = "repo") +load("//dependency_support:org_sourceware_libffi/workspace.bzl", repo_libffi = "repo") +load("//dependency_support/org_gnu_m4:workspace.bzl", repo_m4 = "repo") +load("//dependency_support/net_invisible_island_ncurses:workspace.bzl", repo_ncurses = "repo") +load("//dependency_support:tcl_tcl_tk/workspace.bzl", repo_tcl = "repo") + +def load_external_repositories(): + """Loads external repositories with third-party code.""" + repo_abc() + repo_bison() + repo_bzip2() + repo_flex() + repo_gperf() + repo_iverilog() + repo_libedit() + repo_libffi() + repo_m4() + repo_ncurses() + repo_tcl() + + http_archive( + name = "com_google_googletest", + urls = ["https://github.com/google/googletest/archive/0eea2e9fc63461761dea5f2f517bd6af2ca024fa.zip"], # 2020-04-30 + strip_prefix = "googletest-0eea2e9fc63461761dea5f2f517bd6af2ca024fa", + sha256 = "9463ff914d7c3db02de6bd40a3c412a74e979e3c76eaa89920a49ff8488d6d69", + ) + + http_archive( + name = "com_google_absl", + strip_prefix = "abseil-cpp-a1d6689907864974118e592ef2ac7d716c576aad", + urls = ["https://github.com/abseil/abseil-cpp/archive/a1d6689907864974118e592ef2ac7d716c576aad.zip"], + sha256 = "53b78ffe87db3c737feddda52fa10dcdb75e2d85eed1cb1c5bfd77ca22e53e53", + ) + + http_archive( + name = "com_google_protobuf", + strip_prefix = "protobuf-d0bfd5221182da1a7cc280f3337b5e41a89539cf", # this is 3.11.4, 2020-02-14 + sha256 = "c5fd8f99f0d30c6f9f050bf008e021ccc70d7645ac1f64679c6038e07583b2f3", + urls = ["https://github.com/protocolbuffers/protobuf/archive/d0bfd5221182da1a7cc280f3337b5e41a89539cf.zip"], + ) + + # Protobuf depends on Skylib + http_archive( + name = "bazel_skylib", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz", + ], + sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44", + ) + + git_repository( + name = "boringssl", + commit = "14164f6fef47b7ebd97cdb0cea1624eabd6fe6b8", # 2018-11-26 + remote = "https://github.com/google/boringssl.git", + shallow_since = "1543277914 +0000", + ) + + http_archive( + name = "jinja_archive", + build_file_content = """py_library( + name = "jinja2", + visibility = ["//visibility:public"], + srcs = glob(["jinja2/*.py"]), + deps = ["@markupsafe//:markupsafe"], + )""", + sha256 = "93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", + strip_prefix = "Jinja2-2.11.1/src", + urls = [ + "http://mirror.bazel.build/files.pythonhosted.org/packages/d8/03/e491f423379ea14bb3a02a5238507f7d446de639b623187bccc111fbecdf/Jinja2-2.11.1.tar.gz", + "https://files.pythonhosted.org/packages/d8/03/e491f423379ea14bb3a02a5238507f7d446de639b623187bccc111fbecdf/Jinja2-2.11.1.tar.gz", # 2020-01-30 + ], + ) + + http_archive( + name = "llvm", + urls = ["https://github.com/llvm/llvm-project/archive/307cfdf5338641e3a895857ef02dc9da35cd0eb6.tar.gz"], + sha256 = "5e75125ecadee4f91e07c20bf6612d740913a677348fd33c7264ee8fe7d12b17", + strip_prefix = "llvm-project-307cfdf5338641e3a895857ef02dc9da35cd0eb6/llvm", + build_file = "@//dependency_support/llvm:bundled.BUILD.bazel", + ) + + # Jinja2 depends on MarkupSafe + http_archive( + name = "markupsafe", + build_file_content = """py_library( + name = "markupsafe", + visibility = ["//visibility:public"], + srcs = glob(["*.py"]) + )""", + sha256 = "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + strip_prefix = "MarkupSafe-1.1.1/src/markupsafe", + urls = [ + "http://mirror.bazel.build/files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz", + "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz", # 2019-02-24 + ], + ) + + http_archive( + name = "pybind11_bazel", + strip_prefix = "pybind11_bazel-34206c29f891dbd5f6f5face7b91664c2ff7185c", + urls = ["https://github.com/pybind/pybind11_bazel/archive/34206c29f891dbd5f6f5face7b91664c2ff7185c.zip"], + sha256 = "8d0b776ea5b67891f8585989d54aa34869fc12f14bf33f1dc7459458dd222e95", + ) + + http_archive( + name = "pybind11", + build_file = "@pybind11_bazel//:pybind11.BUILD", + strip_prefix = "pybind11-a54eab92d265337996b8e4b4149d9176c2d428a6", + urls = ["https://github.com/pybind/pybind11/archive/a54eab92d265337996b8e4b4149d9176c2d428a6.tar.gz"], + sha256 = "c9375b7453bef1ba0106849c83881e6b6882d892c9fae5b2572a2192100ffb8a", + ) + + http_archive( + name = "six_archive", + build_file_content = """py_library( + name = "six", + visibility = ["//visibility:public"], + srcs = glob(["*.py"]) + )""", + sha256 = "105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a", + strip_prefix = "six-1.10.0", + urls = [ + "https://mirror.bazel.build/pypi.python.org/packages/source/s/six/six-1.10.0.tar.gz", + "https://pypi.python.org/packages/source/s/six/six-1.10.0.tar.gz", + ], + ) + + http_archive( + name = "com_google_absl_py", + strip_prefix = "abseil-py-06edd9c20592cec39178b94240b5e86f32e19768", + urls = ["https://github.com/abseil/abseil-py/archive/06edd9c20592cec39178b94240b5e86f32e19768.zip"], + sha256 = "6ace3cd8921804aaabc37970590edce05c6664901cc98d30010d09f2811dc56f", + ) + + http_archive( + name = "com_google_re2", + sha256 = "d070e2ffc5476c496a6a872a6f246bfddce8e7797d6ba605a7c8d72866743bf9", + strip_prefix = "re2-506cfa4bffd060c06ec338ce50ea3468daa6c814", + urls = [ + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/google/re2/archive/506cfa4bffd060c06ec338ce50ea3468daa6c814.tar.gz", + "https://github.com/google/re2/archive/506cfa4bffd060c06ec338ce50ea3468daa6c814.tar.gz", + ], + ) + + http_archive( + name = "rules_python", + url = "https://github.com/bazelbuild/rules_python/releases/download/0.0.1/rules_python-0.0.1.tar.gz", + sha256 = "aa96a691d3a8177f3215b14b0edc9641787abaaa30363a080165d06ab65e1161", + ) + + http_archive( + name = "pyfakefs_archive", + build_file_content = """py_library( + name = "pyfakefs", + visibility = ["//visibility:public"], + srcs = glob(["pyfakefs/*.py"]), + )""", + strip_prefix = "pyfakefs-4.0.2", + sha256 = "c415e1c737e3aa72b92af41832a7e0a2c325eb8d3a72a210750714e00fcaeace", + urls = [ + "https://pypi.python.org/packages/68/5f/e5501a707958443e0c0f2706a64b0199deb62a0f1d14bc4ee401ed96ef2a/pyfakefs-4.0.2.tar.gz", + ], + ) + + http_archive( + name = "termcolor_archive", + build_file_content = """py_library( + name = "termcolor", + visibility = ["//visibility:public"], + srcs = glob(["termcolor/*.py"]), + )""", + sha256 = "1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b", + strip_prefix = "termcolor-1.1.0", + urls = [ + "https://pypi.python.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz", + ], + ) + + http_archive( + name = "z3", + urls = ["https://github.com/Z3Prover/z3/archive/z3-4.8.7.tar.gz"], + sha256 = "8c1c49a1eccf5d8b952dadadba3552b0eac67482b8a29eaad62aa7343a0732c3", + strip_prefix = "z3-z3-4.8.7", + build_file = "@//dependency_support/z3:bundled.BUILD.bazel", + ) diff --git a/dependency_support/net_invisible_island_ncurses/BUILD b/dependency_support/net_invisible_island_ncurses/BUILD new file mode 100644 index 0000000000..f5360e11b0 --- /dev/null +++ b/dependency_support/net_invisible_island_ncurses/BUILD @@ -0,0 +1,19 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["notice"]) + +exports_files([ + "ncurses_test.cc", +]) diff --git a/dependency_support/net_invisible_island_ncurses/bundled.BUILD.bazel b/dependency_support/net_invisible_island_ncurses/bundled.BUILD.bazel new file mode 100644 index 0000000000..720a233706 --- /dev/null +++ b/dependency_support/net_invisible_island_ncurses/bundled.BUILD.bazel @@ -0,0 +1,403 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The ncurses C library and unit test. + +package(default_visibility = ["//visibility:public"]) + +load("@com_google_xls//dependency_support:automake_substitution.bzl", "automake_substitution") +load("@com_google_xls//dependency_support:copy.bzl", "copy") +load("@com_google_xls//dependency_support:pseudo_configure.bzl", "pseudo_configure") + +licenses(["notice"]) + +exports_files([ + "LICENSE", + "include/Caps", +]) + +NCURSES_COPTS = [ + "-w", + "-DTRACE", + "-DHAVE_CONFIG_H", + "-D_GNU_SOURCE", + "-DNDEBUG", +] + +CAPLIST = [ + "include/Caps", + "include/Caps-ncurses", +] + +CAPLIST_LOCATIONS = " ".join(["$(location :" + cap + ")" for cap in CAPLIST]) + +AUTOMAKE_SUBSTITUTIONS = { + "NCURSES_MAJOR": "6", + "NCURSES_MINOR": "2", + "NCURSES_PATCH": "20200212", + "NCURSES_MOUSE_VERSION": "2", + "NCURSES_CONST": "const", + "NCURSES_INLINE": "inline", + "NCURSES_SBOOL": "char", + "NCURSES_USE_DATABASE": "1", + "NCURSES_USE_TERMCAP": "0", + "NCURSES_XNAMES": "1", + "HAVE_TERMIOS_H": "1", + "HAVE_TCGETATTR": "1", + "HAVE_TERMIO_H": "1", + "NCURSES_EXT_COLORS": "0", + "BROKEN_LINKER": "0", + "cf_cv_enable_reentrant": "0", + "NCURSES_TPARM_VARARGS": "1", + "NCURSES_TPARM_ARG": "intptr_t", + "HAVE_STDINT_H": "1", + "cf_cv_header_stdbool_h": "1", + "NCURSES_OPAQUE": "0", + "NCURSES_OPAQUE_FORM": "0", + "NCURSES_OPAQUE_MENU": "0", + "NCURSES_OPAQUE_PANEL": "0", + "NCURSES_WATTR_MACROS": "1", + "NCURSES_INTEROP_FUNCS": "1", + "NCURSES_SIZE_T": "short", + "NCURSES_CH_T": "chtype", + "cf_cv_enable_lp64": "1", + "cf_cv_type_of_bool": "unsigned char", + "USE_CXX_BOOL": "defined(__cplusplus)", + "NCURSES_EXT_FUNCS": "1", + "NCURSES_LIBUTF8": "0", + "NEED_WCHAR_H": "1", + "NCURSES_WCHAR_T": "0", + "NCURSES_OK_WCHAR_T": "", + "NCURSES_CCHARW_MAX": "5", + "NCURSES_WCWIDTH_GRAPHICS": "1", + "GENERATED_EXT_FUNCS": "generated", + "NCURSES_SP_FUNCS": "1", + "cf_cv_1UL": "1U", + "HAVE_VSSCANF": "1", + "NCURSES_WINT_T": "0", +} + +cc_library( + name = "ncurses_headers", + hdrs = glob(["include/*"]) + [ + # Generated files are not found by glob. + "include/hashsize.h", + "include/ncurses_cfg.h", + "include/ncurses_def.h", + "include/parametrized.h", + "include/curses.h", + "include/term.h", + # Various source files include these, so call them headers. + "ncurses/tinfo/doalloc.c", + "ncurses/names.c", + ], + includes = ["include", "ncurses"], +) + +cc_library( + name = "ncurses", + srcs = glob( + [ + "ncurses/base/*.c", + "ncurses/*.c", + "ncurses/*.h", + "ncurses/tinfo/*.c", + "ncurses/trace/*.c", + "ncurses/tty/*.c", + "build_sources/*.c", + ], + exclude = glob([ + "ncurses/base/lib_driver.c", + "ncurses/base/sigaction.c", + "ncurses/tinfo/make_keys.c", + "ncurses/tinfo/tinfo_driver.c", + "ncurses/tinfo/make_hash.c", + "ncurses/report_offsets.c", + "ncurses/*_test.c", + "build_sources/*_test.c", + ]), + ) + [ + # Generated files are not found by glob. + "ncurses/codes.c", + "ncurses/comp_captab.c", + "ncurses/comp_userdefs.c", + "ncurses/fallback.c", + "ncurses/init_keytry.h", + "ncurses/lib_gen.c", + "ncurses/lib_keyname.c", + "ncurses/names.c", + "ncurses/unctrl.c", + ], + copts = NCURSES_COPTS, + deps = [":ncurses_headers"], +) + +# Common headers between form and menu. +cc_library( + name = "mf_common", + hdrs = [ + "menu/eti.h", + "menu/mf_common.h", + ], + includes = ["menu"], +) + +cc_library( + name = "form", + srcs = glob(["form/*.c"]) + [ + "form/form.priv.h", + "ncurses/curses.priv.h", + ], + hdrs = ["form/form.h"], + copts = NCURSES_COPTS, + deps = [ + ":mf_common", + ":ncurses", + ], +) + +cc_library( + name = "menu", + srcs = glob(["menu/*.c"]) + [ + "menu/menu.priv.h", + "ncurses/curses.priv.h", + ], + hdrs = ["menu/menu.h"], + copts = NCURSES_COPTS, + deps = [ + ":mf_common", + ":ncurses", + ], +) + +cc_library( + name = "panel", + srcs = glob(["panel/*.c"]) + [ + "panel/panel.priv.h", + "ncurses/curses.priv.h", + ], + hdrs = [ + "panel/panel.h", + ], + copts = NCURSES_COPTS, + deps = [":ncurses"], +) + +genrule( + name = "fallback_c", + srcs = [ + "ncurses/tinfo/MKfallback.sh", + "misc/terminfo.src", + ], + outs = ["ncurses/fallback.c"], + cmd = "$(location :ncurses/tinfo/MKfallback.sh) /usr/share/terminfo $(location :misc/terminfo.src) $$(which tic) > $@", +) + +cc_binary( + name = "make_hash", + srcs = [ + "ncurses/build.priv.h", + "ncurses/curses.priv.h", + "ncurses/tinfo/make_hash.c", + ], + deps = [":ncurses_headers"], + copts = NCURSES_COPTS, +) + +genrule( + name = "comp_captab_c", + srcs = [ + ":make_hash", + "ncurses/tinfo/MKcaptab.sh", + "ncurses/tinfo/MKcaptab.awk", + "include/Caps", + ], + outs = ["ncurses/comp_captab.c"], + cmd = "cp $(location :make_hash) . && $(location :ncurses/tinfo/MKcaptab.sh) $$(which awk) 1 $(location :ncurses/tinfo/MKcaptab.awk) $(location :include/Caps) > $@", +) + +genrule( + name = "comp_userdefs_c", + srcs = [ + ":make_hash", + "include/hashsize.h", + "ncurses/tinfo/MKuserdefs.sh", + ] + CAPLIST, + outs = ["ncurses/comp_userdefs.c"], + cmd = "cp $(location :make_hash) . && $(location ncurses/tinfo/MKuserdefs.sh) $$(which awk) 1 " + CAPLIST_LOCATIONS + " > $@", +) + +genrule( + name = "codes_c", + srcs = [ + "ncurses/tinfo/MKcodes.awk", + "include/Caps", + ], + outs = ["ncurses/codes.c"], + cmd = "/usr/bin/env awk -f $(location ncurses/tinfo/MKcodes.awk) bigstrings=1 $(location :include/Caps) > $@", +) + +genrule( + name = "names_c", + srcs = [ + "ncurses/tinfo/MKnames.awk", + "include/Caps", + ], + outs = ["ncurses/names.c"], + cmd = "/usr/bin/env awk -f $(location :ncurses/tinfo/MKnames.awk) bigstrings=1 $(location :include/Caps) > $@", +) + +genrule( + name = "unctrl_c", + srcs = [ + "ncurses/base/MKunctrl.awk", + ], + outs = ["ncurses/unctrl.c"], + cmd = "/usr/bin/env awk -f $(location :ncurses/base/MKunctrl.awk) bigstrings=1 > $@", +) + +cc_binary( + name = "make_keys", + srcs = [ + "ncurses/build.priv.h", + "ncurses/curses.priv.h", + "ncurses/tinfo/make_keys.c", + ], + deps = [":ncurses_headers"], + copts = NCURSES_COPTS, +) + +genrule( + name = "keys_list", + srcs = [ + "ncurses/tinfo/MKkeys_list.sh", + "include/Caps", + ], + outs = ["keys.list"], + cmd = "$(location :ncurses/tinfo/MKkeys_list.sh) $(location :include/Caps) | LC_ALL=C sort > $@", +) + +genrule( + name = "lib_keyname_c", + srcs = [ + "ncurses/base/MKkeyname.awk", + "keys.list", + ], + outs = ["ncurses/lib_keyname.c"], + cmd = "/usr/bin/env awk -f $(location :ncurses/base/MKkeyname.awk) bigstrings=1 $(location :keys.list) > $@", +) + +automake_substitution( + name = "curses_head", + src = "include/curses.h.in", + out = "include/curses.head", + substitutions = AUTOMAKE_SUBSTITUTIONS, +) + +genrule( + name = "curses_h", + srcs = [ + "include/curses.head", + "include/curses.tail", + "include/curses.wide", + "include/MKkey_defs.sh", + ] + CAPLIST, + outs = ["include/curses.h"], + cmd = "cat $(location :include/curses.head) > $@ && AWK=$$(which awk) $(location :include/MKkey_defs.sh) " + CAPLIST_LOCATIONS + " >> $@ && cat $(location :include/curses.wide) $(location :include/curses.tail) >> $@", +) + +genrule( + name = "lib_gen_c", + srcs = [ + "ncurses/base/MKlib_gen.sh", + "include/curses.h", + "include/ncurses_cfg.h", + "include/ncurses_def.h", + ], + outs = ["ncurses/lib_gen.c"], + cmd = "$(location :ncurses/base/MKlib_gen.sh) \"$$(which cpp) -isystem $$(dirname $(location :include/ncurses_cfg.h))\" $$(which awk) generated < $(location :include/curses.h) > $@", +) + +genrule( + name = "hashsize_h", + srcs = [ + "include/MKhashsize.sh", + "include/Caps", + ], + outs = ["include/hashsize.h"], + cmd = "$(location :include/MKhashsize.sh) $(location :include/Caps) > $@", +) + +genrule( + name = "init_keytry_h", + srcs = [ + ":make_keys", + "keys.list", + ], + outs = ["ncurses/init_keytry.h"], + cmd = "$(location :make_keys) $(location :keys.list) > $@", +) + +automake_substitution( + name = "mkterm_h_awk", + src = "include/MKterm.h.awk.in", + out = "include/MKterm.h.awk", + substitutions = AUTOMAKE_SUBSTITUTIONS, +) + +genrule( + name = "term_h", + srcs = [ + "include/MKterm.h.awk", + "include/Caps", + ], + outs = ["include/term.h"], + cmd = "/usr/bin/env awk -f $(location :include/MKterm.h.awk) $(location :include/Caps) > $@", +) + +genrule( + name = "parametrized_h", + srcs = [ + "include/MKparametrized.sh", + "include/Caps", + ], + outs = ["include/parametrized.h"], + cmd = "(cd $$(dirname $(location :include/MKparametrized.sh)) && ./MKparametrized.sh) > $@", +) + +genrule( + name = "ncurses_def_h", + srcs = [ + "include/MKncurses_def.sh", + "include/ncurses_defs", + ], + outs = ["include/ncurses_def.h"], + cmd = "(cd $$(dirname $(location :include/MKncurses_def.sh)) && ./MKncurses_def.sh) > $@", +) + +pseudo_configure( + name = "ncurses_cfg_h", + src = "include/ncurses_cfg.hin", + out = "include/ncurses_cfg.h", + defs = ['HAVE_LONG_FILE_NAMES', 'MIXEDCASE_FILENAMES', 'HAVE_BIG_CORE', 'PURE_TERMINFO', 'USE_HOME_TERMINFO', 'USE_ROOT_ENVIRON', 'HAVE_UNISTD_H', 'HAVE_REMOVE', 'HAVE_UNLINK', 'HAVE_LINK', 'HAVE_SYMLINK', 'USE_LINKS', 'HAVE_LANGINFO_CODESET', 'HAVE_FSEEKO', 'STDC_HEADERS', 'HAVE_SYS_TYPES_H', 'HAVE_SYS_STAT_H', 'HAVE_STDLIB_H', 'HAVE_STRING_H', 'HAVE_MEMORY_H', 'HAVE_STRINGS_H', 'HAVE_INTTYPES_H', 'HAVE_STDINT_H', 'HAVE_UNISTD_H', 'SIZEOF_SIGNED_CHAR', 'NCURSES_EXT_FUNCS', 'HAVE_ASSUME_DEFAULT_COLORS', 'HAVE_CURSES_VERSION', 'HAVE_HAS_KEY', 'HAVE_RESIZETERM', 'HAVE_RESIZE_TERM', 'HAVE_TERM_ENTRY_H', 'HAVE_USE_DEFAULT_COLORS', 'HAVE_USE_EXTENDED_NAMES', 'HAVE_USE_SCREEN', 'HAVE_USE_WINDOW', 'HAVE_WRESIZE', 'NCURSES_SP_FUNCS', 'HAVE_TPUTS_SP', 'NCURSES_EXT_PUTWIN', 'NCURSES_NO_PADDING', 'USE_SIGWINCH', 'USE_ASSUMED_COLOR', 'USE_HASHMAP', 'GCC_SCANF', 'GCC_PRINTF', 'HAVE_NC_ALLOC_H', 'HAVE_GETTIMEOFDAY', 'STDC_HEADERS', 'HAVE_DIRENT_H', 'TIME_WITH_SYS_TIME', 'HAVE_REGEX_H_FUNCS', 'HAVE_FCNTL_H', 'HAVE_GETOPT_H', 'HAVE_LIMITS_H', 'HAVE_LOCALE_H', 'HAVE_MATH_H', 'HAVE_POLL_H', 'HAVE_SYS_IOCTL_H', 'HAVE_SYS_PARAM_H', 'HAVE_SYS_POLL_H', 'HAVE_SYS_SELECT_H', 'HAVE_SYS_TIME_H', 'HAVE_SYS_TIMES_H', 'HAVE_TTYENT_H', 'HAVE_UNISTD_H', 'HAVE_WCTYPE_H', 'HAVE_UNISTD_H', 'HAVE_GETOPT_H', 'HAVE_GETOPT_HEADER', 'DECL_ENVIRON', 'HAVE_ENVIRON', 'HAVE_PUTENV', 'HAVE_SETENV', 'HAVE_STRDUP', 'HAVE_SYS_TIME_SELECT', 'HAVE_GETCWD', 'HAVE_GETEGID', 'HAVE_GETEUID', 'HAVE_GETOPT', 'HAVE_GETTTYNAM', 'HAVE_LOCALECONV', 'HAVE_POLL', 'HAVE_PUTENV', 'HAVE_REMOVE', 'HAVE_SELECT', 'HAVE_SETBUF', 'HAVE_SETBUFFER', 'HAVE_SETENV', 'HAVE_SETVBUF', 'HAVE_SIGACTION', 'HAVE_STRDUP', 'HAVE_STRSTR', 'HAVE_SYSCONF', 'HAVE_TCGETPGRP', 'HAVE_TIMES', 'HAVE_TSEARCH', 'HAVE_VSNPRINTF', 'HAVE_ISASCII', 'HAVE_NANOSLEEP', 'HAVE_TERMIO_H', 'HAVE_TERMIOS_H', 'HAVE_UNISTD_H', 'HAVE_SYS_IOCTL_H', 'HAVE_TCGETATTR', 'HAVE_VSSCANF', 'HAVE_UNISTD_H', 'HAVE_MKSTEMP', 'HAVE_SIZECHANGE', 'HAVE_WORKING_POLL', 'HAVE_VA_COPY', 'HAVE_UNISTD_H', 'HAVE_FORK', 'HAVE_VFORK', 'HAVE_WORKING_VFORK', 'HAVE_WORKING_FORK', 'USE_FOPEN_BIN_R', 'USE_XTERM_PTY', 'HAVE_TYPEINFO', 'HAVE_IOSTREAM', 'IOSTREAM_NAMESPACE', 'CPP_HAS_STATIC_CAST', 'HAVE_SLK_COLOR', 'HAVE_PANEL_H', 'HAVE_LIBPANEL', 'HAVE_MENU_H', 'HAVE_LIBMENU', 'HAVE_FORM_H', 'HAVE_LIBFORM', 'NCURSES_OSPEED_COMPAT', 'HAVE_CURSES_DATA_BOOLNAMES'], + mappings = {'NC_CONFIG_H': '', 'PACKAGE': '"ncurses"', 'NCURSES_VERSION': '"6.2"', 'NCURSES_PATCHDATE': '20200212', 'SYSTEM_NAME': '"linux-gnu"', 'TERMINFO_DIRS': r'"\/usr\/share\/terminfo"', 'TERMINFO': r'"\/usr\/share\/terminfo"', 'RGB_PATH': r'"\/usr\/share\/X11\/rgb.txt"', 'NCURSES_WRAP_PREFIX': '"_nc_"', 'GCC_SCANFLIKE(fmt,var)': '__attribute__((format(scanf,fmt,var)))', 'GCC_PRINTFLIKE(fmt,var)': '__attribute__((format(printf,fmt,var)))', 'GCC_UNUSED': '__attribute__((unused))', 'GCC_NORETURN': '__attribute__((noreturn))', 'SIG_ATOMIC_T': 'volatile sig_atomic_t', 'USE_OPENPTY_HEADER': '', 'NCURSES_PATHSEP': "0x3a", 'NCURSES_VERSION_STRING': '"6.2.20200212"', 'mbstate_t': 'int'}, +) + +cc_test( + name = "ncurses_test", + srcs = ["@com_google_xls//dependency_support/net_invisible_island_ncurses:ncurses_test.cc"], + deps = [ + ":ncurses", + ], +) diff --git a/dependency_support/net_invisible_island_ncurses/ncurses_test.cc b/dependency_support/net_invisible_island_ncurses/ncurses_test.cc new file mode 100644 index 0000000000..c68e2e4911 --- /dev/null +++ b/dependency_support/net_invisible_island_ncurses/ncurses_test.cc @@ -0,0 +1,68 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Verifies we can compile and link something that depends +// on ncurses, and runs a simple ncurses program. + +#include +#include +#include +#include +#include +#include + +static char msg[] = " --- ncurses_unittest: PASS --- "; + +static void test_ncurses() { + char *term = getenv("TERM"); + if (term == NULL || !strcmp(term, "unknown")) { + setenv("TERM", "vt100", 1); + } + initscr(); + noecho(); + cbreak(); + nonl(); + curs_set(0); + + int c = (COLS - sizeof(msg) -1) / 2; + move(LINES/2, c >= 0 ? c : 0); + + if (has_colors()) + { + start_color(); + init_pair(1, COLOR_RED, COLOR_BLACK); + init_pair(2, COLOR_GREEN, COLOR_BLACK); + init_pair(3, COLOR_YELLOW, COLOR_BLACK); + init_pair(4, COLOR_BLUE, COLOR_BLACK); + init_pair(5, COLOR_CYAN, COLOR_BLACK); + init_pair(6, COLOR_MAGENTA, COLOR_BLACK); + init_pair(7, COLOR_WHITE, COLOR_BLACK); + } + + for (int n = 0; n < sizeof(msg)-1; n++) + { + addch(msg[n]); + attrset(COLOR_PAIR(n % 8)); + refresh(); + usleep(10000); + } + + endwin(); +} + +int main(int argc, char **argv) { + test_ncurses(); + printf("PASS\\n"); + return 0; +} diff --git a/dependency_support/net_invisible_island_ncurses/workspace.bzl b/dependency_support/net_invisible_island_ncurses/workspace.bzl new file mode 100644 index 0000000000..8ddefb71a1 --- /dev/null +++ b/dependency_support/net_invisible_island_ncurses/workspace.bzl @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the ncurses library, used by iverilog.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "net_invisible_island_ncurses", + urls = [ + "https://invisible-mirror.net/archives/ncurses/ncurses-6.2.tar.gz", + ], + strip_prefix = "ncurses-6.2", + sha256 = "30306e0c76e0f9f1f0de987cf1c82a5c21e1ce6568b9227f7da5b71cbea86c9d", + build_file = Label("//dependency_support:net_invisible_island_ncurses/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/org_gnu_bison/BUILD b/dependency_support/org_gnu_bison/BUILD new file mode 100644 index 0000000000..2da35c1571 --- /dev/null +++ b/dependency_support/org_gnu_bison/BUILD @@ -0,0 +1,19 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["restricted"]) # GPLv3 + +exports_files([ + "calc_test.sh", +]) diff --git a/dependency_support/org_gnu_bison/bison.bzl b/dependency_support/org_gnu_bison/bison.bzl new file mode 100644 index 0000000000..f8e4c6fbc4 --- /dev/null +++ b/dependency_support/org_gnu_bison/bison.bzl @@ -0,0 +1,90 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Build rule for generating C or C++ sources with Bison. +""" + +def _genyacc_impl(ctx): + """Implementation for genyacc rule.""" + + # Argument list + args = ctx.actions.args() + args.add("--defines=%s" % ctx.outputs.header_out.path) + args.add("--output-file=%s" % ctx.outputs.source_out.path) + if ctx.attr.prefix: + args.add("--name-prefix=%s" % ctx.attr.prefix) + args.add_all([ctx.expand_location(opt) for opt in ctx.attr.extra_options]) + args.add(ctx.file.src.path) + + # Output files + outputs = ctx.outputs.extra_outs + [ + ctx.outputs.header_out, + ctx.outputs.source_out, + ] + + ctx.actions.run( + executable = ctx.executable._bison, + env = { + "M4": ctx.executable._m4.path, + "BISON_PKGDATADIR": ctx.attr._bison_data_path.files.to_list()[0].path, + }, + arguments = [args], + inputs = ctx.files._bison_data + ctx.files.src, + tools = [ctx.executable._m4], + outputs = outputs, + mnemonic = "Yacc", + progress_message = "Generating %s and %s from %s" % + ( + ctx.outputs.source_out.short_path, + ctx.outputs.header_out.short_path, + ctx.file.src.short_path, + ), + ) + +genyacc = rule( + implementation = _genyacc_impl, + doc = "Generate C/C++-language sources from a Yacc file using Bison.", + attrs = { + "src": attr.label( + mandatory = True, + allow_single_file = [".y", ".yy", ".yc", ".ypp"], + doc = "The .y, .yy, or .yc source file for this rule", + ), + "header_out": attr.output( + mandatory = True, + doc = "The generated 'defines' header file", + ), + "source_out": attr.output(mandatory = True, doc = "The generated source file"), + "prefix": attr.string( + doc = "External symbol prefix for Bison. This string is " + + "passed to bison as the -p option, causing the resulting C " + + "file to define external functions named 'prefix'parse, " + + "'prefix'lex, etc. instead of yyparse, yylex, etc.", + ), + "extra_outs": attr.output_list(doc = "A list of extra generated output files."), + "extra_options": attr.string_list( + doc = "A list of extra options to pass to Bison. These are " + + "subject to $(location ...) expansion.", + ), + "_bison": attr.label( + default = "@org_gnu_bison//:bison", + executable = True, + cfg = "host", + ), + "_bison_data": attr.label(default = "@org_gnu_bison//:bison_runtime_data"), + "_bison_data_path": attr.label(default = "@org_gnu_bison//:data", allow_single_file = True), + "_m4": attr.label(default = "@org_gnu_m4//:m4", executable = True, cfg = "host"), + }, + output_to_genfiles = True, +) diff --git a/dependency_support/org_gnu_bison/bundled.BUILD.bazel b/dependency_support/org_gnu_bison/bundled.BUILD.bazel new file mode 100644 index 0000000000..790dfed7c6 --- /dev/null +++ b/dependency_support/org_gnu_bison/bundled.BUILD.bazel @@ -0,0 +1,278 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:private"]) + +licenses(["restricted"]) + +load("@com_google_xls//dependency_support:copy.bzl", "copy", "touch") +load("@com_google_xls//dependency_support/org_gnu_bison:bison.bzl", "genyacc") +load("@com_google_xls//dependency_support:pseudo_configure.bzl", "pseudo_configure") + +exports_files(["LICENSE"]) + +PKG_DATA_DIR = "./data" + +# TODO(xls-team): Make the locale files be generated correctly +LOCALE_DIR = "/dev/null" + +cc_library( + name = "libbison", + srcs = [ + "lib/allocator.c", + "lib/areadlink.c", + "lib/argmatch.c", + "lib/asnprintf.c", + "lib/asprintf.c", + "lib/basename.c", + "lib/basename-lgpl.c", + "lib/binary-io.c", + "lib/bitrotate.c", + "lib/bitset.c", + "lib/bitset/array.c", + "lib/bitset/list.c", + "lib/bitset/stats.c", + "lib/bitset/table.c", + "lib/bitset/vector.c", + "lib/bitsetv.c", + "lib/c-ctype.c", + "lib/c-strcasecmp.c", + "lib/c-strncasecmp.c", + "lib/cloexec.c", + "lib/close-stream.c", + "lib/closeout.c", + "lib/concat-filename.c", + "lib/dirname.c", + "lib/dirname-lgpl.c", + "lib/dup-safer.c", + "lib/dup-safer-flag.c", + "lib/exitfail.c", + "lib/fatal-signal.c", + "lib/fd-hook.c", + "lib/fd-safer.c", + "lib/fd-safer-flag.c", + "lib/fopen-safer.c", + "lib/fstrcmp.c", + "lib/get-errno.c", + "lib/gethrxtime.c", + "lib/getprogname.c", + "lib/gettime.c", + "lib/gl_array_list.c", + "lib/gl_list.c", + "lib/gl_xlist.c", + "lib/glthread/lock.c", + "lib/glthread/threadlib.c", + "lib/glthread/tls.c", + "lib/hard-locale.c", + "lib/hash.c", + "lib/iswblank.c", + "lib/localcharset.c", + "lib/localtime-buffer.c", + "lib/math.c", + "lib/mbchar.c", + "lib/mbfile.c", + "lib/mbrtowc.c", + "lib/mbswidth.c", + "lib/path-join.c", + "lib/pipe-safer.c", + "lib/pipe2.c", + "lib/pipe2-safer.c", + "lib/printf-args.c", + "lib/printf-frexpl.c", + "lib/printf-parse.c", + "lib/progname.c", + "lib/progreloc.c", + "lib/quotearg.c", + "lib/readlink.c", + "lib/relocatable.c", + "lib/rename.c", + "lib/rmdir.c", + "lib/setenv.c", + "lib/sig-handler.c", + "lib/spawn-pipe.c", + "lib/stripslash.c", + "lib/timespec.c", + "lib/timevar.c", + "lib/unistd.c", + "lib/vasnprintf.c", + "lib/vasprintf.c", + "lib/wait-process.c", + "lib/wctype-h.c", + "lib/xalloc-die.c", + "lib/xconcat-filename.c", + "lib/xhash.c", + "lib/xmalloc.c", + "lib/xmemdup0.c", + "lib/xreadlink.c", + "lib/xsize.c", + "lib/xstrndup.c", + "lib/xtime.c", + ], + copts = [ + "-Wno-vla", + "-Wno-maybe-uninitialized", + "-DHAVE_CONFIG_H", + # These are defined in gnulib's fcntl.h, but they're the only thing we + # need from gnulib, so we define them here instead for simplicity. + "-DO_BINARY=0", + "-DO_TEXT=0", + ], + includes = [ + ".", + "lib", + ], + textual_hdrs = + glob( + ["lib/**/*.h"], + ) + [ + "lib/config.h", + "lib/configmake.h", + "lib/printf-frexp.c", + "lib/timevar.def", + "src/system.h", + "lib/textstyle.h", + ], +) + +# In order to use bison properly in a shell command, set the env variable +# BISON_PKGDATADIR=[workspace dir]/bison/data in any shell command you invoke +# bison in. If you use the genyacc rule, Bazel should do this for for you. +# You will also need to set M4 to the location of m4, which is +# generally $(location @org_gnu_m4//:m4) in a Bazel genrule. + +filegroup( + name = "bison_runtime_data", + srcs = glob(["data/**/*"]), + output_licenses = ["unencumbered"], + path = "data", + visibility = ["//visibility:public"], +) + +exports_files([ + "data", +]) + +cc_library( + name = "bison_src_headers", + textual_hdrs = glob(["src/*.h"]) + [ + "src/scan-skel.c", + "src/scan-gram.c", + "src/scan-code.c", + ], +) + +# Note: This target is used by the Bazel "genyacc" rule, as well as by the +# explictly-declared dependencies. +cc_binary( + name = "bison", + srcs = [ + "src/AnnotationList.c", + "src/InadequacyList.c", + "src/Sbitset.c", + "src/assoc.c", + "src/closure.c", + "src/complain.c", + "src/conflicts.c", + "src/derives.c", + "src/files.c", + "src/fixits.c", + "src/getargs.c", + "src/gram.c", + "src/graphviz.c", + "src/ielr.c", + "src/lalr.c", + "src/location.c", + "src/lr0.c", + "src/main.c", + "src/muscle-tab.c", + "src/named-ref.c", + "src/nullable.c", + "src/output.c", + "src/parse-gram.c", + "src/print.c", + "src/print-graph.c", + "src/print-xml.c", + "src/reader.c", + "src/reduce.c", + "src/relation.c", + "src/scan-code-c.c", + "src/scan-gram-c.c", + "src/scan-skel-c.c", + "src/state.c", + "src/symlist.c", + "src/symtab.c", + "src/tables.c", + "src/uniqstr.c", + ], + copts = [ + "-Wno-vla", + "-DHAVE_CONFIG_H", + "-DPKGDATADIR='\"" + PKG_DATA_DIR + "\"'", + "-DLOCALEDIR='\"" + LOCALE_DIR + "\"'", + # This is defined in gnulib's stdio.h, but we don't need anything else + # from that file. + "-D_GL_ATTRIBUTE_FORMAT_PRINTF(A,B)=", + ], + data = [":bison_runtime_data"], + output_licenses = ["unencumbered"], + visibility = ["//visibility:public"], + deps = [ + ":bison_src_headers", + ":libbison", + ], +) + +copy( + name = "textstyle_h", + src = "lib/textstyle.in.h", + out = "lib/textstyle.h", +) + +pseudo_configure( + name = "config_h", + src = "lib/config.in.h", + out = "lib/config.h", + defs = ['CHECK_PRINTF_SAFE', 'C_LOCALE_MAYBE_EILSEQ', 'DBL_EXPBIT0_WORD', 'ENABLE_NLS', 'FUNC_REALPATH_WORKS', 'GNULIB_CANONICALIZE_LGPL', 'GNULIB_CLOSE_STREAM', 'GNULIB_DIRNAME', 'GNULIB_FD_SAFER_FLAG', 'GNULIB_FOPEN_SAFER', 'GNULIB_FSCANF', 'GNULIB_MALLOC_GNU', 'GNULIB_MSVC_NOTHROW', 'GNULIB_PIPE2_SAFER', 'GNULIB_SCANF', 'GNULIB_SNPRINTF', 'GNULIB_STRERROR', 'GNULIB_TEST_CALLOC_POSIX', 'GNULIB_TEST_CANONICALIZE_FILE_NAME', 'GNULIB_TEST_CLOEXEC', 'GNULIB_TEST_CLOSE', 'GNULIB_TEST_DUP2', 'GNULIB_TEST_ENVIRON', 'GNULIB_TEST_FCNTL', 'GNULIB_TEST_FOPEN', 'GNULIB_TEST_FPRINTF_POSIX', 'GNULIB_TEST_FSYNC', 'GNULIB_TEST_GETDTABLESIZE', 'GNULIB_TEST_GETRUSAGE', 'GNULIB_TEST_GETTIMEOFDAY', 'GNULIB_TEST_ISNAN', 'GNULIB_TEST_ISNAND', 'GNULIB_TEST_ISNANF', 'GNULIB_TEST_ISNANL', 'GNULIB_TEST_ISWBLANK', 'GNULIB_TEST_LDEXPL', 'GNULIB_TEST_MALLOC_POSIX', 'GNULIB_TEST_MBRTOWC', 'GNULIB_TEST_MBSINIT', 'GNULIB_TEST_MEMCHR', 'GNULIB_TEST_OBSTACK_PRINTF', 'GNULIB_TEST_OPEN', 'GNULIB_TEST_PERROR', 'GNULIB_TEST_PIPE2', 'GNULIB_TEST_POSIX_SPAWNATTR_DESTROY', 'GNULIB_TEST_POSIX_SPAWNATTR_INIT', 'GNULIB_TEST_POSIX_SPAWNATTR_SETFLAGS', 'GNULIB_TEST_POSIX_SPAWNATTR_SETSIGMASK', 'GNULIB_TEST_POSIX_SPAWNP', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSE', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_ADDOPEN', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_DESTROY', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_INIT', 'GNULIB_TEST_PRINTF_POSIX', 'GNULIB_TEST_RAISE', 'GNULIB_TEST_READLINK', 'GNULIB_TEST_REALLOC_POSIX', 'GNULIB_TEST_REALPATH', 'GNULIB_TEST_RENAME', 'GNULIB_TEST_SIGACTION', 'GNULIB_TEST_SIGPROCMASK', 'GNULIB_TEST_SNPRINTF', 'GNULIB_TEST_SPRINTF_POSIX', 'GNULIB_TEST_STPCPY', 'GNULIB_TEST_STRDUP', 'GNULIB_TEST_STRERROR', 'GNULIB_TEST_STRNDUP', 'GNULIB_TEST_STRVERSCMP', 'GNULIB_TEST_UNLINK', 'GNULIB_TEST_UNSETENV', 'GNULIB_TEST_VASPRINTF', 'GNULIB_TEST_VSNPRINTF', 'GNULIB_TEST_VSPRINTF_POSIX', 'GNULIB_TEST_WAITPID', 'GNULIB_TEST_WCWIDTH', 'GWINSZ_IN_SYS_IOCTL', 'HAVE_ALLOCA', 'HAVE_ALLOCA_H', 'HAVE_CALLOC_POSIX', 'HAVE_CANONICALIZE_FILE_NAME', 'HAVE_CATGETS', 'HAVE_CLOCK_GETTIME', 'HAVE_CLOCK_SETTIME', 'HAVE_DCGETTEXT', 'HAVE_DECL_ALARM', 'HAVE_DECL_CLEARERR_UNLOCKED', 'HAVE_DECL_FEOF_UNLOCKED', 'HAVE_DECL_FERROR_UNLOCKED', 'HAVE_DECL_FFLUSH_UNLOCKED', 'HAVE_DECL_FGETS_UNLOCKED', 'HAVE_DECL_FPUTC_UNLOCKED', 'HAVE_DECL_FPUTS_UNLOCKED', 'HAVE_DECL_FREAD_UNLOCKED', 'HAVE_DECL_FWRITE_UNLOCKED', 'HAVE_DECL_GETCHAR_UNLOCKED', 'HAVE_DECL_GETC_UNLOCKED', 'HAVE_DECL_GETDTABLESIZE', 'HAVE_DECL_ISWBLANK', 'HAVE_DECL_OBSTACK_PRINTF', 'HAVE_DECL_PROGRAM_INVOCATION_NAME', 'HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME', 'HAVE_DECL_PUTCHAR_UNLOCKED', 'HAVE_DECL_PUTC_UNLOCKED', 'HAVE_DECL_SETENV', 'HAVE_DECL_SNPRINTF', 'HAVE_DECL_STRDUP', 'HAVE_DECL_STRERROR_R', 'HAVE_DECL_STRNDUP', 'HAVE_DECL_STRNLEN', 'HAVE_DECL_UNSETENV', 'HAVE_DECL_VSNPRINTF', 'HAVE_DECL_WCWIDTH', 'HAVE_DECL___FPENDING', 'HAVE_DUP2', 'HAVE_ENVIRON_DECL', 'HAVE_FCNTL', 'HAVE_FEATURES_H', 'HAVE_FREXP_IN_LIBC', 'HAVE_FSYNC', 'HAVE_GETCWD', 'HAVE_GETDTABLESIZE', 'HAVE_GETOPT_H', 'HAVE_GETOPT_LONG_ONLY', 'HAVE_GETRUSAGE', 'HAVE_GETTEXT', 'HAVE_GETTIMEOFDAY', 'HAVE_INTMAX_T', 'HAVE_INTTYPES_H', 'HAVE_INTTYPES_H_WITH_UINTMAX', 'HAVE_ISASCII', 'HAVE_ISNAND_IN_LIBC', 'HAVE_ISNANF_IN_LIBC', 'HAVE_ISWBLANK', 'HAVE_ISWCNTRL', 'HAVE_LANGINFO_CODESET', 'HAVE_LDEXPL', 'HAVE_LIMITS_H', 'HAVE_LINK', 'HAVE_LOCALE_H', 'HAVE_LONG_LONG_INT', 'HAVE_LSTAT', 'HAVE_MALLOC_GNU', 'HAVE_MALLOC_POSIX', 'HAVE_MAP_ANONYMOUS', 'HAVE_MATH_H', 'HAVE_MBRTOWC', 'HAVE_MBSINIT', 'HAVE_MBSTATE_T', 'HAVE_MEMORY_H', 'HAVE_MINMAX_IN_SYS_PARAM_H', 'HAVE_MPROTECT', 'HAVE_OBSTACK_PRINTF', 'HAVE_PIPE', 'HAVE_PIPE2', 'HAVE_POSIX_SPAWN', 'HAVE_POSIX_SPAWNATTR_T', 'HAVE_POSIX_SPAWN_FILE_ACTIONS_T', 'HAVE_RAISE', 'HAVE_READLINK', 'HAVE_READLINKAT', 'HAVE_REALLOC_POSIX', 'HAVE_REALPATH', 'HAVE_SEARCH_H', 'HAVE_SETENV', 'HAVE_SETLOCALE', 'HAVE_SIGACTION', 'HAVE_SIGALTSTACK', 'HAVE_SIGINTERRUPT', 'HAVE_SIGSET_T', 'HAVE_SIG_ATOMIC_T', 'HAVE_SNPRINTF', 'HAVE_SNPRINTF_RETVAL_C99', 'HAVE_SPAWN_H', 'HAVE_STDINT_H', 'HAVE_STDINT_H_WITH_UINTMAX', 'HAVE_STDIO_EXT_H', 'HAVE_STDLIB_H', 'HAVE_STPCPY', 'HAVE_STRDUP', 'HAVE_STRERROR_R', 'HAVE_STRINGS_H', 'HAVE_STRING_H', 'HAVE_STRNDUP', 'HAVE_STRNLEN', 'HAVE_STRUCT_SIGACTION_SA_SIGACTION', 'HAVE_STRUCT_TMS', 'HAVE_STRVERSCMP', 'HAVE_SYMLINK', 'HAVE_SYS_CDEFS_H', 'HAVE_SYS_MMAN_H', 'HAVE_SYS_PARAM_H', 'HAVE_SYS_RESOURCE_H', 'HAVE_SYS_SOCKET_H', 'HAVE_SYS_STAT_H', 'HAVE_SYS_TIMES_H', 'HAVE_SYS_TIME_H', 'HAVE_SYS_TYPES_H', 'HAVE_SYS_WAIT_H', 'HAVE_TCDRAIN', 'HAVE_TOWLOWER', 'HAVE_TSEARCH', 'HAVE_UNISTD_H', 'HAVE_UNSETENV', 'HAVE_UNSIGNED_LONG_LONG_INT', 'HAVE_VASPRINTF', 'HAVE_VSNPRINTF', 'HAVE_WAITID', 'HAVE_WCHAR_H', 'HAVE_WCHAR_T', 'HAVE_WCRTOMB', 'HAVE_WCSLEN', 'HAVE_WCSNLEN', 'HAVE_WCTYPE_H', 'HAVE_WCWIDTH', 'HAVE_WINT_T', 'HAVE_WORKING_O_NOATIME', 'HAVE_WORKING_O_NOFOLLOW', 'HAVE__BOOL', 'HAVE___XPG_STRERROR_R', 'LSTAT_FOLLOWS_SLASHED_SYMLINK', 'MALLOC_0_IS_NONNULL', '__USE_MINGW_ANSI_STDIO', 'STDC_HEADERS', 'STRERROR_R_CHAR_P', 'USE_POSIX_THREADS', 'USE_POSIX_THREADS_WEAK', '_ALL_SOURCE', '_DARWIN_C_SOURCE', '_GNU_SOURCE', '_NETBSD_SOURCE', '_OPENBSD_SOURCE', '_POSIX_PTHREAD_SEMANTICS', '__STDC_WANT_IEC_60559_ATTRIBS_EXT__', '__STDC_WANT_IEC_60559_BFP_EXT__', '__STDC_WANT_IEC_60559_DFP_EXT__', '__STDC_WANT_IEC_60559_FUNCS_EXT__', '__STDC_WANT_IEC_60559_TYPES_EXT__', '__STDC_WANT_LIB_EXT2__', '__STDC_WANT_MATH_SPEC_FUNCS__', '_TANDEM_SOURCE', '_HPUX_ALT_XOPEN_SOCKET_API', '__EXTENSIONS__', 'USE_UNLOCKED_IO', 'WORDS_BIGENDIAN', '_DARWIN_USE_64_BIT_INODE', '_NETBSD_SOURCE', '_USE_STD_STAT', '__GNUC_STDC_INLINE__'], + mappings = {'DBL_EXPBIT0_BIT': '20', 'FLT_EXPBIT0_BIT': '23', 'FLT_EXPBIT0_WORD': '0', 'GETTIMEOFDAY_TIMEZONE': 'struct timezone', 'HAVE_DECL_GETHRTIME': '0', 'HAVE_DECL_MBSWIDTH_IN_WCHAR_H': '0', 'HAVE_DECL__SNPRINTF': '0', 'HAVE_DECL___ARGV': '0', 'INSTALLPREFIX': r'"\/usr\/local"', 'M4': r'"\/usr\/bin\/m4"', 'M4_GNU_OPTION': '""', 'PACKAGE': '"bison"', 'PACKAGE_BUGREPORT': '"bug-bison@gnu.org"', 'PACKAGE_COPYRIGHT_YEAR': '2019', 'PACKAGE_NAME': '"GNU Bison"', 'PACKAGE_STRING': '"GNU Bison 3.5"', 'PACKAGE_TARNAME': '"bison"', 'PACKAGE_URL': r'"http:\/\/www.gnu.org\/software\/bison\/"', 'PACKAGE_VERSION': '"3.5"', 'PROMOTED_MODE_T': 'mode_t', 'BOURNE_SHELL': r'"\/bin\/sh"', 'USER_LABEL_PREFIX': '', 'VERSION': '"3.5"', '_Noreturn': '', '_GL_ASYNC_SAFE': '', '_GL_EXTERN_INLINE_STDHEADER_BUG': '', '_GL_INLINE': 'static _GL_UNUSED', '_GL_EXTERN_INLINE': 'static _GL_UNUSED', '_GL_EXTERN_INLINE_IN_USE': '', '_GL_INLINE_HEADER_CONST_PRAGMA': '', '_GL_INLINE_HEADER_BEGIN': '', '_GL_INLINE_HEADER_END': '', 'restrict': '__restrict', '_Restrict': '', '__restrict__': '', '_GL_UNUSED': '', '_UNUSED_PARAMETER_': '_GL_UNUSED', '_GL_UNUSED_LABEL': '', '_GL_ATTRIBUTE_PURE': '', '_GL_ATTRIBUTE_CONST': '', '_GL_ATTRIBUTE_MALLOC': ''}, +) + +touch( + name = "configmake_h", + out = "lib/configmake.h", + contents = dict( + LIBDIR = '"/usr/local/lib"', + ), +) + +# Compile one of the examples and use that as a basic smoke test. +genyacc( + name = "calc_y", + src = "examples/c/calc/calc.y", + header_out = "calc.h", + source_out = "calc.cc", +) + +cc_binary( + name = "calc", + srcs = ["calc.cc", "calc.h"], +) + +sh_test( + name = "calc_test", + srcs = ["@com_google_xls//dependency_support/org_gnu_bison:calc_test.sh"], + args = ["$(location :calc)"], + data = [ + ":calc", + ], +) diff --git a/dependency_support/org_gnu_bison/calc_test.sh b/dependency_support/org_gnu_bison/calc_test.sh new file mode 100755 index 0000000000..6deffb06c7 --- /dev/null +++ b/dependency_support/org_gnu_bison/calc_test.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +RESULT=$(echo "21*2" | $1) + +if [ "$RESULT" == "42" ]; then + echo "Success" +else + >&2 echo "Encountered unexpected result $RESULT" + exit 1 +fi diff --git a/dependency_support/org_gnu_bison/workspace.bzl b/dependency_support/org_gnu_bison/workspace.bzl new file mode 100644 index 0000000000..294d89872b --- /dev/null +++ b/dependency_support/org_gnu_bison/workspace.bzl @@ -0,0 +1,31 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the Bison parser generator, used by iverilog.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "org_gnu_bison", + urls = [ + "http://ftp.acc.umu.se/mirror/gnu.org/gnu/bison/bison-3.5.tar.xz", + "http://ftp.gnu.org/gnu/bison/bison-3.5.tar.xz", + ], + strip_prefix = "bison-3.5", + sha256 = "55e4a023b1b4ad19095a5f8279f0dc048fa29f970759cea83224a6d5e7a3a641", + build_file = Label("//dependency_support:org_gnu_bison/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/org_gnu_gperf/BUILD b/dependency_support/org_gnu_gperf/BUILD new file mode 100644 index 0000000000..e617bd6d6f --- /dev/null +++ b/dependency_support/org_gnu_gperf/BUILD @@ -0,0 +1,15 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["restricted"]) # GPLv3 diff --git a/dependency_support/org_gnu_gperf/bundled.BUILD.bazel b/dependency_support/org_gnu_gperf/bundled.BUILD.bazel new file mode 100644 index 0000000000..395935dd6e --- /dev/null +++ b/dependency_support/org_gnu_gperf/bundled.BUILD.bazel @@ -0,0 +1,58 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Description: +# A BUILD file for gperf, a perfect hash function generator. + +licenses(["restricted"]) # GPLv3 + +load("@com_google_xls//dependency_support:copy.bzl", "copy") +load("@com_google_xls//dependency_support:pseudo_configure.bzl", "pseudo_configure") + +exports_files(["COPYING"]) + +cc_library( + name = "gperf_includes", + textual_hdrs = glob([ + "src/*.icc", + ]), +) + +cc_binary( + name = "gperf", + srcs = glob([ + "lib/*.cc", + "lib/*.h", + "src/*.cc", + "src/*.h", + ]) + [ + "src/config.h", + ], + deps = [":gperf_includes"], + includes = ["lib", "src"], + copts = [ + "-Wno-register", + "-Wno-strict-aliasing", + ], + output_licenses = ["unencumbered"], + visibility = ["//visibility:public"], +) + +pseudo_configure( + name = "config_h", + src = "src/config.h.in", + out = "src/config.h", + defs = [], + mappings = {'HAVE_DYNAMIC_ARRAY': '0', 'PACKAGE_BUGREPORT': '""', 'PACKAGE_NAME': '""', 'PACKAGE_STRING': '""', 'PACKAGE_TARNAME': '""', 'PACKAGE_VERSION': '""'}, +) diff --git a/dependency_support/org_gnu_gperf/workspace.bzl b/dependency_support/org_gnu_gperf/workspace.bzl new file mode 100644 index 0000000000..627e6de875 --- /dev/null +++ b/dependency_support/org_gnu_gperf/workspace.bzl @@ -0,0 +1,31 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the gperf perfect hash function generator, used by iverilog.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "org_gnu_gperf", + urls = [ + "http://ftp.acc.umu.se/mirror/gnu.org/gnu/gperf/gperf-3.1.tar.gz", + "http://ftp.gnu.org/gnu/gperf/gperf-3.1.tar.gz", + ], + strip_prefix = "gperf-3.1", + sha256 = "588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae2", + build_file = Label("//dependency_support:org_gnu_gperf/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/org_gnu_m4/BUILD b/dependency_support/org_gnu_m4/BUILD new file mode 100644 index 0000000000..e617bd6d6f --- /dev/null +++ b/dependency_support/org_gnu_m4/BUILD @@ -0,0 +1,15 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +licenses(["restricted"]) # GPLv3 diff --git a/dependency_support/org_gnu_m4/bundled.BUILD.bazel b/dependency_support/org_gnu_m4/bundled.BUILD.bazel new file mode 100644 index 0000000000..7ab7c4e781 --- /dev/null +++ b/dependency_support/org_gnu_m4/bundled.BUILD.bazel @@ -0,0 +1,122 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Description: +# A BUILD file for m4 based on FSF stock source. + +load("@com_google_xls//dependency_support:copy.bzl", "copy", "touch") +load("@com_google_xls//dependency_support:pseudo_configure.bzl", "pseudo_configure") + +package( + default_visibility = ["//visibility:public"], + features = [ + "-parse_headers", + "no_layering_check", + ], +) + +licenses(["restricted"]) # GPLv3 + +exports_files(["COPYING"]) + +HEADERS_WITH_C_EXTENSION = [ + "lib/frexp.c", + "lib/isnan.c", + "lib/printf-frexp.c", +] + +cc_library( + name = "m4_headers", + textual_hdrs = glob( + ["lib/**/*.h", "src/*.h"], + exclude = ["lib/spawn_int.h"] + ) + [ + "lib/config.h", + "lib/configmake.h", + "build-aux/snippet/unused-parameter.h", + ] + HEADERS_WITH_C_EXTENSION, + includes = ["lib", "build-aux/snippet"], + visibility = ["//visibility:private"], +) + +cc_binary( + name = "m4", + srcs = glob( + ["lib/*.c", "src/*.c"], + exclude = HEADERS_WITH_C_EXTENSION + [ + "lib/msvc-nothrow.c", + "lib/fcntl.c", + "lib/fflush.c", + "lib/fpending.c", + "lib/frexpl.c", + "lib/fseeko.c", + "lib/gettimeofday.c", + "lib/localeconv.c", + "lib/nl_langinfo.c", + "lib/pipe2.c", + "lib/regcomp.c", + "lib/regex.c", + "lib/regexec.c", + "lib/regex_internal.c", + "lib/sigaction.c", + "lib/sigprocmask.c", + "lib/snprintf.c", + "lib/spawn_faction_destroy.c", + "lib/spawn_faction_init.c", + "lib/spawnattr_setflags.c", + "lib/spawnattr_setsigmask.c", + "lib/spawni.c", + "lib/spawnp.c", + "lib/strchrnul.c", + "lib/strerror-override.c", + "lib/waitpid.c", + ], + ), + deps = [ + ":m4_headers", + ], + output_licenses = ["unencumbered"], + defines = [ + "_IO_ftrylockfile", + # Glibc 2.28 made _IO_IN_BACKUP private. For now, work around this + # problem by defining it ourselves. FIXME: Do not rely on glibc + # internals. + "_IO_IN_BACKUP=0x100", + "_GNU_SOURCE", + "O_BINARY=0", + ], + copts = [ + "-Wno-attributes", + ], +) + +pseudo_configure( + name = "config_h", + src = "lib/config.hin", + out = "lib/config.h", + defs = ['CHECK_PRINTF_SAFE', 'C_LOCALE_MAYBE_EILSEQ', 'DBL_EXPBIT0_WORD', 'FUNC_NL_LANGINFO_YESEXPR_WORKS', 'FUNC_REALPATH_WORKS', 'GNULIB_CANONICALIZE_LGPL', 'GNULIB_CLOSE_STREAM', 'GNULIB_DIRNAME', 'GNULIB_FD_SAFER_FLAG', 'GNULIB_FFLUSH', 'GNULIB_FILENAMECAT', 'GNULIB_FOPEN_SAFER', 'GNULIB_FSCANF', 'GNULIB_LOCK', 'GNULIB_PIPE2_SAFER', 'GNULIB_SCANF', 'GNULIB_SIGPIPE', 'GNULIB_SNPRINTF', 'GNULIB_STRERROR', 'GNULIB_TEST_BTOWC', 'GNULIB_TEST_CANONICALIZE_FILE_NAME', 'GNULIB_TEST_CHDIR', 'GNULIB_TEST_CLOEXEC', 'GNULIB_TEST_CLOSE', 'GNULIB_TEST_CLOSEDIR', 'GNULIB_TEST_DIRFD', 'GNULIB_TEST_DUP', 'GNULIB_TEST_DUP2', 'GNULIB_TEST_ENVIRON', 'GNULIB_TEST_FCLOSE', 'GNULIB_TEST_FCNTL', 'GNULIB_TEST_FDOPEN', 'GNULIB_TEST_FFLUSH', 'GNULIB_TEST_FOPEN', 'GNULIB_TEST_FPURGE', 'GNULIB_TEST_FREXP', 'GNULIB_TEST_FREXPL', 'GNULIB_TEST_FSEEK', 'GNULIB_TEST_FSEEKO', 'GNULIB_TEST_FSTAT', 'GNULIB_TEST_FTELL', 'GNULIB_TEST_FTELLO', 'GNULIB_TEST_GETCWD', 'GNULIB_TEST_GETDTABLESIZE', 'GNULIB_TEST_GETPAGESIZE', 'GNULIB_TEST_GETTIMEOFDAY', 'GNULIB_TEST_LINK', 'GNULIB_TEST_LOCALECONV', 'GNULIB_TEST_LSEEK', 'GNULIB_TEST_LSTAT', 'GNULIB_TEST_MALLOC_POSIX', 'GNULIB_TEST_MBRTOWC', 'GNULIB_TEST_MBSINIT', 'GNULIB_TEST_MBTOWC', 'GNULIB_TEST_MEMCHR', 'GNULIB_TEST_MKDTEMP', 'GNULIB_TEST_MKSTEMP', 'GNULIB_TEST_NL_LANGINFO', 'GNULIB_TEST_OPEN', 'GNULIB_TEST_OPENDIR', 'GNULIB_TEST_PIPE2', 'GNULIB_TEST_POSIX_SPAWNATTR_DESTROY', 'GNULIB_TEST_POSIX_SPAWNATTR_INIT', 'GNULIB_TEST_POSIX_SPAWNATTR_SETFLAGS', 'GNULIB_TEST_POSIX_SPAWNATTR_SETSIGMASK', 'GNULIB_TEST_POSIX_SPAWNP', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSE', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_ADDOPEN', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_DESTROY', 'GNULIB_TEST_POSIX_SPAWN_FILE_ACTIONS_INIT', 'GNULIB_TEST_PUTENV', 'GNULIB_TEST_RAISE', 'GNULIB_TEST_RAWMEMCHR', 'GNULIB_TEST_READDIR', 'GNULIB_TEST_READLINK', 'GNULIB_TEST_REALPATH', 'GNULIB_TEST_RENAME', 'GNULIB_TEST_RMDIR', 'GNULIB_TEST_SECURE_GETENV', 'GNULIB_TEST_SETENV', 'GNULIB_TEST_SETLOCALE', 'GNULIB_TEST_SIGACTION', 'GNULIB_TEST_SIGNBIT', 'GNULIB_TEST_SIGPROCMASK', 'GNULIB_TEST_SLEEP', 'GNULIB_TEST_SNPRINTF', 'GNULIB_TEST_STAT', 'GNULIB_TEST_STRCHRNUL', 'GNULIB_TEST_STRDUP', 'GNULIB_TEST_STRERROR', 'GNULIB_TEST_STRNDUP', 'GNULIB_TEST_STRNLEN', 'GNULIB_TEST_STRSIGNAL', 'GNULIB_TEST_STRSTR', 'GNULIB_TEST_STRTOD', 'GNULIB_TEST_SYMLINK', 'GNULIB_TEST_UNSETENV', 'GNULIB_TEST_VASPRINTF', 'GNULIB_TEST_WAITPID', 'GNULIB_TEST_WCRTOMB', 'GNULIB_TEST_WCTOB', 'GNULIB_TEST_WCTOMB', 'GNULIB_TEST_WRITE', 'HAVE_ALLOCA', 'HAVE_ALLOCA_H', 'HAVE_BTOWC', 'HAVE_CANONICALIZE_FILE_NAME', 'HAVE_CLOSEDIR', 'HAVE_DECL_ALARM', 'HAVE_DECL_CLEARERR_UNLOCKED', 'HAVE_DECL_DIRFD', 'HAVE_DECL_FEOF_UNLOCKED', 'HAVE_DECL_FERROR_UNLOCKED', 'HAVE_DECL_FFLUSH_UNLOCKED', 'HAVE_DECL_FGETS_UNLOCKED', 'HAVE_DECL_FPUTC_UNLOCKED', 'HAVE_DECL_FPUTS_UNLOCKED', 'HAVE_DECL_FREAD_UNLOCKED', 'HAVE_DECL_FSEEKO', 'HAVE_DECL_FTELLO', 'HAVE_DECL_FWRITE_UNLOCKED', 'HAVE_DECL_GETCHAR_UNLOCKED', 'HAVE_DECL_GETC_UNLOCKED', 'HAVE_DECL_GETDTABLESIZE', 'HAVE_DECL_GETENV', 'HAVE_DECL_PROGRAM_INVOCATION_NAME', 'HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME', 'HAVE_DECL_PUTCHAR_UNLOCKED', 'HAVE_DECL_PUTC_UNLOCKED', 'HAVE_DECL_SETENV', 'HAVE_DECL_SIGALTSTACK', 'HAVE_DECL_SLEEP', 'HAVE_DECL_SNPRINTF', 'HAVE_DECL_STRDUP', 'HAVE_DECL_STRERROR_R', 'HAVE_DECL_STRNDUP', 'HAVE_DECL_STRNLEN', 'HAVE_DECL_STRSIGNAL', 'HAVE_DECL_SYS_SIGLIST', 'HAVE_DECL_UNSETENV', 'HAVE_DECL_WCTOB', 'HAVE_DECL___FPENDING', 'HAVE_DIRENT_H', 'HAVE_DIRFD', 'HAVE_DUP2', 'HAVE_ENVIRON_DECL', 'HAVE_FCNTL', 'HAVE_FEATURES_H', 'HAVE_FREXPL_IN_LIBC', 'HAVE_FREXP_IN_LIBC', 'HAVE_FSEEKO', 'HAVE_GETCWD', 'HAVE_GETDTABLESIZE', 'HAVE_GETEGID', 'HAVE_GETEUID', 'HAVE_GETGID', 'HAVE_GETOPT_H', 'HAVE_GETOPT_LONG_ONLY', 'HAVE_GETPAGESIZE', 'HAVE_GETTIMEOFDAY', 'HAVE_GETUID', 'HAVE_INTMAX_T', 'HAVE_INTTYPES_H', 'HAVE_INTTYPES_H_WITH_UINTMAX', 'HAVE_ISBLANK', 'HAVE_ISNAND_IN_LIBC', 'HAVE_ISNANF_IN_LIBC', 'HAVE_ISNANL_IN_LIBC', 'HAVE_ISWCNTRL', 'HAVE_ISWCTYPE', 'HAVE_LANGINFO_CODESET', 'HAVE_LANGINFO_H', 'HAVE_LC_MESSAGES', 'HAVE_LDEXPL_IN_LIBC', 'HAVE_LDEXP_IN_LIBC', 'HAVE_LIMITS_H', 'HAVE_LINK', 'HAVE_LONG_LONG_INT', 'HAVE_LSTAT', 'HAVE_MALLOC_H', 'HAVE_MALLOC_POSIX', 'HAVE_MAP_ANONYMOUS', 'HAVE_MATH_H', 'HAVE_MBRTOWC', 'HAVE_MBSINIT', 'HAVE_MBSTATE_T', 'HAVE_MEMORY_H', 'HAVE_MEMPCPY', 'HAVE_MINMAX_IN_SYS_PARAM_H', 'HAVE_MKDTEMP', 'HAVE_MKSTEMP', 'HAVE_MPROTECT', 'HAVE_NEWLOCALE', 'HAVE_NL_LANGINFO', 'HAVE_OPENDIR', 'HAVE_PIPE', 'HAVE_PIPE2', 'HAVE_POSIX_SPAWN', 'HAVE_POSIX_SPAWNATTR_T', 'HAVE_POSIX_SPAWN_FILE_ACTIONS_T', 'HAVE_RAISE', 'HAVE_RAWMEMCHR', 'HAVE_READDIR', 'HAVE_READLINK', 'HAVE_REALPATH', 'HAVE_SEARCH_H', 'HAVE_SECURE_GETENV', 'HAVE_SETENV', 'HAVE_SETLOCALE', 'HAVE_SETRLIMIT', 'HAVE_SIGACTION', 'HAVE_SIGALTSTACK', 'HAVE_SIGINTERRUPT', 'HAVE_SIGSET_T', 'HAVE_SIG_ATOMIC_T', 'HAVE_SLEEP', 'HAVE_SNPRINTF', 'HAVE_SNPRINTF_RETVAL_C99', 'HAVE_SPAWN_H', 'HAVE_STACK_OVERFLOW_HANDLING', 'HAVE_STACK_T', 'HAVE_STDINT_H', 'HAVE_STDINT_H_WITH_UINTMAX', 'HAVE_STDIO_EXT_H', 'HAVE_STDLIB_H', 'HAVE_STRCHRNUL', 'HAVE_STRDUP', 'HAVE_STRERROR_R', 'HAVE_STRINGS_H', 'HAVE_STRING_H', 'HAVE_STRNDUP', 'HAVE_STRNLEN', 'HAVE_STRSIGNAL', 'HAVE_STRUCT_SIGACTION_SA_SIGACTION', 'HAVE_SYMLINK', 'HAVE_SYS_CDEFS_H', 'HAVE_SYS_MMAN_H', 'HAVE_SYS_PARAM_H', 'HAVE_SYS_SOCKET_H', 'HAVE_SYS_STAT_H', 'HAVE_SYS_TIME_H', 'HAVE_SYS_TYPES_H', 'HAVE_SYS_WAIT_H', 'HAVE_TOWLOWER', 'HAVE_TSEARCH', 'HAVE_UCONTEXT_H', 'HAVE_UNISTD_H', 'HAVE_UNSETENV', 'HAVE_UNSIGNED_LONG_LONG_INT', 'HAVE_USELOCALE', 'HAVE_VASPRINTF', 'HAVE_WAITID', 'HAVE_WCHAR_H', 'HAVE_WCHAR_T', 'HAVE_WCRTOMB', 'HAVE_WCSLEN', 'HAVE_WCSNLEN', 'HAVE_WCTOB', 'HAVE_WCTYPE_H', 'HAVE_WINT_T', 'HAVE_WORKING_O_NOATIME', 'HAVE_WORKING_O_NOFOLLOW', 'HAVE_WORKING_POSIX_SPAWN', 'HAVE__BOOL', 'HAVE___BUILTIN_EXPECT', 'HAVE___FPURGE', 'HAVE___FREADING', 'LSTAT_FOLLOWS_SLASHED_SYMLINK', 'MALLOC_0_IS_NONNULL', 'RENAME_OPEN_FILE_WORKS', 'SIGNAL_SAFE_LIST', 'STDC_HEADERS', 'STRERROR_R_CHAR_P', 'USE_UNLOCKED_IO', '_DARWIN_USE_64_BIT_INODE'], + mappings = {'DBL_EXPBIT0_BIT': '20', 'FAULT_YIELDS_SIGBUS': '0', 'FLEXIBLE_ARRAY_MEMBER': '', 'FLT_EXPBIT0_BIT': '23', 'FLT_EXPBIT0_WORD': '0', 'FUNC_FFLUSH_STDIN': '0', 'GETTIMEOFDAY_TIMEZONE': 'struct', 'HAVE_DECL_FPURGE': '0', 'HAVE_DECL__SNPRINTF': '0', 'HAVE_DECL___ARGV': '0', 'LDBL_EXPBIT0_BIT': '0', 'LDBL_EXPBIT0_WORD': '2', 'PACKAGE': '"m4"', 'PACKAGE_BUGREPORT': '"bug-m4@gnu.org"', 'PACKAGE_NAME': '"GNU M4"', 'PACKAGE_STRING': '"GNU m4 1.4.18"', 'PACKAGE_TARNAME': '"m4"', 'PACKAGE_URL': '"www.gnu.org_software_m4"', 'PACKAGE_VERSION': '"1.4.18"', 'PROMOTED_MODE_T': 'mode_t', 'SYSCMD_SHELL': r'"\/bin\/sh"', 'USER_LABEL_PREFIX': '', 'VERSION': '"1.4.18"', 'restrict': '__restrict', '_UNUSED_PARAMETER_': '_GL_UNUSED'}, + additional = { + '_GL_ARG_NONNULL(o)': '', + '_GL_ATTRIBUTE_FORMAT_PRINTF(a, b)': '', + }, +) + +touch( + name = "configmake_h", + out = "lib/configmake.h", + contents = dict( + LIBDIR = '"/usr/local/lib"', + ), +) diff --git a/dependency_support/org_gnu_m4/workspace.bzl b/dependency_support/org_gnu_m4/workspace.bzl new file mode 100644 index 0000000000..172efdc88c --- /dev/null +++ b/dependency_support/org_gnu_m4/workspace.bzl @@ -0,0 +1,31 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the m4 macro processor, used by Bison.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "org_gnu_m4", + urls = [ + "http://ftp.acc.umu.se/mirror/gnu.org/gnu/m4/m4-1.4.18.tar.xz", + "http://ftp.gnu.org/gnu/m4/m4-1.4.18.tar.xz", + ], + strip_prefix = "m4-1.4.18", + sha256 = "f2c1e86ca0a404ff281631bdc8377638992744b175afb806e25871a24a934e07", + build_file = Label("//dependency_support:org_gnu_m4/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/org_sourceware_bzip2/bundled.BUILD.bazel b/dependency_support/org_sourceware_bzip2/bundled.BUILD.bazel new file mode 100644 index 0000000000..075e57007e --- /dev/null +++ b/dependency_support/org_sourceware_bzip2/bundled.BUILD.bazel @@ -0,0 +1,38 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # BSD derivative, see LICENSE file + +exports_files(["LICENSE"]) + +cc_library( + name = "bzip2", + srcs = [ + "blocksort.c", + "bzlib.c", + "bzlib_private.h", + "compress.c", + "crctable.c", + "decompress.c", + "huffman.c", + "randtable.c", + ], + hdrs = [ + "bzlib.h", + ], + includes = ["."], + deps = [], +) diff --git a/dependency_support/org_sourceware_bzip2/workspace.bzl b/dependency_support/org_sourceware_bzip2/workspace.bzl new file mode 100644 index 0000000000..fd5ddaaa85 --- /dev/null +++ b/dependency_support/org_sourceware_bzip2/workspace.bzl @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the bz2lib library, used by iverilog.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "org_sourceware_bzip2", + urls = [ + "https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz", + ], + strip_prefix = "bzip2-1.0.8", + sha256 = "ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269", + build_file = Label("//dependency_support:org_sourceware_bzip2/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/org_sourceware_libffi/bundled.BUILD.bazel b/dependency_support/org_sourceware_libffi/bundled.BUILD.bazel new file mode 100644 index 0000000000..f382d9c61f --- /dev/null +++ b/dependency_support/org_sourceware_libffi/bundled.BUILD.bazel @@ -0,0 +1,350 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@com_google_xls//dependency_support:automake_substitution.bzl", "automake_substitution") +load("@com_google_xls//dependency_support:copy.bzl", "copy") + +AUTOMAKE_SUBSTITUTIONS = { + "TARGET": "X86_64", + "HAVE_LONG_DOUBLE": "1", + "FFI_EXEC_TRAMPOLINE_TABLE": "0", +} + +automake_substitution( + name = "ffi_h", + src = "include/ffi.h.in", + out = "generated/include/ffi.h", + substitutions = AUTOMAKE_SUBSTITUTIONS, +) + +genrule( + name = "fficonfig_h", + srcs = ["fficonfig.h.in"], + outs = ["generated/fficonfig.h"], + cmd = "sed 's.#undef.//#undef.g' $(location :fficonfig.h.in) > $@", +) + +DEFINES = [ + "HAVE_ALLOCA=1", + "HAVE_ALLOCA_H=1", + "HAVE_AS_CFI_PSEUDO_OP=1", + "HAVE_AS_X86_64_UNWIND_SECTION_TYPE=1", + "HAVE_AS_X86_PCREL=1", + "HAVE_HIDDEN_VISIBILITY_ATTRIBUTE=1", + "HAVE_INTTYPES_H=1", + "HAVE_MEMCPY=1", + "HAVE_MKOSTEMP=1", + "HAVE_MMAP=1", + "HAVE_MMAP_ANON=1", + "HAVE_MMAP_DEV_ZERO=1", + "HAVE_RO_EH_FRAME=1", + "HAVE_STDINT_H=1", + "STDC_HEADERS=1", +] + +copy( + name = "ffitarget_h", + src = "src/x86/ffitarget.h", + out = "generated/include/ffitarget.h", +) + +cc_library( + name = "libffi", + srcs = [ + "generated/fficonfig.h", + "generated/include/ffi.h", + "generated/include/ffitarget.h", + "include/ffi_cfi.h", + "include/ffi_common.h", + "src/closures.c", + "src/debug.c", + "src/java_raw_api.c", + "src/prep_cif.c", + "src/raw_api.c", + "src/types.c", + "src/x86/asmnames.h", + "src/x86/ffi.c", + "src/x86/ffi64.c", + "src/x86/ffiw64.c", + "src/x86/internal.h", + "src/x86/internal64.h", + "src/x86/sysv.S", + "src/x86/unix64.S", + "src/x86/win64.S", + ], + copts = [ + "-Wno-deprecated-declarations", + ], + includes = [ + "generated", + "generated/include", + "include", + ], + defines = DEFINES, + textual_hdrs = ["src/dlmalloc.c"], + visibility = ["//visibility:public"], +) + +[cc_test( + name = "%s_call_test" % call_test, + srcs = [ + "testsuite/libffi.call/ffitest.h", + "testsuite/libffi.call/%s.c" % call_test, + ], + deps = [ + ":libffi", + ], +) for call_test in [ + "align_mixed", + "align_stdcall", + "err_bad_typedef", + "float", + "float1", + "float2", + "float3", + "float4", + "float_va", + "many", + "many2", + "many_double", + "many_mixed", + "negint", + "offsets", + "pr1172638", + "promotion", + "pyobjc-tc", + "return_dbl", + "return_dbl1", + "return_dbl2", + "return_fl", + "return_fl1", + "return_fl2", + "return_fl3", + "return_ldl", + "return_ll", + "return_ll1", + "return_sc", + "return_sl", + "return_uc", + "return_ul", + "strlen", + "strlen2", + "strlen3", + "strlen4", + "struct1", + "struct10", + "struct2", + "struct3", + "struct4", + "struct5", + "struct6", + "struct7", + "struct8", + "struct9", + "uninitialized", + "va_1", + "va_struct1", + "va_struct2", + "va_struct3", +]] + +cc_library( + name = "bhaible_test_support", + hdrs = [ + "testsuite/libffi.bhaible/alignof.h", + ], + textual_hdrs = [ + "testsuite/libffi.bhaible/testcases.c", + ], +) + +[cc_test( + name = "%s_bhaible_test" % bhaible_test, + srcs = [ + "testsuite/libffi.bhaible/%s.c" % bhaible_test, + ], + deps = [ + ":libffi", + ":bhaible_test_support", + ], +) for bhaible_test in [ + "test-call", + "test-callback", +]] + +[cc_test( + name = "%s_closures_test" % closures_test, + srcs = [ + "testsuite/libffi.closures/ffitest.h", + "testsuite/libffi.closures/%s.c" % closures_test, + ], + deps = [ + ":libffi", + ], +) for closures_test in [ + "closure_fn0", + "closure_fn1", + "closure_fn2", + "closure_fn3", + "closure_fn4", + "closure_fn5", + "closure_fn6", + "closure_loc_fn0", + "closure_simple", + "cls_12byte", + "cls_16byte", + "cls_18byte", + "cls_19byte", + "cls_1_1byte", + "cls_20byte", + "cls_20byte1", + "cls_24byte", + "cls_2byte", + "cls_3_1byte", + "cls_3byte1", + "cls_3byte2", + "cls_3float", + "cls_4_1byte", + "cls_4byte", + "cls_5_1_byte", + "cls_5byte", + "cls_64byte", + "cls_6_1_byte", + "cls_6byte", + "cls_7_1_byte", + "cls_7byte", + "cls_8byte", + "cls_9byte1", + "cls_9byte2", + "cls_align_double", + "cls_align_float", + "cls_align_longdouble", + "cls_align_longdouble_split", + "cls_align_longdouble_split2", + "cls_align_pointer", + "cls_align_sint16", + "cls_align_sint32", + "cls_align_sint64", + "cls_align_uint16", + "cls_align_uint32", + "cls_align_uint64", + "cls_dbls_struct", + "cls_double", + "cls_double_va", + "cls_float", + "cls_longdouble", + "cls_longdouble_va", + "cls_many_mixed_args", + "cls_many_mixed_float_double", + "cls_multi_schar", + "cls_multi_sshort", + "cls_multi_sshortchar", + "cls_multi_uchar", + "cls_multi_ushort", + "cls_multi_ushortchar", + "cls_pointer", + "cls_pointer_stack", + "cls_schar", + "cls_sint", + "cls_sshort", + "cls_struct_va1", + "cls_uchar", + "cls_uchar_va", + "cls_uint", + "cls_uint_va", + "cls_ulong_va", + "cls_ulonglong", + "cls_ushort", + "cls_ushort_va", + "err_bad_abi", + "huge_struct", + "nested_struct", + "nested_struct1", + "nested_struct10", + "nested_struct11", + "nested_struct2", + "nested_struct3", + "nested_struct4", + "nested_struct5", + "nested_struct6", + "nested_struct7", + "nested_struct8", + "nested_struct9", + "problem1", + "stret_large", + "stret_large2", + "stret_medium", + "stret_medium2", + "testclosure", +]] + +[cc_test( + name = "%s_complex_test" % complex_test, + srcs = glob([ + "testsuite/libffi.complex/*.inc", + ]) + [ + "testsuite/libffi.call/ffitest.h", + "testsuite/libffi.complex/ffitest.h", + "testsuite/libffi.complex/%s.c" % complex_test, + ], + deps = [ + ":libffi", + ], +) for complex_test in [ + "cls_align_complex_double", + "cls_align_complex_float", + "cls_align_complex_longdouble", + "cls_complex_double", + "cls_complex_float", + "cls_complex_longdouble", + "cls_complex_struct_double", + "cls_complex_struct_float", + "cls_complex_struct_longdouble", + "cls_complex_va_double", + "cls_complex_va_float", + "cls_complex_va_longdouble", + "complex_double", + "complex_float", + "complex_int", + "complex_longdouble", + "many_complex_double", + "many_complex_float", + "many_complex_longdouble", + "return_complex1_double", + "return_complex1_float", + "return_complex1_longdouble", + "return_complex2_double", + "return_complex2_float", + "return_complex2_longdouble", + "return_complex_double", + "return_complex_float", + "return_complex_longdouble", +]] + + +[cc_test( + name = "%s_go_test" % go_test, + srcs = [ + "testsuite/libffi.call/ffitest.h", + "testsuite/libffi.go/ffitest.h", + "testsuite/libffi.go/static-chain.h", + "testsuite/libffi.go/%s.c" % go_test, + ], + deps = [ + ":libffi", + ], +) for go_test in [ + "aa-direct", + "closure1", +]] diff --git a/dependency_support/org_sourceware_libffi/workspace.bzl b/dependency_support/org_sourceware_libffi/workspace.bzl new file mode 100644 index 0000000000..b434eb7e96 --- /dev/null +++ b/dependency_support/org_sourceware_libffi/workspace.bzl @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the libffi library, used by Yosys.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "org_sourceware_libffi", + urls = [ + "https://github.com/libffi/libffi/releases/download/v3.3/libffi-3.3.tar.gz", + ], + strip_prefix = "libffi-3.3", + sha256 = "72fba7922703ddfa7a028d513ac15a85c8d54c8d67f55fa5a4802885dc652056", + build_file = Label("//dependency_support:org_sourceware_libffi/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/pseudo_configure.bzl b/dependency_support/pseudo_configure.bzl new file mode 100644 index 0000000000..452ac8960c --- /dev/null +++ b/dependency_support/pseudo_configure.bzl @@ -0,0 +1,52 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Fake configuration step for hacky substitutions in ".in" files.""" + +def pseudo_configure(name, src, out, defs, mappings, additional = None): + """Creates a genrule that performs a fake 'configure' step on a file. + + Args: + name: Name to use for the created genrule. + src: ".in" file to transform. + out: Path to place the output file contents. + defs: List of definitions to #define as `1`. + mappings: Mapping of definitions with non-trivial values. + additional: Optional mapping of definitions to prepend to the file. + """ + additional = additional or {} + + cmd = "" + + for k, v in additional.items(): + cmd += "echo '#define %s %s' >> $@ &&" % (k, v) + + cmd += "cat $<" + all_defs = "" + for def_ in defs: + cmd += r"| perl -p -e 's/#\s*undef \b(" + def_ + r")\b/#define $$1 1/'" + all_defs += "#define " + def_ + " 1\\n" + for key, value in mappings.items(): + cmd += r"| perl -p -e 's/#\s*undef \b" + key + r"\b/#define " + str(key) + " " + str(value) + "/'" + cmd += r"| perl -p -e 's/#\s*define \b(" + key + r")\b 0/#define $$1 " + str(value) + "/'" + all_defs += "#define " + key + " " + value + "\\n" + cmd += r"| perl -p -e 's/\@DEFS\@/" + all_defs + "/'" + cmd += " >> $@" + native.genrule( + name = name, + srcs = [src], + outs = [out], + cmd = cmd, + message = "Configuring " + src, + ) diff --git a/dependency_support/pybind11/BUILD b/dependency_support/pybind11/BUILD new file mode 100644 index 0000000000..882dc0a057 --- /dev/null +++ b/dependency_support/pybind11/BUILD @@ -0,0 +1,15 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Required to make this a package. diff --git a/dependency_support/pybind11/pybind11.bzl b/dependency_support/pybind11/pybind11.bzl new file mode 100644 index 0000000000..c6a8a0f644 --- /dev/null +++ b/dependency_support/pybind11/pybind11.bzl @@ -0,0 +1,27 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Wraps the pybind_extension rule with a py_library rule.""" + +load("@pybind11_bazel//:build_defs.bzl", "pybind_extension") + +def xls_pybind_extension(**kwargs): + name = kwargs["name"] + py_deps = kwargs.pop("py_deps", []) + pybind_extension(**kwargs) + native.py_library( + name = name, + data = [name + ".so"], + deps = py_deps, + ) diff --git a/dependency_support/tcl_tcl_tk/bundled.BUILD.bazel b/dependency_support/tcl_tcl_tk/bundled.BUILD.bazel new file mode 100644 index 0000000000..930b0466fe --- /dev/null +++ b/dependency_support/tcl_tcl_tk/bundled.BUILD.bazel @@ -0,0 +1,232 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Description: Tcl scripting language. + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +# The following settings are taken from the command line build when executing +# configure : +TCL_COPTS = [ + "-DHAVE_ZLIB=", # Enables ZLIB + "-DTCL_DBGX=", + "-DHAVE_LIMITS_H=1", + "-DHAVE_UNISTD_H=1", + "-DHAVE_SYS_PARAM_H=1", + "-DUSE_THREAD_ALLOC=1", + "-D_REENTRANT=1", + "-D_THREAD_SAFE=1", + "-DHAVE_PTHREAD_ATTR_SETSTACKSIZE=1", + "-DHAVE_PTHREAD_ATFORK=1", + "-DTCL_THREADS=1", + "-DPEEK_XCLOSEIM=1", + "-D_LARGEFILE64_SOURCE=1", + "-DTCL_WIDE_INT_TYPE=long\ long", + "-DHAVE_STRUCT_STAT64=1", + "-DHAVE_OPEN64=1", + "-DHAVE_LSEEK64=1", + "-DHAVE_TYPE_OFF64_T=1", + "-DHAVE_GETCWD=1", + "-DHAVE_OPENDIR=1", + "-DHAVE_STRSTR=1", + "-DHAVE_STRTOL=1", + "-DHAVE_STRTOLL=1", + "-DHAVE_STRTOULL=1", + "-DHAVE_TMPNAM=1", + "-DHAVE_WAITPID=1", + "-DHAVE_GETPWUID_R_5=1", + "-DHAVE_GETPWUID_R=1", + "-DHAVE_GETPWNAM_R_5=1", + "-DHAVE_GETPWNAM_R=1", + "-DHAVE_GETGRGID_R_5=1", + "-DHAVE_GETGRGID_R=1", + "-DHAVE_GETGRNAM_R_5=1", + "-DHAVE_GETGRNAM_R=1", + "-DHAVE_GETHOSTBYNAME_R_6=1", + "-DHAVE_GETHOSTBYNAME_R=1", + "-DHAVE_GETHOSTBYADDR_R_8=1", + "-DHAVE_GETHOSTBYADDR_R=1", + "-DUSE_TERMIOS=1", + "-DTIME_WITH_SYS_TIME=1", + "-DHAVE_TM_ZONE=1", + "-DHAVE_GMTIME_R=1", + "-DHAVE_LOCALTIME_R=1", + "-DHAVE_TM_GMTOFF=1", + "-DHAVE_SYS_TIME_H=1", + "-DHAVE_TIMEZONE_VAR=1", + "-DHAVE_ST_BLKSIZE=1", + "-DSTDC_HEADERS=1", + "-DHAVE_SIGNED_CHAR=1", + "-DHAVE_LANGINFO=1", + "-DHAVE_SYS_IOCTL_H=1", + "-DTCL_SHLIB_EXT=\\\".so\\\"", + "-Wno-implicit-int", + "-fno-strict-aliasing", + "-fPIC", +] + +# tclAlloc uses additional define +cc_library( + name = "tclAlloc", + srcs = ["generic/tclAlloc.c"], + hdrs = glob([ + "generic/*.h", + "unix/*.h", + ]), + includes = ["generic", "unix"], + copts = TCL_COPTS + [ + "-DUSE_TCLALLOC=0", + ], +) + +# tclUnixInit uses additional define +cc_library( + name = "tclUnixInit", + srcs = [ + "generic/tcl.h", + "generic/tclDecls.h", + "generic/tclInt.h", + "generic/tclIntDecls.h", + "generic/tclPort.h", + "generic/tclTomMathDecls.h", + "unix/tclUnixInit.c", + ], + hdrs = glob([ + "generic/*.h", + "unix/*.h", + ]), + copts = TCL_COPTS + [ + "-DTCL_LIBRARY=\\\"dependency_support/tcl_tk/library\\\"", + "-DTCL_PACKAGE_PATH=\\\"dependency_support/tcl_tk/tcl8.6.4\\\"", + ], + includes = ["generic", "unix"], +) + +# pkg-config +cc_library( + name = "tclPkgConfig", + srcs = [ + "compat/unistd.h", + "generic/tclPkgConfig.c", + ], + hdrs = glob([ + "generic/*.h", + "unix/*.h", + ]), + copts = [ + "-DCFG_INSTALL_LIBDIR='\"this_LIBDIR_does_not_exist\"'", + "-DCFG_INSTALL_BINDIR='\"this_BINDIR_does_not_exist\"'", + "-DCFG_INSTALL_DOCDIR='\"this_DOCDIR_does_not_exist\"'", + "-DCFG_INSTALL_INCDIR='\"this_INCDIR_does_not_exist\"'", + "-DCFG_INSTALL_SCRDIR='\"this_SCRDIR_does_not_exist\"'", + "-DCFG_RUNTIME_LIBDIR='\"this_LIBDIR_does_not_exist\"'", + "-DCFG_RUNTIME_BINDIR='\"this_BINDIR_does_not_exist\"'", + "-DCFG_RUNTIME_DOCDIR='\"this_DOCDIR_does_not_exist\"'", + "-DCFG_RUNTIME_INCDIR='\"this_INCDIR_does_not_exist\"'", + "-DCFG_RUNTIME_SCRDIR='\"this_SCRDIR_does_not_exist\"'", + "-DTCL_CFGVAL_ENCODING='\"ascii\"'", + ], + includes = [ + "generic/", + "unix/", + ], +) + +# This is the libtcl +cc_library( + name = "tcl", + srcs = [ + "generic/regcomp.c", + "generic/regerror.c", + "generic/regexec.c", + "generic/regfree.c", + "unix/tclLoadDl.c", + ] + glob( + [ + "generic/tcl*.c", + "unix/tcl*.c", + "libtommath/*.c", + ], + exclude = [ + "generic/tclLoadNone.c", + "generic/tclPkgConfig.c", + "generic/tclUniData.c", + "libtommath/bn_deprecated.c", + "libtommath/bn_mp_init_i32.c", + "libtommath/bn_mp_init_i64.c", + "libtommath/bn_mp_init_u32.c", + "libtommath/bn_mp_init_u64.c", + "libtommath/bn_mp_init_ul.c", + "libtommath/bn_mp_init_l.c", + "libtommath/bn_mp_init_ll.c", + "libtommath/bn_mp_init_ull.c", + "libtommath/bn_mp_iseven.c", + "libtommath/bn_mp_set_u64.c", + "libtommath/bn_mp_set_ul.c", + "libtommath/bn_mp_set_ull.c", + "libtommath/bn_s_mp_exptmod.c", + "libtommath/bn_s_mp_exptmod_fast.c", + "unix/tclAppInit.c", + "unix/tclLoad*.c", + "unix/tclUnixInit.c", + "unix/tclXtNotify.c", + "unix/tclXtTest.c", + ], + ), + hdrs = glob([ + "generic/*.h", + "generic/reg*.c", + "libtommath/*.h", + ]), + copts = TCL_COPTS + [ + "-w", + "$(STACK_FRAME_UNLIMITED)", # regexec.c + ], + includes = [ + "generic/", + "libtommath/", + "unix/", + "xlib/", + ], + linkopts = ["-ldl", "-lpthread"], + textual_hdrs = glob([ + "generic/*.decls", + ]) + [ + "generic/tclUniData.c", + ], + deps = [ + ":tclAlloc", + ":tclPkgConfig", + ":tclUnixInit", + "@zlib//:zlib", + ], +) + +# tcl shell +cc_binary( + name = "tclsh", + srcs = ["unix/tclAppInit.c"], + copts = TCL_COPTS + [ + "-w", + ], + includes = ["generic", "unix"], + deps = [ + ":tcl", + ":tclAlloc", + ":tclPkgConfig", + ":tclUnixInit", + ], +) diff --git a/dependency_support/tcl_tcl_tk/workspace.bzl b/dependency_support/tcl_tcl_tk/workspace.bzl new file mode 100644 index 0000000000..6d7bc644a9 --- /dev/null +++ b/dependency_support/tcl_tcl_tk/workspace.bzl @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads the ABC system for sequential synthesis and verification, used by yosys.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def repo(): + maybe( + http_archive, + name = "tcl_tcl_tk", + urls = [ + "https://prdownloads.sourceforge.net/tcl/tcl8.6.10-src.tar.gz", + ], + strip_prefix = "tcl8.6.10", + sha256 = "5196dbf6638e3df8d5c87b5815c8c2b758496eb6f0e41446596c9a4e638d87ed", + build_file = Label("//dependency_support:tcl_tcl_tk/bundled.BUILD.bazel"), + ) diff --git a/dependency_support/z3/BUILD b/dependency_support/z3/BUILD new file mode 100644 index 0000000000..882dc0a057 --- /dev/null +++ b/dependency_support/z3/BUILD @@ -0,0 +1,15 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Required to make this a package. diff --git a/dependency_support/z3/build_defs.bzl b/dependency_support/z3/build_defs.bzl new file mode 100644 index 0000000000..efa130ceda --- /dev/null +++ b/dependency_support/z3/build_defs.bzl @@ -0,0 +1,115 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Macros for generating Z3 input sources/headers.""" + +MK_MAKE_SRCS = [ + "src/api/api_commands.cpp", + "src/api/api_log_macros.cpp", + "src/api/dll/gparams_register_modules.cpp", + "src/api/dll/install_tactic.cpp", + "src/api/dll/mem_initializer.cpp", + "src/util/z3_version.h", +] + +MK_MAKE_HDRS = [ + "src/api/api_log_macros.h", +] + +PARAMS_HDRS = [ + "src/ackermannization/ackermannization_params.hpp", + "src/ackermannization/ackermannize_bv_tactic_params.hpp", + "src/ast/fpa/fpa2bv_rewriter_params.hpp", + "src/ast/normal_forms/nnf_params.hpp", + "src/ast/pattern/pattern_inference_params_helper.hpp", + "src/ast/pp_params.hpp", + "src/ast/rewriter/arith_rewriter_params.hpp", + "src/ast/rewriter/array_rewriter_params.hpp", + "src/ast/rewriter/bool_rewriter_params.hpp", + "src/ast/rewriter/bv_rewriter_params.hpp", + "src/ast/rewriter/fpa_rewriter_params.hpp", + "src/ast/rewriter/poly_rewriter_params.hpp", + "src/ast/rewriter/rewriter_params.hpp", + "src/math/polynomial/algebraic_params.hpp", + "src/math/realclosure/rcf_params.hpp", + "src/model/model_params.hpp", + "src/model/model_evaluator_params.hpp", + "src/muz/base/fp_params.hpp", + "src/nlsat/nlsat_params.hpp", + "src/opt/opt_params.hpp", + "src/parsers/util/parser_params.hpp", + "src/sat/sat_asymm_branch_params.hpp", + "src/sat/sat_params.hpp", + "src/sat/sat_scc_params.hpp", + "src/sat/sat_simplifier_params.hpp", + "src/smt/params/smt_params_helper.hpp", + "src/solver/parallel_params.hpp", + "src/solver/solver_params.hpp", + "src/solver/combined_solver_params.hpp", + "src/tactic/smtlogics/qfufbv_tactic_params.hpp", + "src/tactic/sls/sls_params.hpp", + "src/tactic/tactic_params.hpp", + "src/util/lp/lp_params.hpp", +] + +DB_HDRS = [ + "src/ast/pattern/database.h", +] + +GEN_SRCS = MK_MAKE_SRCS + +GEN_HDRS = MK_MAKE_HDRS + PARAMS_HDRS + DB_HDRS + +def gen_srcs(): + """Runs provided Python scripts for source generation.""" + copy_cmds = [] + for out in MK_MAKE_SRCS + MK_MAKE_HDRS: + copy_cmds.append("cp external/z3/" + out + " $(@D)/$$(dirname " + out + ")") + copy_cmds = ";\n".join(copy_cmds) + ";\n" + + native.genrule( + name = "gen_srcs", + srcs = [ + "LICENSE.txt", + "scripts/mk_make.py", + ] + native.glob(["src/**", "examples/**"]), + tools = ["scripts/mk_make.py"], + outs = MK_MAKE_SRCS + MK_MAKE_HDRS, + # We can't use $(location) here, since the bundled script internally + # makes assumptions about where files are located. + cmd = "cd external/z3; " + + "python scripts/mk_make.py; " + + "cd ../..;" + + copy_cmds, + ) + + for params_hdr in PARAMS_HDRS: + src_file = params_hdr[0:-4] + ".pyg" + native.genrule( + name = "gen_" + params_hdr[0:-4], + srcs = [src_file], + tools = ["scripts/pyg2hpp.py"], + outs = [params_hdr], + cmd = "python $(location scripts/pyg2hpp.py) $< $$(dirname $@)", + ) + + for db_hdr in DB_HDRS: + src = db_hdr[0:-1] + "smt2" + native.genrule( + name = "gen_" + db_hdr[0:-2], + srcs = [src], + tools = ["scripts/mk_pat_db.py"], + outs = [db_hdr], + cmd = "python $(location scripts/mk_pat_db.py) $< $@", + ) diff --git a/dependency_support/z3/bundled.BUILD.bazel b/dependency_support/z3/bundled.BUILD.bazel new file mode 100644 index 0000000000..73a54d4623 --- /dev/null +++ b/dependency_support/z3/bundled.BUILD.bazel @@ -0,0 +1,493 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Description: +# Z3 is a theorem prover from Microsoft Research. + +load("@com_google_xls//dependency_support/z3:build_defs.bzl", "gen_srcs", "GEN_SRCS", "GEN_HDRS") + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +package( + default_visibility = ["//visibility:private"], + features = [ + "-layering_check", + "-parse_headers", + ], +) + +gen_srcs() + +common_copts = [ + "-fexceptions", + "-fsigned-char", + "-Wno-unused-variable", + "-Wno-non-virtual-dtor", + "-Wno-delete-non-virtual-dtor", + "-Wno-switch", + "-Wno-reorder", + "-Wno-string-conversion", + "-Wno-overloaded-virtual", + # "-Wno-sometimes-uninitialized", + "-D_MP_INTERNAL", + "-D_AMD64_", + "-D_USE_THREAD_LOCAL", + "-D_EXTERNAL_RELEASE", + "-D_LINUX_", + "-D_NO_OMP_", +] + +common_linkopts = [ + "-lpthread", +] + +cc_library( + name = "z3lib", + srcs = GEN_SRCS + glob([ + "src/ackermannization/*.cpp", + "src/api/*.cpp", + "src/ast/*.cpp", + "src/ast/fpa/*.cpp", + "src/ast/macros/*.cpp", + "src/ast/normal_forms/*.cpp", + "src/ast/pattern/*.cpp", + "src/ast/proofs/*.cpp", + "src/ast/rewriter/*.cpp", + "src/ast/rewriter/bit_blaster/*.cpp", + "src/ast/substitution/*.cpp", + "src/cmd_context/*.cpp", + "src/cmd_context/extra_cmds/*.cpp", + "src/math/automata/*.cpp", + "src/math/dd/*.cpp", + "src/math/euclid/*.cpp", + "src/math/grobner/*.cpp", + "src/math/hilbert/*.cpp", + "src/math/interval/*.cpp", + "src/math/lp/*.cpp", + "src/math/polynomial/*.cpp", + "src/math/realclosure/*.cpp", + "src/math/simplex/*.cpp", + "src/math/subpaving/*.cpp", + "src/math/subpaving/tactic/*.cpp", + "src/model/*.cpp", + "src/muz/base/*.cpp", + "src/muz/bmc/*.cpp", + "src/muz/clp/*.cpp", + "src/muz/dataflow/*.cpp", + "src/muz/ddnf/*.cpp", + "src/muz/fp/*.cpp", + "src/muz/rel/*.cpp", + "src/muz/spacer/*.cpp", + "src/muz/tab/*.cpp", + "src/muz/transforms/*.cpp", + "src/nlsat/*.cpp", + "src/nlsat/tactic/*.cpp", + "src/opt/*.cpp", + "src/parsers/smt2/*.cpp", + "src/parsers/util/*.cpp", + "src/qe/*.cpp", + "src/sat/*.cpp", + "src/sat/sat_solver/*.cpp", + "src/sat/tactic/*.cpp", + "src/smt/*.cpp", + "src/smt/params/*.cpp", + "src/smt/proto_model/*.cpp", + "src/smt/tactic/*.cpp", + "src/solver/*.cpp", + "src/tactic/*.cpp", + "src/tactic/aig/*.cpp", + "src/tactic/arith/*.cpp", + "src/tactic/bv/*.cpp", + "src/tactic/core/*.cpp", + "src/tactic/fd_solver/*.cpp", + "src/tactic/fpa/*.cpp", + "src/tactic/portfolio/*.cpp", + "src/tactic/sls/*.cpp", + "src/tactic/smtlogics/*.cpp", + "src/tactic/ufbv/*.cpp", + "src/test/fuzzing/*.cpp", + "src/util/*.cpp", + "src/util/lp/*.cpp", + ]), + hdrs = GEN_HDRS + glob([ + "src/ackermannization/*.h", + "src/ackermannization/*.hpp", + "src/api/*.h", + "src/api/*.hpp", + "src/ast/*.h", + "src/ast/*.hpp", + "src/ast/fpa/*.h", + "src/ast/fpa/*.hpp", + "src/ast/macros/*.h", + "src/ast/macros/*.hpp", + "src/ast/normal_forms/*.h", + "src/ast/normal_forms/*.hpp", + "src/ast/pattern/*.h", + "src/ast/pattern/*.hpp", + "src/ast/proofs/*.h", + "src/ast/proofs/*.hpp", + "src/ast/rewriter/*.h", + "src/ast/rewriter/*.hpp", + "src/ast/rewriter/bit_blaster/*.h", + "src/ast/rewriter/bit_blaster/*.hpp", + "src/ast/substitution/*.h", + "src/ast/substitution/*.hpp", + "src/cmd_context/*.h", + "src/cmd_context/*.hpp", + "src/cmd_context/extra_cmds/*.h", + "src/cmd_context/extra_cmds/*.hpp", + "src/math/automata/*.h", + "src/math/automata/*.hpp", + "src/math/grobner/*.h", + "src/math/grobner/*.hpp", + "src/math/hilbert/*.h", + "src/math/hilbert/*.hpp", + "src/math/interval/*.h", + "src/math/interval/*.hpp", + "src/math/dd/*.h", + "src/math/dd/*.hpp", + "src/math/euclid/*.h", + "src/math/euclid/*.hpp", + "src/math/lp/*.h", + "src/math/lp/*.hpp", + "src/math/polynomial/*.h", + "src/math/polynomial/*.hpp", + "src/math/simplex/*.h", + "src/math/simplex/*.hpp", + "src/math/realclosure/*.h", + "src/math/realclosure/*.hpp", + "src/math/subpaving/*.h", + "src/math/subpaving/*.hpp", + "src/math/subpaving/tactic/*.h", + "src/math/subpaving/tactic/*.hpp", + "src/model/*.h", + "src/model/*.hpp", + "src/muz/base/*.h", + "src/muz/base/*.hpp", + "src/muz/bmc/*.h", + "src/muz/bmc/*.hpp", + "src/muz/clp/*.h", + "src/muz/clp/*.hpp", + "src/muz/dataflow/*.h", + "src/muz/dataflow/*.hpp", + "src/muz/ddnf/*.h", + "src/muz/ddnf/*.hpp", + "src/muz/fp/*.h", + "src/muz/fp/*.hpp", + "src/muz/rel/*.h", + "src/muz/rel/*.hpp", + "src/muz/spacer/*.h", + "src/muz/spacer/*.hpp", + "src/muz/tab/*.h", + "src/muz/tab/*.hpp", + "src/muz/transforms/*.h", + "src/muz/transforms/*.hpp", + "src/nlsat/*.h", + "src/nlsat/*.hpp", + "src/nlsat/tactic/*.h", + "src/nlsat/tactic/*.hpp", + "src/opt/*.h", + "src/opt/*.hpp", + "src/parsers/smt2/*.h", + "src/parsers/smt2/*.hpp", + "src/parsers/util/*.h", + "src/parsers/util/*.hpp", + "src/qe/*.h", + "src/qe/*.hpp", + "src/sat/*.h", + "src/sat/*.hpp", + "src/sat/sat_solver/*.h", + "src/sat/sat_solver/*.hpp", + "src/sat/tactic/*.h", + "src/sat/tactic/*.hpp", + "src/smt/*.h", + "src/smt/params/*.h", + "src/smt/proto_model/*.h", + "src/smt/tactic/*.h", + "src/smt/tactic/*.hpp", + "src/solver/*.h", + "src/solver/*.hpp", + "src/tactic/*.h", + "src/tactic/*.hpp", + "src/tactic/aig/*.h", + "src/tactic/aig/*.hpp", + "src/tactic/arith/*.h", + "src/tactic/arith/*.hpp", + "src/tactic/core/*.h", + "src/tactic/core/*.hpp", + "src/tactic/bv/*.h", + "src/tactic/bv/*.hpp", + "src/tactic/fd_solver/*.h", + "src/tactic/fd_solver/*.hpp", + "src/tactic/fpa/*.h", + "src/tactic/fpa/*.hpp", + "src/tactic/portfolio/*.h", + "src/tactic/portfolio/*.hpp", + "src/tactic/sls/*.h", + "src/tactic/sls/*.hpp", + "src/tactic/smtlogics/*.h", + "src/tactic/smtlogics/*.hpp", + "src/tactic/ufbv/*.h", + "src/tactic/ufbv/*.hpp", + "src/test/fuzzing/*.h", + "src/test/fuzzing/*.hpp", + "src/util/*.h", + "src/util/*.hpp", + "src/util/lp/*.h", + "src/util/lp/*.hpp", + ]), + copts = common_copts, + #features = ["-use_header_modules"], # Incompatible with -fexceptions. + includes = [ + "src/util", + "src/", + ], +) + +cc_library( + name = "api", + hdrs = glob( + [ + "src/api/z3*.h", + "src/api/c++/z3++.h", + ], + ), + deps = [":z3lib"], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "z3", + srcs = glob( + [ + "src/shell/*.cpp", + "src/shell/*.h", + ], + exclude = [ + "src/shell/gparams_register_modules.cpp", + "src/shell/install_tactic.cpp", + "src/shell/mem_initializer.cpp", + ], + ), + copts = common_copts, + linkopts = common_linkopts, + features = ["-use_header_modules"], # Incompatible with -fexceptions. + includes = ["src/shell"], + visibility = ["//visibility:public"], + deps = [ + ":z3lib", + ], +) + +cc_binary( + name = "test-z3", + srcs = glob( + [ + "src/test/*.cpp", + "src/test/*.h", + ], + exclude = [ + "src/test/gparams_register_modules.cpp", + "src/test/install_tactic.cpp", + "src/test/mem_initializer.cpp", + ], + ), + copts = common_copts, + linkopts = common_linkopts, + features = ["-use_header_modules"], # Incompatible with -fexceptions. + includes = ["src/test"], + visibility = ["//visibility:public"], + deps = [ + ":z3lib", + ], +) + +cc_binary( + name = "cpp_example", + srcs = glob( + [ + "examples/c++/*.cpp", + "examples/c++/*.h", + ], + ), + copts = [ + "-fexceptions", + "-Wno-implicit-function-declaration", + "-Wno-sometimes-uninitialized", + "-Wno-unused-variable", + ], + linkopts = common_linkopts, + features = ["-use_header_modules"], # Incompatible with -fexceptions. + includes = ["examples/c++"], + deps = [":api"], +) + +cc_binary( + name = "c_example", + srcs = glob( + [ + "examples/c/*.c", + "examples/c/*.h", + ], + ), + copts = [ + "-fexceptions", + "-Wno-implicit-function-declaration", + "-Wno-sometimes-uninitialized", + "-Wno-unused-variable", + ], + linkopts = common_linkopts, + features = ["-use_header_modules"], # Incompatible with -fexceptions. + includes = ["examples/c"], + deps = [":api"], +) + +cc_binary( + name = "maxsat", + srcs = glob( + [ + "examples/maxsat/*.c", + "examples/maxsat/*.h", + ], + ), + copts = [ + "-fexceptions", + "-Wno-implicit-function-declaration", + "-Wno-sometimes-uninitialized", + "-Wno-unused-variable", + ], + linkopts = common_linkopts, + features = ["-use_header_modules"], # Incompatible with -fexceptions. + includes = ["examples/maxsat"], + deps = [":api"], +) + +cc_library( + name = "z3main", + srcs = glob( + [ + "src/shell/*.cpp", + "src/shell/*.h", + ], + exclude = [ + "src/shell/gparams_register_modules.cpp", + "src/shell/install_tactic.cpp", + "src/shell/mem_initializer.cpp", + ], + ), + copts = ["-Dmain=z3_main"] + common_copts, + features = ["-use_header_modules"], # Incompatible with -fexceptions. + deps = [ + ":z3lib", + ], +) + +modules = [ + "random", + "vector", + "symbol_table", + "region", + "symbol", + "heap", + "hashtable", + "rational", + "inf_rational", + "ast", + "optional", + "bit_vector", + "string_buffer", + "map", + "diff_logic", + "uint_set", + "expr_rand", + "list", + "small_object_allocator", + "timeout", + "proof_checker", + "simplifier", + "bit_blaster", + "var_subst", + "simple_parser", + "api", + "old_interval", + "get_implied_equalities", + "arith_simplifier_plugin", + "matcher", + "object_allocator", + "mpq", + "total_order", + "dl_table", + "dl_context", + "dl_util", + "dl_product_relation", + "dl_relation", + "parray", + "stack", + "escaped", + "buffer", + "chashtable", + "ex", + "api_bug", + "arith_rewriter", + "check_assumptions", + "smt_context", + "theory_dl", + "model_retrieval", + "factor_rewriter", + "smt2print_parse", + "substitution", + "polynomial", + "upolynomial", + "algebraic", + "prime_generator", + "permutation", + "ext_numeral", + "interval", + "f2n", + "hwf", + "trigo", + "bits", + "mpbq", + "mpfx", + "mpff", + "horn_subsume_model_converter", + "model2expr", + "hilbert_basis", + "heap_trie", + "karr", + "no_overflow", + "datalog_parser", + "datalog_parser_file", + "rcf", + "polynorm", + "dl_query", + "expr_substitution", +] + +failed_modules = [ + "memory", # TODO(b/122357007): Fails despite being a NOP on non-Windows platforms + "nlarith_util", + "nlsat", + "quant_solve", + "qe_arith", + # TODO(b/67053323): Following fail with asan. + "mpz", + "bv_simplifier_plugin", +] + +medium_modules = [ + "mpf", +] diff --git a/xls/BUILD b/xls/BUILD new file mode 100644 index 0000000000..9d38dd0c68 --- /dev/null +++ b/xls/BUILD @@ -0,0 +1,29 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# XLS: Accelerator Synthesis + +package_group( + name = "xls_internal", + packages = [ + "//xls/...", + ], +) + +package( + default_visibility = [":xls_internal"], + licenses = ["notice"], # Apache 2.0 +) + +exports_files(["LICENSE"]) diff --git a/xls/README.md b/xls/README.md new file mode 100644 index 0000000000..78414c5b30 --- /dev/null +++ b/xls/README.md @@ -0,0 +1,3 @@ +# XLS: Accelerator Synthesis + +Open source landing zone. diff --git a/xls/build/BUILD b/xls/build/BUILD new file mode 100644 index 0000000000..ea4b341092 --- /dev/null +++ b/xls/build/BUILD @@ -0,0 +1,18 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package( + default_visibility = [":xls_internal"], + licenses = ["notice"], # Apache 2.0 +) diff --git a/xls/build/build_defs.bzl b/xls/build/build_defs.bzl new file mode 100644 index 0000000000..b433ee8635 --- /dev/null +++ b/xls/build/build_defs.bzl @@ -0,0 +1,369 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Contains macros for DSLX test targets.""" + +# genrules are used in this file +load("@bazel_skylib//rules:build_test.bzl", "build_test") + +_IR_CONVERTER_MAIN = "//xls/dslx:ir_converter_main" +_OPT_MAIN = "//xls/tools:opt_main" +_CODEGEN_MAIN = "//xls/tools:codegen_main" +_DSLX_TEST = "//xls/dslx/interpreter:dslx_test" +_INTERPRETER_MAIN = "//xls/dslx/interpreter:interpreter_main" + +DEFAULT_DELAY_MODEL = "unit" + +def _codegen_stem(codegen_params): + """Returns a string based on codegen params for use in target names. + + String contains notable elements from ths codegen parameters such as clock + period, delay model, etc. + + Args: + codegen_params: Codegen parameters. + + Returns: + String based on codegen params. + """ + delay_model = codegen_params.get("delay_model", DEFAULT_DELAY_MODEL) + if "clock_period_ps" in codegen_params: + return "clock_{}ps_model_{}".format( + codegen_params["clock_period_ps"], + delay_model, + ) + else: + return "stages_{}_model_{}".format( + codegen_params["pipeline_stages"], + delay_model, + ) + +def _codegen( + name, + srcs, + codegen_params, + entry = None, + tags = []): + """Generates a Verilog file by running codegen_main on the source IR files. + + Args: + name: Name of the Verilog file to generate. + srcs: IR sources. + codegen_params: Codegen configuration used for Verilog generation. + entry: Name of entry function to codegen. + tags: Tags to add to RTL target. + """ + codegen_flags = [] + codegen_flags.append("--delay_model=" + + codegen_params.get("delay_model", DEFAULT_DELAY_MODEL)) + + CODEGEN_FLAGS = ( + "clock_period_ps", + "pipeline_stages", + "entry", + "input_valid_signal", + "output_valid_signal", + "module_name", + "clock_margin_percent", + ) + for flag_name in CODEGEN_FLAGS: + if flag_name in codegen_params: + codegen_flags.append("--{}={}".format( + flag_name, + codegen_params[flag_name], + )) + verilog_file = name + ".v" + module_sig_file = name + ".sig.pbtxt" + native.genrule( + name = name, + srcs = srcs, + outs = [verilog_file, module_sig_file], + cmd = ("$(location %s) %s --output_signature_path=$(@D)/%s " + + "--output_verilog_path=$(@D)/%s $<") % ( + _CODEGEN_MAIN, + " ".join(codegen_flags), + module_sig_file, + verilog_file, + ), + exec_tools = [_CODEGEN_MAIN], + tags = tags, + ) + +def _make_benchmark_args(package_name, name, entry, args): + benchmark_args = [package_name + "/" + name + ".ir"] + if entry: + benchmark_args.append("--entry={}".format(entry)) + benchmark_args += args + return benchmark_args + +def dslx_codegen(name, dslx_dep, configs, entry = None, tags = None): + """Exercises code generation to create Verilog (post IR conversion). + + Multiple code generation configurations can be given. + + Args: + name: Describes base name of the targets to create; must be suffixed with + "_codegen". + dslx_dep: A label that indicates where the IR targets live; + that is, it is the corresponding dslx_test rule's "name" as a label. + configs: List of code-generation configurations, which can specify + any/all of: clock_period_ps, pipeline_stages, entry, + clock_margin_percent, delay_model. + entry: Entry function name to use for code generation. + tags: Tags to use for the resulting test targets. + """ + if not name.endswith("_codegen"): + fail("Codegen name must end with '_codegen': " + repr(name)) + base_name = name[:-len("_codegen")] + tags = tags or [] + package_name = dslx_dep.split(":")[0].lstrip("/") or native.package_name() + for params in configs: + _codegen( + name = "{}_{}".format(base_name, _codegen_stem(params)), + srcs = [dslx_dep + "_opt_ir"], + codegen_params = params, + entry = entry, + tags = tags, + ) + + # Also create a codegen benchmark target. + codegen_benchmark_args = _make_benchmark_args(package_name, base_name + ".opt", entry, args = []) + codegen_benchmark_args.append("--delay_model={}".format( + params.get("delay_model", DEFAULT_DELAY_MODEL), + )) + for flag_name in ( + "clock_period_ps", + "pipeline_stages", + "entry", + "clock_margin_percent", + ): + if flag_name in params: + codegen_benchmark_args.append("--{}={}".format( + flag_name, + params[flag_name], + )) + + native.sh_test( + name = "{}_benchmark_codegen_test_{}".format( + base_name, + _codegen_stem(params), + ), + srcs = ["//xls/tools:benchmark_test_sh"], + args = codegen_benchmark_args, + data = [ + "//xls/dslx:ir_converter_main", + "//xls/tools:benchmark_main", + "//xls/tools:opt_main", + dslx_dep + "_all_ir", + ], + tags = tags, + ) + +# TODO(meheff): dslx_test includes a bunch of XLS internal specific stuff such +# as generating benchmarks and convert IR. These should be factored out so we +# have a clean macro for end-user use. +def dslx_test( + name, + srcs, + deps = None, + entry = None, + args = None, + convert_ir = True, + prove_unopt_eq_opt = True, + generate_benchmark = True, + tags = []): + """Runs all test cases inside of a DSLX source file as a test target. + + Args: + name: 'Base' name for the targets that get created. + srcs: '.x' file sources. + deps: Dependent '.x' file sources. + entry: Name (currently *mangled* name) of the entry point that should be + converted / code generated. + args: Additional arguments to pass to the DSLX interpreter and IR + converter. + convert_ir: Whether or not to convert the DSLX code to IR. + generate_benchmark: Whether or not to create a benchmark target (that + analyses XLS scheduled critical path). + prove_unopt_eq_opt: Whether or not to generate a test to compare semantics + of opt vs. non-opt IR. Only enabled if convert_ir is true. + tags: Tags to place on all generated targets. + """ + args = args or [] + deps = deps or [] + if len(srcs) != 1: + fail("More than one source not currently supported.") + if entry and not type(entry) != str: + fail("Entry argument must be a string.") + src = srcs[0] + + native.sh_test( + name = name + "_dslx_test", + srcs = [_DSLX_TEST], + args = [native.package_name() + "/" + src] + args, + data = [ + _INTERPRETER_MAIN, + src, + ] + deps, + tags = tags, + ) + + # TODO(meheff): Move this to a different internal-only bzl file. + if convert_ir: + native.sh_test( + name = name + "_ir_converter_test", + srcs = ["//xls/dslx:ir_converter_test_sh"], + args = [native.package_name() + "/" + src] + args, + data = [ + "//xls/dslx:ir_converter_main", + src, + ] + deps, + tags = tags, + ) + native.genrule( + name = name + "_ir", + srcs = [src] + deps, + outs = [name + ".ir"], + cmd = "$(location //xls/dslx:ir_converter_main) $(SRCS) > $@", + exec_tools = ["//xls/dslx:ir_converter_main"], + tags = tags, + ) + native.genrule( + name = name + "_opt_ir", + srcs = [src] + deps, + outs = [name + ".opt.ir"], + cmd = "$(location //xls/dslx:ir_converter_main) $(SRCS) | $(location //xls/tools:opt_main) --entry=%s - > $@" % (entry or ""), + exec_tools = [ + "//xls/dslx:ir_converter_main", + "//xls/tools:opt_main", + ], + tags = tags, + ) + native.filegroup( + name = name + "_all_ir", + srcs = [name + ".opt.ir", name + ".ir"], + ) + + if prove_unopt_eq_opt: + native.sh_test( + name = name + "_opt_equivalence_test", + srcs = ["//xls/tools:check_ir_equivalence_sh"], + args = [ + native.package_name() + "/" + name + ".ir", + native.package_name() + "/" + name + ".opt.ir", + ] + (["--function=" + entry] if entry else []), + size = "large", + data = [ + ":" + name + "_all_ir", + "//xls/tools:check_ir_equivalence_main", + ], + #tags = tags + ["manual", "optonly"], + tags = tags + ["optonly"], + ) + + if generate_benchmark: + benchmark_args = _make_benchmark_args(native.package_name(), name, entry, args) + + # Add test which executes benchmark_main on the IR. + native.sh_test( + name = name + "_benchmark_test", + srcs = ["//xls/tools:benchmark_test_sh"], + args = benchmark_args, + data = [ + "//xls/tools:benchmark_main", + ":" + name + "_all_ir", + ], + tags = tags, + ) + + # Add test which evaluates the IR with the interpreter and verifies + # the result before and after optimizations match. + native.sh_test( + name = name + "_benchmark_eval_test", + srcs = ["//xls/tools:benchmark_eval_test_sh"], + args = benchmark_args + ["--random_inputs=100", "--optimize_ir"], + data = [ + "//xls/tools:eval_ir_main", + ":" + name + "_all_ir", + ], + tags = tags + ["optonly"], + ) + + native.filegroup( + name = name + "_source", + srcs = srcs, + ) + native.test_suite( + name = name, + tests = [name + "_dslx_test"], + tags = tags, + ) + +# TODO(meheff): This macro should define some tests to sanity check the +# generated RTL. +def dslx_generated_rtl( + name, + srcs, + codegen_params, + deps = None, + entry = None, + tags = []): + """Generates RTL from DSLX sources using a released toolchain. + + Args: + name: Base name for the targets that get created. The Verilog file will + have the name '{name}.v'. + srcs: '.x' file sources. + codegen_params: Codegen configuration used for Verilog generation. + deps: Dependent '.x' file sources. + entry: Name of entry function to codegen. + tags: Tags to place on all generated targets. + """ + deps = deps or [] + if len(srcs) != 1: + fail("More than one source not currently supported.") + src = srcs[0] + + native.genrule( + name = name + "_ir", + srcs = [src] + deps, + outs = [name + ".ir"], + cmd = "$(location %s) $(SRCS) > $@" % _IR_CONVERTER_MAIN, + exec_tools = [_IR_CONVERTER_MAIN], + tags = tags, + ) + + native.genrule( + name = name + "_opt_ir", + srcs = [":{}_ir".format(name)], + outs = [name + ".opt.ir"], + cmd = "$(location %s) --entry=%s $(SRCS) > $@" % (_OPT_MAIN, entry or ""), + exec_tools = [_OPT_MAIN], + tags = tags, + ) + + _codegen( + name, + srcs = [name + "_opt_ir"], + codegen_params = codegen_params, + entry = entry, + tags = tags, + ) + + # Add a build test to ensure changes to BUILD and bzl files do not break + # targets built with released toolchains. + build_test( + name = name + "_build_test", + targets = [":" + name], + ) diff --git a/xls/build/elab_test.bzl b/xls/build/elab_test.bzl new file mode 100644 index 0000000000..37556b7d7d --- /dev/null +++ b/xls/build/elab_test.bzl @@ -0,0 +1,19 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Build rules for elaboration tests.""" + +def elab_test(name, src, hdrs, top): + """No elaboration / lint checking yet in OSS builds.""" + pass diff --git a/xls/build/iverilog_test.bzl b/xls/build/iverilog_test.bzl new file mode 100644 index 0000000000..040b95c2a9 --- /dev/null +++ b/xls/build/iverilog_test.bzl @@ -0,0 +1,88 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Contains internal XLS macros.""" + +load("//xls/build:elab_test.bzl", "elab_test") + +def iverilog_test(name, top, main, srcs, execute = True, tick_defines = None): + """Defines Icarus Verilog test, with associated elaboration test. + + The elaboration test helps to identify issues where iverilog is overly + lenient. + + Args: + name: Name of the target. This name will be a suite of the elaboration test + (-elab_test suffix) and iverilog simulation test (-run_test suffix). + top: Top-level module name for the test to run. + main: Main source file to compile via iverilog. + srcs: Supporting source files, including ``main``. + execute: If true then run the test through Iverilog. Otherwise only linting + and elaboration is performed. + tick_defines: A map containing Verilog tick-defines + (eg, "`define FOO 0"). + """ + if main not in srcs: + fail("main verilog source %r not in srcs %r" % (main, srcs)) + + tick_defines = tick_defines or {} + + tests = [] + + et = elab_test( + name, + src = main, + hdrs = [src for src in srcs if src != main], + top = top, + ) + if et: + tests.append(et) + + defines = " ".join( + ["-D{}={}".format(k, v) for (k, v) in sorted(tick_defines.items())], + ) + + native.genrule( + name = name + "-iverilog-build", + srcs = srcs, + # Note: GENDIR is a builtin environment variable for genrule commands + # that points at the genfiles' location, we have to add it to the + # searched set of paths for inclusions so we can include generated + # verilog files as we can generated C++ files in cc_library rules. + cmd = "$(location @com_icarus_iverilog//:iverilog) -s %s $(location %s) %s -o $@ -g2001 -I$(GENDIR)" % (top, main, defines), + outs = [name + ".iverilog.out"], + exec_tools = ["@com_icarus_iverilog//:iverilog"], + ) + if execute: + native.genrule( + name = name + "-vvp-runner", + srcs = [":" + name + "-iverilog-build"], + cmd = "$(location //xls/tools:generate_vvp_runner) $< > $@", + outs = [name + "-vvp-runner.sh"], + exec_tools = ["//xls/tools:generate_vvp_runner"], + ) + native.sh_test( + name = name + "-run_test", + srcs = [":" + name + "-vvp-runner"], + data = [ + ":" + name + "-iverilog-build", + "@com_icarus_iverilog//:vvp", + ], + ) + tests.append(":" + name + "-run_test") + + native.test_suite( + name = name, + tests = tests, + ) diff --git a/xls/build/py_proto_library.bzl b/xls/build/py_proto_library.bzl new file mode 100644 index 0000000000..79580b6a0b --- /dev/null +++ b/xls/build/py_proto_library.bzl @@ -0,0 +1,24 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Adapter between open source and Google-internal py_proto_library rules.""" + +load("@com_google_protobuf//:protobuf.bzl", "py_proto_library") + +def xls_py_proto_library(name, internal_deps, srcs, deps = []): + py_proto_library( + name = name, + srcs = srcs, + deps = deps, + ) diff --git a/xls/build/pybind11.bzl b/xls/build/pybind11.bzl new file mode 100644 index 0000000000..7c08996f37 --- /dev/null +++ b/xls/build/pybind11.bzl @@ -0,0 +1,23 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Adapter for Google-internal pybind_extension rule vs OSS rule.""" + +load("//third_party/pybind11_bazel:build_defs.bzl", "pybind_extension") + +def xls_pybind_extension(**kwargs): + py_deps = kwargs.pop("py_deps", []) + deps = kwargs.pop("deps", []) + kwargs["deps"] = py_deps + deps + pybind_extension(**kwargs) diff --git a/xls/codegen/BUILD b/xls/codegen/BUILD new file mode 100644 index 0000000000..0c9bb27d88 --- /dev/null +++ b/xls/codegen/BUILD @@ -0,0 +1,341 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# cc_proto_library is used in this file +load("//xls/build:py_proto_library.bzl", "xls_py_proto_library") + +package( + default_visibility = ["//xls:xls_internal"], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "name_to_bit_count", + hdrs = ["name_to_bit_count.h"], + deps = [ + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/strings", + "//xls/common:integral_types", + "//xls/ir:bits", + "//xls/ir:type", + ], +) + +cc_library( + name = "combinational_generator", + srcs = ["combinational_generator.cc"], + hdrs = ["combinational_generator.h"], + deps = [ + ":flattening", + ":module_builder", + ":module_signature", + ":name_to_bit_count", + ":node_expressions", + ":vast", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_test( + name = "combinational_generator_test", + srcs = ["combinational_generator_test.cc"], + data = glob([ + "testdata/combinational_generator_test_*", + ]), + shard_count = 10, + deps = [ + ":combinational_generator", + "//xls/common/status:matchers", + "//xls/examples:sample_packages", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:ir_interpreter", + "//xls/ir:ir_parser", + "//xls/ir:value_helpers", + "//xls/simulation:module_simulator", + "//xls/simulation:verilog_simulators", + "//xls/simulation:verilog_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "vast", + srcs = ["vast.cc"], + hdrs = ["vast.h"], + deps = [ + ":module_signature_cc_proto", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:variant", + "//xls/common:indent", + "//xls/common:visitor", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir:bits", + "@com_google_re2//:re2", + ], +) + +cc_library( + name = "finite_state_machine", + srcs = ["finite_state_machine.cc"], + hdrs = ["finite_state_machine.h"], + deps = [ + ":vast", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "//xls/common:casts", + "//xls/common/logging", + "//xls/common/status:status_macros", + ], +) + +cc_test( + name = "vast_test", + srcs = ["vast_test.cc"], + deps = [ + ":vast", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "finite_state_machine_test", + srcs = ["finite_state_machine_test.cc"], + data = glob(["testdata/finite_state_machine_test_*"]), + deps = [ + ":finite_state_machine", + ":vast", + "//xls/common/logging", + "//xls/common/status:matchers", + "//xls/simulation:verilog_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "pipeline_generator", + srcs = ["pipeline_generator.cc"], + hdrs = ["pipeline_generator.h"], + deps = [ + ":finite_state_machine", + ":flattening", + ":module_builder", + ":module_signature", + ":module_signature_cc_proto", + ":name_to_bit_count", + ":node_expressions", + ":vast", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:optional", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/delay_model:delay_estimator", + "//xls/delay_model:delay_estimators", + "//xls/ir", + "//xls/scheduling:pipeline_schedule", + ], +) + +cc_library( + name = "module_signature", + srcs = ["module_signature.cc"], + hdrs = ["module_signature.h"], + deps = [ + ":module_signature_cc_proto", + ":vast", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:optional", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:type", + "//xls/ir:value", + ], +) + +cc_library( + name = "node_expressions", + srcs = ["node_expressions.cc"], + hdrs = ["node_expressions.h"], + deps = [ + ":flattening", + ":vast", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:type", + ], +) + +cc_library( + name = "flattening", + srcs = ["flattening.cc"], + hdrs = ["flattening.h"], + deps = [ + ":vast", + "@com_google_absl//absl/status", + "@com_google_absl//absl/types:span", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:bits_ops", + "//xls/ir:type", + "//xls/ir:value", + "//xls/ir:xls_type_cc_proto", + ], +) + +cc_library( + name = "module_builder", + srcs = ["module_builder.cc"], + hdrs = ["module_builder.h"], + deps = [ + ":flattening", + ":node_expressions", + ":vast", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:type", + "//xls/ir:value", + ], +) + +proto_library( + name = "module_signature_proto", + srcs = ["module_signature.proto"], + deps = ["//xls/ir:xls_type_proto"], +) + +cc_proto_library( + name = "module_signature_cc_proto", + deps = [":module_signature_proto"], +) + +xls_py_proto_library( + name = "module_signature_py_pb2", + srcs = ["module_signature.proto"], + internal_deps = [ + ":module_signature_proto", + "//xls/ir:xls_type_proto", + ], + deps = [ + "//xls/ir:xls_type_py_pb2", + ], +) + +cc_test( + name = "pipeline_generator_test", + srcs = ["pipeline_generator_test.cc"], + data = glob([ + "testdata/pipeline_generator_test_*", + ]), + shard_count = 10, + deps = [ + ":pipeline_generator", + "//xls/common/status:matchers", + "//xls/delay_model:delay_estimator", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/scheduling:pipeline_schedule", + "//xls/simulation:module_simulator", + "//xls/simulation:module_testbench", + "//xls/simulation:verilog_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "module_signature_test", + srcs = ["module_signature_test.cc"], + deps = [ + ":module_signature", + "//xls/common/status:matchers", + "//xls/ir:bits", + "//xls/ir:value", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "flattening_test", + srcs = ["flattening_test.cc"], + deps = [ + ":flattening", + "//xls/common/status:matchers", + "//xls/ir:ir_test_base", + "//xls/ir:type", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "module_builder_test", + srcs = ["module_builder_test.cc"], + data = glob(["testdata/module_builder_test_*"]), + shard_count = 10, + deps = [ + ":module_builder", + ":vast", + "//xls/common/status:matchers", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:function_builder", + "//xls/ir:type", + "//xls/ir:value", + "//xls/simulation:verilog_test_base", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/xls/codegen/combinational_generator.cc b/xls/codegen/combinational_generator.cc new file mode 100644 index 0000000000..e452d8c668 --- /dev/null +++ b/xls/codegen/combinational_generator.cc @@ -0,0 +1,123 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/combinational_generator.h" + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/strings/str_split.h" +#include "xls/codegen/flattening.h" +#include "xls/codegen/module_builder.h" +#include "xls/codegen/node_expressions.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/dfs_visitor.h" +#include "xls/ir/node.h" +#include "xls/ir/node_iterator.h" + +namespace xls { +namespace verilog { + +xabsl::StatusOr ToCombinationalModuleText( + Function* func, bool use_system_verilog) { + XLS_VLOG(2) << "Generating combinational module for function:"; + XLS_VLOG_LINES(2, func->DumpIr()); + + VerilogFile f; + ModuleBuilder mb(func->name(), &f, /*use_system_verilog=*/use_system_verilog); + + // Build the module signature. + ModuleSignatureBuilder sig_builder(mb.module()->name()); + for (Param* param : func->params()) { + sig_builder.AddDataInput(param->name(), + param->GetType()->GetFlatBitCount()); + } + const int64 output_width = func->return_value()->GetType()->GetFlatBitCount(); + sig_builder.AddDataOutput("out", output_width); + sig_builder.WithFunctionType(func->GetType()); + sig_builder.WithCombinationalInterface(); + XLS_ASSIGN_OR_RETURN(ModuleSignature signature, sig_builder.Build()); + + // Map from Node* to the Verilog expression representing its value. + absl::flat_hash_map node_exprs; + + // Add parameters explicitly so the input ports are added in the order they + // appear in the parameters of the function. + for (Param* param : func->params()) { + if (param->GetType()->GetFlatBitCount() == 0) { + XLS_RET_CHECK_EQ(param->users().size(), 0); + continue; + } + XLS_ASSIGN_OR_RETURN( + node_exprs[param], + mb.AddInputPort(param->As()->name(), param->GetType())); + } + + for (Node* node : TopoSort(func)) { + if (node->Is()) { + // Parameters are added in the above loop. + continue; + } + + // Verilog has no zero-bit data types so elide such types. They should have + // no uses. + if (node->GetType()->GetFlatBitCount() == 0) { + XLS_RET_CHECK_EQ(node->users().size(), 0); + continue; + } + + // Emit non-bits-typed literals as module-level constants because in general + // these complicated types cannot be handled inline, and constructing them + // in Verilog may require a sequence of assignments. + if (node->Is() && !node->GetType()->IsBits()) { + XLS_ASSIGN_OR_RETURN( + node_exprs[node], + mb.DeclareModuleConstant(node->GetName(), + node->As()->value())); + continue; + } + + std::vector inputs; + for (Node* operand : node->operands()) { + inputs.push_back(node_exprs.at(operand)); + } + if (node->users().size() > 1 || node == func->return_value() || + !mb.CanEmitAsInlineExpression(node)) { + XLS_ASSIGN_OR_RETURN(node_exprs[node], + mb.EmitAsAssignment(node->GetName(), node, inputs)); + } else { + XLS_ASSIGN_OR_RETURN(node_exprs[node], + mb.EmitAsInlineExpression(node, inputs)); + } + } + + // Skip adding an output port to the Verilog module if the output is + // zero-width. + if (output_width > 0) { + XLS_RETURN_IF_ERROR(mb.AddOutputPort("out", func->return_value()->GetType(), + node_exprs.at(func->return_value()))); + } + std::string text = f.Emit(); + + XLS_VLOG(2) << "Verilog output:"; + XLS_VLOG_LINES(2, text); + + return ModuleGeneratorResult{text, signature}; +} + +} // namespace verilog +} // namespace xls diff --git a/xls/codegen/combinational_generator.h b/xls/codegen/combinational_generator.h new file mode 100644 index 0000000000..c0aefcd6da --- /dev/null +++ b/xls/codegen/combinational_generator.h @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_CODEGEN_COMBINATIONAL_GENERATOR_H_ +#define THIRD_PARTY_XLS_CODEGEN_COMBINATIONAL_GENERATOR_H_ + +#include + +#include "xls/codegen/module_signature.h" +#include "xls/codegen/name_to_bit_count.h" +#include "xls/codegen/vast.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" + +namespace xls { +namespace verilog { + +// Emits the given function as a combinational Verilog module. If +// use_system_verilog is true the generated module will be SystemVerilog +// otherwise it will be Verilog. +xabsl::StatusOr ToCombinationalModuleText( + Function* func, bool use_system_verilog = true); + +} // namespace verilog +} // namespace xls + +#endif // THIRD_PARTY_XLS_CODEGEN_COMBINATIONAL_GENERATOR_H_ diff --git a/xls/codegen/combinational_generator_test.cc b/xls/codegen/combinational_generator_test.cc new file mode 100644 index 0000000000..3c4201a92d --- /dev/null +++ b/xls/codegen/combinational_generator_test.cc @@ -0,0 +1,488 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/combinational_generator.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/examples/sample_packages.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_interpreter.h" +#include "xls/ir/ir_parser.h" +#include "xls/ir/package.h" +#include "xls/ir/value_helpers.h" +#include "xls/simulation/module_simulator.h" +#include "xls/simulation/verilog_simulators.h" +#include "xls/simulation/verilog_test_base.h" + +namespace xls { +namespace verilog { +namespace { + +using status_testing::IsOkAndHolds; + +constexpr char kTestName[] = "combinational_generator_test"; +constexpr char kTestdataPath[] = "xls/codegen/testdata"; + +class CombinationalGeneratorTest : public VerilogTestBase {}; + +TEST_P(CombinationalGeneratorTest, RrotToCombinationalText) { + auto rrot32_data = sample_packages::BuildRrot32(); + Function* f = rrot32_data.second; + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.RunAndReturnSingleOutput( + {{"x", UBits(0x12345678ULL, 32)}, {"y", UBits(4, 32)}}), + IsOkAndHolds(UBits(0x81234567, 32))); +} + +TEST_P(CombinationalGeneratorTest, RandomExpression) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u8 = package.GetBitsType(8); + auto a = fb.Param("a", u8); + auto b = fb.Param("b", u8); + auto c = fb.Param("c", u8); + auto a_minus_b = a - b; + auto lhs = (a_minus_b * a_minus_b); + auto rhs = (c * a_minus_b); + auto out = lhs + rhs; + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.BuildWithReturnValue(out)); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + // Value should be: (7-2)*(7-2) + 3*(7-2) = 40 + EXPECT_THAT(simulator.RunAndReturnSingleOutput( + {{"a", UBits(7, 8)}, {"b", UBits(2, 8)}, {"c", UBits(3, 8)}}), + IsOkAndHolds(UBits(40, 8))); +} + +TEST_P(CombinationalGeneratorTest, ReturnsLiteral) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + fb.Literal(UBits(123, 8)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.RunAndReturnSingleOutput(ModuleSimulator::BitsMap()), + IsOkAndHolds(UBits(123, 8))); +} + +TEST_P(CombinationalGeneratorTest, ReturnsTupleLiteral) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + fb.Literal(Value::Tuple({Value(UBits(123, 8)), Value(UBits(42, 32))})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT( + simulator.Run(absl::flat_hash_map()), + IsOkAndHolds(Value::Tuple({Value(UBits(123, 8)), Value(UBits(42, 32))}))); +} + +TEST_P(CombinationalGeneratorTest, ReturnsEmptyTuple) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + fb.Literal(Value::Tuple({})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.Run(absl::flat_hash_map()), + IsOkAndHolds(Value::Tuple({}))); +} + +TEST_P(CombinationalGeneratorTest, PassesEmptyTuple) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + fb.Param("x", package.GetTupleType({})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.Run({{"x", Value::Tuple({})}}), + IsOkAndHolds(Value::Tuple({}))); +} + +TEST_P(CombinationalGeneratorTest, TakesEmptyTuple) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u8 = package.GetBitsType(8); + auto a = fb.Param("a", u8); + fb.Param("b", package.GetTupleType({})); + auto c = fb.Param("c", u8); + fb.Add(a, c); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.Run({{"a", Value(UBits(42, 8))}, + {"b", Value::Tuple({})}, + {"c", Value(UBits(100, 8))}}), + IsOkAndHolds(Value(UBits(142, 8)))); +} + +TEST_P(CombinationalGeneratorTest, ReturnsParam) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u8 = package.GetBitsType(8); + fb.Param("a", u8); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.RunAndReturnSingleOutput({{"a", UBits(0x42, 8)}}), + IsOkAndHolds(UBits(0x42, 8))); +} + +TEST_P(CombinationalGeneratorTest, ExpressionWhichRequiresNamedIntermediate) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u8 = package.GetBitsType(8); + auto a = fb.Param("a", u8); + auto b = fb.Param("b", u8); + auto a_plus_b = a + b; + auto out = fb.BitSlice(a_plus_b, /*start=*/3, /*width=*/4); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.BuildWithReturnValue(out)); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.RunAndReturnSingleOutput( + {{"a", UBits(0x42, 8)}, {"b", UBits(0x33, 8)}}), + IsOkAndHolds(UBits(14, 4))); +} + +TEST_P(CombinationalGeneratorTest, ExpressionsOfTuples) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u8 = package.GetBitsType(8); + Type* u10 = package.GetBitsType(10); + Type* u16 = package.GetBitsType(16); + Type* tuple_u10_u16 = package.GetTupleType({u10, u16}); + auto a = fb.Param("a", u8); + auto b = fb.Param("b", u10); + auto c = fb.Param("c", tuple_u10_u16); + + // Glom all the inputs together into a big tuple. + auto a_b_c = fb.Tuple({a, b, c}); + + // Then extract some elements and perform some arithmetic operations on them + // after zero-extending them to the same width (16-bits). + auto a_plus_b = fb.ZeroExtend(fb.TupleIndex(a_b_c, 0), 16) + + fb.ZeroExtend(fb.TupleIndex(a_b_c, 1), 16); + auto c_tmp = fb.TupleIndex(a_b_c, 2); + auto c0_minus_c1 = + fb.ZeroExtend(fb.TupleIndex(c_tmp, 0), 16) - fb.TupleIndex(c_tmp, 1); + + // Result should be a two-tuple containing {a + b, c[0] - c[1]} + auto return_value = fb.Tuple({a_plus_b, c0_minus_c1}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.BuildWithReturnValue(return_value)); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.Run({{"a", Value(UBits(42, 8))}, + {"b", Value(UBits(123, 10))}, + {"c", Value::Tuple({Value(UBits(333, 10)), + Value(UBits(222, 16))})}}), + IsOkAndHolds(Value::Tuple( + {Value(UBits(165, 16)), Value(UBits(111, 16))}))); +} + +TEST_P(CombinationalGeneratorTest, TupleLiterals) { + std::string text = R"( +package TupleLiterals + +fn main(x: bits[123]) -> bits[123] { + literal.1: (bits[123], bits[123], bits[123]) = literal(value=(0x10000, 0x2000, 0x300)) + tuple_index.2: bits[123] = tuple_index(literal.1, index=0) + tuple_index.3: bits[123] = tuple_index(literal.1, index=1) + tuple_index.4: bits[123] = tuple_index(literal.1, index=2) + add.6: bits[123] = add(tuple_index.2, tuple_index.3) + add.7: bits[123] = add(tuple_index.4, x) + ret add.8: bits[123] = add(add.6, add.7) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(text)); + + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN( + auto result, ToCombinationalModuleText(entry, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0x40, 123))}}), + IsOkAndHolds(Value(UBits(0x12340, 123)))); +} + +TEST_P(CombinationalGeneratorTest, ArrayLiteral) { + std::string text = R"( +package ArrayLiterals + +fn main(x: bits[32], y: bits[32]) -> bits[44] { + literal.1: bits[44][3][2] = literal(value=[[1, 2, 3], [4, 5, 6]]) + array_index.2: bits[44][3] = array_index(literal.1, x) + ret array_index.3: bits[44] = array_index(array_index.2, y) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(text)); + + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN( + auto result, ToCombinationalModuleText(entry, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT( + simulator.Run({{"x", Value(UBits(0, 32))}, {"y", Value(UBits(1, 32))}}), + IsOkAndHolds(Value(UBits(2, 44)))); + EXPECT_THAT( + simulator.Run({{"x", Value(UBits(1, 32))}, {"y", Value(UBits(0, 32))}}), + IsOkAndHolds(Value(UBits(4, 44)))); +} + +TEST_P(CombinationalGeneratorTest, OneHot) { + std::string text = R"( +package OneHot + +fn main(x: bits[3]) -> bits[4] { + ret one_hot.1: bits[4] = one_hot(x, lsb_prio=true) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(text)); + + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN( + auto result, ToCombinationalModuleText(entry, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0b000, 3))}}), + IsOkAndHolds(Value(UBits(0b1000, 4)))); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0b001, 3))}}), + IsOkAndHolds(Value(UBits(0b0001, 4)))); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0b010, 3))}}), + IsOkAndHolds(Value(UBits(0b0010, 4)))); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0b011, 3))}}), + IsOkAndHolds(Value(UBits(0b0001, 4)))); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0b100, 3))}}), + IsOkAndHolds(Value(UBits(0b0100, 4)))); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0b101, 3))}}), + IsOkAndHolds(Value(UBits(0b0001, 4)))); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0b110, 3))}}), + IsOkAndHolds(Value(UBits(0b0010, 4)))); + EXPECT_THAT(simulator.Run({{"x", Value(UBits(0b111, 3))}}), + IsOkAndHolds(Value(UBits(0b0001, 4)))); +} + +TEST_P(CombinationalGeneratorTest, OneHotSelect) { + std::string text = R"( +package OneHotSelect + +fn main(p: bits[2], x: bits[16], y: bits[16]) -> bits[16] { + ret one_hot_sel.1: bits[16] = one_hot_sel(p, cases=[x, y]) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(text)); + + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN( + auto result, ToCombinationalModuleText(entry, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + absl::flat_hash_map args = { + {"x", Value(UBits(0x00ff, 16))}, {"y", Value(UBits(0xf0f0, 16))}}; + args["p"] = Value(UBits(0b00, 2)); + EXPECT_THAT(simulator.Run(args), IsOkAndHolds(Value(UBits(0x0000, 16)))); + args["p"] = Value(UBits(0b01, 2)); + EXPECT_THAT(simulator.Run(args), IsOkAndHolds(Value(UBits(0x00ff, 16)))); + args["p"] = Value(UBits(0b10, 2)); + EXPECT_THAT(simulator.Run(args), IsOkAndHolds(Value(UBits(0xf0f0, 16)))); + args["p"] = Value(UBits(0b11, 2)); + EXPECT_THAT(simulator.Run(args), IsOkAndHolds(Value(UBits(0xf0ff, 16)))); +} + +TEST_P(CombinationalGeneratorTest, CrazyParameterTypes) { + std::string text = R"( +package CrazyParameterTypes + +fn main(a: bits[32], + b: (bits[32], ()), + c: bits[32][3], + d: (bits[32], bits[32])[1], + e: (bits[32][2], (), ()), + f: bits[0], + g: bits[1]) -> bits[32] { + tuple_index.1: bits[32] = tuple_index(b, index=0) + literal.2: bits[32] = literal(value=0) + array_index.3: bits[32] = array_index(c, g) + array_index.4: (bits[32], bits[32]) = array_index(d, literal.2) + tuple_index.5: bits[32] = tuple_index(array_index.4, index=1) + tuple_index.6: bits[32][2] = tuple_index(e, index=0) + array_index.7: bits[32] = array_index(tuple_index.6, g) + ret or.8: bits[32] = or(a, tuple_index.1, array_index.3, tuple_index.5, array_index.7) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(text)); + + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN( + auto result, ToCombinationalModuleText(entry, UseSystemVerilog())); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + result.verilog_text); + + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + + std::minstd_rand engine; + std::vector arguments = RandomFunctionArguments(entry, &engine); + XLS_ASSERT_OK_AND_ASSIGN(Value expected, + ir_interpreter::Run(entry, arguments)); + EXPECT_THAT(simulator.Run(arguments), IsOkAndHolds(expected)); +} + +TEST_P(CombinationalGeneratorTest, TwoDArray) { + // Build up a two dimensional array from scalars, then deconstruct it and do + // something with the elements. + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u8 = package.GetBitsType(8); + auto a = fb.Param("a", u8); + auto b = fb.Param("b", u8); + auto c = fb.Param("c", u8); + auto row_0 = fb.Array({a, b, c}, a.GetType()); + auto row_1 = fb.Array({a, b, c}, a.GetType()); + auto two_d = fb.Array({row_0, row_1}, row_0.GetType()); + fb.Add(fb.ArrayIndex(fb.ArrayIndex(two_d, fb.Literal(UBits(0, 8))), + fb.Literal(UBits(2, 8))), + fb.ArrayIndex(fb.ArrayIndex(two_d, fb.Literal(UBits(1, 8))), + fb.Literal(UBits(1, 8)))); + + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.Run({{"a", Value(UBits(123, 8))}, + {"b", Value(UBits(42, 8))}, + {"c", Value(UBits(100, 8))}}), + IsOkAndHolds(Value(UBits(142, 8)))); +} + +TEST_P(CombinationalGeneratorTest, ReturnTwoDArray) { + // Build up a two dimensional array from scalars and return it. + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u8 = package.GetBitsType(8); + auto a = fb.Param("a", u8); + auto b = fb.Param("b", u8); + auto row_0 = fb.Array({a, b}, a.GetType()); + auto row_1 = fb.Array({b, a}, a.GetType()); + fb.Array({row_0, row_1}, row_0.GetType()); + + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT( + simulator.Run({{"a", Value(UBits(123, 8))}, {"b", Value(UBits(42, 8))}}), + IsOkAndHolds(Value::ArrayOrDie({ + Value::ArrayOrDie({Value(UBits(123, 8)), Value(UBits(42, 8))}), + Value::ArrayOrDie({Value(UBits(42, 8)), Value(UBits(123, 8))}), + }))); +} + +TEST_P(CombinationalGeneratorTest, BuildComplicatedType) { + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u8 = package.GetBitsType(8); + // Construct some terrible abomination of tuples and arrays. + auto a = fb.Param("a", u8); + auto b = fb.Param("b", u8); + auto c = fb.Param("c", u8); + auto row_0 = fb.Array({a, b}, a.GetType()); + auto row_1 = fb.Array({b, a}, a.GetType()); + auto ar = fb.Array({row_0, row_1}, row_0.GetType()); + auto tuple = fb.Tuple({ar, a}); + // Deconstruct it and return some scalar element. + fb.ArrayIndex(fb.ArrayIndex(fb.TupleIndex(tuple, 0), a), c); + + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto result, + ToCombinationalModuleText(f, UseSystemVerilog())); + ModuleSimulator simulator(result.signature, result.verilog_text, + GetSimulator()); + EXPECT_THAT(simulator.Run({{"a", Value(UBits(0, 8))}, + {"b", Value(UBits(42, 8))}, + {"c", Value(UBits(1, 8))}}), + IsOkAndHolds(Value(UBits(42, 8)))); +} + +INSTANTIATE_TEST_SUITE_P(CombinationalGeneratorTestInstantiation, + CombinationalGeneratorTest, + testing::ValuesIn(kDefaultSimulationTargets), + ParameterizedTestName); + +} // namespace +} // namespace verilog +} // namespace xls diff --git a/xls/codegen/finite_state_machine.cc b/xls/codegen/finite_state_machine.cc new file mode 100644 index 0000000000..b92013379d --- /dev/null +++ b/xls/codegen/finite_state_machine.cc @@ -0,0 +1,535 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/finite_state_machine.h" + +#include "absl/algorithm/container.h" +#include "absl/strings/str_cat.h" +#include "xls/common/status/status_macros.h" + +namespace xls { +namespace verilog { + +void FsmBlockBase::EmitAssignments(StatementBlock* statement_block) const { + for (const auto& assignment : assignments_) { + statement_block->Add(assignment.lhs, assignment.rhs); + } + for (const ConditionalFsmBlock& cond_block : conditional_blocks_) { + if (cond_block.HasAssignments()) { + Conditional* conditional = statement_block->Add( + statement_block->parent(), cond_block.condition()); + cond_block.EmitConditionalAssignments(conditional, + conditional->consequent()); + } + } +} + +void FsmBlockBase::EmitStateTransitions(StatementBlock* statement_block, + LogicRef* state_next_var) const { + if (next_state_ != nullptr) { + statement_block->Add(state_next_var, + next_state_->state_value()); + } + for (const ConditionalFsmBlock& cond_block : conditional_blocks_) { + if (cond_block.HasStateTransitions()) { + Conditional* conditional = statement_block->Add( + statement_block->parent(), cond_block.condition()); + cond_block.EmitConditionalStateTransitions( + conditional, conditional->consequent(), state_next_var); + } + } +} + +bool FsmBlockBase::HasAssignments() const { + return !assignments_.empty() || + absl::c_any_of(conditional_blocks_, [](const ConditionalFsmBlock& b) { + return b.HasAssignments(); + }); +} + +bool FsmBlockBase::HasStateTransitions() const { + return next_state_ != nullptr || + absl::c_any_of(conditional_blocks_, [](const ConditionalFsmBlock& b) { + return b.HasStateTransitions(); + }); +} + +bool FsmBlockBase::HasAssignmentToOutput(const FsmOutput& output) const { + return ( + absl::c_any_of( + assignments_, + [&](const Assignment& a) { return a.lhs == output.logic_ref; }) || + absl::c_any_of(conditional_blocks_, [&](const ConditionalFsmBlock& b) { + return b.HasAssignmentToOutput(output); + })); +} + +ConditionalFsmBlock& ConditionalFsmBlock::ElseOnCondition( + Expression* condition) { + XLS_CHECK(next_alternate_ == nullptr && final_alternate_ == nullptr); + next_alternate_ = absl::make_unique( + absl::StrFormat("%s else (%s)", debug_name_, condition->Emit()), file_, + condition); + return *next_alternate_; +} + +UnconditionalFsmBlock& ConditionalFsmBlock::Else() { + XLS_CHECK(next_alternate_ == nullptr && final_alternate_ == nullptr); + final_alternate_ = absl::make_unique( + absl::StrFormat("%s else", debug_name_), file_); + return *final_alternate_; +} + +void ConditionalFsmBlock::EmitConditionalAssignments( + Conditional* conditional, StatementBlock* statement_block) const { + EmitAssignments(statement_block); + if (next_alternate_ != nullptr && next_alternate_->HasAssignments()) { + next_alternate_->EmitConditionalAssignments( + conditional, conditional->AddAlternate(next_alternate_->condition())); + } else if (final_alternate_ != nullptr && + final_alternate_->HasAssignments()) { + final_alternate_->EmitAssignments(conditional->AddAlternate()); + } +} + +void ConditionalFsmBlock::EmitConditionalStateTransitions( + Conditional* conditional, StatementBlock* statement_block, + LogicRef* state_next_var) const { + EmitStateTransitions(statement_block, state_next_var); + if (next_alternate_ != nullptr && next_alternate_->HasStateTransitions()) { + next_alternate_->EmitConditionalStateTransitions( + conditional, conditional->AddAlternate(next_alternate_->condition()), + state_next_var); + } else if (final_alternate_ != nullptr && + final_alternate_->HasStateTransitions()) { + final_alternate_->EmitStateTransitions(conditional->AddAlternate(), + state_next_var); + } +} + +bool ConditionalFsmBlock::HasAssignments() const { + return FsmBlock::HasAssignments() || + (next_alternate_ != nullptr && next_alternate_->HasAssignments()) || + (final_alternate_ != nullptr && final_alternate_->HasAssignments()); +} + +bool ConditionalFsmBlock::HasStateTransitions() const { + return FsmBlock::HasStateTransitions() || + (next_alternate_ != nullptr && + next_alternate_->HasStateTransitions()) || + (final_alternate_ != nullptr && + final_alternate_->HasStateTransitions()); +} + +bool ConditionalFsmBlock::HasAssignmentToOutput(const FsmOutput& output) const { + return FsmBlock::HasAssignmentToOutput(output) || + (next_alternate_ != nullptr && + next_alternate_->HasAssignmentToOutput(output)) || + (final_alternate_ != nullptr && + final_alternate_->HasAssignmentToOutput(output)); +} + +LogicRef* FsmBuilder::AddRegDef(absl::string_view name, Expression* width, + RegInit init) { + defs_.push_back(module_->parent()->Make(name, width, init)); + return module_->parent()->Make(defs_.back()); +} + +FsmCounter* FsmBuilder::AddDownCounter(absl::string_view name, int64 width) { + LogicRef* ref = AddRegDef(name, file_->PlainLiteral(width)); + LogicRef* ref_next = + AddRegDef(absl::StrCat(name, "_next"), file_->PlainLiteral(width)); + counters_.push_back(FsmCounter{ref, ref_next, width}); + return &counters_.back(); +} + +FsmOutput* FsmBuilder::AddOutputAsExpression(absl::string_view name, + Expression* width, + Expression* default_value) { + RegInit init = UninitializedSentinel(); + if (default_value != nullptr) { + init = default_value; + } + LogicRef* logic_ref = AddRegDef(name, width, init); + outputs_.push_back(FsmOutput{logic_ref, default_value}); + return &outputs_.back(); +} + +FsmOutput* FsmBuilder::AddExistingOutput(LogicRef* logic_ref, + Expression* default_value) { + outputs_.push_back(FsmOutput{logic_ref, default_value}); + return &outputs_.back(); +} + +FsmRegister* FsmBuilder::AddRegisterAsExpression(absl::string_view name, + Expression* width, + Expression* reset_value) { + // A reset value can only be specified if the FSM has a reset signal. + XLS_CHECK(reset_value == nullptr || reset_.has_value()); + LogicRef* logic_ref = reset_value == nullptr + ? AddRegDef(name, width) + : AddRegDef(name, width, reset_value); + LogicRef* logic_ref_next = AddRegDef(absl::StrCat(name, "_next"), width); + registers_.push_back(FsmRegister{logic_ref, logic_ref_next, reset_value}); + return ®isters_.back(); +} + +FsmRegister* FsmBuilder::AddRegister(absl::string_view name, int64 width, + absl::optional reset_value) { + return AddRegisterAsExpression( + name, file_->PlainLiteral(width), + reset_value.has_value() ? file_->PlainLiteral(*reset_value) : nullptr); +} + +FsmRegister* FsmBuilder::AddRegister1(absl::string_view name, + absl::optional reset_value) { + return AddRegisterAsExpression( + name, /*width=*/file_->PlainLiteral(1), + reset_value.has_value() ? file_->PlainLiteral(*reset_value) : nullptr); +} + +FsmRegister* FsmBuilder::AddExistingRegister(LogicRef* reg) { + LogicRef* logic_ref_next = + AddRegDef(absl::StrCat(reg->GetName(), "_next"), reg->def()->width()); + registers_.push_back(FsmRegister{reg, logic_ref_next}); + return ®isters_.back(); +} + +FsmState* FsmBuilder::AddState(absl::string_view name) { + if (state_local_param_ == nullptr) { + state_local_param_ = file_->Make(file_); + } + XLS_CHECK(!absl::c_any_of(states_, [&](const FsmState& s) { + return s.name() == name; + })) << absl::StrFormat("State with name \"%s\" already exists.", name); + Expression* state_value = state_local_param_->AddItem( + absl::StrCat("State", name), file_->PlainLiteral(states_.size())); + states_.emplace_back(name, file_, state_value); + return &states_.back(); +} + +absl::Status FsmBuilder::BuildStateTransitionLogic(LogicRef* state, + LogicRef* state_next) { + // Construct an always block encapsulating the combinational logic for + // determining state transitions. + module_->Add(); + module_->Add("FSM state transition logic."); + AlwaysBase* ac; + if (use_system_verilog_) { + ac = module_->Add(file_); + } else { + ac = module_->Add(file_, std::vector( + {ImplicitEventExpression()})); + } + + // Set default assignments. + ac->statements()->Add(state_next, state); + + Case* case_statement = ac->statements()->Add(file_, state); + for (const auto& fsm_state : states_) { + fsm_state.EmitStateTransitions( + case_statement->AddCaseArm(fsm_state.state_value()), state_next); + } + // If the number of states is not a power of two then add an unreachable + // default case which sets the next state to X. This ensures all values of + // the case are covered. + if (states_.size() != 1 << StateRegisterWidth()) { + StatementBlock* statements = case_statement->AddCaseArm(DefaultSentinel()); + statements->Add( + state_next, file_->Make(StateRegisterWidth())); + } + return absl::OkStatus(); +} + +UnconditionalFsmBlock& ConditionalFsmBlock::FindOrAddFinalAlternate() { + ConditionalFsmBlock* alternate = this; + while (alternate->next_alternate_ != nullptr) { + alternate = alternate->next_alternate_.get(); + } + if (alternate->final_alternate_ != nullptr) { + return *alternate->final_alternate_; + } + return alternate->Else(); +} + +absl::Status FsmBlockBase::AddDefaultOutputAssignments( + const FsmOutput& output) { + XLS_VLOG(3) << absl::StreamFormat( + "AddDefaultOutputAssignments for output %s in block \"%s\"", + output.logic_ref->GetName(), debug_name()); + // The count of the assignments along any code path through the block. + int64 assignment_count = 0; + for (const Assignment& assignment : assignments_) { + if (assignment.lhs == output.logic_ref) { + XLS_VLOG(3) << absl::StreamFormat( + "Output is unconditionally assigned %s in block \"%s\"", + assignment.rhs->Emit(), debug_name()); + assignment_count++; + } + } + for (ConditionalFsmBlock& cond_block : conditional_blocks_) { + if (!cond_block.HasAssignmentToOutput(output)) { + continue; + } + // The conditional block has an assignment to the output somewhere beneath + // it. Make sure there is an assignment on each alternate of the + // conditional. + XLS_VLOG(3) << "Conditional block " << cond_block.debug_name() + << " assigns output " << output.logic_ref->GetName(); + cond_block.FindOrAddFinalAlternate(); + XLS_RETURN_IF_ERROR(cond_block.ForEachAlternate( + [&](FsmBlockBase* alternate) -> absl::Status { + return alternate->AddDefaultOutputAssignments(output); + })); + assignment_count++; + } + if (assignment_count > 1) { + return absl::InvalidArgumentError(absl::StrFormat( + "Output \"%s\" may be assigned more than once along a code path.", + output.logic_ref->GetName())); + } + if (assignment_count == 0) { + XLS_VLOG(3) << absl::StreamFormat( + "Adding assignment of %s to default value %s in block \"%s\"", + output.logic_ref->Emit(), output.default_value->Emit(), debug_name()); + AddAssignment(output.logic_ref, output.default_value); + } + return absl::OkStatus(); +} + +absl::Status ConditionalFsmBlock::ForEachAlternate( + std::function f) { + for (ConditionalFsmBlock* alternate = this; alternate != nullptr; + alternate = alternate->next_alternate_.get()) { + XLS_RETURN_IF_ERROR(f(alternate)); + if (alternate->final_alternate_ != nullptr) { + return f(alternate->final_alternate_.get()); + } + } + return absl::OkStatus(); +} + +absl::Status FsmBlockBase::RemoveAssignment(LogicRef* logic_ref) { + int64 size_before = assignments_.size(); + assignments_.erase( + std::remove_if(assignments_.begin(), assignments_.end(), + [&](const Assignment& a) { return a.lhs == logic_ref; }), + assignments_.end()); + if (size_before == assignments_.size()) { + return absl::InvalidArgumentError( + absl::StrFormat("Assignment to %s does not exist in block \"%s\".", + logic_ref->GetName(), debug_name())); + } + if (size_before > assignments_.size() + 1) { + return absl::InvalidArgumentError( + absl::StrFormat("Multiple assignment to %s exist in block \"%s\".", + logic_ref->GetName(), debug_name())); + } + return absl::OkStatus(); +} + +namespace { + +// Returns true iff the given expressions are all non-null and the same. +// Sameness means same pointer value or expressions are literals with same +// underlying Bits value. Literals require this special handled because literals +// are typically created on the fly for each use. So a 1'h1 in one part of the +// code will generally not refer to the same Literal object as a 1'h1 in another +// part of the code. +bool AllSameAndNonNull(absl::Span exprs) { + Expression* same_expr = nullptr; + for (Expression* expr : exprs) { + if (expr == nullptr) { + return false; + } + if (same_expr == nullptr) { + same_expr = expr; + continue; + } + if (expr != same_expr && !(expr->IsLiteral() && same_expr->IsLiteral() && + expr->AsLiteralOrDie()->bits() == + same_expr->AsLiteralOrDie()->bits())) { + return false; + } + } + return true; +} + +} // namespace + +xabsl::StatusOr FsmBlockBase::HoistCommonConditionalAssignments( + const FsmOutput& output) { + XLS_VLOG(3) << absl::StreamFormat( + "HoistCommonConditionalAssignments for output %s in block \"%s\"", + output.logic_ref->GetName(), debug_name()); + + for (const Assignment& assignment : assignments_) { + if (assignment.lhs == output.logic_ref) { + XLS_VLOG(3) << absl::StreamFormat( + "Output is unconditionally assigned %s in block \"%s\"", + assignment.rhs->Emit(), debug_name()); + return assignment.rhs; + } + } + + for (ConditionalFsmBlock& cond_block : conditional_blocks_) { + if (!cond_block.HasAssignmentToOutput(output)) { + continue; + } + XLS_VLOG(3) << absl::StreamFormat( + "Conditional block \"%s\" assigns output %s", cond_block.debug_name(), + output.logic_ref->GetName()); + std::vector rhses; + XLS_RETURN_IF_ERROR(cond_block.ForEachAlternate( + [&](FsmBlockBase* alternate) -> absl::Status { + XLS_ASSIGN_OR_RETURN( + Expression * rhs, + alternate->HoistCommonConditionalAssignments(output)); + XLS_VLOG(3) << absl::StreamFormat( + "Alternate block \"%s\" assigns output %s to %s", + cond_block.debug_name(), output.logic_ref->GetName(), + rhs == nullptr ? "nullptr" : rhs->Emit()); + rhses.push_back(rhs); + return absl::OkStatus(); + })); + if (!AllSameAndNonNull(rhses)) { + XLS_VLOG(3) << absl::StreamFormat( + "Not all conditional block assign output %s to same value", + output.logic_ref->GetName()); + return nullptr; + } + + XLS_VLOG(3) << absl::StreamFormat( + "Conditional block assigns output %s to same value %s on all " + "alternates", + output.logic_ref->GetName(), rhses.front()->Emit()); + XLS_RETURN_IF_ERROR(cond_block.ForEachAlternate( + [&](FsmBlockBase* alternate) -> absl::Status { + return alternate->RemoveAssignment(output.logic_ref); + })); + AddAssignment(output.logic_ref, rhses.front()); + return rhses.front(); + } + return nullptr; +} + +absl::Status FsmBuilder::BuildOutputLogic(LogicRef* state) { + if (registers_.empty() && outputs_.empty() && counters_.empty()) { + return absl::OkStatus(); + } + + // Construct an always block encapsulating the combinational logic for + // determining state transitions. + module_->Add(); + module_->Add("FSM output logic."); + + AlwaysBase* ac; + if (use_system_verilog_) { + ac = module_->Add(file_); + } else { + ac = module_->Add(file_, std::vector( + {ImplicitEventExpression()})); + } + + for (const FsmRegister& reg : registers_) { + ac->statements()->Add(reg.next, reg.logic_ref); + } + + // For each state there should exactly one assignment to each output along any + // code path. This prevents infinite looping during simulation caused by the + // "glitch" of assigning a value twice to the reg. See: + // https://github.com/steveicarus/iverilog/issues/321. This single assignment + // propertry is achieved by sinking the assignments to the default value into + // conditional blocks to exactly cover those code paths which have no + // assignment. Flopped regs such as the next state and next counter values do + // not need this treatment because their value only changes on clock edges + // which avoids any propagate of a multi-assignment glitch during simulation. + for (FsmState& fsm_state : states_) { + XLS_VLOG(3) << "Adding default assignments for state " << fsm_state.name(); + for (const FsmOutput& output : outputs_) { + XLS_RETURN_IF_ERROR(fsm_state.AddDefaultOutputAssignments(output)); + XLS_RETURN_IF_ERROR( + fsm_state.HoistCommonConditionalAssignments(output).status()); + } + } + + for (const FsmCounter& counter : counters_) { + ac->statements()->Add( + counter.next, file_->Sub(counter.logic_ref, file_->PlainLiteral(1))); + } + Case* case_statement = ac->statements()->Add(file_, state); + for (const auto& fsm_state : states_) { + fsm_state.EmitAssignments( + case_statement->AddCaseArm(fsm_state.state_value())); + } + + // If the state vector is wide enough to allow values not encoded in the state + // enum add a default case and assign outputs to the default value. + if (states_.size() != 1 << StateRegisterWidth()) { + StatementBlock* statements = case_statement->AddCaseArm(DefaultSentinel()); + for (const FsmOutput& output : outputs_) { + statements->Add(output.logic_ref, + output.default_value); + } + } + + return absl::OkStatus(); +} + +absl::Status FsmBuilder::Build() { + if (is_built_) { + return absl::InternalError("FSM has already been built."); + } + is_built_ = true; + + module_->Add(); + module_->Add(absl::StrCat(name_, " ", "FSM:")); + + LocalParamItemRef* state_bits = module_->Add(file_)->AddItem( + "StateBits", file_->PlainLiteral(StateRegisterWidth())); + + // For each state, define its numeric encoding as a LocalParam gathered in + // state_values. + module_->AddModuleMember(state_local_param_); + + Expression* initial_state_value = states_.front().state_value(); + LogicRef* state = + module_->AddRegAsExpression("state", state_bits, initial_state_value); + LogicRef* state_next = module_->AddRegAsExpression("state_next", state_bits, + initial_state_value); + for (RegDef* def : defs_) { + module_->AddModuleMember(def); + } + + XLS_RETURN_IF_ERROR(BuildStateTransitionLogic(state, state_next)); + XLS_RETURN_IF_ERROR(BuildOutputLogic(state)); + + AlwaysFlop* af = module_->Add(file_, clk_, reset_); + if (reset_.has_value()) { + af->AddRegister(state, state_next, /*reset_value=*/initial_state_value); + } else { + af->AddRegister(state, state_next); + } + for (const FsmRegister& reg : registers_) { + af->AddRegister(reg.logic_ref, reg.next, /*reset_value=*/reg.reset_value); + } + for (const FsmCounter& counter : counters_) { + af->AddRegister(counter.logic_ref, counter.next); + } + + return absl::OkStatus(); +} + +} // namespace verilog +} // namespace xls diff --git a/xls/codegen/finite_state_machine.h b/xls/codegen/finite_state_machine.h new file mode 100644 index 0000000000..ccc1fa8680 --- /dev/null +++ b/xls/codegen/finite_state_machine.h @@ -0,0 +1,429 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_CODEGEN_FINITE_STATE_MACHINE_H_ +#define THIRD_PARTY_XLS_CODEGEN_FINITE_STATE_MACHINE_H_ + +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/types/optional.h" +#include "xls/codegen/vast.h" +#include "xls/common/casts.h" +#include "xls/common/logging/logging.h" + +namespace xls { +namespace verilog { + +// Encapsulates an output signal driven by finite state machine logic. +struct FsmOutput { + LogicRef* logic_ref; + Expression* default_value; +}; + +// Encapsulates a registered output signal driven by finite state machine +// logic. +struct FsmRegister { + LogicRef* logic_ref; + + // The value of the register in the next cycle. + LogicRef* next; + + // The expression defining the reset state of the register. May be nullptr in + // which case the reset state is undefined. + Expression* reset_value; +}; + +// Represents a down-counting cycle counter controlled by the finite state +// machine logic. +struct FsmCounter { + // The value of the counter. + LogicRef* logic_ref; + + // The value of the counter in the next cycle. + LogicRef* next; + + // Width of the counter in bits. + int64 width; +}; + +// A single assignment of an FSM output to a value. +struct Assignment { + LogicRef* lhs; + Expression* rhs; +}; + +class FsmState; +class ConditionalFsmBlock; + +// Abstraction representing a control-flow-equivalent block of logic in an FSM +// (i.e., a basic block). +class FsmBlockBase { + public: + explicit FsmBlockBase(absl::string_view debug_name, VerilogFile* file) + : debug_name_(debug_name), file_(file) {} + virtual ~FsmBlockBase() = default; + + // Returns true if this block has any output assignments (or state + // transitions). This includes any assignments (state transitions) in nested + // conditional blocks. + virtual bool HasAssignments() const; + virtual bool HasStateTransitions() const; + + // Returns true if this block may assign a value to the given output. This + // includes any assignments in nested conditional blocks. + virtual bool HasAssignmentToOutput(const FsmOutput& output) const; + + // Emits the output assignments contained in this block as blocking + // assignments in the given VAST StatementBlock including any nested + // conditional assignments. + void EmitAssignments(StatementBlock* statement_block) const; + + // Emits the state transition (if any) contained in this block as a blocking + // assignment in the given VAST StatementBlock including any nested state + // transitions. + void EmitStateTransitions(StatementBlock* statement_block, + LogicRef* state_next_var) const; + + protected: + friend class FsmBuilder; + + std::string debug_name() const { return debug_name_; } + + // Adds the assignment of 'logic_ref' to 'value' to the block. + void AddAssignment(LogicRef* logic_ref, Expression* value) { + for (const auto& assignment : assignments_) { + XLS_CHECK_NE(logic_ref, assignment.lhs) + << logic_ref->GetName() << " already assigned."; + } + assignments_.push_back(Assignment{logic_ref, value}); + } + + // Remove the assignment of the given LogicRef from the unconditional + // assignments in this block. Does not recurse into contained conditional + // blocks. Returns an error if there is not exactly one assignment to the + // LogicRef in the block. + absl::Status RemoveAssignment(LogicRef* logic_ref); + + // Adds assignments of the given output to its default value along all code + // paths which do not have an assignment of the output. Returns an error if + // the output may be assigned more than once on any code path. Upon completion + // of this function, the output will be assigned exactly once on all code + // paths through the block. + absl::Status AddDefaultOutputAssignments(const FsmOutput& output); + + // Hoists conditional assignments which are identical along all paths in the + // block into a single assignment. For example, given: + // + // if (foo) begin + // a = 1; + // b = 1; + // end else begin + // a = 1; + // b = 0; + // end + // + // After hoisting the code will look like: + // + // a = 1; + // if (foo) begin + // b = 1; + // end else begin + // b = 0; + // end + // + xabsl::StatusOr HoistCommonConditionalAssignments( + const FsmOutput& output); + + // An name which is used to uniquely identify the block in log messages. The + // name does not affect the emitted Verilog. + std::string debug_name_; + VerilogFile* file_; + + FsmState* next_state_ = nullptr; + std::vector assignments_; + + // The conditional blocks within this block (if any). This lowers to a + // sequence of 'if' statements. A std::list is used for pointer stability. + std::list conditional_blocks_; +}; + +// Base class for curiously recurring template pattern to support polymorphic +// chaining. This class holds fluent style methods for constructing the +// finite state machine. +template +class FsmBlock : public FsmBlockBase { + public: + explicit FsmBlock(absl::string_view debug_name, VerilogFile* file) + : FsmBlockBase(debug_name, file) {} + virtual ~FsmBlock() = default; + + // Sets the next state to transition to. + T& NextState(FsmState* next_state) { + XLS_CHECK(next_state_ == nullptr); + next_state_ = next_state; + return down_cast(*this); + } + + // Sets the given output to the given value. This occurs immediately and + // asynchronously. + T& SetOutput(FsmOutput* output, int64 value) { + return SetOutputAsExpression(output, file_->PlainLiteral(value)); + } + T& SetOutputAsExpression(FsmOutput* output, Expression* value) { + AddAssignment(output->logic_ref, value); + return down_cast(*this); + } + + // Sets the given register to the given value in the next cycle. + T& SetRegisterNext(FsmRegister* reg, int64 value) { + return SetRegisterNextAsExpression(reg, file_->PlainLiteral(value)); + } + T& SetRegisterNextAsExpression(FsmRegister* reg, Expression* value) { + AddAssignment(reg->next, value); + return down_cast(*this); + } + + // Sets the given counter to the given value in the next cycle. + T& SetCounter(FsmCounter* counter, int64 value) { + return SetCounterAsExpression(counter, file_->PlainLiteral(value)); + } + T& SetCounterAsExpression(FsmCounter* counter, Expression* value) { + AddAssignment(counter->next, value); + return down_cast(*this); + } + + // Adds a conditional statement using the given condition to the block. + // Returns the resulting conditional block. + ConditionalFsmBlock& OnCondition(Expression* condition) { + conditional_blocks_.emplace_back( + absl::StrFormat("%s: if (%s)", debug_name(), condition->Emit()), file_, + condition); + return conditional_blocks_.back(); + } + + // Adds a conditional statement based on the given counter equal to + // zero. Returns the resulting conditional block. + ConditionalFsmBlock& OnCounterIsZero(FsmCounter* counter) { + conditional_blocks_.emplace_back( + absl::StrFormat("%s: if counter %s == 0", debug_name(), + counter->logic_ref->GetName()), + file_, file_->Equals(counter->logic_ref, file_->PlainLiteral(0))); + return conditional_blocks_.back(); + } +}; + +// An unconditional block of logic within an FSM state. +class UnconditionalFsmBlock : public FsmBlock { + public: + explicit UnconditionalFsmBlock(absl::string_view debug_name, + VerilogFile* file) + : FsmBlock(debug_name, file) {} +}; + +// An unconditional block of logic within an FSM state. +class ConditionalFsmBlock : public FsmBlock { + public: + explicit ConditionalFsmBlock(absl::string_view debug_name, VerilogFile* file, + Expression* condition) + : FsmBlock(debug_name, file), + condition_(condition) {} + + // Appends an "else if" to the conditional ladder. Returns the resulting + // conditional block. + ConditionalFsmBlock& ElseOnCondition(Expression* condition); + + // Terminates the conditional ladder with an "else". Returns the resulting + // block. + UnconditionalFsmBlock& Else(); + + Expression* condition() const { return condition_; } + + bool HasAssignments() const override; + bool HasStateTransitions() const override; + bool HasAssignmentToOutput(const FsmOutput& output) const override; + + protected: + friend class FsmBlockBase; + + // Emits the VAST conditional ladder for this conditional block and the nested + // assignments (state transitions). 'conditional' is the VAST conditional + // statement corresponding to this conditional block. + void EmitConditionalAssignments(Conditional* conditional, + StatementBlock* statement_block) const; + void EmitConditionalStateTransitions(Conditional* conditional, + StatementBlock* statement_block, + LogicRef* state_next_var) const; + + // Calls the given function on each alternate in this conditional block. For + // example, if the conditional block represents: + // + // if (a) begin + // ...Block A... + // end else if (b) begin + // ...Block B... + // end else begin + // ...Block C... + // end + // + // The function will be called on blocks A, B, and C. + absl::Status ForEachAlternate(std::function f); + + // If the conditional block has a final alternate (unconditional else block) + // then it is returned. Otherwise a final alternate is created and returned. + UnconditionalFsmBlock& FindOrAddFinalAlternate(); + + Expression* condition_; + + // The next alternate (else if) of the conditional ladder. Only one of + // next_alternate_ or final_alternate_ may be non-null. Might be representable + // as an absl::variant but an absl::variant of std::unique_ptrs is awkward to + // manipulate. + std::unique_ptr next_alternate_; + + // The final alternate (else) of the conditional ladder. + std::unique_ptr final_alternate_; +}; + +// Abstraction representing a state in the FSM. For convenience derives from +// UnconditionalFsmBlock which exposes the UnconditionalFsmBlock interface (eg, +// NextState). This enables code like the following: +// +// auto st = fsm.NewState(...); +// st->SetOutput(x, value).NextState(next_st); +class FsmState : public UnconditionalFsmBlock { + public: + explicit FsmState(absl::string_view name, VerilogFile* file, + Expression* state_value) + : UnconditionalFsmBlock(name, file), + name_(name), + state_value_(state_value) {} + + std::string name() const { return name_; } + + // The VAST expression of the numeric encoding of this state in the FSM state + // variable. + Expression* state_value() const { return state_value_; } + + protected: + std::string name_; + Expression* state_value_; +}; + +// Abstraction for building finite state machines in Verilog using VAST. +class FsmBuilder { + public: + FsmBuilder(absl::string_view name, Module* module, LogicRef* clk, + bool use_system_verilog, + absl::optional reset = absl::nullopt) + : name_(name), + module_(module), + file_(module->parent()), + clk_(clk), + use_system_verilog_(use_system_verilog), + reset_(reset) {} + + // Adds an FSM-controled signal to the FSM with the given name. A RegDef named + // 'name' is added to the module. + FsmOutput* AddOutput(absl::string_view name, int64 width, + int64 default_value) { + return AddOutputAsExpression(name, file_->PlainLiteral(width), + file_->PlainLiteral(default_value)); + } + FsmOutput* AddOutputAsExpression(absl::string_view name, Expression* width, + Expression* default_value); + + FsmOutput* AddOutput1(absl::string_view name, int64 default_value) { + return AddOutputAsExpression(name, /*width=*/file_->PlainLiteral(1), + file_->PlainLiteral(default_value)); + } + + // Overload which adds a previously defined reg as a FSM-controlled signal. + FsmOutput* AddExistingOutput(LogicRef* logic_ref, Expression* default_value); + + // Adds a FSM-driven register with the given name. RegDefs named 'name' and + // 'name_next' are added to the module. The state of the register is affected + // by calling SetRegisterNext. + FsmRegister* AddRegister(absl::string_view name, int64 width, + absl::optional reset_value = absl::nullopt); + FsmRegister* AddRegisterAsExpression(absl::string_view name, + Expression* width, + Expression* reset_value = nullptr); + + FsmRegister* AddRegister1(absl::string_view name, + absl::optional reset_value = absl::nullopt); + + // Overload which adds a previously defined reg as an FSM-controlled signal. A + // RegDef named "*_next" is added to the module where "*" is the name of the + // given LogicRef. + FsmRegister* AddExistingRegister(LogicRef* reg); + + // Add a cycle down-counter with the given name and width. + FsmCounter* AddDownCounter(absl::string_view name, int64 width); + + // Add a new state to the FSM. + FsmState* AddState(absl::string_view name); + + // Builds the FSM in the module. + absl::Status Build(); + + private: + // Creates a RegDef of the given name, width and optional initial + // value. Returns a LogicRef referring to it. The RegDef is added to the + // module inline with the FSM logic when Build is called. + LogicRef* AddRegDef(absl::string_view name, Expression* width, + RegInit init = UninitializedSentinel()); + + // Build the always block containing the logic for state transitions. + absl::Status BuildStateTransitionLogic(LogicRef* state, LogicRef* state_next); + + // Build the always block containing the logic for the FSM outputs. + absl::Status BuildOutputLogic(LogicRef* state); + + // Returns the state register width. + int64 StateRegisterWidth() const { + return std::max(int64{1}, Bits::MinBitCountUnsigned(states_.size() - 1)); + } + + std::string name_; + Module* module_; + VerilogFile* file_; + LogicRef* clk_; + bool use_system_verilog_; + absl::optional reset_; + + // Output and registers defined by the FSM prior to called Build (such as + // AddOutput and AddRegister). These are added to the module when Build is + // called. Delaying insertion of the RegDefs enables placing them inline with + // the rest of the FSM logic. + std::vector defs_; + + // The localparam statement defining the concrete values for each state. + LocalParam* state_local_param_ = nullptr; + + // Whether the build method has been called on this FsmBuilder. The build + // method may only be called once. + bool is_built_ = false; + + std::list states_; + std::list counters_; + std::list outputs_; + std::list registers_; +}; + +} // namespace verilog +} // namespace xls + +#endif // THIRD_PARTY_XLS_CODEGEN_FINITE_STATE_MACHINE_H_ diff --git a/xls/codegen/finite_state_machine_test.cc b/xls/codegen/finite_state_machine_test.cc new file mode 100644 index 0000000000..d7bdd44834 --- /dev/null +++ b/xls/codegen/finite_state_machine_test.cc @@ -0,0 +1,345 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/finite_state_machine.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/codegen/vast.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/matchers.h" +#include "xls/simulation/verilog_test_base.h" + +namespace xls { +namespace verilog { +namespace { + +using status_testing::StatusIs; +using ::testing::HasSubstr; + +constexpr char kTestName[] = "finite_state_machine_test"; +constexpr char kTestdataPath[] = "xls/codegen/testdata"; + +class FiniteStateMachineTest : public VerilogTestBase {}; + +TEST_P(FiniteStateMachineTest, TrivialFsm) { + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef* clk = module->AddInput("clk"); + FsmBuilder fsm("TrivialFsm", module, clk, UseSystemVerilog()); + auto foo = fsm.AddState("Foo"); + auto bar = fsm.AddState("Bar"); + + foo->NextState(bar); + + XLS_ASSERT_OK(fsm.Build()); + XLS_VLOG(1) << f.Emit(); + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + f.Emit()); +} + +TEST_P(FiniteStateMachineTest, TrivialFsmWithOutputs) { + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef* clk = module->AddInput("clk"); + FsmBuilder fsm("TrivialFsm", module, clk, UseSystemVerilog()); + auto foo = fsm.AddState("Foo"); + auto bar = fsm.AddState("Bar"); + + auto baz_out = fsm.AddOutput1("baz", /*default_value=*/false); + auto qux_out = fsm.AddRegister("qux", 7); + + foo->NextState(bar); + foo->SetOutput(baz_out, 1); + + bar->NextState(foo); + // qux counts how many times the state "foo" has been entered. + bar->SetRegisterNextAsExpression( + qux_out, f.Add(qux_out->logic_ref, f.PlainLiteral(1))); + + XLS_ASSERT_OK(fsm.Build()); + XLS_VLOG(1) << f.Emit(); + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + f.Emit()); +} + +TEST_P(FiniteStateMachineTest, SimpleFsm) { + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef1* clk = module->AddInput("clk"); + LogicRef1* rst_n = module->AddInput("rst_n"); + LogicRef* ready_in = module->AddInput("ready_in"); + LogicRef* done_out = module->AddOutput("done_out"); + + // The "done" output is a wire, create a reg copy for assignment in the FSM. + LogicRef* done = module->AddReg1("done"); + module->Add(done_out, done); + + FsmBuilder fsm("SimpleFsm", module, clk, UseSystemVerilog(), + Reset{rst_n, /*async=*/false, /*active_low=*/true}); + auto idle_state = fsm.AddState("Idle"); + auto busy_state = fsm.AddState("Busy"); + auto done_state = fsm.AddState("Done"); + + auto fsm_done_out = + fsm.AddExistingOutput(done, + /*default_value=*/f.PlainLiteral(0)); + + idle_state->OnCondition(ready_in).NextState(busy_state); + busy_state->NextState(done_state); + done_state->SetOutput(fsm_done_out, 1); + + XLS_ASSERT_OK(fsm.Build()); + XLS_VLOG(1) << f.Emit(); + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + f.Emit()); +} + +TEST_P(FiniteStateMachineTest, FsmWithNestedLogic) { + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef1* clk = module->AddInput("clk"); + LogicRef1* rst_n = module->AddInput("rst_n"); + LogicRef* foo = module->AddInput("foo"); + LogicRef* bar = module->AddInput("bar"); + LogicRef* qux = module->AddOutput("qux_out"); + + FsmBuilder fsm("NestLogic", module, clk, UseSystemVerilog(), + Reset{rst_n, /*async=*/false, /*active_low=*/true}); + auto a_state = fsm.AddState("A"); + auto b_state = fsm.AddState("B"); + + auto fsm_qux_out = fsm.AddOutput("qux", /*width=*/8, + /*default_value=*/0); + + a_state->OnCondition(foo) + .NextState(b_state) + + // Nested Conditional + .OnCondition(bar) + .SetOutput(fsm_qux_out, 42) + .Else() + .SetOutput(fsm_qux_out, 123); + b_state->OnCondition(f.LogicalAnd(foo, bar)).NextState(a_state); + + XLS_ASSERT_OK(fsm.Build()); + + module->Add(qux, fsm_qux_out->logic_ref); + + XLS_VLOG(1) << f.Emit(); + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + f.Emit()); +} + +TEST_P(FiniteStateMachineTest, CounterFsm) { + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef1* clk = module->AddInput("clk"); + LogicRef1* rst = module->AddInput("rst"); + FsmBuilder fsm("CounterFsm", module, clk, UseSystemVerilog(), + Reset{rst, /*async=*/true, /*active_low=*/false}); + auto foo = fsm.AddState("Foo"); + auto bar = fsm.AddState("Bar"); + auto qux = fsm.AddState("Qux"); + + auto counter = fsm.AddDownCounter("counter", 6); + foo->SetCounter(counter, 42).NextState(bar); + bar->OnCounterIsZero(counter).NextState(qux); + qux->NextState(foo); + + XLS_ASSERT_OK(fsm.Build()); + XLS_VLOG(1) << f.Emit(); + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + f.Emit()); +} + +TEST_P(FiniteStateMachineTest, ComplexFsm) { + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef* clk = module->AddInput("clk"); + LogicRef* foo_in = module->AddInput("foo_in"); + LogicRef* bar_in = module->AddOutput("bar_in"); + LogicRef* qux_in = module->AddOutput("qux_in"); + + FsmBuilder fsm("ComplexFsm", module, clk, UseSystemVerilog()); + auto hungry = fsm.AddState("Hungry"); + auto sad = fsm.AddState("Sad"); + auto happy = fsm.AddState("Happy"); + auto awake = fsm.AddState("Awake"); + auto sleepy = fsm.AddState("Sleepy"); + + auto sleep = fsm.AddOutput1("sleep", 0); + auto walk = fsm.AddOutput1("walk", 0); + auto run = fsm.AddOutput1("run", 1); + auto die = fsm.AddOutput1("die", 1); + + hungry->OnCondition(foo_in).NextState(happy).Else().NextState(sad); + hungry->OnCondition(qux_in).SetOutput(walk, 0).SetOutput(die, 1); + + sad->NextState(awake); + sad->SetOutput(walk, 0); + sad->SetOutput(run, 1); + + awake->NextState(sleepy); + + sleepy->OnCondition(bar_in) + .NextState(hungry) + .ElseOnCondition(qux_in) + .NextState(sad); + + happy->OnCondition(bar_in).SetOutput(die, 0); + happy->OnCondition(foo_in).NextState(hungry).SetOutput(sleep, 1); + + XLS_ASSERT_OK(fsm.Build()); + XLS_VLOG(1) << f.Emit(); + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + f.Emit()); +} + +TEST_P(FiniteStateMachineTest, OutputAssignments) { + // Test various conditional and unconditional assignments of output regs in + // different states. Verify the proper insertion of assignment of default + // values to the outputs such that each code path has exactly one assignment + // per output. + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef1* clk = module->AddInput("clk"); + LogicRef1* rst_n = module->AddInput("rst_n"); + + LogicRef* a = module->AddInput("a"); + LogicRef* b = module->AddInput("b"); + + FsmBuilder fsm("SimpleFsm", module, clk, UseSystemVerilog(), + Reset{rst_n, /*async=*/false, /*active_low=*/true}); + auto out_42 = fsm.AddOutput("out_42", /*width=*/8, /*default_value=*/42); + auto out_123 = fsm.AddOutput("out_123", /*width=*/8, /*default_value=*/123); + + auto idle_state = fsm.AddState("Idle"); + idle_state->NextState(idle_state); + + { + auto state = fsm.AddState("AssignmentToDefaultValue"); + state->SetOutput(out_42, 42); + state->SetOutput(out_123, 123); + state->NextState(idle_state); + } + + { + auto state = fsm.AddState("AssignmentToNondefaultValue"); + state->SetOutput(out_42, 33); + state->SetOutput(out_123, 22); + state->NextState(idle_state); + } + + { + auto state = fsm.AddState("ConditionalAssignToDefaultValue"); + state->OnCondition(a).SetOutput(out_42, 42); + state->OnCondition(b).SetOutput(out_123, 123); + state->NextState(idle_state); + } + + { + auto state = fsm.AddState("ConditionalAssignToNondefaultValue"); + state->OnCondition(a).SetOutput(out_42, 1); + state->OnCondition(b).SetOutput(out_123, 2).Else().SetOutput(out_123, 4); + state->NextState(idle_state); + } + + { + auto state = fsm.AddState("NestedConditionalAssignToNondefaultValue"); + state->OnCondition(a).OnCondition(b).SetOutput(out_42, 1).Else().SetOutput( + out_123, 7); + state->NextState(idle_state); + } + + { + auto state = fsm.AddState("AssignToNondefaultValueAtDifferentDepths"); + ConditionalFsmBlock& if_a = state->OnCondition(a); + if_a.SetOutput(out_42, 1); + if_a.Else().OnCondition(b).SetOutput(out_42, 77); + state->NextState(idle_state); + } + + XLS_ASSERT_OK(fsm.Build()); + XLS_VLOG(1) << f.Emit(); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + f.Emit()); +} + +TEST_P(FiniteStateMachineTest, MultipleAssignments) { + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef1* clk = module->AddInput("clk"); + LogicRef1* rst_n = module->AddInput("rst_n"); + + LogicRef* a = module->AddInput("a"); + + FsmBuilder fsm("SimpleFsm", module, clk, UseSystemVerilog(), + Reset{rst_n, /*async=*/false, /*active_low=*/true}); + auto out = fsm.AddOutput("out", /*width=*/8, /*default_value=*/42); + + auto state = fsm.AddState("State"); + state->SetOutput(out, 123); + state->OnCondition(a).SetOutput(out, 44); + + XLS_VLOG(1) << f.Emit(); + EXPECT_THAT( + fsm.Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Output \"out\" may be assigned more than once"))); +} + +TEST_P(FiniteStateMachineTest, MultipleConditionalAssignments) { + VerilogFile f; + Module* module = f.Add(f.Make(TestBaseName(), &f)); + + LogicRef1* clk = module->AddInput("clk"); + LogicRef1* rst_n = module->AddInput("rst_n"); + + LogicRef* a = module->AddInput("a"); + LogicRef* b = module->AddInput("b"); + + FsmBuilder fsm("SimpleFsm", module, clk, UseSystemVerilog(), + Reset{rst_n, /*async=*/false, /*active_low=*/true}); + auto out = fsm.AddOutput("out", /*width=*/8, /*default_value=*/42); + + auto state = fsm.AddState("State"); + state->OnCondition(a).SetOutput(out, 44); + // Even setting output to same value is an error. + state->OnCondition(b).SetOutput(out, 44); + + XLS_VLOG(1) << f.Emit(); + EXPECT_THAT( + fsm.Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Output \"out\" may be assigned more than once"))); +} + +INSTANTIATE_TEST_SUITE_P(FiniteStateMachineTestInstantiation, + FiniteStateMachineTest, + testing::ValuesIn(kDefaultSimulationTargets), + ParameterizedTestName); + +} // namespace +} // namespace verilog +} // namespace xls diff --git a/xls/codegen/flattening.cc b/xls/codegen/flattening.cc new file mode 100644 index 0000000000..89319dab00 --- /dev/null +++ b/xls/codegen/flattening.cc @@ -0,0 +1,170 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/flattening.h" + +#include "absl/status/status.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/package.h" + +namespace xls { + +// Gathers the Bits objects at the leaves of the Value. +static void GatherValueLeaves(const Value& value, std::vector* leaves) { + switch (value.kind()) { + case ValueKind::kBits: + leaves->push_back(value.bits()); + break; + case ValueKind::kTuple: + case ValueKind::kArray: + for (const Value& e : value.elements()) { + GatherValueLeaves(e, leaves); + } + break; + default: + XLS_LOG(FATAL) << "Invalid value kind: " << value.kind(); + } +} + +Bits FlattenValueToBits(const Value& value) { + std::vector leaves; + GatherValueLeaves(value, &leaves); + return bits_ops::Concat(leaves); +} + +xabsl::StatusOr UnflattenBitsToValue(const Bits& bits, + const Type* type) { + if (bits.bit_count() != type->GetFlatBitCount()) { + return absl::InvalidArgumentError( + absl::StrFormat("Cannot unflatten input. Has %d bits, expected %d bits", + bits.bit_count(), type->GetFlatBitCount())); + } + if (type->IsBits()) { + return Value(bits); + } + if (type->IsTuple()) { + std::vector elements; + const TupleType* tuple_type = type->AsTupleOrDie(); + for (int64 i = 0; i < tuple_type->size(); ++i) { + Type* element_type = tuple_type->element_type(i); + XLS_ASSIGN_OR_RETURN( + Value element, UnflattenBitsToValue( + bits.Slice(GetFlatBitIndexOfElement(tuple_type, i), + element_type->GetFlatBitCount()), + element_type)); + elements.push_back(element); + } + return Value::Tuple(elements); + } + if (type->IsArray()) { + std::vector elements; + const ArrayType* array_type = type->AsArrayOrDie(); + for (int64 i = 0; i < array_type->size(); ++i) { + XLS_ASSIGN_OR_RETURN( + Value element, + UnflattenBitsToValue( + bits.Slice(GetFlatBitIndexOfElement(array_type, i), + array_type->element_type()->GetFlatBitCount()), + array_type->element_type())); + elements.push_back(element); + } + return Value::Array(elements); + } + return absl::InvalidArgumentError( + absl::StrFormat("Invalid type: %s", type->ToString())); +} + +xabsl::StatusOr UnflattenBitsToValue(const Bits& bits, + const TypeProto& type_proto) { + // Create a dummy package for converting a TypeProto into a Type*. + Package p("unflatten_dummy"); + XLS_ASSIGN_OR_RETURN(Type * type, p.GetTypeFromProto(type_proto)); + return UnflattenBitsToValue(bits, type); +} + +int64 GetFlatBitIndexOfElement(const TupleType* tuple_type, int64 index) { + XLS_CHECK_GE(index, 0); + XLS_CHECK_LT(index, tuple_type->size()); + int64 flat_index = 0; + for (int64 i = tuple_type->size() - 1; i > index; --i) { + flat_index += tuple_type->element_type(i)->GetFlatBitCount(); + } + return flat_index; +} + +int64 GetFlatBitIndexOfElement(const ArrayType* array_type, int64 index) { + XLS_CHECK_GE(index, 0); + XLS_CHECK_LT(index, array_type->size()); + return (array_type->size() - index - 1) * + array_type->element_type()->GetFlatBitCount(); +} + +// Recursive helper for Unflatten functions. +verilog::Expression* UnflattenArrayHelper(int64 flat_index_offset, + verilog::IndexableExpression* input, + ArrayType* array_type, + verilog::VerilogFile* file) { + std::vector elements; + const int64 element_width = array_type->element_type()->GetFlatBitCount(); + for (int64 i = 0; i < array_type->size(); ++i) { + const int64 element_start = + flat_index_offset + GetFlatBitIndexOfElement(array_type, i); + if (array_type->element_type()->IsArray()) { + elements.push_back(UnflattenArrayHelper( + element_start, input, array_type->element_type()->AsArrayOrDie(), + file)); + } else { + elements.push_back( + file->Slice(input, element_start + element_width - 1, element_start)); + } + } + return file->ArrayAssignmentPattern(elements); +} + +verilog::Expression* UnflattenArray(verilog::IndexableExpression* input, + ArrayType* array_type, + verilog::VerilogFile* file) { + return UnflattenArrayHelper(/*flat_index_offset=*/0, input, array_type, file); +} + +verilog::Expression* UnflattenArrayShapedTupleElement( + verilog::IndexableExpression* input, TupleType* tuple_type, + int64 tuple_index, verilog::VerilogFile* file) { + XLS_CHECK(tuple_type->element_type(tuple_index)->IsArray()); + ArrayType* array_type = tuple_type->element_type(tuple_index)->AsArrayOrDie(); + return UnflattenArrayHelper( + /*flat_index_offset=*/GetFlatBitIndexOfElement(tuple_type, tuple_index), + input, array_type, file); +} + +verilog::Expression* FlattenArray(verilog::IndexableExpression* input, + ArrayType* array_type, + verilog::VerilogFile* file) { + std::vector elements; + for (int64 i = 0; i < array_type->size(); ++i) { + verilog::IndexableExpression* element = + file->Index(input, i); // array_type->size() - i - 1); + if (array_type->element_type()->IsArray()) { + elements.push_back(FlattenArray( + element, array_type->element_type()->AsArrayOrDie(), file)); + } else { + elements.push_back(element); + } + } + return file->Concat(elements); +} + +} // namespace xls diff --git a/xls/codegen/flattening.h b/xls/codegen/flattening.h new file mode 100644 index 0000000000..670abd615f --- /dev/null +++ b/xls/codegen/flattening.h @@ -0,0 +1,72 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Library defining how arrays and tuples are lowered into vectors of bits by +// the generators. +#ifndef THIRD_PARTY_XLS_CODEGEN_FLATTENING_H_ +#define THIRD_PARTY_XLS_CODEGEN_FLATTENING_H_ + +#include "absl/types/span.h" +#include "xls/codegen/vast.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" +#include "xls/ir/xls_type.pb.h" + +namespace xls { + +// Flattens this arbitrarily-typed Value to a bits type containing the same +// total number of bits. Tuples are flattened by concatenating all of the leaf +// elements. The zero-th tuple element ends up in the highest-indexed bits in +// the resulting vector. Similarly, in a flattened array the zero-th element +// ends up in the highest index bits. This is in line with the behavior of +// Verilog concatenate operation. +Bits FlattenValueToBits(const Value& value); + +// Unflattens the given Bits to a Value of the given type. This is the inverse +// of FlattenValueToBits. +xabsl::StatusOr UnflattenBitsToValue(const Bits& bits, const Type* type); +xabsl::StatusOr UnflattenBitsToValue(const Bits& bits, + const TypeProto& type_proto); + +// Returns the index of the first bit of tuple element at 'index' where the +// tuple is flattened into a vector of bits. +int64 GetFlatBitIndexOfElement(const TupleType* tuple_type, int64 index); + +// Overload which returns the index of an element for an array type. +int64 GetFlatBitIndexOfElement(const ArrayType* array_type, int64 index); + +// Unflattens the given VAST expression into a unpacked array +// representation. 'array_type' is the underlying XLS type of the expression. +// Uses the SystemVerilog-only array assignment construct. +verilog::Expression* UnflattenArray(verilog::IndexableExpression* input, + ArrayType* array_type, + verilog::VerilogFile* file); + +// Flattens the given VAST expression into a flat bit vector. 'input' must be an +// unpacked array. 'array_type' is the underlying XLS type of the expression. +verilog::Expression* FlattenArray(verilog::IndexableExpression* input, + ArrayType* array_type, + verilog::VerilogFile* file); + +// Unflattens the array element at the given index of 'input', a flattened +// tuple. 'tuple_type' is the underlying XLS type of the tuple. +verilog::Expression* UnflattenArrayShapedTupleElement( + verilog::IndexableExpression* input, TupleType* tuple_type, + int64 tuple_index, verilog::VerilogFile* file); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_CODEGEN_FLATTENING_H_ diff --git a/xls/codegen/flattening_test.cc b/xls/codegen/flattening_test.cc new file mode 100644 index 0000000000..b3763593c5 --- /dev/null +++ b/xls/codegen/flattening_test.cc @@ -0,0 +1,163 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/flattening.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/type.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class ValueFlatteningTest : public IrTestBase {}; + +TEST_F(ValueFlatteningTest, FlatIndexing) { + Package p(TestName()); + Type* b0 = p.GetBitsType(0); + Type* b42 = p.GetBitsType(42); + BitsType* b5 = p.GetBitsType(5); + + TupleType* t_empty = p.GetTupleType({}); + EXPECT_EQ(t_empty->GetFlatBitCount(), 0); + + TupleType* t1 = p.GetTupleType({b42}); + EXPECT_EQ(t1->GetFlatBitCount(), 42); + EXPECT_EQ(GetFlatBitIndexOfElement(t1, 0), 0); + + TupleType* t2 = p.GetTupleType({b42, b0, b5, b5, b0}); + EXPECT_EQ(t2->GetFlatBitCount(), 52); + EXPECT_EQ(GetFlatBitIndexOfElement(t2, 0), 10); + EXPECT_EQ(GetFlatBitIndexOfElement(t2, 1), 10); + EXPECT_EQ(GetFlatBitIndexOfElement(t2, 2), 5); + EXPECT_EQ(GetFlatBitIndexOfElement(t2, 3), 0); + EXPECT_EQ(GetFlatBitIndexOfElement(t2, 4), 0); + + ArrayType* a_of_b5 = p.GetArrayType(32, b5); + EXPECT_EQ(a_of_b5->GetFlatBitCount(), 160); + EXPECT_EQ(GetFlatBitIndexOfElement(a_of_b5, 0), 155); + EXPECT_EQ(GetFlatBitIndexOfElement(a_of_b5, 15), 80); + EXPECT_EQ(GetFlatBitIndexOfElement(a_of_b5, 31), 0); + + ArrayType* array_2d = p.GetArrayType(4, a_of_b5); + EXPECT_EQ(array_2d->GetFlatBitCount(), 640); + EXPECT_EQ(GetFlatBitIndexOfElement(array_2d, 0), 480); + EXPECT_EQ(GetFlatBitIndexOfElement(array_2d, 2), 160); + + // Nested tuple with a nested array. + TupleType* t3 = p.GetTupleType({t2, b5, b42, array_2d}); + EXPECT_EQ(t3->GetFlatBitCount(), 739); + EXPECT_EQ(GetFlatBitIndexOfElement(t3, 0), 687); + EXPECT_EQ(GetFlatBitIndexOfElement(t3, 1), 682); + EXPECT_EQ(GetFlatBitIndexOfElement(t3, 2), 640); +} + +TEST_F(ValueFlatteningTest, FlattenValues) { + Package p(TestName()); + + Bits empty_bits; + EXPECT_EQ(empty_bits, FlattenValueToBits(Value(empty_bits))); + EXPECT_THAT(UnflattenBitsToValue(empty_bits, p.GetBitsType(0)), + IsOkAndHolds(Value(empty_bits))); + + Bits forty_two = UBits(42, 123); + EXPECT_EQ(forty_two, FlattenValueToBits(Value(forty_two))); + EXPECT_THAT(UnflattenBitsToValue(forty_two, p.GetBitsType(123)), + IsOkAndHolds(Value(forty_two))); + + // Empty tuple should flatten to a zero-bit Bits object. + EXPECT_EQ(empty_bits, FlattenValueToBits(Value::Tuple({}))); + EXPECT_THAT(UnflattenBitsToValue(empty_bits, p.GetTupleType({})), + IsOkAndHolds(Value::Tuple({}))); + + Bits abc = UBits(0xabcdef, 24); + Value tuple_abc = Value::Tuple( + {Value(UBits(0xab, 8)), Value(UBits(0xc, 4)), Value(UBits(0xdef, 12))}); + EXPECT_EQ(abc, FlattenValueToBits(tuple_abc)); + EXPECT_THAT(UnflattenBitsToValue(abc, p.GetTypeForValue(tuple_abc)), + IsOkAndHolds(tuple_abc)); + + XLS_ASSERT_OK_AND_ASSIGN( + Value arr, Value::Array({Value(UBits(0x12, 8)), Value(UBits(0x34, 8))})); + EXPECT_EQ(UBits(0x1234, 16), FlattenValueToBits(arr)); + EXPECT_THAT(UnflattenBitsToValue(UBits(0x1234, 16), p.GetTypeForValue(arr)), + IsOkAndHolds(arr)); + + // Two-element array of tuples. + Bits abc123 = UBits(0xabcdef123456ULL, 48); + Value tuple_123 = Value::Tuple( + {Value(UBits(0x12, 8)), Value(UBits(0x3, 4)), Value(UBits(0x456, 12))}); + XLS_ASSERT_OK_AND_ASSIGN(Value abc_array, + Value::Array({tuple_abc, tuple_123})); + EXPECT_EQ(abc123, FlattenValueToBits(abc_array)); + EXPECT_THAT(UnflattenBitsToValue(abc123, p.GetTypeForValue(abc_array)), + IsOkAndHolds(abc_array)); +} + +TEST_F(ValueFlatteningTest, ExpressionFlattening) { + Package p(TestName()); + Type* b5 = p.GetBitsType(5); + ArrayType* a_of_b5 = p.GetArrayType(3, b5); + ArrayType* array_2d = p.GetArrayType(2, a_of_b5); + + verilog::VerilogFile f; + verilog::Module* m = f.AddModule(TestName()); + + EXPECT_EQ(FlattenArray(m->AddUnpackedArrayReg("foo", f.PlainLiteral(5), + {f.PlainLiteral(3)}), + a_of_b5, &f) + ->Emit(), + "{foo[0], foo[1], foo[2]}"); + EXPECT_EQ( + FlattenArray( + m->AddUnpackedArrayReg("foo", f.PlainLiteral(5), + {f.PlainLiteral(2), f.PlainLiteral(3)}), + array_2d, &f) + ->Emit(), + "{{foo[0][0], foo[0][1], foo[0][2]}, {foo[1][0], foo[1][1], foo[1][2]}}"); +} + +TEST_F(ValueFlatteningTest, ExpressionUnflattening) { + Package p(TestName()); + Type* b5 = p.GetBitsType(5); + ArrayType* a_of_b5 = p.GetArrayType(3, b5); + ArrayType* array_2d = p.GetArrayType(2, a_of_b5); + + verilog::VerilogFile f; + verilog::Module* m = f.AddModule(TestName()); + + EXPECT_EQ(UnflattenArray(m->AddReg("foo", 15), a_of_b5, &f)->Emit(), + "'{foo[14:10], foo[9:5], foo[4:0]}"); + EXPECT_EQ(UnflattenArray(m->AddReg("foo", 30), array_2d, &f)->Emit(), + "'{'{foo[29:25], foo[24:20], foo[19:15]}, '{foo[14:10], foo[9:5], " + "foo[4:0]}}"); + + TupleType* tuple_type = p.GetTupleType({array_2d, b5, a_of_b5}); + EXPECT_EQ( + UnflattenArrayShapedTupleElement(m->AddReg("foo", 50), tuple_type, 0, &f) + ->Emit(), + "'{'{foo[49:45], foo[44:40], foo[39:35]}, '{foo[34:30], foo[29:25], " + "foo[24:20]}}"); + EXPECT_EQ( + UnflattenArrayShapedTupleElement(m->AddReg("foo", 50), tuple_type, 2, &f) + ->Emit(), + "'{foo[14:10], foo[9:5], foo[4:0]}"); +} + +} // namespace +} // namespace xls diff --git a/xls/codegen/module_builder.cc b/xls/codegen/module_builder.cc new file mode 100644 index 0000000000..49e92fb5a5 --- /dev/null +++ b/xls/codegen/module_builder.cc @@ -0,0 +1,595 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/module_builder.h" + +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" +#include "xls/codegen/flattening.h" +#include "xls/codegen/node_expressions.h" +#include "xls/codegen/vast.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/nodes.h" +#include "xls/ir/package.h" + +namespace xls { +namespace verilog { + +namespace { + +// Returns the bounds of the potentially-nested array type as a vector of +// int64. Ordering of the vector is outer-most bound to inner-most. For example, +// given array type 'bits[32][4][5]' yields {5, 4, 32}. +std::vector NestedArrayBounds(ArrayType* type) { + std::vector bounds; + Type* t = type; + while (t->IsArray()) { + bounds.push_back(t->AsArrayOrDie()->size()); + t = t->AsArrayOrDie()->element_type(); + } + return bounds; +} + +// Creates UnpackedArrayBounds corresponding to the given array type. If +// use_system_verilog is true, the bound are expressed using "sizes" (e.g., +// "[3][4][5]") other wise it is expressed using ranges (e.g., +// "[0:2][0:3][0:4]"). +std::vector MakeUnpackedArrayBounds( + ArrayType* type, VerilogFile* file, bool use_system_verilog) { + std::vector bounds; + for (int64 size : NestedArrayBounds(type)) { + if (use_system_verilog) { + bounds.push_back(UnpackedArrayBound(file->PlainLiteral(size))); + } else { + bounds.push_back(UnpackedArrayBound( + std::make_pair(file->PlainLiteral(0), file->PlainLiteral(size - 1)))); + } + } + return bounds; +} + +// Returns the width of the element of the potentially nested array type. For +// example, given array type 'bits[32][4][5]' yields 32. +int64 NestedElementWidth(ArrayType* type) { + Type* t = type; + while (t->IsArray()) { + t = t->AsArrayOrDie()->element_type(); + } + return t->GetFlatBitCount(); +} + +// Flattens a value into a single bits-typed expression. Tuples and arrays are +// represented as a concatenation of their elements. +xabsl::StatusOr FlattenValueToExpression(const Value& value, + VerilogFile* file) { + XLS_RET_CHECK_GT(value.GetFlatBitCount(), 0); + if (value.IsBits()) { + return file->Literal(value.bits()); + } + // Compound types are represented as a concatentation of their elements. + std::vector elements; + for (const Value& element : value.elements()) { + if (element.GetFlatBitCount() > 0) { + XLS_ASSIGN_OR_RETURN(Expression * element_expr, + FlattenValueToExpression(element, file)); + elements.push_back(element_expr); + } + } + return file->Concat(elements); +} + +// Returns the given array value as an array assignment pattern. For example, +// the array: +// +// [bits[8]:42, bits[8]:10, bits[8]:2] +// +// would produce: +// +// '{8'h42, 8'h10, 8'h2} +xabsl::StatusOr ValueToArrayAssignmentPattern( + const Value& value, VerilogFile* file) { + XLS_RET_CHECK(value.IsArray()); + std::vector pieces; + for (const Value& element : value.elements()) { + Expression* element_expr; + if (element.IsArray()) { + XLS_ASSIGN_OR_RETURN(element_expr, + ValueToArrayAssignmentPattern(element, file)); + } else { + XLS_ASSIGN_OR_RETURN(element_expr, + FlattenValueToExpression(element, file)); + } + pieces.push_back(element_expr); + } + return file->Make(pieces); +} + +} // namespace + +absl::Status ModuleBuilder::AddAssignment( + Expression* lhs, Expression* rhs, Type* xls_type, + std::function add_assignment_statement) { + // Array assignment is only supported in SystemVerilog. In Verilog, arrays + // must be assigned element-by-element. + if (!use_system_verilog_ && xls_type != nullptr && xls_type->IsArray()) { + ArrayType* array_type = xls_type->AsArrayOrDie(); + for (int64 i = 0; i < array_type->size(); ++i) { + XLS_RETURN_IF_ERROR( + AddAssignment(file_->Index(lhs->AsIndexableExpressionOrDie(), i), + file_->Index(rhs->AsIndexableExpressionOrDie(), i), + array_type->element_type(), add_assignment_statement)); + } + } else { + add_assignment_statement(lhs, rhs); + } + return absl::OkStatus(); +} + +absl::Status ModuleBuilder::AddAssignmentFromValue( + Expression* lhs, const Value& value, + std::function add_assignment_statement) { + if (value.IsArray()) { + if (use_system_verilog_) { + // If using system verilog emit using an array assignment pattern like so: + // logic [4:0] foo [0:4][0:1] = '{'{5'h0, 5'h1}, '{..}, ...} + XLS_ASSIGN_OR_RETURN(Expression * rhs, + ValueToArrayAssignmentPattern(value, file_)); + add_assignment_statement(lhs, rhs); + } else { + for (int64 i = 0; i < value.size(); ++i) { + XLS_RETURN_IF_ERROR(AddAssignmentFromValue( + file_->Index(lhs->AsIndexableExpressionOrDie(), i), + value.element(i), add_assignment_statement)); + } + } + } else { + XLS_ASSIGN_OR_RETURN(Expression * flattened_expr, + FlattenValueToExpression(value, file_)); + add_assignment_statement(lhs, flattened_expr); + } + return absl::OkStatus(); +} + +ModuleBuilder::ModuleBuilder(absl::string_view name, VerilogFile* file, + bool use_system_verilog) + : module_name_(SanitizeIdentifier(name)), + file_(file), + use_system_verilog_(use_system_verilog) { + module_ = file_->AddModule(module_name_); + functions_section_ = module_->Add(file_); + constants_section_ = module_->Add(file_); + input_section_ = module_->Add(file_); + declaration_and_assignment_section_ = module_->Add(file_); + output_section_ = module_->Add(file_); + + NewDeclarationAndAssignmentSections(); +} + +void ModuleBuilder::NewDeclarationAndAssignmentSections() { + declaration_subsections_.push_back( + declaration_and_assignment_section_->Add(file_)); + assignment_subsections_.push_back( + declaration_and_assignment_section_->Add(file_)); +} + +LogicRef* ModuleBuilder::DeclareUnpackedArrayWire(absl::string_view name, + ArrayType* array_type, + ModuleSection* section) { + return file_->Make(section->Add( + name, file_->PlainLiteral(NestedElementWidth(array_type)), + MakeUnpackedArrayBounds(array_type, file_, use_system_verilog_))); +} + +LogicRef* ModuleBuilder::DeclareUnpackedArrayReg(absl::string_view name, + ArrayType* array_type, + ModuleSection* section) { + return file_->Make(section->Add( + name, file_->PlainLiteral(NestedElementWidth(array_type)), + MakeUnpackedArrayBounds(array_type, file_, use_system_verilog_))); +} + +absl::Status ModuleBuilder::AssignFromSlice( + Expression* lhs, Expression* rhs, Type* xls_type, int64 slice_start, + std::function add_assignment_statement) { + if (xls_type->IsArray()) { + ArrayType* array_type = xls_type->AsArrayOrDie(); + for (int64 i = 0; i < array_type->size(); ++i) { + XLS_RETURN_IF_ERROR( + AssignFromSlice(file_->Index(lhs->AsIndexableExpressionOrDie(), i), + rhs, array_type->element_type(), + slice_start + GetFlatBitIndexOfElement(array_type, i), + add_assignment_statement)); + } + } else { + add_assignment_statement( + lhs, file_->Slice(rhs->AsIndexableExpressionOrDie(), + /*hi=*/slice_start + xls_type->GetFlatBitCount() - 1, + /*lo=*/slice_start)); + } + return absl::OkStatus(); +} + +xabsl::StatusOr ModuleBuilder::AddInputPort(absl::string_view name, + Type* type) { + LogicRef* port = module_->AddPort(Direction::kInput, SanitizeIdentifier(name), + type->GetFlatBitCount()); + if (!type->IsArray()) { + return port; + } + // All inputs are flattened so unflatten arrays with a sequence of + // assignments. + LogicRef* ar = DeclareUnpackedArrayWire( + absl::StrCat(SanitizeIdentifier(name), "_unflattened"), + type->AsArrayOrDie(), input_section()); + XLS_RETURN_IF_ERROR(AssignFromSlice( + ar, port, type->AsArrayOrDie(), 0, [&](Expression* lhs, Expression* rhs) { + input_section()->Add(lhs, rhs); + })); + return ar; +} + +LogicRef* ModuleBuilder::AddInputPort(absl::string_view name, int64 bit_count) { + return module_->AddPort(Direction::kInput, SanitizeIdentifier(name), + bit_count); +} + +absl::Status ModuleBuilder::AddOutputPort(absl::string_view name, Type* type, + Expression* value) { + LogicRef* output_port = module_->AddPort( + Direction::kOutput, SanitizeIdentifier(name), type->GetFlatBitCount()); + if (type->IsArray()) { + // The output is flattened so flatten arrays with a sequence of assignments. + XLS_RET_CHECK(value->IsIndexableExpression()); + output_section()->Add( + output_port, FlattenArray(value->AsIndexableExpressionOrDie(), + type->AsArrayOrDie(), file_)); + } else { + output_section()->Add(output_port, value); + } + return absl::OkStatus(); +} + +absl::Status ModuleBuilder::AddOutputPort(absl::string_view name, + int64 bit_count, Expression* value) { + LogicRef* output_port = + module_->AddPort(Direction::kOutput, SanitizeIdentifier(name), bit_count); + output_section()->Add(output_port, value); + return absl::OkStatus(); +} + +xabsl::StatusOr ModuleBuilder::DeclareModuleConstant( + absl::string_view name, const Value& value) { + // To generate XLS types we need a package. + // TODO(meheff): There should be a way of generating a Type for a value + // without instantiating a package. + Package p("TypeGenerator"); + Type* type = p.GetTypeForValue(value); + LogicRef* ref; + if (type->IsArray()) { + ref = DeclareUnpackedArrayWire(SanitizeIdentifier(name), + type->AsArrayOrDie(), constants_section()); + } else { + ref = module_->AddWire(SanitizeIdentifier(name), type->GetFlatBitCount(), + constants_section()); + } + XLS_RETURN_IF_ERROR( + AddAssignmentFromValue(ref, value, [&](Expression* lhs, Expression* rhs) { + constants_section()->Add(lhs, rhs); + })); + return ref; +} + +LogicRef* ModuleBuilder::DeclareVariable(absl::string_view name, Type* type) { + if (type->IsArray()) { + return DeclareUnpackedArrayWire( + SanitizeIdentifier(name), type->AsArrayOrDie(), declaration_section()); + } + return module_->AddWire(SanitizeIdentifier(name), type->GetFlatBitCount(), + declaration_section()); +} + +bool ModuleBuilder::CanEmitAsInlineExpression( + Node* node, absl::optional> users_of_expression) { + if (node->GetType()->IsArray()) { + // TODO(meheff): With system verilog we can do array assignment. + return false; + } + absl::Span users = + users_of_expression.has_value() ? *users_of_expression : node->users(); + for (Node* user : users) { + for (int64 i = 0; i < user->operand_count(); ++i) { + if (user->operand(i) == node && OperandMustBeNamedReference(user, i)) { + return false; + } + } + } + // To sidestep Verilog's jolly bit-width inference rules, emit arithmetic + // expressions as assignments. This gives the results of these expression + // explicit bit-widths. + switch (node->op()) { + case Op::kAdd: + case Op::kSub: + case Op::kSMul: + case Op::kUMul: + case Op::kSDiv: + case Op::kUDiv: + return false; + default: + break; + } + return true; +} + +xabsl::StatusOr ModuleBuilder::EmitAsInlineExpression( + Node* node, absl::Span inputs) { + if (MustEmitAsFunction(node)) { + XLS_ASSIGN_OR_RETURN(VerilogFunction * func, DefineFunction(node)); + return file_->Make(func, inputs); + } + return NodeToExpression(node, inputs, file_); +} + +xabsl::StatusOr ModuleBuilder::EmitAsAssignment( + absl::string_view name, Node* node, absl::Span inputs) { + LogicRef* ref = DeclareVariable(name, node->GetType()); + if (node->GetType()->IsArray()) { + // Array-shaped operations are handled specially. XLS arrays are represented + // as unpacked arrays in Verilog/SystemVerilog and unpacked arrays must be + // assigned element-by-element in Verilog. + ArrayType* array_type = node->GetType()->AsArrayOrDie(); + switch (node->op()) { + case Op::kArray: { + for (int64 i = 0; i < inputs.size(); ++i) { + XLS_RETURN_IF_ERROR(AddAssignment( + file_->Index(ref, file_->PlainLiteral(i)), inputs[i], + array_type->element_type(), + [&](Expression* lhs, Expression* rhs) { + assignment_section()->Add(lhs, rhs); + })); + } + break; + } + case Op::kArrayIndex: + XLS_RETURN_IF_ERROR(AddAssignment( + ref, + file_->Index(inputs[0]->AsIndexableExpressionOrDie(), inputs[1]), + array_type, [&](Expression* lhs, Expression* rhs) { + assignment_section()->Add(lhs, rhs); + })); + break; + case Op::kTupleIndex: + XLS_RETURN_IF_ERROR(AssignFromSlice( + ref, inputs[0], array_type, + GetFlatBitIndexOfElement( + node->operand(0)->GetType()->AsTupleOrDie(), + node->As()->index()), + [&](Expression* lhs, Expression* rhs) { + assignment_section()->Add(lhs, rhs); + })); + break; + default: + return absl::UnimplementedError( + absl::StrCat("Unsupported array-shaped op: ", node->ToString())); + } + } else { + XLS_ASSIGN_OR_RETURN(Expression * expr, + EmitAsInlineExpression(node, inputs)); + XLS_RETURN_IF_ERROR(Assign(ref, expr, node->GetType())); + } + return ref; +} + +absl::Status ModuleBuilder::Assign(LogicRef* lhs, Expression* rhs, Type* type) { + XLS_RETURN_IF_ERROR( + AddAssignment(lhs, rhs, type, [&](Expression* lhs, Expression* rhs) { + assignment_section()->Add(lhs, rhs); + })); + return absl::OkStatus(); +} + +xabsl::StatusOr ModuleBuilder::DeclareRegister( + absl::string_view name, Type* type, Expression* next, + absl::optional reset_value) { + LogicRef* reg; + if (type->IsArray()) { + // Currently, an array register requires SystemVerilog because there is an + // array assignment in the always flop block. + reg = DeclareUnpackedArrayReg(SanitizeIdentifier(name), + type->AsArrayOrDie(), declaration_section()); + } else { + reg = module_->AddReg(SanitizeIdentifier(name), type->GetFlatBitCount(), + /*init=*/absl::nullopt, declaration_section()); + } + return Register{ + .ref = reg, + .next = next, + .reset_value = reset_value.has_value() ? reset_value.value() : nullptr, + .xls_type = type}; +} + +xabsl::StatusOr ModuleBuilder::DeclareRegister( + absl::string_view name, int64 bit_count, Expression* next, + absl::optional reset_value) { + return Register{ + .ref = module_->AddReg(SanitizeIdentifier(name), bit_count, + /*init=*/absl::nullopt, declaration_section()), + .next = next, + .reset_value = reset_value.has_value() ? reset_value.value() : nullptr, + .xls_type = nullptr}; +} + +absl::Status ModuleBuilder::AssignRegisters( + LogicRef* clk, absl::Span registers, + Expression* load_enable, absl::optional rst) { + // Construct an always_ff block. + std::vector sensitivity_list; + sensitivity_list.push_back(file_->Make(clk)); + if (rst.has_value()) { + if (rst->active_low) { + sensitivity_list.push_back(file_->Make(rst->signal)); + } else { + sensitivity_list.push_back(file_->Make(rst->signal)); + } + } + AlwaysBase* always; + if (use_system_verilog_) { + always = assignment_section()->Add(file_, sensitivity_list); + } else { + always = assignment_section()->Add(file_, sensitivity_list); + } + // assignment_block is the block in which the foo <= foo_next assignments + // go. It can either be conditional (if there is a reset signal) or + // unconditional. + StatementBlock* assignment_block = always->statements(); + if (rst.has_value()) { + // Registers have a reset signal. Conditionally assign the registers based + // on whether the reset signal is asserted. + Expression* rst_condition; + if (rst->active_low) { + rst_condition = file_->LogicalNot(rst->signal); + } else { + rst_condition = rst->signal; + } + Conditional* conditional = + always->statements()->Add(file_, rst_condition); + for (const Register& reg : registers) { + XLS_RET_CHECK_NE(reg.reset_value, nullptr); + XLS_RETURN_IF_ERROR(AddAssignment( + reg.ref, reg.reset_value, reg.xls_type, + [&](Expression* lhs, Expression* rhs) { + conditional->consequent()->Add(lhs, rhs); + })); + } + assignment_block = conditional->AddAlternate(); + } + // Assign registers to the next value for the non-reset case (either no + // reset signal or reset signal is not asserted). + for (const Register& reg : registers) { + XLS_RETURN_IF_ERROR(AddAssignment( + reg.ref, reg.next, reg.xls_type, [&](Expression* lhs, Expression* rhs) { + assignment_block->Add( + lhs, load_enable == nullptr + ? rhs + : file_->Ternary(load_enable, rhs, lhs)); + })); + } + return absl::OkStatus(); +} + +bool ModuleBuilder::MustEmitAsFunction(Node* node) { + switch (node->op()) { + case Op::kSMul: + case Op::kUMul: + return true; + default: + return false; + } +} + +std::string ModuleBuilder::VerilogFunctionName(Node* node) { + switch (node->op()) { + case Op::kSMul: + case Op::kUMul: + // Multiplies may be mixed width so include result and operand widths in + // the name. + return absl::StrFormat( + "%s%db_%db_x_%db", OpToString(node->op()), node->BitCountOrDie(), + node->operand(0)->BitCountOrDie(), node->operand(1)->BitCountOrDie()); + default: + XLS_LOG(FATAL) << "Cannot emit node as function: " << node->ToString(); + } +} + +namespace { + +// Defines and returns a function which implements the given SMul node. +VerilogFunction* DefineSmulFunction(Node* node, absl::string_view function_name, + ModuleSection* section) { + XLS_CHECK_EQ(node->op(), Op::kSMul); + VerilogFile* file = section->file(); + + VerilogFunction* func = + section->Add(function_name, node->BitCountOrDie(), file); + XLS_CHECK_EQ(node->operand_count(), 2); + Expression* lhs = func->AddArgument("lhs", node->operand(0)->BitCountOrDie()); + Expression* rhs = func->AddArgument("rhs", node->operand(1)->BitCountOrDie()); + // The code conservatively assigns signed-casted inputs to temporary + // variables, uses them in the multiply expression which is assigned to + // another signed temporary. Finally, this is unsign-casted and assigned to + // the return value of the function. These shenanigans ensure no surprising + // sign/zero extensions of any values. + LogicRef* signed_lhs = func->AddRegDef( + "signed_lhs", file->PlainLiteral(node->operand(0)->BitCountOrDie()), + /*init=*/UninitializedSentinel(), /*is_signed=*/true); + LogicRef* signed_rhs = func->AddRegDef( + "signed_rhs", file->PlainLiteral(node->operand(1)->BitCountOrDie()), + /*init=*/UninitializedSentinel(), /*is_signed=*/true); + LogicRef* signed_result = func->AddRegDef( + "signed_result", file->PlainLiteral(node->BitCountOrDie()), + /*init=*/UninitializedSentinel(), /*is_signed=*/true); + func->AddStatement(signed_lhs, + file->Make(lhs)); + func->AddStatement(signed_rhs, + file->Make(rhs)); + func->AddStatement(signed_result, + file->Mul(signed_lhs, signed_rhs)); + func->AddStatement( + func->return_value_ref(), file->Make(signed_result)); + + return func; +} + +// Defines and returns a function which implements the given UMul node. +VerilogFunction* DefineUmulFunction(Node* node, absl::string_view function_name, + ModuleSection* section) { + XLS_CHECK_EQ(node->op(), Op::kUMul); + VerilogFile* file = section->file(); + + VerilogFunction* func = + section->Add(function_name, node->BitCountOrDie(), file); + XLS_CHECK_EQ(node->operand_count(), 2); + Expression* lhs = func->AddArgument("lhs", node->operand(0)->BitCountOrDie()); + Expression* rhs = func->AddArgument("rhs", node->operand(1)->BitCountOrDie()); + func->AddStatement(func->return_value_ref(), + file->Mul(lhs, rhs)); + + return func; +} + +} // namespace + +xabsl::StatusOr ModuleBuilder::DefineFunction(Node* node) { + std::string function_name = VerilogFunctionName(node); + if (node_functions_.contains(function_name)) { + return node_functions_.at(function_name); + } + VerilogFunction* func; + switch (node->op()) { + case Op::kSMul: + func = DefineSmulFunction(node, function_name, functions_section_); + break; + case Op::kUMul: + func = DefineUmulFunction(node, function_name, functions_section_); + break; + default: + XLS_LOG(FATAL) << "Cannot define node as function: " << node->ToString(); + } + node_functions_[function_name] = func; + return func; +} + +} // namespace verilog +} // namespace xls diff --git a/xls/codegen/module_builder.h b/xls/codegen/module_builder.h new file mode 100644 index 0000000000..e7cdffb0bd --- /dev/null +++ b/xls/codegen/module_builder.h @@ -0,0 +1,266 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_CODEGEN_MODULE_BUILDER_H_ +#define THIRD_PARTY_XLS_CODEGEN_MODULE_BUILDER_H_ + +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "xls/codegen/vast.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/node.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" + +namespace xls { +namespace verilog { + +// An abstraction wrapping a VAST module which assists with lowering of XLS IR +// into Verilog. Key functionality: +// (1) Handles mapping of XLS types to Verilog types. +// (2) Hides Verilog vs. SystemVerilog differences and enables targeting +// either from same code. +// (3) Imposes common organization to the module. +class ModuleBuilder { + public: + ModuleBuilder(absl::string_view name, VerilogFile* file, + bool use_system_verilog); + + // Returns the underlying module being constructed. + Module* module() { return module_; } + + // Add an input port of the given XLS type to the module. + xabsl::StatusOr AddInputPort(absl::string_view name, Type* type); + + // Add an input port of the given width. + LogicRef* AddInputPort(absl::string_view name, int64 bit_count); + + // Add an output port of the given XLS type to the module. The output is + // assigned the given value. + absl::Status AddOutputPort(absl::string_view name, Type* type, + Expression* value); + + // Add an output port of the given width to the module. The output is assigned + // the given value. + absl::Status AddOutputPort(absl::string_view name, int64 bit_count, + Expression* value); + + // Returns whether the given node can be emitted as an inline expression in + // Verilog (the alternative is to assign the expression of node to a temporary + // variable). Generally operations on bits-typed values can be emitted inline + // (for example, Op::kAnd). Operations on compound types such as arrays and + // tuples may require declaration of temporary variables and one or more + // assignment statements. Also, the users of a node may also force the node to + // be emitted as a temporary variable, if, for example, the emitted code for + // the user indexes into the node's value. 'users_of_expression', if given, + // are the users 'node' in the emitted Verilog to consider when determining + // whether node can be emitted inline. If not specified all users of node are + // considered. + bool CanEmitAsInlineExpression(Node* node, + absl::optional> + users_of_expression = absl::nullopt); + + // Returns the given node as a Verilog expression. 'inputs' contains the + // operand expressions for the node. + xabsl::StatusOr EmitAsInlineExpression( + Node* node, absl::Span inputs); + + // Emits the node as one or more assignments to a newly declared variable with + // the given name. 'inputs' contains the operand expressions for the + // node. Returns a reference to the declared variable. + xabsl::StatusOr EmitAsAssignment( + absl::string_view name, Node* node, absl::Span inputs); + + // Declares a variable with the given name and XLS type. Returns a reference + // to the variable. + LogicRef* DeclareVariable(absl::string_view name, Type* type); + + // Assigns the rhs to the lhs using continuous assignment where both sides + // have the given XLS type. The emitted verilog may require multiple + // assignment statements for compound types such as arrays. + absl::Status Assign(LogicRef* lhs, Expression* rhs, Type* type); + + // Declares variable with the given name and assigns the given value to + // it. Returns a reference to the variable. + xabsl::StatusOr DeclareModuleConstant(absl::string_view name, + const Value& Value); + + // Data structure describing a register (collection of flops). + struct Register { + // Reference to the declared logic/reg variable holding the register value. + LogicRef* ref; + + // The expression to assign to the register at each clock. + Expression* next; + + // The register value upon reset. Should be non-null iff AssignRegisters is + // called with non-null Reset argument. + Expression* reset_value; + + // Optional XLS type of this register. Can be null. + Type* xls_type; + }; + + // Declares a register of the given XLS type. Arguments: + // name: name of the declared Verilog register. + // type: XLS type of the register. + // next: The expression to assign to the register each clock. + // reset_value: The value of the register on reset. Should be non-null iff + // the corresponding AssignRegisters call includes a non-null Reset + // argument. + // + // Declared registers must be passed to a subsequent AssignRegisters call for + // assignment within an always block. + xabsl::StatusOr DeclareRegister( + absl::string_view name, Type* type, Expression* next, + absl::optional reset_value = absl::nullopt); + + // As above, but declares a register of a given bit width. + xabsl::StatusOr DeclareRegister( + absl::string_view name, int64 bit_count, Expression* next, + absl::optional reset_value = absl::nullopt); + + // Construct an always block to assign values to the registers. Arguments: + // clk: Clock signal to use for registers. + // registers: Registers to assign within this block. + // load_enable: Optional load enable signal. The register is loaded only if + // this signal is asserted. + // rst: Optional reset signal. + absl::Status AssignRegisters(LogicRef* clk, + absl::Span registers, + Expression* load_enable = nullptr, + absl::optional rst = absl::nullopt); + + // For organization (not functionality) the module is divided into several + // sections. The emitted module has the following structure: + // + // module foo( + // ... + // ); + // { functions_section } + // // definitions of functions used in module. + // { constants_section } + // // declarations of module-level constants. + // { input_section } + // // converts potentially flattened input values to + // // module-internal form (e.g. unpacked array). + // { declarations_sections_[0] } + // // declarations of module variables. + // { assignments_sections_[0] } + // // assignments to module variables and always_ff sections. + // { declarations_sections_[1] } // Optional + // { assignments_sections_[1] } // Optional + // ... + // { output_section } + // // assigns the output port(s) including any flattening. + // endmodule + // + // The declarations and assignment sections appear as a pair and more than one + // instance of this pair of sections can be added to the module by calling + // NewDeclarationAndAssignmentSections. + + // Creates new declaration and assignment sections immediately after the + // current declaration and assignment sections. + void NewDeclarationAndAssignmentSections(); + + // Methods to returns one of the various sections in the module. + ModuleSection* declaration_section() const { + return declaration_subsections_.back(); + } + ModuleSection* assignment_section() const { + return assignment_subsections_.back(); + } + ModuleSection* functions_section() const { return functions_section_; } + ModuleSection* constants_section() const { return constants_section_; } + ModuleSection* input_section() const { return input_section_; } + ModuleSection* output_section() const { return output_section_; } + + private: + // Declares an unpacked array wire/reg variable of the given XLS array type in + // the given ModuleSection. + LogicRef* DeclareUnpackedArrayWire(absl::string_view name, + ArrayType* array_type, + ModuleSection* section); + LogicRef* DeclareUnpackedArrayReg(absl::string_view name, + ArrayType* array_type, + ModuleSection* section); + + // Assigns 'rhs' to 'lhs'. Depending upon the type this may require multiple + // assignment statements (e.g., for array assignments in Verilog). The + // function add_assignment_statement should add a single assignment + // statement. This function argument enables customization of the type of + // assignment (continuous, blocking, or non-blocking) as well as the location + // where the assignment statements are added. + absl::Status AddAssignment( + Expression* lhs, Expression* rhs, Type* xls_type, + std::function add_assignment_statement); + + // Assigns the arbitrarily-typed Value 'value' to 'lhs'. Depending upon the + // type this may require multiple assignment statements. The function + // add_assignment_statement should add a single assignment statement. + absl::Status AddAssignmentFromValue( + Expression* lhs, const Value& value, + std::function add_assignment_statement); + + // Extracts a slice from the bits-typed 'rhs' and assigns it to 'lhs' in + // unflattened form. Depending upon the type this may require multiple + // assignment statements. The function add_assignment_statement should add a + // single assignment statement. + absl::Status AssignFromSlice( + Expression* lhs, Expression* rhs, Type* xls_type, int64 slice_start, + std::function add_assignment_statement); + + // Returns true if the node must be emitted as a function. + bool MustEmitAsFunction(Node* node); + + // Returns the name of the function which implements node. The function name + // should encapsulate all metainformation about the node (opcode, bitwidth, + // etc) because a function definition may be reused to implement multiple + // identical nodes (for example, two different 32-bit multiplies may map to + // the same function). + std::string VerilogFunctionName(Node* node); + + // Defines a function which implements the given node. If a function already + // exists which implements this node then the existing function is returned. + xabsl::StatusOr DefineFunction(Node* node); + + std::string module_name_; + VerilogFile* file_; + + // True if SystemVerilog constructs can be used. Otherwise the emitted code is + // strictly Verilog. + bool use_system_verilog_; + + Module* module_; + ModuleSection* functions_section_; + ModuleSection* constants_section_; + ModuleSection* input_section_; + ModuleSection* declaration_and_assignment_section_; + std::vector declaration_subsections_; + std::vector assignment_subsections_; + ModuleSection* output_section_; + + // Verilog functions defined inside the module. Map is indexed by the function + // name. + absl::flat_hash_map node_functions_; +}; + +} // namespace verilog +} // namespace xls + +#endif // THIRD_PARTY_XLS_CODEGEN_MODULE_BUILDER_H_ diff --git a/xls/codegen/module_builder_test.cc b/xls/codegen/module_builder_test.cc new file mode 100644 index 0000000000..2c3bd85fe3 --- /dev/null +++ b/xls/codegen/module_builder_test.cc @@ -0,0 +1,309 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/module_builder.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/codegen/vast.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/package.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" +#include "xls/simulation/verilog_test_base.h" + +namespace xls { +namespace verilog { +namespace { + +constexpr char kTestName[] = "module_builder_test"; +constexpr char kTestdataPath[] = "xls/codegen/testdata"; + +Value Make1DArray(int64 element_width, absl::Span elements) { + std::vector values; + for (int64 element : elements) { + values.push_back(Value(UBits(element, element_width))); + } + return Value::ArrayOrDie(values); +} + +Value Make2DArray(int64 element_width, + absl::Span> elements) { + std::vector rows; + for (const auto& row : elements) { + rows.push_back(Make1DArray(element_width, row)); + } + return Value::ArrayOrDie(rows); +} + +class ModuleBuilderTest : public VerilogTestBase {}; + +TEST_P(ModuleBuilderTest, AddTwoNumbers) { + VerilogFile file; + Package p(TestBaseName()); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * x, + mb.AddInputPort("x", p.GetBitsType(32))); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * y, + mb.AddInputPort("y", p.GetBitsType(32))); + XLS_ASSERT_OK(mb.AddOutputPort("out", p.GetBitsType(32), file.Add(x, y))); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, NewSections) { + VerilogFile file; + Package p(TestBaseName()); + Type* u32 = p.GetBitsType(32); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * x, mb.AddInputPort("x", u32)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * y, mb.AddInputPort("y", u32)); + + LogicRef* a = mb.DeclareVariable("a", u32); + XLS_ASSERT_OK(mb.Assign(a, file.Add(x, y), u32)); + + mb.NewDeclarationAndAssignmentSections(); + LogicRef* b = mb.DeclareVariable("b", u32); + XLS_ASSERT_OK(mb.Assign(b, file.Add(a, y), u32)); + + XLS_ASSERT_OK(mb.AddOutputPort("out", u32, file.Negate(b))); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, Registers) { + VerilogFile file; + Package p(TestBaseName()); + Type* u32 = p.GetBitsType(32); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * x, mb.AddInputPort("x", u32)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * y, mb.AddInputPort("y", u32)); + LogicRef* clk = mb.AddInputPort("clk", 1); + + XLS_ASSERT_OK_AND_ASSIGN(ModuleBuilder::Register a, + mb.DeclareRegister("a", u32, file.Add(x, y))); + XLS_ASSERT_OK_AND_ASSIGN(ModuleBuilder::Register b, + mb.DeclareRegister("b", u32, y)); + XLS_ASSERT_OK(mb.AssignRegisters(clk, {a, b})); + + XLS_ASSERT_OK(mb.AddOutputPort("out", u32, file.Add(a.ref, b.ref))); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, RegisterWithReset) { + VerilogFile file; + Package p(TestBaseName()); + Type* u32 = p.GetBitsType(32); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * x, mb.AddInputPort("x", u32)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * y, mb.AddInputPort("y", u32)); + LogicRef* clk = mb.AddInputPort("clk", 1); + LogicRef* rst = mb.AddInputPort("rst", 1); + + XLS_ASSERT_OK_AND_ASSIGN( + ModuleBuilder::Register a, + mb.DeclareRegister("a", u32, file.Add(x, y), + /*reset_value=*/file.Literal(UBits(0, 32)))); + XLS_ASSERT_OK_AND_ASSIGN( + ModuleBuilder::Register b, + mb.DeclareRegister("b", u32, y, + /*reset_value=*/file.Literal(UBits(0x42, 32)))); + XLS_ASSERT_OK(mb.AssignRegisters(clk, {a, b}, + /*load_enable=*/nullptr, + Reset{.signal = rst->AsLogicRefNOrDie<1>(), + .asynchronous = false, + .active_low = false})); + + XLS_ASSERT_OK(mb.AddOutputPort("out", u32, file.Add(a.ref, b.ref))); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, RegisterWithLoadEnable) { + VerilogFile file; + Package p(TestBaseName()); + Type* u32 = p.GetBitsType(32); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * x, mb.AddInputPort("x", u32)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * y, mb.AddInputPort("y", u32)); + LogicRef* clk = mb.AddInputPort("clk", 1); + LogicRef* load_enable = mb.AddInputPort("le", 1); + + XLS_ASSERT_OK_AND_ASSIGN(ModuleBuilder::Register a, + mb.DeclareRegister("a", u32, file.Add(x, y))); + XLS_ASSERT_OK_AND_ASSIGN(ModuleBuilder::Register b, + mb.DeclareRegister("b", u32, y)); + XLS_ASSERT_OK(mb.AssignRegisters(clk, {a, b}, load_enable)); + + XLS_ASSERT_OK(mb.AddOutputPort("out", u32, file.Add(a.ref, b.ref))); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, RegisterWithLoadEnableAndReset) { + VerilogFile file; + Package p(TestBaseName()); + Type* u32 = p.GetBitsType(32); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * x, mb.AddInputPort("x", u32)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * y, mb.AddInputPort("y", u32)); + LogicRef* clk = mb.AddInputPort("clk", 1); + LogicRef* rst = mb.AddInputPort("rstn", 1); + LogicRef* load_enable = mb.AddInputPort("le", 1); + + XLS_ASSERT_OK_AND_ASSIGN( + ModuleBuilder::Register a, + mb.DeclareRegister("a", u32, file.Add(x, y), + /*reset_value=*/file.Literal(UBits(0, 32)))); + XLS_ASSERT_OK_AND_ASSIGN( + ModuleBuilder::Register b, + mb.DeclareRegister("b", u32, y, + /*reset_value=*/file.Literal(UBits(0x42, 32)))); + XLS_ASSERT_OK(mb.AssignRegisters(clk, {a, b}, load_enable, + Reset{.signal = rst->AsLogicRefNOrDie<1>(), + .asynchronous = true, + .active_low = true})); + + XLS_ASSERT_OK(mb.AddOutputPort("out", u32, file.Add(a.ref, b.ref))); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, ComplexComputation) { + VerilogFile file; + Package p(TestBaseName()); + Type* u32 = p.GetBitsType(32); + Type* u16 = p.GetBitsType(16); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * x, mb.AddInputPort("x", u32)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * y, mb.AddInputPort("y", u32)); + mb.declaration_section()->Add("Declaration section."); + mb.assignment_section()->Add("Assignment section."); + LogicRef* a = mb.DeclareVariable("a", u32); + LogicRef* b = mb.DeclareVariable("b", u16); + LogicRef* c = mb.DeclareVariable("c", u16); + XLS_ASSERT_OK(mb.Assign(a, file.Shrl(x, y), u32)); + XLS_ASSERT_OK(mb.Assign(b, file.Slice(y, 16, 0), u16)); + XLS_ASSERT_OK(mb.Assign(c, file.Add(b, b), u16)); + XLS_ASSERT_OK(mb.AddOutputPort("out", u16, file.Add(b, c))); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, ReturnConstantArray) { + VerilogFile file; + // The XLS IR package is just used for type management. + Package package(TestBaseName()); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + Value ar_value = Make2DArray(7, {{0x33, 0x12, 0x42}, {0x1, 0x2, 0x3}}); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * ar, + mb.DeclareModuleConstant("ar", ar_value)); + XLS_ASSERT_OK(mb.AddOutputPort("out", package.GetTypeForValue(ar_value), ar)); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, PassThroughArray) { + VerilogFile file; + // The XLS IR package is just used for type management. + Package package(TestBaseName()); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + ArrayType* ar_type = package.GetArrayType(4, package.GetBitsType(13)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * a, mb.AddInputPort("a", ar_type)); + XLS_ASSERT_OK(mb.AddOutputPort("out", ar_type, a)); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, ReturnConstantTuple) { + VerilogFile file; + // The XLS IR package is just used for type management. + Package package(TestBaseName()); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + Value tuple = + Value::Tuple({Value(UBits(0x8, 8)), Make1DArray(24, {0x3, 0x6, 0x9}), + Value(UBits(0xab, 16))}); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * t, mb.DeclareModuleConstant("t", tuple)); + XLS_ASSERT_OK(mb.AddOutputPort("out", package.GetTypeForValue(tuple), t)); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, PassThroughTuple) { + VerilogFile file; + // The XLS IR package is just used for type management. + Package package(TestBaseName()); + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + TupleType* tuple_type = package.GetTupleType( + {package.GetBitsType(42), package.GetArrayType(7, package.GetBitsType(6)), + package.GetTupleType({})}); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * a, mb.AddInputPort("a", tuple_type)); + XLS_ASSERT_OK(mb.AddOutputPort("out", tuple_type, a)); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} + +TEST_P(ModuleBuilderTest, SmulAsFunction) { + VerilogFile file; + Package package(TestBaseName()); + FunctionBuilder fb(TestBaseName(), &package); + Type* u32 = package.GetBitsType(32); + BValue x_smul_y = fb.SMul(fb.Param("x", u32), fb.Param("y", u32)); + BValue z_smul_z = fb.SMul(fb.Param("z", u32), fb.Param("z", u32)); + + ModuleBuilder mb(TestBaseName(), &file, + /*use_system_verilog=*/UseSystemVerilog()); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * x, mb.AddInputPort("x", u32)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * y, mb.AddInputPort("y", u32)); + XLS_ASSERT_OK_AND_ASSIGN(LogicRef * z, mb.AddInputPort("z", u32)); + XLS_ASSERT_OK( + mb.EmitAsAssignment("x_smul_y", x_smul_y.node(), {x, y}).status()); + XLS_ASSERT_OK( + mb.EmitAsAssignment("z_smul_z", z_smul_z.node(), {z, z}).status()); + + ExpectVerilogEqualToGoldenFile(GoldenFilePath(kTestName, kTestdataPath), + file.Emit()); +} +INSTANTIATE_TEST_SUITE_P(ModuleBuilderTestInstantiation, ModuleBuilderTest, + testing::ValuesIn(kDefaultSimulationTargets), + ParameterizedTestName); + +} // namespace +} // namespace verilog +} // namespace xls diff --git a/xls/codegen/module_signature.cc b/xls/codegen/module_signature.cc new file mode 100644 index 0000000000..d13976acff --- /dev/null +++ b/xls/codegen/module_signature.cc @@ -0,0 +1,274 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/module_signature.h" + +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/package.h" + +namespace xls { +namespace verilog { + +ModuleSignatureBuilder& ModuleSignatureBuilder::WithClock( + absl::string_view name) { + XLS_CHECK(!proto_.has_clock_name()); + proto_.set_clock_name(ToProtoString(name)); + return *this; +} + +ModuleSignatureBuilder& ModuleSignatureBuilder::WithReset( + absl::string_view name, bool asynchronous, bool active_low) { + XLS_CHECK(!proto_.has_reset()); + ResetProto* reset = proto_.mutable_reset(); + reset->set_name(ToProtoString(name)); + reset->set_asynchronous(asynchronous); + reset->set_active_low(active_low); + return *this; +} + +ModuleSignatureBuilder& ModuleSignatureBuilder::WithReadyValidInterface( + absl::string_view input_ready, absl::string_view input_valid, + absl::string_view output_ready, absl::string_view output_valid) { + XLS_CHECK_EQ(proto_.interface_oneof_case(), + ModuleSignatureProto::INTERFACE_ONEOF_NOT_SET); + ReadyValidInterface* interface = proto_.mutable_ready_valid(); + interface->set_input_ready(ToProtoString(input_ready)); + interface->set_input_valid(ToProtoString(input_valid)); + interface->set_output_ready(ToProtoString(output_ready)); + interface->set_output_valid(ToProtoString(output_valid)); + return *this; +} + +ModuleSignatureBuilder& ModuleSignatureBuilder::WithFixedLatencyInterface( + int64 latency) { + XLS_CHECK_EQ(proto_.interface_oneof_case(), + ModuleSignatureProto::INTERFACE_ONEOF_NOT_SET); + FixedLatencyInterface* interface = proto_.mutable_fixed_latency(); + interface->set_latency(latency); + return *this; +} + +ModuleSignatureBuilder& ModuleSignatureBuilder::WithCombinationalInterface() { + XLS_CHECK_EQ(proto_.interface_oneof_case(), + ModuleSignatureProto::INTERFACE_ONEOF_NOT_SET); + proto_.mutable_combinational(); + return *this; +} + +ModuleSignatureBuilder& ModuleSignatureBuilder::WithPipelineInterface( + int64 latency, int64 initiation_interval, + absl::optional pipeline_control) { + XLS_CHECK_EQ(proto_.interface_oneof_case(), + ModuleSignatureProto::INTERFACE_ONEOF_NOT_SET); + PipelineInterface* interface = proto_.mutable_pipeline(); + interface->set_latency(latency); + interface->set_initiation_interval(initiation_interval); + if (pipeline_control.has_value()) { + *interface->mutable_pipeline_control() = *pipeline_control; + } + return *this; +} + +ModuleSignatureBuilder& ModuleSignatureBuilder::WithFunctionType( + FunctionType* function_type) { + XLS_CHECK(!proto_.has_function_type()); + *proto_.mutable_function_type() = function_type->ToProto(); + return *this; +} + +ModuleSignatureBuilder& ModuleSignatureBuilder::AddDataInput( + absl::string_view name, int64 width) { + PortProto* port = proto_.add_data_ports(); + port->set_direction(DIRECTION_INPUT); + port->set_name(ToProtoString(name)); + port->set_width(width); + return *this; +} + +ModuleSignatureBuilder& ModuleSignatureBuilder::AddDataOutput( + absl::string_view name, int64 width) { + PortProto* port = proto_.add_data_ports(); + port->set_direction(DIRECTION_OUTPUT); + port->set_name(ToProtoString(name)); + port->set_width(width); + return *this; +} + +xabsl::StatusOr ModuleSignatureBuilder::Build() { + return ModuleSignature::FromProto(proto_); +} + +/*static*/ xabsl::StatusOr ModuleSignature::FromProto( + const ModuleSignatureProto& proto) { + // TODO(meheff): do more validation here. + // Validate widths/number of function type. + if ((proto.has_pipeline() || proto.has_ready_valid()) && + !proto.has_clock_name()) { + return absl::InvalidArgumentError("Missing clock signal"); + } + + ModuleSignature signature; + signature.proto_ = proto; + for (const PortProto& port : proto.data_ports()) { + if (port.direction() == DIRECTION_INPUT) { + signature.data_inputs_.push_back(port); + } else if (port.direction() == DIRECTION_OUTPUT) { + signature.data_outputs_.push_back(port); + } else { + return absl::InvalidArgumentError("Invalid port direction."); + } + } + return signature; +} + +int64 ModuleSignature::TotalDataInputBits() const { + int64 total = 0; + for (const PortProto& port : data_inputs()) { + total += port.width(); + } + return total; +} + +int64 ModuleSignature::TotalDataOutputBits() const { + int64 total = 0; + for (const PortProto& port : data_outputs()) { + total += port.width(); + } + return total; +} + +// Checks that the given inputs match one-to-one to the input ports (matched by +// name). Returns a vector containing the inputs in the same order as the input +// ports. +template +static xabsl::StatusOr> CheckAndReturnOrderedInputs( + absl::Span input_ports, + const absl::flat_hash_map& inputs) { + absl::flat_hash_set port_names; + std::vector ordered_inputs; + for (const PortProto& port : input_ports) { + port_names.insert(port.name()); + + if (!inputs.contains(port.name())) { + return absl::InvalidArgumentError(absl::StrFormat( + "Input '%s' was not passed as an argument.", port.name())); + } + ordered_inputs.push_back(&inputs.at(port.name())); + } + + // Verify every passed in input is accounted for. + for (const auto& pair : inputs) { + if (!port_names.contains(pair.first)) { + return absl::InvalidArgumentError( + absl::StrFormat("Unexpected input value named '%s'.", pair.first)); + } + } + return ordered_inputs; +} + +absl::Status ModuleSignature::ValidateInputs( + const absl::flat_hash_map& input_bits) const { + XLS_ASSIGN_OR_RETURN(std::vector ordered_inputs, + CheckAndReturnOrderedInputs(data_inputs(), input_bits)); + for (int64 i = 0; i < ordered_inputs.size(); ++i) { + const PortProto& port = data_inputs()[i]; + const Bits* input = ordered_inputs[i]; + if (port.width() != input->bit_count()) { + return absl::InvalidArgumentError( + absl::StrFormat("Expected input '%s' to have width %d, has width %d", + port.name(), port.width(), input->bit_count())); + } + } + return absl::OkStatus(); +} + +static std::string TypeProtoToString(const TypeProto& proto) { + // Create a dummy package for creating Type*'s from a proto. + // TODO(meheff): Find a better way to manage types. We need types disconnected + // from any IR package. + Package p("dummy_package"); + auto type_status = p.GetTypeFromProto(proto); + if (!type_status.ok()) { + return ""; + } + return type_status.value()->ToString(); +} + +static xabsl::StatusOr TypeProtosEqual(const TypeProto& a, + const TypeProto& b) { + // Create a dummy package for creating Type*'s from a proto. + // TODO(meheff): Find a better way to manage types. We need types disconnected + // from any IR package. + Package p("dummy_package"); + XLS_ASSIGN_OR_RETURN(Type * a_type, p.GetTypeFromProto(a)); + XLS_ASSIGN_OR_RETURN(Type * b_type, p.GetTypeFromProto(b)); + return a_type == b_type; +} + +absl::Status ModuleSignature::ValidateInputs( + const absl::flat_hash_map& input_values) const { + if (!proto().has_function_type()) { + return absl::InvalidArgumentError( + "Cannot validate Value inputs because signature has no function_type " + "field"); + } + XLS_ASSIGN_OR_RETURN( + std::vector ordered_inputs, + CheckAndReturnOrderedInputs(data_inputs(), input_values)); + XLS_RET_CHECK_EQ(data_inputs().size(), + proto().function_type().parameters_size()); + for (int64 i = 0; i < ordered_inputs.size(); ++i) { + const Value* input = ordered_inputs[i]; + const TypeProto& expected_type_proto = + proto().function_type().parameters(i); + XLS_ASSIGN_OR_RETURN(TypeProto value_type_proto, input->TypeAsProto()); + XLS_ASSIGN_OR_RETURN(bool types_equal, TypeProtosEqual(expected_type_proto, + value_type_proto)); + if (!types_equal) { + return absl::InvalidArgumentError(absl::StrFormat( + "Input value '%s' is wrong type. Expected '%s', got '%s'", + data_inputs()[i].name(), TypeProtoToString(expected_type_proto), + TypeProtoToString(value_type_proto))); + } + } + return absl::OkStatus(); +} + +xabsl::StatusOr> +ModuleSignature::ToKwargs(absl::Span inputs) const { + if (inputs.size() != data_inputs().size()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Expected %d arguments, got %d.", data_inputs().size(), inputs.size())); + } + absl::flat_hash_map kwargs; + for (int64 i = 0; i < data_inputs().size(); ++i) { + kwargs[data_inputs()[i].name()] = inputs[i]; + } + return kwargs; +} + +std::ostream& operator<<(std::ostream& os, const ModuleSignature& signature) { + os << signature.ToString(); + return os; +} + +} // namespace verilog +} // namespace xls diff --git a/xls/codegen/module_signature.h b/xls/codegen/module_signature.h new file mode 100644 index 0000000000..bd765fd7d9 --- /dev/null +++ b/xls/codegen/module_signature.h @@ -0,0 +1,144 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_CODEGEN_MODULE_SIGNATURE_H_ +#define THIRD_PARTY_XLS_CODEGEN_MODULE_SIGNATURE_H_ + +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/types/optional.h" +#include "xls/codegen/module_signature.pb.h" +#include "xls/codegen/vast.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" + +namespace xls { +namespace verilog { + +class ModuleSignature; + +inline std::string ToProtoString(absl::string_view s) { + return std::string(s); +} + +// A builder for constructing ModuleSignatures (descriptions of Verilog module +// interfaces). +class ModuleSignatureBuilder { + public: + explicit ModuleSignatureBuilder(absl::string_view module_name) { + proto_.set_module_name(ToProtoString(module_name)); + } + + // Sets the clock as having the given name. + ModuleSignatureBuilder& WithClock(absl::string_view name); + + // Sets the reset signal as having the given name and properties. + ModuleSignatureBuilder& WithReset(absl::string_view name, bool asynchronous, + bool active_low); + + // Defines the module interface as using ready/valid flow control with signals + // of the given names. + ModuleSignatureBuilder& WithReadyValidInterface( + absl::string_view input_ready, absl::string_view input_valid, + absl::string_view output_ready, absl::string_view output_valid); + + // Defines the module interface as fixed latency. + ModuleSignatureBuilder& WithFixedLatencyInterface(int64 latency); + + // Defines the module interface as pipelined with the given latency and + // initiation interval. + ModuleSignatureBuilder& WithPipelineInterface( + int64 latency, int64 initiation_interval, + absl::optional pipeline_control = absl::nullopt); + + // Defines the module interface as purely combinational. + ModuleSignatureBuilder& WithCombinationalInterface(); + + // Sets the type of the function to the given string. The expected form is + // defined by xls::FunctionType::ToString. + ModuleSignatureBuilder& WithFunctionType(FunctionType* function_type); + + // Add data input/outputs to the interface. Control signals such as the clock, + // reset, ready/valid signals, etc should not be added using these methods. + ModuleSignatureBuilder& AddDataInput(absl::string_view name, int64 width); + ModuleSignatureBuilder& AddDataOutput(absl::string_view name, int64 width); + + xabsl::StatusOr Build(); + + private: + ModuleSignatureProto proto_; +}; + +// An abstraction describing the interface to a Verilog module. At the moment +// this is a thin wrapper around a proto and most of the fields are accessed +// directly through the proto (ModuleSignature::proto). However the class has +// the benefit of invariant enforcement, convenience methods, and is a framework +// to expand the interface. +class ModuleSignature { + public: + static xabsl::StatusOr FromProto( + const ModuleSignatureProto& proto); + + const std::string& module_name() const { return proto_.module_name(); } + + const ModuleSignatureProto& proto() const { return proto_; } + + // Returns the data inputs/outputs of module. This does not include clock, + // reset, etc. These ports necessarily exist in the proto as well but are + // duplicated here for convenience. + absl::Span data_inputs() const { return data_inputs_; } + absl::Span data_outputs() const { return data_outputs_; } + + // Returns the total number of bits of the data input/outputs. + int64 TotalDataInputBits() const; + int64 TotalDataOutputBits() const; + + std::string ToString() const { return proto_.DebugString(); } + + // Verifies that the given data input Bits(Values) are exactly the expected + // set and of the appropriate type for the module. + absl::Status ValidateInputs( + const absl::flat_hash_map& input_bits) const; + absl::Status ValidateInputs( + const absl::flat_hash_map& input_values) const; + + // Converts the ordered set of Value arguments to the module of the signature + // into an argument name-value map. + xabsl::StatusOr> ToKwargs( + absl::Span inputs) const; + + private: + ModuleSignatureProto proto_; + + // These ports also exist in the proto, but are duplicated here to enable the + // convenience methods data_inputs() and data_outputs(). + std::vector data_inputs_; + std::vector data_outputs_; +}; + +// Abstraction gathering the Verilog text and module signature produced by the +// generator. +struct ModuleGeneratorResult { + std::string verilog_text; + ModuleSignature signature; +}; + +std::ostream& operator<<(std::ostream& os, const ModuleSignature& signature); + +} // namespace verilog +} // namespace xls + +#endif // THIRD_PARTY_XLS_CODEGEN_MODULE_SIGNATURE_H_ diff --git a/xls/codegen/module_signature.proto b/xls/codegen/module_signature.proto new file mode 100644 index 0000000000..f802ad6724 --- /dev/null +++ b/xls/codegen/module_signature.proto @@ -0,0 +1,129 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package xls.verilog; + +import "xls/ir/xls_type.proto"; + +enum DirectionProto { + DIRECTION_INVALID = 0; + DIRECTION_INPUT = 1; + DIRECTION_OUTPUT = 2; +} + +message PortProto { + optional DirectionProto direction = 1; + optional string name = 2; + // The width of a port may be zero corresponding to zero-width XLS data types + // such as empty tuples. These zero-width PortPorts have no corresponding port + // in the Verilog module as Verilog does not support zero-width data + // types. However, having zero-width PortProtos maintains a one-to-one + // correspondence between ports in the signature and parameters in the XLS + // function. + optional int64 width = 3; +} + +// Module produces its result in a fixed number of cycles without flow control. +message FixedLatencyInterface { + // Latency (in number of cycles) to produce an output after being presented an + // input. + optional int64 latency = 1; +} + +// Module uses ready/valid flow control on the input and output. +message ReadyValidInterface { + // Port names for input and output ready/valid signaling. + optional string input_ready = 1; + optional string input_valid = 2; + optional string output_ready = 3; + optional string output_valid = 4; +} + +// Describes a "valid" signal control scheme of pipeline registers. A single bit +// "valid" input is added to the module. This signal should be asserted when the +// data input ports(s) to the module are driven. The valid signal is passed +// along the pipeline registers and serves as the load enable for the pipeline +// registers. +message ValidProto { + // Input valid signal name to use on the module interface. Required. + optional string input_name = 1; + + // Name for the "valid" output that has been passed through the pipe stages; + // i.e. the input_name signal presented at cycle 0 shows up at output_name + // after L cycles with a pipeline of latency L. If not specified then the + // valid signal is not output from the module. + optional string output_name = 2; +} + +// Proto describing manual control scheme of pipeline registers. With this +// control scheme, the module includes an input with one bit per stage in the +// pipeline. Bit N of this input controls the load-enable of the pipeline +// registers of the N-th pipeline stage. +message ManualPipelineControl { + optional string input_name = 1; +} + +// Describes how the pipeline registers are controlled. +message PipelineControl { + oneof interface_oneof { + ValidProto valid = 1; + ManualPipelineControl manual = 2; + } +} + +// Module with a pipelined device function. +message PipelineInterface { + optional int64 latency = 1; + optional int64 initiation_interval = 2; + + // Describes how the pipeline registers are controlled (load enables). If not + // specified then the registers are loaded every cycle. + optional PipelineControl pipeline_control = 3; +} + +// Module with purely combinational logic. +message CombinationalInterface {} + +message ResetProto { + optional string name = 1; + optional bool asynchronous = 2; + optional bool active_low = 3; +} + +message ModuleSignatureProto { + // Name of the module. + optional string module_name = 1; + + // The data ports of the module. This does not include control ports such as + // clk, ready/valid, etc. + repeated PortProto data_ports = 2; + + // Name of the clock port (if any). + optional string clock_name = 3; + + // Describes the reset signal (if any). + optional ResetProto reset = 4; + + oneof interface_oneof { + FixedLatencyInterface fixed_latency = 5; + ReadyValidInterface ready_valid = 6; + PipelineInterface pipeline = 7; + CombinationalInterface combinational = 8; + } + + // The XLS function type that it generated a module for. + optional FunctionTypeProto function_type = 9; +} diff --git a/xls/codegen/module_signature_test.cc b/xls/codegen/module_signature_test.cc new file mode 100644 index 0000000000..7a0d182a73 --- /dev/null +++ b/xls/codegen/module_signature_test.cc @@ -0,0 +1,130 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/module_signature.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits.h" +#include "xls/ir/value.h" + +namespace xls { +namespace verilog { +namespace { + +using status_testing::StatusIs; +using ::testing::HasSubstr; + +std::string TestName() { + return ::testing::UnitTest::GetInstance()->current_test_info()->name(); +} + +TEST(ModuleSignatureTest, SimpledFixedLatencyInterface) { + ModuleSignatureBuilder b(TestName()); + + b.AddDataInput("x", 42).AddDataOutput("y", 2).WithFixedLatencyInterface(123); + + XLS_ASSERT_OK_AND_ASSIGN(ModuleSignature signature, b.Build()); + ASSERT_EQ(signature.data_inputs().size(), 1); + EXPECT_EQ(signature.data_inputs().front().width(), 42); + EXPECT_EQ(signature.data_inputs().front().name(), "x"); + EXPECT_EQ(signature.TotalDataInputBits(), 42); + + ASSERT_EQ(signature.data_outputs().size(), 1); + EXPECT_EQ(signature.data_outputs().front().width(), 2); + EXPECT_EQ(signature.data_outputs().front().name(), "y"); + EXPECT_EQ(signature.TotalDataOutputBits(), 2); + + ASSERT_TRUE(signature.proto().has_fixed_latency()); + EXPECT_EQ(signature.proto().fixed_latency().latency(), 123); +} + +TEST(ModuleSignatureTest, ReadyValidInterface) { + ModuleSignatureBuilder b(TestName()); + + b.WithReadyValidInterface("input_rdy", "input_vld", "output_rdy", + "output_vld") + .WithClock("the_clk") + .WithReset("reset_me", /*asynchronous=*/true, /*active_low=*/false) + .AddDataInput("x", 42) + .AddDataInput("y", 2) + .AddDataInput("z", 44444) + .AddDataOutput("o1", 1) + .AddDataOutput("o2", 3); + + XLS_ASSERT_OK_AND_ASSIGN(ModuleSignature signature, b.Build()); + ASSERT_TRUE(signature.proto().has_ready_valid()); + EXPECT_EQ(signature.proto().ready_valid().input_ready(), "input_rdy"); + EXPECT_EQ(signature.proto().ready_valid().input_valid(), "input_vld"); + EXPECT_EQ(signature.proto().ready_valid().output_ready(), "output_rdy"); + EXPECT_EQ(signature.proto().ready_valid().output_valid(), "output_vld"); + + EXPECT_EQ(signature.TotalDataInputBits(), 44488); + EXPECT_EQ(signature.TotalDataOutputBits(), 4); + + EXPECT_EQ(signature.proto().clock_name(), "the_clk"); + EXPECT_TRUE(signature.proto().has_reset()); + EXPECT_EQ(signature.proto().reset().name(), "reset_me"); + EXPECT_TRUE(signature.proto().reset().asynchronous()); + EXPECT_FALSE(signature.proto().reset().active_low()); + + EXPECT_EQ(signature.data_inputs().size(), 3); + EXPECT_EQ(signature.data_outputs().size(), 2); +} + +TEST(ModuleSignatureTest, PipelineInterface) { + ModuleSignatureBuilder b(TestName()); + + b.WithPipelineInterface(/*latency=*/2, /*initiation_interval=*/3) + .WithClock("clk") + .AddDataInput("in", 4) + .AddDataOutput("out", 5); + + XLS_ASSERT_OK_AND_ASSIGN(ModuleSignature signature, b.Build()); + ASSERT_TRUE(signature.proto().has_pipeline()); + EXPECT_EQ(signature.proto().pipeline().latency(), 2); + EXPECT_EQ(signature.proto().pipeline().initiation_interval(), 3); +} + +TEST(ModuleSignatureTest, PipelineInterfaceMissingClock) { + ModuleSignatureBuilder b(TestName()); + + b.WithPipelineInterface(/*latency=*/2, /*initiation_interval=*/3) + .AddDataInput("in", 4) + .AddDataOutput("out", 5); + + EXPECT_THAT(b.Build(), StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Missing clock"))); +} + +TEST(ModuleSignatureTest, ToKwargs) { + ModuleSignatureBuilder b(TestName()); + b.AddDataInput("x", 42) + .AddDataInput("y", 2) + .AddDataOutput("z", 32) + .WithFixedLatencyInterface(123); + XLS_ASSERT_OK_AND_ASSIGN(ModuleSignature signature, b.Build()); + + absl::flat_hash_map kwargs; + XLS_ASSERT_OK_AND_ASSIGN( + kwargs, signature.ToKwargs({Value(UBits(7, 42)), Value(UBits(0, 2))})); + EXPECT_THAT(kwargs, testing::UnorderedElementsAre( + testing::Pair("x", Value(UBits(7, 42))), + testing::Pair("y", Value(UBits(0, 2))))); +} + +} // namespace +} // namespace verilog +} // namespace xls diff --git a/xls/codegen/name_to_bit_count.h b/xls/codegen/name_to_bit_count.h new file mode 100644 index 0000000000..ab16317a31 --- /dev/null +++ b/xls/codegen/name_to_bit_count.h @@ -0,0 +1,37 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Helpful typedefs for common mappings that resolve names into bit-values or +// bit-counts. +// +// Note that these types are unordered, so stabilizing sorts must be performed +// on their keys if reproducible traversals are required. + +#ifndef THIRD_PARTY_XLS_CODEGEN_NAME_TO_BIT_COUNT_H_ +#define THIRD_PARTY_XLS_CODEGEN_NAME_TO_BIT_COUNT_H_ + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "xls/common/integral_types.h" +#include "xls/ir/bits.h" +#include "xls/ir/type.h" + +namespace xls { + +using NameToBitCount = absl::flat_hash_map; +using NameToBits = absl::flat_hash_map; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_CODEGEN_NAME_TO_BIT_COUNT_H_ diff --git a/xls/codegen/node_expressions.cc b/xls/codegen/node_expressions.cc new file mode 100644 index 0000000000..76c8434ea5 --- /dev/null +++ b/xls/codegen/node_expressions.cc @@ -0,0 +1,571 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/codegen/node_expressions.h" + +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "xls/codegen/flattening.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/nodes.h" +#include "xls/ir/type.h" + +namespace xls { +namespace verilog { + +bool OperandMustBeNamedReference(Node* node, int64 operand_no) { + // Returns true if the emitted expression for the specified operand is + // necessarily indexable. Generally, if the expression emitted for a node is + // an indexing operation and the operand is emitted as an indexable expression + // then there is no need to make the operand a declared expression because + // indexing/slicing can be chained. + // + // For example a kArrayIndex of a kArrayIndex can be emitted as a chained VAST + // Index expression like so: + // + // reg [42:0] foo = bar[42][7] + // + // In this case, no need to make bar[42] a named temporary. + auto operand_is_indexable = [&]() { + switch (node->operand(operand_no)->op()) { + case Op::kArrayIndex: + case Op::kParam: + // These operations are emitted as VAST Index operations which + // can be indexed. + return true; + default: + return false; + } + }; + switch (node->op()) { + case Op::kBitSlice: + XLS_CHECK_EQ(operand_no, 0); + return !operand_is_indexable(); + case Op::kArrayIndex: + return operand_no == 0 && !operand_is_indexable(); + case Op::kOneHot: + case Op::kOneHotSel: + return operand_no == 0 && !operand_is_indexable(); + case Op::kTupleIndex: + // Tuples are represented as flat vectors and kTupleIndex operation is a + // slice out of the flat vector. The exception is if the element is an + // Array. In this case, the element must be unflattened into an unpacked + // array which requires that it be a named reference. + return node->GetType()->IsArray() || !operand_is_indexable(); + case Op::kShra: + // Shra indexes the sign bit of the zero-th operand. + return operand_no == 0; + case Op::kSignExt: + // For operands wider than one bit, sign extend slices out the sign bit of + // the operand so its operand needs to be a reference. + // TODO(meheff): It might be better to have a unified place to hold both + // the Verilog expression and constraints for the Ops, a la + // op_specification.py + XLS_CHECK_EQ(operand_no, 0); + return node->operand(operand_no)->BitCountOrDie() > 1 && + !operand_is_indexable(); + case Op::kEncode: + // The expression of the encode operation indexes individual bits of the + // operand. + return true; + case Op::kReverse: + return true; + default: + return false; + } +} + +namespace { + +// Returns given Value as a VAST Literal created in the given file. must_flatten +// indicates if the value must be emitted as a flat bit vector. This argument is +// used when invoked recursively for when a tuple includes nested array +// elements. In this case, the nested array elements must be flattened rather +// than emitted as an unpacked array. +Expression* ValueToVastLiteral(const Value& value, VerilogFile* file, + bool must_flatten = false) { + if (value.IsBits()) { + return file->Literal(value.bits()); + } else if (value.IsTuple()) { + std::vector elements; + for (const Value& element : value.elements()) { + elements.push_back( + ValueToVastLiteral(element, file, /*must_flatten=*/true)); + } + return file->Concat(elements); + } else { + XLS_CHECK(value.IsArray()); + std::vector elements; + for (const Value& element : value.elements()) { + elements.push_back(ValueToVastLiteral(element, file, must_flatten)); + } + if (must_flatten) { + return file->Concat(elements); + } + return file->ArrayAssignmentPattern(elements); + } +} + +xabsl::StatusOr EmitSel(Select* sel, Expression* selector, + absl::Span cases, + int64 caseno, VerilogFile* file) { + if (caseno + 1 == cases.size()) { + return cases[caseno]; + } + XLS_ASSIGN_OR_RETURN(Expression * rhs, + EmitSel(sel, selector, cases, caseno + 1, file)); + return file->Ternary( + file->Equals( + selector, + file->Literal(caseno, + /*bit_count=*/sel->selector()->BitCountOrDie())), + cases[caseno], rhs); +} + +xabsl::StatusOr EmitOneHot(OneHot* one_hot, + IndexableExpression* input, + VerilogFile* file) { + const int64 input_width = one_hot->operand(0)->BitCountOrDie(); + const int64 output_width = one_hot->BitCountOrDie(); + auto do_index_input = [&](int64 i) { + int64 index = + one_hot->priority() == LsbOrMsb::kLsb ? i : input_width - i - 1; + return file->Index(input, index); + }; + // When LSb has priority, does: x[hi_offset:0] + // When MSb has priority, does: x[bit_count-1:bit_count-1-hi_offset] + auto do_slice_input = [&](int64 hi_offset) { + if (one_hot->priority() == LsbOrMsb::kLsb) { + return file->Slice(input, hi_offset, 0); + } + return file->Slice(input, input_width - 1, input_width - 1 - hi_offset); + }; + std::vector one_hot_bits; + Expression* all_zero; + for (int64 i = 0; i < output_width; ++i) { + if (i == 0) { + one_hot_bits.push_back(do_index_input(i)); + continue; + } + + Expression* higher_priority_bits_zero; + if (i == 1) { + higher_priority_bits_zero = file->LogicalNot(do_index_input(0)); + } else { + higher_priority_bits_zero = file->Equals( + do_slice_input(i - 1), file->Literal(UBits(0, /*bit_count=*/i))); + } + + if (i < output_width - 1) { + one_hot_bits.push_back( + file->LogicalAnd(do_index_input(i), higher_priority_bits_zero)); + } else { + // Default case when all inputs are zero. + all_zero = higher_priority_bits_zero; + } + } + if (one_hot->priority() == LsbOrMsb::kLsb) { + one_hot_bits.push_back(all_zero); + // Reverse the order of the bits because bit index and indexing of concat + // elements are reversed. That is, the zero-th operand of concat becomes the + // most-significant part (highest index) of the result. + std::reverse(one_hot_bits.begin(), one_hot_bits.end()); + } else { + one_hot_bits.insert(one_hot_bits.begin(), all_zero); + } + return file->Concat(one_hot_bits); +} + +xabsl::StatusOr EmitOneHotSelect( + OneHotSelect* sel, IndexableExpression* selector, + absl::Span inputs, VerilogFile* file) { + if (!sel->GetType()->IsBits()) { + return absl::UnimplementedError(absl::StrFormat( + "Only bits-typed one-hot select supported:: %s", sel->ToString())); + } + int64 sel_width = sel->BitCountOrDie(); + Expression* sum = nullptr; + for (int64 i = 0; i < inputs.size(); ++i) { + Expression* masked_input = + sel_width == 1 + ? file->BitwiseAnd(inputs[i], file->Index(selector, i)) + : file->BitwiseAnd(inputs[i], file->Concat( + /*replication=*/sel_width, + {file->Index(selector, i)})); + if (sum == nullptr) { + sum = masked_input; + } else { + sum = file->BitwiseOr(sum, masked_input); + } + } + return sum; +} + +// Returns an OR reduction of the given expressions. That is: +// exprs[0] | exprs[1] | ... | exprs[n] +xabsl::StatusOr OrReduction(absl::Span exprs, + VerilogFile* file) { + XLS_RET_CHECK(!exprs.empty()); + Expression* reduction = exprs[0]; + for (int i = 1; i < exprs.size(); ++i) { + reduction = file->BitwiseOr(reduction, exprs[i]); + } + return reduction; +} + +// Emits the given encode op and returns the Verilog expression. +xabsl::StatusOr EmitEncode(Encode* encode, + IndexableExpression* operand, + VerilogFile* file) { + std::vector output_bits(encode->BitCountOrDie()); + // Encode produces the OR reduction of the ordinal positions of the set bits + // of the input. For example, if bit 5 and bit 42 are set in the input, the + // result is 5 | 42. + for (int64 i = 0; i < encode->BitCountOrDie(); ++i) { + std::vector elements; + for (int64 j = 0; j < encode->operand(0)->BitCountOrDie(); ++j) { + if (j & (1 << i)) { + elements.push_back(file->Index(operand, j)); + } + } + XLS_RET_CHECK(!elements.empty()); + XLS_ASSIGN_OR_RETURN(output_bits[i], OrReduction(elements, file)); + } + std::reverse(output_bits.begin(), output_bits.end()); + return file->Concat(output_bits); +} + +// Reverses the order of the bits of the operand. +xabsl::StatusOr EmitReverse(Node* reverse, + IndexableExpression* operand, + VerilogFile* file) { + const int64 width = reverse->BitCountOrDie(); + std::vector output_bits(width); + for (int64 i = 0; i < width; ++i) { + // The concat below naturally reverse bit indices so lhs and rhs can use the + // same index. + output_bits[i] = file->Index(operand, i); + } + return file->Concat(output_bits); +} + +// Emits a shift (shll, shrl, shra). +xabsl::StatusOr EmitShift(Node* shift, Expression* operand, + Expression* shift_amount, + VerilogFile* file) { + Expression* shifted_operand; + if (shift->op() == Op::kShra) { + // To perform an arithmetic shift right the left operand must be cast to a + // signed value, ie: + // + // $signed(x) >>> y + // + // Also, wrap the expression in $unsigned to prevent the signed property + // from leaking out into the rest of the expression. + // + // $unsigned($signed(x) >>> y) op ... + // + // Without the unsigned the '>>>' expression would be treated as a signed + // value potentially affecting the evaluation of 'op'. This unsigned cast + // is also necessary for correctness of the shift evaluation when the shift + // appears in a ternary expression because of Verilog type rules. + shifted_operand = file->Make( + file->Shra(file->Make(operand), shift_amount)); + } else if (shift->op() == Op::kShrl) { + shifted_operand = file->Shrl(operand, shift_amount); + } else { + XLS_CHECK_EQ(shift->op(), Op::kShll); + shifted_operand = file->Shll(operand, shift_amount); + } + + // Verilog semantics are not defined for shifting by more than or equal to + // the operand width so guard the shift with a test for overshifting (if + // the width of the shift-amount is wide enough to overshift). + const int64 width = shift->BitCountOrDie(); + if (shift->operand(1)->BitCountOrDie() < Bits::MinBitCountUnsigned(width)) { + // Shift amount is not wide enough to overshift. + return shifted_operand; + } + + Expression* width_expr = + file->Literal(width, /*bit_count=*/shift->operand(1)->BitCountOrDie()); + Expression* overshift_value; + if (shift->op() == Op::kShra) { + // Shrl: overshift value is all sign bits. + overshift_value = file->Concat( + /*replication=*/width, + {file->Index(operand->AsIndexableExpressionOrDie(), width - 1)}); + } else { + // Shll or shrl: overshift value is zero. + overshift_value = file->Literal(0, shift->BitCountOrDie()); + } + return file->Ternary(file->GreaterThanEquals(shift_amount, width_expr), + overshift_value, shifted_operand); +} + +// Emits a decode instruction. +xabsl::StatusOr EmitDecode(Decode* decode, Expression* operand, + VerilogFile* file) { + Expression* result = + file->Shll(file->Literal(1, decode->BitCountOrDie()), operand); + if (Bits::MinBitCountUnsigned(decode->BitCountOrDie()) > + decode->operand(0)->BitCountOrDie()) { + // Output is wide enough to accommodate every possible input value. No need + // to guard the input to avoid overshifting. + return result; + } + // If operand value is greater than the width of the output, zero shoud be + // emitted. + return file->Ternary( + file->GreaterThanEquals( + operand, + file->Literal(/*value=*/decode->BitCountOrDie(), + /*bit_count=*/decode->operand(0)->BitCountOrDie())), + + file->Literal(/*value=*/0, /*bit_count=*/decode->BitCountOrDie()), + file->Shll(file->Literal(1, decode->BitCountOrDie()), operand)); +} + +// Emits an multiply with potentially mixed bit-widths. +xabsl::StatusOr EmitMultiply(Node* mul, Expression* lhs, + Expression* rhs, VerilogFile* file) { + // TODO(meheff): Arbitrary widths are supported via functions in module + // builder. Unify the expression generation in this file with module builder + // some how. + XLS_RET_CHECK_EQ(mul->BitCountOrDie(), mul->operand(0)->BitCountOrDie()); + XLS_RET_CHECK_EQ(mul->BitCountOrDie(), mul->operand(1)->BitCountOrDie()); + XLS_RET_CHECK(mul->op() == Op::kUMul || mul->op() == Op::kSMul); + if (mul->op() == Op::kSMul) { + return file->Make( + file->Mul(file->Make(lhs), file->Make(rhs))); + } else { + return file->Mul(lhs, rhs); + } +} + +} // namespace + +xabsl::StatusOr NodeToExpression( + Node* node, absl::Span inputs, VerilogFile* file) { + auto unimplemented = [&]() { + return absl::UnimplementedError( + absl::StrFormat("Node cannot be emitted as a Verilog expression: %s", + node->ToString())); + }; + auto do_nary_op = + [&](std::function f) { + Expression* accum = inputs[0]; + for (int64 i = 1; i < inputs.size(); ++i) { + accum = f(accum, inputs[i]); + } + return accum; + }; + switch (node->op()) { + case Op::kAdd: + return file->Add(inputs[0], inputs[1]); + case Op::kAnd: + return do_nary_op([file](Expression* lhs, Expression* rhs) { + return file->BitwiseAnd(lhs, rhs); + }); + case Op::kAndReduce: + return file->AndReduce(inputs[0]); + case Op::kNand: + return file->BitwiseNot( + do_nary_op([file](Expression* lhs, Expression* rhs) { + return file->BitwiseAnd(lhs, rhs); + })); + case Op::kNor: + return file->BitwiseNot( + do_nary_op([file](Expression* lhs, Expression* rhs) { + return file->BitwiseOr(lhs, rhs); + })); + case Op::kArray: { + std::vector elements(inputs.begin(), inputs.end()); + return file->ArrayAssignmentPattern(elements); + } + case Op::kArrayIndex: { + // Hack to avoid indexing scalar registers, this can be removed when we + // support querying types of definitions in the VAST AST. + if (node->operand(1)->Is() && + node->operand(1)->As()->IsZero()) { + return file->Index(inputs[0]->AsIndexableExpressionOrDie(), + file->PlainLiteral(0)); + } + return file->Index(inputs[0]->AsIndexableExpressionOrDie(), inputs[1]); + } + case Op::kBitSlice: { + BitSlice* slice = node->As(); + if (slice->width() == 1) { + return file->Index(inputs[0]->AsIndexableExpressionOrDie(), + slice->start()); + } else { + return file->Slice(inputs[0]->AsIndexableExpressionOrDie(), + slice->start() + slice->width() - 1, slice->start()); + } + } + case Op::kConcat: + return file->Concat(inputs); + case Op::kUDiv: + return file->Div(inputs[0], inputs[1]); + case Op::kEq: + return file->Equals(inputs[0], inputs[1]); + case Op::kUGe: + return file->GreaterThanEquals(inputs[0], inputs[1]); + case Op::kUGt: + return file->GreaterThan(inputs[0], inputs[1]); + case Op::kDecode: + return EmitDecode(node->As(), inputs[0], file); + case Op::kEncode: + return EmitEncode(node->As(), + inputs[0]->AsIndexableExpressionOrDie(), file); + case Op::kIdentity: + return inputs[0]; + case Op::kInvoke: + return unimplemented(); + case Op::kCountedFor: + return unimplemented(); + case Op::kLiteral: + return ValueToVastLiteral(node->As()->value(), file); + case Op::kULe: + return file->LessThanEquals(inputs[0], inputs[1]); + case Op::kULt: + return file->LessThan(inputs[0], inputs[1]); + case Op::kMap: + return unimplemented(); + case Op::kUMul: + case Op::kSMul: + return EmitMultiply(node, inputs[0], inputs[1], file); + case Op::kNe: + return file->NotEquals(inputs[0], inputs[1]); + case Op::kNeg: + return file->Negate(inputs[0]); + case Op::kNot: + return file->BitwiseNot(inputs[0]); + case Op::kOneHot: + return EmitOneHot(node->As(), + inputs[0]->AsIndexableExpressionOrDie(), file); + case Op::kOneHotSel: + return EmitOneHotSelect(node->As(), + inputs[0]->AsIndexableExpressionOrDie(), + inputs.subspan(1), file); + case Op::kOr: + return do_nary_op([file](Expression* lhs, Expression* rhs) { + return file->BitwiseOr(lhs, rhs); + }); + case Op::kOrReduce: + return file->OrReduce(inputs[0]); + case Op::kParam: + return unimplemented(); + case Op::kReverse: + return EmitReverse(node, inputs[0]->AsIndexableExpressionOrDie(), file); + case Op::kSel: { + Select* sel = node->As(); + absl::Span cases = + sel->default_value().has_value() + ? operands.subspan(1, operands.size() - 2) + : operands.subspan(1); + absl::optional default_value = + sel->default_value().has_value() + ? absl::optional(operands.back()) + : absl::nullopt; + return evaluator->Select(operands[0], cases, default_value); + } + case Op::kShll: + XLS_RETURN_IF_ERROR(check_operand_count(2)); + return evaluator->ShiftLeftLogical(operands[0], operands[1]); + case Op::kShra: + XLS_RETURN_IF_ERROR(check_operand_count(2)); + return evaluator->ShiftRightArith(operands[0], operands[1]); + case Op::kShrl: + XLS_RETURN_IF_ERROR(check_operand_count(2)); + return evaluator->ShiftRightLogical(operands[0], operands[1]); + case Op::kSignExt: + XLS_RETURN_IF_ERROR(check_operand_count(1)); + return evaluator->SignExtend(operands[0], node->BitCountOrDie()); + case Op::kSMul: + return default_handler(node); + case Op::kSub: + return default_handler(node); + case Op::kTuple: + return default_handler(node); + case Op::kTupleIndex: + return default_handler(node); + case Op::kUDiv: + return default_handler(node); + case Op::kUGe: + XLS_RETURN_IF_ERROR(check_operand_count(2)); + return Vector( + {evaluator->Not(evaluator->ULessThan(operands[0], operands[1]))}); + case Op::kUGt: + XLS_RETURN_IF_ERROR(check_operand_count(2)); + return Vector({evaluator->ULessThan(operands[1], operands[0])}); + case Op::kULe: + XLS_RETURN_IF_ERROR(check_operand_count(2)); + return Vector( + {evaluator->Not(evaluator->ULessThan(operands[1], operands[0]))}); + case Op::kULt: + XLS_RETURN_IF_ERROR(check_operand_count(2)); + return Vector({evaluator->ULessThan(operands[0], operands[1])}); + case Op::kUMul: + return default_handler(node); + case Op::kXor: + return evaluator->BitwiseXor(operands); + case Op::kXorReduce: + return evaluator->XorReduce(operands[0]); + case Op::kZeroExt: + XLS_RETURN_IF_ERROR(check_operand_count(1)); + return evaluator->ZeroExtend(operands[0], node->BitCountOrDie()); + } +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_ABSTRACT_NODE_EVALUATOR_H_ diff --git a/xls/ir/big_int.cc b/xls/ir/big_int.cc new file mode 100644 index 0000000000..cd7f946adb --- /dev/null +++ b/xls/ir/big_int.cc @@ -0,0 +1,244 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/big_int.h" + +#include "openssl/bn.h" +#include "xls/common/logging/logging.h" + +namespace xls { + +BigInt::BigInt() { BN_init(&bn_); } + +BigInt::BigInt(const BigInt& other) { + BN_init(&bn_); + BN_copy(&bn_, &other.bn_); +} + +BigInt::BigInt(BigInt&& other) { + memcpy(&bn_, &other.bn_, sizeof(bn_)); + BN_init(&other.bn_); +} + +BigInt::~BigInt() { BN_free(&bn_); } + +BigInt& BigInt::operator=(const BigInt& other) { + BN_copy(&bn_, &other.bn_); + return *this; +} + +BigInt& BigInt::operator=(BigInt&& other) { + memcpy(&bn_, &other.bn_, sizeof(bn_)); + BN_init(&other.bn_); + return *this; +} + +bool BigInt::operator==(const BigInt& other) const { + return BN_cmp(&bn_, &other.bn_) == 0; +} + +/* static */ +BigInt BigInt::MakeUnsigned(const Bits& bits) { + BigInt value; + std::vector byte_vector = bits.ToBytes(); + BN_bin2bn(byte_vector.data(), byte_vector.size(), &value.bn_); + return value; +} + +/* static */ +BigInt BigInt::MakeSigned(const Bits& bits) { + if (bits.bit_count() == 0 || !bits.msb()) { + return MakeUnsigned(bits); + } + BigInt value; + // 'bits' is a twos-complement negative number, invert the bits and add one to + // get the magnitude. Then negate it to produce the correct value in the + // BigInt. + std::vector byte_vector = bits.ToBytes(); + for (auto& byte : byte_vector) { + byte = ~byte; + } + // ToBytes pads the most significant bits with zeroes to fit the return value + // in whole bytes. Set them back to zero after the bit inversion above. + int bits_in_msb = bits.bit_count() % 8; + if (bits_in_msb) { // There are pad bits. + byte_vector[0] &= Mask(bits_in_msb); + } + + BN_bin2bn(byte_vector.data(), byte_vector.size(), &value.bn_); + BN_add_word(&value.bn_, 1); + BN_set_negative(&value.bn_, 1); + return value; +} + +Bits BigInt::ToSignedBits() const { + if (BN_is_zero(&bn_)) { + return Bits(); + } + bool is_negative = BN_is_negative(&bn_); + + // In twos-complement, negative values are stored as their positive + // counterpart - 1, bit inverted. First compute the positive counterpart - 1. + BigInt decremented_if_negative = *this; + BN_set_negative(&decremented_if_negative.bn_, 0); + if (is_negative) { + BN_sub_word(&decremented_if_negative.bn_, 1); + } + + std::vector byte_vector; + byte_vector.resize(BN_num_bytes(&decremented_if_negative.bn_)); + XLS_CHECK(BN_bn2bin_padded(byte_vector.data(), byte_vector.size(), + &decremented_if_negative.bn_)); + + if (is_negative) { + for (uint8& byte : byte_vector) { + byte = ~byte; + } + } + + int64 result_bit_count = SignedBitCount(); + BitsRope rope(result_bit_count); + rope.push_back(Bits::FromBytes(byte_vector, result_bit_count - 1)); + rope.push_back(is_negative ? Bits::AllOnes(1) : Bits(1)); + return rope.Build(); +} + +Bits BigInt::ToUnsignedBits() const { + XLS_CHECK(!BN_is_negative(&bn_)); + int64 bit_count = BN_num_bits(&bn_); + std::vector byte_vector; + byte_vector.resize(BN_num_bytes(&bn_)); + + XLS_CHECK(BN_bn2bin_padded(byte_vector.data(), byte_vector.size(), &bn_)); + return Bits::FromBytes(byte_vector, bit_count); +} + +xabsl::StatusOr BigInt::ToSignedBitsWithBitCount(int64 bit_count) const { + int64 min_bit_count = SignedBitCount(); + if (bit_count < min_bit_count) { + return absl::InvalidArgumentError(absl::StrFormat( + "Specified bit count (%d) is less than minimum required (%d)!", + bit_count, min_bit_count)); + } + + // Can't use bits_ops due to circular dependency. + BitsRope rope(bit_count); + Bits bits = ToSignedBits(); + rope.push_back(bits); + if (BN_is_negative(&bn_)) { + rope.push_back(Bits::AllOnes(bit_count - min_bit_count)); + } else { + rope.push_back(Bits(bit_count - min_bit_count)); + } + return rope.Build(); +} + +xabsl::StatusOr BigInt::ToUnsignedBitsWithBitCount( + int64 bit_count) const { + int64 min_bit_count = UnsignedBitCount(); + if (bit_count < min_bit_count) { + return absl::InvalidArgumentError(absl::StrFormat( + "Specified bit count (%d) is less than minimum required (%d)!", + bit_count, min_bit_count)); + } + + // Can't use bits_ops due to circular dependency. + BitsRope rope(bit_count); + Bits bits = ToUnsignedBits(); + rope.push_back(bits); + rope.push_back(Bits(bit_count - min_bit_count)); + return rope.Build(); +} + +int64 BigInt::SignedBitCount() const { + if (BN_is_zero(&bn_)) { + return 0; + } + if (BN_is_negative(&bn_)) { + // Value is negative. If it only has a single bit set (eg, 0x8000) then the + // minimum number of bits to represent the value in twos-complement is the + // same as the minimum number of bits for the unsigned representation of + // the magnitude. Otherwise, it requires one extra bit. + BigInt magnitude = Negate(*this); + BN_sub_word(&magnitude.bn_, 1); + return BN_num_bits(&magnitude.bn_) + 1; + } + + // Value is positive. Twos complement requires a leading zero. + return BN_num_bits(&bn_) + 1; +} + +int64 BigInt::UnsignedBitCount() const { + XLS_CHECK_EQ(BN_is_negative(&bn_), 0) << "Value must be non-negative."; + if (BN_is_negative(&bn_)) { + return 0; + } + return BN_num_bits(&bn_); +} + +/* static */ BigInt BigInt::Add(const BigInt& lhs, const BigInt& rhs) { + BigInt value; + BN_add(&value.bn_, &lhs.bn_, &rhs.bn_); + return value; +} + +/* static */ BigInt BigInt::Sub(const BigInt& lhs, const BigInt& rhs) { + BigInt value; + BN_sub(&value.bn_, &lhs.bn_, &rhs.bn_); + return value; +} + +/* static */ BigInt BigInt::Mul(const BigInt& lhs, const BigInt& rhs) { + BigInt value; + // Note: The documentation about BN_CTX in bn.h indicates that it's possible + // to pass null to public methods that take a BN_CTX*, but that's not true. + BN_CTX* ctx = BN_CTX_new(); + BN_mul(&value.bn_, &lhs.bn_, &rhs.bn_, ctx); + BN_CTX_free(ctx); + return value; +} + +/* static */ BigInt BigInt::Div(const BigInt& lhs, const BigInt& rhs) { + BigInt value; + // Note: The documentation about BN_CTX in bn.h indicates that it's possible + // to pass null to public methods that take a BN_CTX*, but that's not true. + BN_CTX* ctx = BN_CTX_new(); + BN_div(&value.bn_, /*rem=*/nullptr, &lhs.bn_, &rhs.bn_, ctx); + BN_CTX_free(ctx); + return value; +} + +/* static */ BigInt BigInt::Negate(const BigInt& input) { + BigInt value = input; + BN_set_negative(&value.bn_, !BN_is_negative(&value.bn_)); + return value; +} + +/* static */ bool BigInt::LessThan(const BigInt& lhs, const BigInt& rhs) { + return BN_cmp(&lhs.bn_, &rhs.bn_) < 0; +} + +std::ostream& operator<<(std::ostream& os, const BigInt& big_int) { + if (BigInt::LessThan(big_int, BigInt())) { + os << "-" + << BigInt::Negate(big_int).ToUnsignedBits().ToString( + FormatPreference::kHex, /*include_bit_count=*/true); + } else { + os << big_int.ToUnsignedBits().ToString(FormatPreference::kHex, + /*include_bit_count=*/true); + } + return os; +} + +} // namespace xls diff --git a/xls/ir/big_int.h b/xls/ir/big_int.h new file mode 100644 index 0000000000..6d3ac428e0 --- /dev/null +++ b/xls/ir/big_int.h @@ -0,0 +1,77 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_BIG_INT_H_ +#define THIRD_PARTY_XLS_IR_BIG_INT_H_ + +#include "openssl/bn.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" + +namespace xls { + +// Class which wraps OpenSSL's bignum library to provide support for arbitrary +// width integer arithmetic operations. +class BigInt { + public: + // Make (un)signed BigInt from the given bits object. MakeSigned assumes a + // twos-complement representation. + static BigInt MakeSigned(const Bits& bits); + static BigInt MakeUnsigned(const Bits& bits); + + BigInt(); + BigInt(const BigInt& other); + BigInt(BigInt&& other); + ~BigInt(); + BigInt& operator=(const BigInt& other); + BigInt& operator=(BigInt&& other); + + bool operator==(const BigInt& other) const; + bool operator!=(const BigInt& other) const { return !(*this == other); } + + // Returns the BigInt value as a (un)signed Bits object. The Bits object + // returned from ToSignedBits is in twos-complement representation. If a width + // is unspecified, then the Bits object has the minimum number of bits + // required to hold the value. + Bits ToSignedBits() const; + Bits ToUnsignedBits() const; + + // Returns the BigInt value as a Bits object of the specified width. Returns + // an error if the value doesn't fit in the specified bit count. + xabsl::StatusOr ToSignedBitsWithBitCount(int64 bit_count) const; + xabsl::StatusOr ToUnsignedBitsWithBitCount(int64 bit_count) const; + + // Returns the minimum number of bits required to hold this BigInt value. For + // SignedBitCount this is the number of bits required to hold the value in + // twos-complement representation. + int64 SignedBitCount() const; + int64 UnsignedBitCount() const; + + // Various arithmetic and comparison operations. + static BigInt Add(const BigInt& lhs, const BigInt& rhs); + static BigInt Sub(const BigInt& lhs, const BigInt& rhs); + static BigInt Mul(const BigInt& lhs, const BigInt& rhs); + static BigInt Div(const BigInt& lhs, const BigInt& rhs); + static BigInt Negate(const BigInt& input); + static bool LessThan(const BigInt& lhs, const BigInt& rhs); + + private: + BIGNUM bn_; +}; + +std::ostream& operator<<(std::ostream& os, const BigInt& big_int); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_BIG_INT_H_ diff --git a/xls/ir/big_int_test.cc b/xls/ir/big_int_test.cc new file mode 100644 index 0000000000..eff7dae848 --- /dev/null +++ b/xls/ir/big_int_test.cc @@ -0,0 +1,305 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/big_int.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits.h" +#include "xls/ir/number_parser.h" + +namespace xls { +namespace { + +using status_testing::StatusIs; + +class BigIntTest : public ::testing::Test { + protected: + BigInt MakeBigInt(int64 value) { + return BigInt::MakeSigned(SBits(value, 64)); + } + + BigInt MakeBigInt(absl::string_view hex_string) { + std::pair sign_magnitude = + GetSignAndMagnitude(hex_string).value(); + if (sign_magnitude.first) { + // Negative number (leading '-'). + return BigInt::Negate(BigInt::MakeUnsigned(sign_magnitude.second)); + } + return BigInt::MakeUnsigned(sign_magnitude.second); + } +}; + +TEST_F(BigIntTest, EqualityOperator) { + auto one = BigInt::MakeUnsigned(UBits(1, 64)); + auto two = BigInt::MakeUnsigned(UBits(2, 64)); + EXPECT_TRUE((one == one)); + EXPECT_FALSE((one == two)); + EXPECT_FALSE((one != one)); + EXPECT_TRUE((one != two)); +} + +TEST_F(BigIntTest, CopyConstructor) { + auto original = BigInt::MakeUnsigned(UBits(1, 64)); + auto copy = BigInt(original); + + EXPECT_EQ(original, BigInt::MakeUnsigned(UBits(1, 64))); + EXPECT_EQ(original, copy); +} + +TEST_F(BigIntTest, MoveConstructor) { + auto original = BigInt::MakeUnsigned(UBits(1, 64)); + auto moved = BigInt(std::move(original)); + + EXPECT_EQ(moved, BigInt::MakeUnsigned(UBits(1, 64))); +} + +TEST_F(BigIntTest, CopyAssignment) { + auto original = BigInt::MakeUnsigned(UBits(1, 64)); + auto copy = BigInt(); + copy = original; + + EXPECT_EQ(original, BigInt::MakeUnsigned(UBits(1, 64))); + EXPECT_EQ(original, copy); +} + +TEST_F(BigIntTest, MoveAssignment) { + auto original = BigInt::MakeUnsigned(UBits(1, 64)); + auto moved = BigInt(); + moved = std::move(original); + + EXPECT_EQ(moved, BigInt::MakeUnsigned(UBits(1, 64))); +} + +TEST_F(BigIntTest, MakeUnsigned) { + BigInt zero = BigInt::MakeUnsigned(UBits(0, 0)); + EXPECT_EQ(zero.UnsignedBitCount(), 0); + EXPECT_EQ(zero.ToUnsignedBits(), UBits(0, 0)); + + // Round-trip Bits values through BigInt and verify value and bit-width (which + // should be the minimum to hold the value). + EXPECT_EQ(BigInt::MakeUnsigned(UBits(1, 64)).ToUnsignedBits(), UBits(1, 1)); + EXPECT_EQ(BigInt::MakeUnsigned(UBits(2, 64)).ToUnsignedBits(), UBits(2, 2)); + EXPECT_EQ(BigInt::MakeUnsigned(UBits(3, 64)).ToUnsignedBits(), UBits(3, 2)); + EXPECT_EQ(BigInt::MakeUnsigned(UBits(4, 64)).ToUnsignedBits(), UBits(4, 3)); + EXPECT_EQ(BigInt::MakeUnsigned(UBits(5, 64)).ToUnsignedBits(), UBits(5, 3)); + EXPECT_EQ(BigInt::MakeUnsigned(UBits(6, 64)).ToUnsignedBits(), UBits(6, 3)); + EXPECT_EQ(BigInt::MakeUnsigned(UBits(7, 64)).ToUnsignedBits(), UBits(7, 3)); + EXPECT_EQ(BigInt::MakeUnsigned(UBits(8, 64)).ToUnsignedBits(), UBits(8, 4)); + + EXPECT_EQ(BigInt::MakeUnsigned(UBits(1234567, 64)).ToUnsignedBits(), + UBits(1234567, 21)); + + std::string wide_input = "0xabcd_abcd_1234_1234_aaaa_bbbb_cccc_dddd"; + XLS_ASSERT_OK_AND_ASSIGN(Bits wide_bits, ParseNumber(wide_input)); + EXPECT_EQ(BigInt::MakeUnsigned(wide_bits).ToUnsignedBits().ToString( + FormatPreference::kHex), + wide_input); +} + +TEST_F(BigIntTest, MakeSigned) { + BigInt zero = BigInt::MakeSigned(UBits(0, 0)); + EXPECT_EQ(zero.SignedBitCount(), 0); + EXPECT_EQ(zero.ToSignedBits(), UBits(0, 0)); + + // Round-trip a signed Bits values through BigInt and verify value and + // bit-width (which should be the minimum to hold the value in twos-complement + // representation). + EXPECT_EQ(BigInt::MakeSigned(SBits(0, 1)).ToSignedBits(), SBits(0, 0)); + EXPECT_EQ(BigInt::MakeSigned(SBits(1, 2)).ToSignedBits(), SBits(1, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(1, 3)).ToSignedBits(), SBits(1, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(1, 4)).ToSignedBits(), SBits(1, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(1, 5)).ToSignedBits(), SBits(1, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(1, 6)).ToSignedBits(), SBits(1, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(1, 7)).ToSignedBits(), SBits(1, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(1, 8)).ToSignedBits(), SBits(1, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(1, 64)).ToSignedBits(), SBits(1, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(2, 64)).ToSignedBits(), SBits(2, 3)); + EXPECT_EQ(BigInt::MakeSigned(SBits(3, 64)).ToSignedBits(), SBits(3, 3)); + EXPECT_EQ(BigInt::MakeSigned(SBits(4, 64)).ToSignedBits(), SBits(4, 4)); + EXPECT_EQ(BigInt::MakeSigned(SBits(5, 64)).ToSignedBits(), SBits(5, 4)); + EXPECT_EQ(BigInt::MakeSigned(SBits(6, 64)).ToSignedBits(), SBits(6, 4)); + EXPECT_EQ(BigInt::MakeSigned(SBits(7, 64)).ToSignedBits(), SBits(7, 4)); + EXPECT_EQ(BigInt::MakeSigned(SBits(8, 64)).ToSignedBits(), SBits(8, 5)); + EXPECT_EQ(BigInt::MakeSigned(SBits(127, 8)).ToSignedBits(), SBits(127, 8)); + + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 1)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 2)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 3)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 4)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 5)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 6)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 7)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 8)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-1, 64)).ToSignedBits(), SBits(-1, 1)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-2, 64)).ToSignedBits(), SBits(-2, 2)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-3, 64)).ToSignedBits(), SBits(-3, 3)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-4, 64)).ToSignedBits(), SBits(-4, 3)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-5, 64)).ToSignedBits(), SBits(-5, 4)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-6, 64)).ToSignedBits(), SBits(-6, 4)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-7, 64)).ToSignedBits(), SBits(-7, 4)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-8, 64)).ToSignedBits(), SBits(-8, 4)); + EXPECT_EQ(BigInt::MakeSigned(SBits(-128, 8)).ToSignedBits(), SBits(-128, 8)); + + EXPECT_EQ(BigInt::MakeSigned(UBits(1234567, 64)).ToSignedBits(), + UBits(1234567, 22)); + + std::string wide_input = "0xabcd_abcd_1234_1234_aaaa_bbbb_cccc_dddd"; + XLS_ASSERT_OK_AND_ASSIGN(Bits wide_bits, ParseNumber(wide_input)); + EXPECT_EQ(BigInt::MakeSigned(wide_bits).ToSignedBits().ToString( + FormatPreference::kHex), + wide_input); +} + +TEST_F(BigIntTest, ToSignedBitsWithBitCount) { + XLS_ASSERT_OK_AND_ASSIGN( + auto small_positive, + BigInt::MakeSigned(SBits(42, 64)).ToSignedBitsWithBitCount(32)); + EXPECT_EQ(small_positive, SBits(42, 32)); + + EXPECT_THAT(BigInt::MakeSigned(SBits(3e9, 64)).ToSignedBitsWithBitCount(32), + StatusIs(absl::StatusCode::kInvalidArgument)); + + XLS_ASSERT_OK_AND_ASSIGN( + auto small_negative, + BigInt::MakeSigned(SBits(-42, 64)).ToSignedBitsWithBitCount(32)); + EXPECT_EQ(small_negative, SBits(-42, 32)); + + EXPECT_THAT(BigInt::MakeSigned(SBits(-3e9, 64)).ToSignedBitsWithBitCount(32), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_F(BigIntTest, ToUnsignedBitsWithBitCount) { + XLS_ASSERT_OK_AND_ASSIGN( + auto small_positive, + BigInt::MakeUnsigned(UBits(42, 64)).ToUnsignedBitsWithBitCount(32)); + EXPECT_EQ(small_positive, UBits(42, 32)); + + EXPECT_THAT( + BigInt::MakeUnsigned(UBits(6e9, 64)).ToUnsignedBitsWithBitCount(32), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST_F(BigIntTest, SignedBitCount) { + EXPECT_EQ(MakeBigInt(0).SignedBitCount(), 0); + EXPECT_EQ(MakeBigInt(-1).SignedBitCount(), 1); + EXPECT_EQ(MakeBigInt(-2).SignedBitCount(), 2); + EXPECT_EQ(MakeBigInt(-3).SignedBitCount(), 3); + EXPECT_EQ(MakeBigInt(-4).SignedBitCount(), 3); + EXPECT_EQ(MakeBigInt(1).SignedBitCount(), 2); + EXPECT_EQ(MakeBigInt(127).SignedBitCount(), 8); + EXPECT_EQ(MakeBigInt(128).SignedBitCount(), 9); + EXPECT_EQ(MakeBigInt(-128).SignedBitCount(), 8); + EXPECT_EQ(MakeBigInt(-129).SignedBitCount(), 9); +} + +TEST_F(BigIntTest, UnignedBitCount) { + EXPECT_EQ(MakeBigInt(0).UnsignedBitCount(), 0); + EXPECT_EQ(MakeBigInt(1).UnsignedBitCount(), 1); + EXPECT_EQ(MakeBigInt(127).UnsignedBitCount(), 7); + EXPECT_EQ(MakeBigInt(128).UnsignedBitCount(), 8); + EXPECT_EQ(MakeBigInt(129).UnsignedBitCount(), 8); +} + +TEST_F(BigIntTest, Add) { + EXPECT_EQ(BigInt::Add(MakeBigInt(0), MakeBigInt(0)), MakeBigInt(0)); + EXPECT_EQ(BigInt::Add(MakeBigInt(42), MakeBigInt(123)), MakeBigInt(165)); + EXPECT_EQ( + BigInt::Add(MakeBigInt("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff"), + MakeBigInt("0x1")), + MakeBigInt("0x1_0000_0000_0000_0000_0000_0000_0000_0000_0000")); + EXPECT_EQ( + BigInt::Add( + MakeBigInt("0xabcd_ffff_1234_aaaa_0101_3333_4444_1328_abcd_0042"), + MakeBigInt("0xffff_1234_aaaa_0101_3333_4444_1328_abcd_0042_0033")), + MakeBigInt("0x1_abcd_1233_bcde_abab_3434_7777_576c_bef5_ac0f_0075")); +} + +TEST_F(BigIntTest, Sub) { + EXPECT_EQ(BigInt::Sub(MakeBigInt(42), MakeBigInt(123)), MakeBigInt(-81)); + EXPECT_EQ( + BigInt::Sub( + MakeBigInt("0xabcd_ffff_1234_aaaa_0101_3333_4444_1328_abcd_0042"), + MakeBigInt("0xffff_1234_aaaa_0101_3333_4444_1328_abcd_0042_0033")), + MakeBigInt("-0x5431_1235_9875_5657_3232_1110_cee4_98a4_5474_fff1")); +} + +TEST_F(BigIntTest, Negate) { + EXPECT_EQ(BigInt::Negate(MakeBigInt(0)), MakeBigInt(0)); + EXPECT_EQ(BigInt::Negate(MakeBigInt(123456)), MakeBigInt(-123456)); + EXPECT_EQ(BigInt::Negate( + MakeBigInt("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff")), + MakeBigInt("-0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff")); +} + +TEST_F(BigIntTest, Multiply) { + EXPECT_EQ(BigInt::Mul(MakeBigInt(0), MakeBigInt(0)), MakeBigInt(0)); + EXPECT_EQ(BigInt::Mul(MakeBigInt(123456), MakeBigInt(0)), MakeBigInt(0)); + EXPECT_EQ(BigInt::Mul(MakeBigInt(123456), MakeBigInt(1)), MakeBigInt(123456)); + + EXPECT_EQ(BigInt::Mul(MakeBigInt(1000), MakeBigInt(42)), MakeBigInt(42000)); + EXPECT_EQ(BigInt::Mul(MakeBigInt(-1000), MakeBigInt(42)), MakeBigInt(-42000)); + EXPECT_EQ(BigInt::Mul(MakeBigInt(-1000), MakeBigInt(-42)), MakeBigInt(42000)); + + EXPECT_EQ( + BigInt::Mul(MakeBigInt("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff"), + MakeBigInt("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff")), + MakeBigInt("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_fffe_0000_0000_" + "0000_0000_0000_0000_0000_0000_0001")); +} + +TEST_F(BigIntTest, Divide) { + EXPECT_EQ(BigInt::Div(MakeBigInt(0), MakeBigInt(10)), MakeBigInt(0)); + + // Rounding should match C semantics. + EXPECT_EQ(BigInt::Div(MakeBigInt(10), MakeBigInt(2)), MakeBigInt(10 / 2)); + EXPECT_EQ(BigInt::Div(MakeBigInt(10), MakeBigInt(3)), MakeBigInt(10 / 3)); + EXPECT_EQ(BigInt::Div(MakeBigInt(10), MakeBigInt(4)), MakeBigInt(10 / 4)); + + EXPECT_EQ(BigInt::Div(MakeBigInt(-10), MakeBigInt(2)), MakeBigInt(-10 / 2)); + EXPECT_EQ(BigInt::Div(MakeBigInt(-10), MakeBigInt(3)), MakeBigInt(-10 / 3)); + EXPECT_EQ(BigInt::Div(MakeBigInt(-10), MakeBigInt(4)), MakeBigInt(-10 / 4)); + + EXPECT_EQ(BigInt::Div(MakeBigInt(10), MakeBigInt(-2)), MakeBigInt(10 / -2)); + EXPECT_EQ(BigInt::Div(MakeBigInt(10), MakeBigInt(-3)), MakeBigInt(10 / -3)); + EXPECT_EQ(BigInt::Div(MakeBigInt(10), MakeBigInt(-4)), MakeBigInt(10 / -4)); + + EXPECT_EQ(BigInt::Div(MakeBigInt(-10), MakeBigInt(-2)), MakeBigInt(-10 / -2)); + EXPECT_EQ(BigInt::Div(MakeBigInt(-10), MakeBigInt(-3)), MakeBigInt(-10 / -3)); + EXPECT_EQ(BigInt::Div(MakeBigInt(-10), MakeBigInt(-4)), MakeBigInt(-10 / -4)); + + EXPECT_EQ( + BigInt::Div( + MakeBigInt("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff"), + MakeBigInt("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff")), + MakeBigInt(1)); + + EXPECT_EQ( + BigInt::Div( + MakeBigInt("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff"), + MakeBigInt("0xffff_ffff")), + MakeBigInt("0x1_0000_0001_0000_0001_0000_0001_0000_0001")); +} + +TEST_F(BigIntTest, LessThan) { + EXPECT_TRUE(BigInt::LessThan(MakeBigInt(2), MakeBigInt(10))); + EXPECT_FALSE(BigInt::LessThan(MakeBigInt(10), MakeBigInt(10))); + EXPECT_FALSE(BigInt::LessThan(MakeBigInt(12), MakeBigInt(10))); +} +} // namespace +} // namespace xls diff --git a/xls/ir/bit_push_buffer.h b/xls/ir/bit_push_buffer.h new file mode 100644 index 0000000000..4f2a8421e8 --- /dev/null +++ b/xls/ir/bit_push_buffer.h @@ -0,0 +1,63 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_BIT_PUSH_BUFFER_H_ +#define THIRD_PARTY_XLS_IR_BIT_PUSH_BUFFER_H_ + +#include + +#include "xls/common/integral_types.h" +#include "xls/common/math_util.h" + +namespace xls { + +// Mutable structure for pushing bits into; e.g. in the serializing/flattening +// process where we're about to push values over the wire. +class BitPushBuffer { + public: + // Pushes a bit into the buffer -- see GetUint8Data() comment below on the + // ordering with which these pushed bits are returned in the byte sequence. + void PushBit(bool bit) { + bitmap_.push_back(bit); + } + + // Retrieves the pushed bits as a sequence of bytes. + // + // The first-pushed bit goes into the MSb of the 0th byte. Concordantly, the + // final byte, if it is partial, will have padding zeroes in the least + // significant bits. + std::vector GetUint8Data() const { + // Implementation note: bitmap does not expose its underlying storage. + std::vector result; + result.resize(CeilOfRatio(bitmap_.size(), 8UL), 0); + for (int64 i = 0; i < static_cast(bitmap_.size()); ++i) { + result[i / 8] |= bitmap_[i] << (7 - i % 8); + } + return result; + } + + bool empty() const { return bitmap_.empty(); } + + // Returns the number of bytes required to store the currently-pushed bits. + int64 size_in_bytes() const { + return CeilOfRatio(bitmap_.size(), 8UL); + } + + private: + std::vector bitmap_; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_BIT_PUSH_BUFFER_H_ diff --git a/xls/ir/bit_push_buffer_test.cc b/xls/ir/bit_push_buffer_test.cc new file mode 100644 index 0000000000..20d1ac78be --- /dev/null +++ b/xls/ir/bit_push_buffer_test.cc @@ -0,0 +1,89 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/bit_push_buffer.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace xls { +namespace { + +using ::testing::IsEmpty; + +TEST(BitPushBufferTest, IsEmptyAfterConstruction) { + BitPushBuffer buffer; + + EXPECT_TRUE(buffer.empty()); + EXPECT_EQ(buffer.size_in_bytes(), 0); + EXPECT_THAT(buffer.GetUint8Data(), IsEmpty()); +} + +TEST(BitPushBufferTest, HasSingle0AfterPushingFalse) { + BitPushBuffer buffer; + + buffer.PushBit(false); + + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(buffer.size_in_bytes(), 1); + EXPECT_EQ(buffer.GetUint8Data(), std::vector{0}); +} + +TEST(BitPushBufferTest, HasSingle1InMsbAfterPushingTrue) { + BitPushBuffer buffer; + + buffer.PushBit(true); + + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(buffer.size_in_bytes(), 1); + EXPECT_EQ(buffer.GetUint8Data(), std::vector{1 << 7}); +} + +TEST(BitPushBufferTest, Has1InSecondMsbAfterPushingFalseTrue) { + BitPushBuffer buffer; + + buffer.PushBit(false); + buffer.PushBit(true); + + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(buffer.size_in_bytes(), 1); + EXPECT_EQ(buffer.GetUint8Data(), std::vector{1 << 6}); +} + +TEST(BitPushBufferTest, IsOneByteAfterPushing8Values) { + BitPushBuffer buffer; + + for (int i = 0; i < 8; i++) { + buffer.PushBit(false); + } + + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(buffer.size_in_bytes(), 1); +} + +TEST(BitPushBufferTest, Has1SecondBytesMsbAfterPushing8False1True) { + BitPushBuffer buffer; + + for (int i = 0; i < 8; i++) { + buffer.PushBit(false); + } + buffer.PushBit(true); + + EXPECT_FALSE(buffer.empty()); + EXPECT_EQ(buffer.size_in_bytes(), 2); + EXPECT_EQ(buffer.GetUint8Data(), std::vector({0, 1 << 7})); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/bits.cc b/xls/ir/bits.cc new file mode 100644 index 0000000000..22f776d6f2 --- /dev/null +++ b/xls/ir/bits.cc @@ -0,0 +1,312 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/bits.h" + +#include "absl/base/casts.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" + +namespace xls { + +/* static */ Bits Bits::FromBytes(absl::Span bytes, + int64 bit_count) { + XLS_CHECK_GE(bit_count, 0); + int64 byte_count = bytes.size(); + XLS_CHECK_LE(bit_count, byte_count * 8); + InlineBitmap bitmap(bit_count); + for (int64 i = 0; i < byte_count; ++i) { + bitmap.SetByte(byte_count - i - 1, bytes[i]); + } + return Bits(std::move(bitmap)); +} + +/* static */ int64 Bits::MinBitCountSigned(int64 value) { + if (value == 0) { + return 0; + } + if (value < 0) { + // The expression -(value + 1) cannot overflow when value is negative. + return MinBitCountUnsigned(-(value + 1)) + 1; + } + return MinBitCountUnsigned(value) + 1; +} + +xabsl::StatusOr UBitsWithStatus(uint64 value, int64 bit_count) { + if (Bits::MinBitCountUnsigned(value) > bit_count) { + return absl::InvalidArgumentError( + absl::StrFormat("Value %#x requires %d bits to fit in an unsigned " + "datatype, but attempting to fit in %d bits.", + value, Bits::MinBitCountUnsigned(value), bit_count)); + } + return Bits(InlineBitmap::FromWord(value, bit_count, /*fill=*/false)); +} + +xabsl::StatusOr SBitsWithStatus(int64 value, int64 bit_count) { + if (Bits::MinBitCountSigned(value) > bit_count) { + return absl::InvalidArgumentError( + absl::StrFormat("Value %#x requires %d bits to fit in an signed " + "datatype, but attempting to fit in %d bits.", + value, Bits::MinBitCountSigned(value), bit_count)); + } + bool fill = (value >> 63); + return Bits(InlineBitmap::FromWord(value, bit_count, fill)); +} + +Bits::Bits(absl::Span bits) : bitmap_(bits.size()) { + for (int64 i = 0; i < bits.size(); ++i) { + bitmap_.Set(i, bits[i]); + } +} + +/* static */ +Bits Bits::AllOnes(int64 bit_count) { + return bit_count == 0 ? Bits() : SBits(-1, bit_count); +} + +/* static */ +Bits Bits::MaxSigned(int64 bit_count) { + return Bits::AllOnes(bit_count).UpdateWithSet(bit_count - 1, false); +} + +/* static */ +Bits Bits::MinSigned(int64 bit_count) { + return Bits::PowerOfTwo(bit_count - 1, bit_count); +} + +absl::InlinedVector Bits::ToBitVector() const { + absl::InlinedVector bits(bit_count()); + for (int64 i = 0; i < bit_count(); ++i) { + bits[i] = bitmap_.Get(i); + } + return bits; +} + +/* static */ +Bits Bits::PowerOfTwo(int64 set_bit_index, int64 bit_count) { + Bits result(bit_count); + result.bitmap_.Set(set_bit_index, true); + return result; +} + +bool Bits::IsOne() const { return PopCount() == 1 && Get(0); } + +int64 Bits::PopCount() const { + int64 count = 0; + for (int64 i = 0; i < bit_count(); ++i) { + if (Get(i)) { + ++count; + } + } + return count; +} + +int64 Bits::CountLeadingZeros() const { + for (int64 i = 0; i < bit_count(); ++i) { + if (Get(bit_count() - 1 - i)) { + return i; + } + } + return bit_count(); +} + +int64 Bits::CountLeadingOnes() const { + for (int64 i = 0; i < bit_count(); ++i) { + if (!Get(bit_count() - 1 - i)) { + return i; + } + } + return bit_count(); +} + +int64 Bits::CountTrailingZeros() const { + for (int64 i = 0; i < bit_count(); ++i) { + if (Get(i)) { + return i; + } + } + return bit_count(); +} + +int64 Bits::CountTrailingOnes() const { + for (int64 i = 0; i < bit_count(); ++i) { + if (!Get(i)) { + return i; + } + } + return bit_count(); +} + +bool Bits::HasSingleRunOfSetBits(int64* leading_zero_count, + int64* set_bit_count, + int64* trailing_zero_count) const { + int64 leading_zeros = CountLeadingZeros(); + XLS_CHECK_GE(leading_zeros, 0); + int64 trailing_zeros = CountTrailingZeros(); + XLS_CHECK_GE(trailing_zeros, 0); + if (bit_count() == trailing_zeros) { + XLS_CHECK_EQ(leading_zeros, bit_count()); + return false; + } + for (int64 i = trailing_zeros; i < bit_count() - leading_zeros; ++i) { + if (Get(i) != 1) { + return false; + } + } + *leading_zero_count = leading_zeros; + *trailing_zero_count = trailing_zeros; + *set_bit_count = bit_count() - leading_zeros - trailing_zeros; + XLS_CHECK_GE(*set_bit_count, 0); + return true; +} + +bool Bits::FitsInUint64() const { return FitsInNBitsUnsigned(64); } + +bool Bits::FitsInInt64() const { return FitsInNBitsSigned(64); } + +bool Bits::FitsInNBitsUnsigned(int64 n) const { + // All bits at and above bit 'n' must be zero. + for (int64 i = n; i < bit_count(); ++i) { + if (Get(i)) { + return false; + } + } + return true; +} + +bool Bits::FitsInNBitsSigned(int64 n) const { + if (n == 0) { + return IsAllZeros(); + } + + // All bits at and above bit N-1 must be the same. + for (int64 i = n - 1; i < bit_count(); ++i) { + if (Get(i) != msb()) { + return false; + } + } + return true; +} + +xabsl::StatusOr Bits::ToUint64() const { + if (bit_count() == 0) { + // By convention, an empty Bits has a numeric value of zero. + return 0; + } + if (!FitsInUint64()) { + return absl::InvalidArgumentError(absl::StrCat( + "Bits value cannot be represented as an unsigned 64-bit value: ", + ToString())); + } + return bitmap_.GetWord(0); +} + +xabsl::StatusOr Bits::ToInt64() const { + if (!FitsInInt64()) { + return absl::InvalidArgumentError(absl::StrCat( + "Bits value cannot be represented as a signed 64-bit value: ", + ToString())); + } + + uint64 word = bitmap_.GetWord(0); + if (msb() && bit_count() < 64) { + word |= -1ULL << bit_count(); + } + + return absl::bit_cast(word); +} + +Bits Bits::Slice(int64 start, int64 width) const { + XLS_CHECK_GE(width, 0); + XLS_CHECK_LE(start + width, bit_count()) + << "start: " << start << " width: " << width; + Bits result(width); + for (int64 i = 0; i < width; ++i) { + if (Get(start + i)) { + result.bitmap_.Set(i, true); + } + } + return result; +} + +std::string Bits::ToString(FormatPreference preference, + bool include_bit_count) const { + if (preference == FormatPreference::kDefault) { + if (bit_count() <= 64) { + preference = FormatPreference::kDecimal; + } else { + preference = FormatPreference::kHex; + } + } + std::string result; + if (preference == FormatPreference::kBinary) { + result = "0b"; + } else if (preference == FormatPreference::kHex) { + result = "0x"; + } + absl::StrAppend(&result, ToRawDigits(preference)); + if (include_bit_count) { + absl::StrAppendFormat(&result, " [%d bits]", bit_count()); + } + return result; +} + +std::string Bits::ToRawDigits(FormatPreference preference, + bool emit_leading_zeros) const { + XLS_CHECK_NE(preference, FormatPreference::kDefault); + if (preference == FormatPreference::kDecimal) { + // Leading zeros don't make a lot of sense in decimal format as there is no + // clean correspondence between decimal digits and binary digits. + XLS_CHECK(!emit_leading_zeros) + << "emit_leading_zeros not supported for decimal format."; + // TODO(meheff): 2019/4/3 Add support for arbitrary width decimal emission. + XLS_CHECK(FitsInUint64()) + << "Decimal output not supported for values which do " + "not fit in a uint64"; + return absl::StrCat(ToUint64().value()); + } + if (bit_count() == 0) { + return "0"; + } + + XLS_CHECK((preference == FormatPreference::kBinary) || + (preference == FormatPreference::kHex)); + const int64 kSeparatorPeriod = 4; + const int64 digit_width = preference == FormatPreference::kBinary ? 1 : 4; + const int64 digit_count = CeilOfRatio(bit_count(), digit_width); + std::string result; + bool eliding_leading_zeros = !emit_leading_zeros; + for (int64 digit_no = digit_count - 1; digit_no >= 0; --digit_no) { + // Add a '_' every kSeparatorPeriod digits. + if (((digit_no + 1) % kSeparatorPeriod == 0) && !result.empty()) { + absl::StrAppend(&result, "_"); + } + // Slice out a Bits which contains 1 digit. + int64 start = digit_no * digit_width; + int64 width = std::min(digit_width, bit_count() - start); + // As single digit necessarily fits in a uint64, so the value() is safe. + uint64 digit_value = Slice(start, width).ToUint64().value(); + if (digit_value == 0 && eliding_leading_zeros && digit_no != 0) { + continue; + } + eliding_leading_zeros = false; + absl::StrAppend(&result, absl::StrFormat("%x", digit_value)); + } + return result; +} + +} // namespace xls diff --git a/xls/ir/bits.h b/xls/ir/bits.h new file mode 100644 index 0000000000..839e4a6665 --- /dev/null +++ b/xls/ir/bits.h @@ -0,0 +1,309 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_BITS_H_ +#define THIRD_PARTY_XLS_IR_BITS_H_ + +#include +#include +#include +#include + +#include "absl/base/casts.h" +#include "absl/strings/str_format.h" +#include "xls/common/bits_util.h" +#include "xls/common/integral_types.h" +#include "xls/common/logging/logging.h" +#include "xls/common/math_util.h" +#include "xls/common/status/statusor.h" +#include "xls/data_structures/inline_bitmap.h" +#include "xls/ir/bit_push_buffer.h" +#include "xls/ir/format_preference.h" + +namespace xls { +namespace internal { + +// Helper for use in initializer list; checks that lhs >= rhs and returns lhs as +// a result. +template +T CheckGe(T lhs, T rhs) { + XLS_CHECK_GE(lhs, rhs); + return lhs; +} + +} // namespace internal + +// Helper class that is used to represent a literal value that is a vector of +// bits with a given width (bit_count). +class Bits { + public: + Bits() : Bits(0) {} + + // Creates a zero-initialized bits object with the given bit width. + explicit Bits(int64 bit_count) + : bitmap_(internal::CheckGe(bit_count, int64{0})) {} + + // Creates a bits object with the given bits values. The n-th element of the + // argument becomes the n-th bit of the Bits object. A std::vector + // cannot be passed in because of std::vector specialization. Use + // absl::InlinedVector instead. + explicit Bits(absl::Span bits); + + // Returns a Bits object with all ones. If 'bit_count' is zero, an empty Bits + // object is returned as, vacuously, an empty bits object has all of its bits + // set to one. + static Bits AllOnes(int64 bit_count); + + // These functions create Bits objects with the maximum or minimum signed + // value with the specified width. + static Bits MaxSigned(int64 bit_count); + static Bits MinSigned(int64 bit_count); + + // Returns a Bits object with exactly one bit set. + static Bits PowerOfTwo(int64 set_bit_index, int64 bit_count); + + // Constructs a Bits object from a vector of bytes. Bytes are in big endian + // order where the byte zero is the most significant byte. The size of 'bytes' + // must be at least than bit_count / 8. Any bits beyond 'bit_count' in 'bytes' + // are ignored. + static Bits FromBytes(absl::Span bytes, int64 bit_count); + + // Note: we flatten into the pushbuffer with the MSb pushed first. + void FlattenTo(BitPushBuffer* buffer) const { + for (int64 i = 0; i < bit_count(); ++i) { + buffer->PushBit(Get(bit_count() - i - 1)); + } + } + + // Return the Bits object as a vector of bytes. Bytes are in big endian order + // where the byte zero of the returned vector is the most significant + // byte. The returned vector has size ceil(bit_count()/8). Any bits in the + // returned vector beyond the bit_count()-th are set to zero. + std::vector ToBytes() const { + std::vector v(bitmap_.byte_count()); + ToBytes(absl::MakeSpan(v)); + return v; + } + + // Implements ToBytes() as above by inserting values into a user-provided raw + // byte buffer. + // + // "big_endian" determines whether the least significant byte from this bits + // object ends up in the 0th byte (on false), or in the N-1th byte (on true). + // This can be useful to control when calling machine code. + void ToBytes(absl::Span bytes, bool big_endian = true) const { + int64 byte_count = bitmap_.byte_count(); + XLS_DCHECK(bytes.size() >= byte_count); + // Use raw access to avoid evaluating ABSL_ASSERT on every reference. + uint8* buffer = bytes.data(); + for (int64 i = 0; i < byte_count; ++i) { + int64 index = big_endian ? byte_count - i - 1 : i; + buffer[index] = bitmap_.GetByte(i); + } + // Mask off final byte. + if (bit_count() % 8 != 0) { + buffer[big_endian ? 0 : byte_count - 1] &= Mask(bit_count() % 8); + } + } + + // Return the Bits object as a vector of bits. The n-th element of the + // returned vector is the n-th bit of the Bits object. Returns + // absl::InlinedVector to avoid std::vector specialization. + absl::InlinedVector ToBitVector() const; + + int64 bit_count() const { return bitmap_.bit_count(); } + + // Returns a string representation of the Bits object. A kDefault format + // preference emits a decimal string if the bit count is less than or equal to + // 64, or a hexadecimal string otherwise. If include_bit_count is true, then + // also emit the bit width as a suffix; example: "0xabcd [16 bits]". + std::string ToString(FormatPreference preference = FormatPreference::kDefault, + bool include_bit_count = false) const; + + // Emits the bits value as an string of digits. Supported FormatPreferences + // are: kDecimal, kHex, and kBinary. The string is not prefixed (e.g., no + // leading "0x"). Hexadecimal and binary numbers have '_' separators inserted + // every 4th digit. For example, the decimal number 1000 in binary: + // 111_1110_1000. If emit_leading_zeros is true, binary and hexadecimal + // formatted number will include leading zero up to the width of the + // underlying Bits object. For example, if emit_leading_zeros is true: + // Bits(42, 11) will result in '02a' and '000_0010_1010' for binary and + // hexadecimal format respectively. + std::string ToRawDigits(FormatPreference preference, + bool emit_leading_zeros = false) const; + + // Return the most-significant bit. + bool msb() const { return bit_count() == 0 ? false : Get(bit_count() - 1); } + + // Get/Set individual bits in the underlying Bitmap. + // LSb is bit 0, MSb is at location bit_count() - 1. + bool Get(int64 index) const { return bitmap_.Get(index); } + + // As above, but retrieves with index "0" starting at the MSb side of the bit + // vector. + bool GetFromMsb(int64 index) const { + XLS_DCHECK_LT(index, bit_count()); + return bitmap_.Get(bit_count() - index - 1); + } + + ABSL_MUST_USE_RESULT + Bits UpdateWithSet(int64 index, bool value) const { + Bits clone = *this; + clone.bitmap_.Set(index, value); + return clone; + } + + // Returns whether the bits are all zeros/ones. + bool IsAllOnes() const { return bitmap_.IsAllOnes(); } + bool IsAllZeros() const { return bitmap_.IsAllZeroes(); } + + // Returns true if the bits interpreted as an unsigned number is equal to one. + bool IsOne() const; + + // Returns true if the Bits value as an unsigned number is a power of two. + bool IsPowerOfTwo() const { return PopCount() == 1; } + + // Returns the number of ones set in the Bits value. + int64 PopCount() const; + + // Counts the number of contiguous zero (ones) bits present from the MSb + // (towards the LSb). Result is `0 <= x <= bits.bit_count()`. + int64 CountLeadingZeros() const; + int64 CountLeadingOnes() const; + + // Counts the number of contiguous zero (ones) bits present from the LSb + // (towards the MSb). Result is `0 <= x <= bits.bit_count()`. + int64 CountTrailingZeros() const; + int64 CountTrailingOnes() const; + + // Checks whether the bits value has a single contiguous run of set bits; i.e. + // matches: + // + // 0b 0* 1+ 0* + // + // And returns the leading zero count in leading_zero_count, the number of set + // bits in set_bit_count, and the number of trailing zeros in + // trailing_zero_count if true is returned. + bool HasSingleRunOfSetBits(int64* leading_zero_count, int64* set_bit_count, + int64* trailing_zero_count) const; + + // Returns true if the (unsigned/signed) value held by the Bits object fits in + // 'n' bits. + bool FitsInNBitsUnsigned(int64 n) const; + bool FitsInNBitsSigned(int64 n) const; + + // Returns true if the (unsigned/signed) value held by the Bits object fits in + // uint64/int64. + bool FitsInUint64() const; + bool FitsInInt64() const; + + // Converts the value held by this "bits" object into a uint64 (int64). + // ToUint64 interprets the bits as unsigned. ToInt64 interprets the bits in + // twos-complement representation. Returns an error if the *value* cannot be + // represented in 64 bits (the width of the Bits object can be arbitrarily + // large). + xabsl::StatusOr ToUint64() const; + xabsl::StatusOr ToInt64() const; + + // Returns whether this "bits" object is identical to the other in both + // bit_count() and held value. + bool operator==(const Bits& other) const { return bitmap_ == other.bitmap_; } + bool operator!=(const Bits& other) const { return !(*this == other); } + + // Slices a range of bits from the Bits object. 'start' is the first index in + // the slice. 'start' is zero-indexed with zero being the LSb (same indexing + // as Get/Set). 'width' is the number of bits to slice out and is the + // bit_count of the result. + Bits Slice(int64 start, int64 width) const; + + // Returns the minimum number of bits required to store the given value as an + // unsigned number. + static int64 MinBitCountUnsigned(uint64 value) { + return value == 0 ? 0 : FloorOfLog2(value) + 1; + } + + // Returns the minimum number of bits required to store the given value as + // twos complement signed number. + static int64 MinBitCountSigned(int64 value); + + private: + friend class BitsRope; + friend xabsl::StatusOr UBitsWithStatus(uint64, int64); + friend xabsl::StatusOr SBitsWithStatus(int64, int64); + + explicit Bits(InlineBitmap&& bitmap) : bitmap_(bitmap) {} + + InlineBitmap bitmap_; +}; + +// Helper for "stringing together" bits objects into a final result, avoiding +// intermediate allocations. +class BitsRope { + public: + explicit BitsRope(int64 total_bit_count) : bitmap_(total_bit_count) {} + + // Pushes the bits object into the bit string being built. + // + // Note that bit 0 of the first bits object pushed becomes the LSb of the + // final result; so something like: + // + // rope.push_back(a_1 a_0) + // rope.push_back(b_2 b_1 b_0) + // + // Creates a resulting rope: + // + // b_2 b_1 b_0 a_1 a_0 + // + // So b.Get(0) is now at result.Get(2). + void push_back(const Bits& bits) { + for (int64 i = 0; i < bits.bit_count(); ++i) { + bitmap_.Set(index_ + i, bits.Get(i)); + } + index_ += bits.bit_count(); + } + + void push_back(bool bit) { bitmap_.Set(index_++, bit); } + + Bits Build() { + XLS_CHECK_EQ(index_, bitmap_.bit_count()); + return Bits{std::move(bitmap_)}; + } + + private: + InlineBitmap bitmap_; + int64 index_ = 0; +}; + +// Creates an Bits object which holds the given unsigned/signed value. Width +// must be large enough to hold the value. The bits object itelf has no +// signedness, but the UBits and SBits factories affect how the minimum bit +// width is computed for checking against 'width' and whether the value is +// zero or sign extended. +xabsl::StatusOr UBitsWithStatus(uint64 value, int64 bit_count); +xabsl::StatusOr SBitsWithStatus(int64 value, int64 bit_count); +inline Bits UBits(uint64 value, int64 bit_count) { + return UBitsWithStatus(value, bit_count).value(); +} +inline Bits SBits(int64 value, int64 bit_count) { + return SBitsWithStatus(value, bit_count).value(); +} + +inline std::ostream& operator<<(std::ostream& os, const Bits& bits) { + os << bits.ToString(FormatPreference::kDefault, /*include_bit_count=*/true); + return os; +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_BITS_H_ diff --git a/xls/ir/bits_ops.cc b/xls/ir/bits_ops.cc new file mode 100644 index 0000000000..673ff1c392 --- /dev/null +++ b/xls/ir/bits_ops.cc @@ -0,0 +1,506 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/bits_ops.h" + +#include + +#include "xls/common/logging/logging.h" +#include "xls/ir/big_int.h" + +namespace xls { +namespace bits_ops { +namespace { + +// Converts the given bits value to signed value of the given bit count. Uses +// truncation or sign-extension to narrow/widen the value. +Bits TruncateOrSignExtend(const Bits& bits, int64 bit_count) { + if (bits.bit_count() == bit_count) { + return bits; + } else if (bits.bit_count() < bit_count) { + return SignExtend(bits, bit_count); + } else { + return bits.Slice(0, bit_count); + } +} + +} // namespace + +Bits And(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (lhs.bit_count() <= 64) { + return UBits(lhs.ToUint64().value() & rhs.ToUint64().value(), + lhs.bit_count()); + } + std::vector bytes = lhs.ToBytes(); + std::vector rhs_bytes = rhs.ToBytes(); + for (int64 i = 0; i < bytes.size(); ++i) { + bytes[i] = bytes[i] & rhs_bytes[i]; + } + return Bits::FromBytes(bytes, lhs.bit_count()); +} + +Bits NaryAnd(absl::Span operands) { + Bits accum = operands.at(0); + for (int64 i = 1; i < operands.size(); ++i) { + accum = And(accum, operands[i]); + } + return accum; +} + +Bits Or(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (lhs.bit_count() <= 64) { + uint64 lhs_int = lhs.ToUint64().value(); + uint64 rhs_int = rhs.ToUint64().value(); + uint64 result = (lhs_int | rhs_int); + return UBits(result, lhs.bit_count()); + } + std::vector bytes = lhs.ToBytes(); + std::vector rhs_bytes = rhs.ToBytes(); + for (int64 i = 0; i < bytes.size(); ++i) { + bytes[i] = bytes[i] | rhs_bytes[i]; + } + return Bits::FromBytes(bytes, lhs.bit_count()); +} + +Bits NaryOr(absl::Span operands) { + Bits accum = operands.at(0); + for (int64 i = 1; i < operands.size(); ++i) { + accum = Or(accum, operands[i]); + } + return accum; +} + +Bits Xor(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (lhs.bit_count() <= 64) { + uint64 lhs_int = lhs.ToUint64().value(); + uint64 rhs_int = rhs.ToUint64().value(); + uint64 result = (lhs_int ^ rhs_int); + return UBits(result, lhs.bit_count()); + } + std::vector bytes = lhs.ToBytes(); + std::vector rhs_bytes = rhs.ToBytes(); + for (int64 i = 0; i < bytes.size(); ++i) { + bytes[i] = bytes[i] ^ rhs_bytes[i]; + } + return Bits::FromBytes(bytes, lhs.bit_count()); +} + +Bits NaryXor(absl::Span operands) { + Bits accum = operands.at(0); + for (int64 i = 1; i < operands.size(); ++i) { + accum = Xor(accum, operands[i]); + } + return accum; +} + +Bits Nand(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (lhs.bit_count() <= 64) { + return UBits(~(lhs.ToUint64().value() & rhs.ToUint64().value()) & + Mask(lhs.bit_count()), + lhs.bit_count()); + } + std::vector bytes = lhs.ToBytes(); + std::vector rhs_bytes = rhs.ToBytes(); + for (int64 i = 0; i < bytes.size(); ++i) { + bytes[i] = ~(bytes[i] & rhs_bytes[i]); + } + return Bits::FromBytes(bytes, lhs.bit_count()); +} + +Bits NaryNand(absl::Span operands) { + Bits accum = operands.at(0); + for (int64 i = 1; i < operands.size(); ++i) { + accum = And(accum, operands[i]); + } + return Not(accum); +} + +Bits Nor(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (lhs.bit_count() <= 64) { + return UBits(~(lhs.ToUint64().value() | rhs.ToUint64().value()) & + Mask(lhs.bit_count()), + lhs.bit_count()); + } + std::vector bytes = lhs.ToBytes(); + std::vector rhs_bytes = rhs.ToBytes(); + for (int64 i = 0; i < bytes.size(); ++i) { + bytes[i] = ~(bytes[i] | rhs_bytes[i]); + } + return Bits::FromBytes(bytes, lhs.bit_count()); +} + +Bits NaryNor(absl::Span operands) { + Bits accum = operands.at(0); + for (int64 i = 1; i < operands.size(); ++i) { + accum = Or(accum, operands[i]); + } + return Not(accum); +} + +Bits Not(const Bits& bits) { + if (bits.bit_count() <= 64) { + return UBits((~bits.ToUint64().value()) & Mask(bits.bit_count()), + bits.bit_count()); + } + std::vector bytes = bits.ToBytes(); + for (uint8& byte : bytes) { + byte = ~byte; + } + return Bits::FromBytes(bytes, bits.bit_count()); +} + +Bits AndReduce(const Bits& operand) { + // Is every bit set? + return operand.IsAllOnes() ? UBits(1, 1) : UBits(0, 1); +} + +Bits OrReduce(const Bits& operand) { + // Is any bit set? + return operand.IsAllZeros() ? UBits(0, 1) : UBits(1, 1); +} + +Bits XorReduce(const Bits& operand) { + // Are there an odd number of bits set? + return operand.PopCount() & 1 ? UBits(1, 1) : UBits(0, 1); +} + +Bits Add(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (lhs.bit_count() <= 64) { + uint64 lhs_int = lhs.ToUint64().value(); + uint64 rhs_int = rhs.ToUint64().value(); + uint64 result = (lhs_int + rhs_int) & Mask(lhs.bit_count()); + return UBits(result, lhs.bit_count()); + } + + Bits sum = BigInt::Add(BigInt::MakeSigned(lhs), BigInt::MakeSigned(rhs)) + .ToSignedBits(); + return TruncateOrSignExtend(sum, lhs.bit_count()); +} + +Bits Sub(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (lhs.bit_count() <= 64) { + uint64 lhs_int = lhs.ToUint64().value(); + uint64 rhs_int = rhs.ToUint64().value(); + uint64 result = (lhs_int - rhs_int) & Mask(lhs.bit_count()); + return UBits(result, lhs.bit_count()); + } + Bits diff = BigInt::Sub(BigInt::MakeSigned(lhs), BigInt::MakeSigned(rhs)) + .ToSignedBits(); + return TruncateOrSignExtend(diff, lhs.bit_count()); +} + +Bits Mul(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (lhs.bit_count() <= 64) { + uint64 lhs_int = lhs.ToUint64().value(); + uint64 rhs_int = rhs.ToUint64().value(); + uint64 result = (lhs_int * rhs_int) & Mask(lhs.bit_count()); + return UBits(result, lhs.bit_count()); + } + + BigInt product = + BigInt::Mul(BigInt::MakeSigned(SignExtend(lhs, lhs.bit_count())), + BigInt::MakeSigned(SignExtend(rhs, lhs.bit_count()))); + return product.ToSignedBitsWithBitCount(lhs.bit_count() * 2) + .value() + .Slice(0, lhs.bit_count()); +} + +Bits SMul(const Bits& lhs, const Bits& rhs) { + const int64 result_width = lhs.bit_count() + rhs.bit_count(); + if (result_width <= 64) { + int64 lhs_int = lhs.ToInt64().value(); + int64 rhs_int = rhs.ToInt64().value(); + int64 result = lhs_int * rhs_int; + return SBits(result, result_width); + } + + BigInt product = + BigInt::Mul(BigInt::MakeSigned(lhs), BigInt::MakeSigned(rhs)); + return product.ToSignedBitsWithBitCount(result_width).value(); +} + +Bits UMul(const Bits& lhs, const Bits& rhs) { + const int64 result_width = lhs.bit_count() + rhs.bit_count(); + if (result_width <= 64) { + uint64 lhs_int = lhs.ToUint64().value(); + uint64 rhs_int = rhs.ToUint64().value(); + uint64 result = lhs_int * rhs_int; + return UBits(result, result_width); + } + + BigInt product = + BigInt::Mul(BigInt::MakeUnsigned(lhs), BigInt::MakeUnsigned(rhs)); + return product.ToUnsignedBitsWithBitCount(result_width).value(); +} + +Bits UDiv(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (rhs.IsAllZeros()) { + return Bits::AllOnes(lhs.bit_count()); + } + BigInt quotient = + BigInt::Div(BigInt::MakeUnsigned(lhs), BigInt::MakeUnsigned(rhs)); + return ZeroExtend(quotient.ToUnsignedBits(), lhs.bit_count()); +} + +Bits SDiv(const Bits& lhs, const Bits& rhs) { + XLS_CHECK_EQ(lhs.bit_count(), rhs.bit_count()); + if (rhs.IsAllZeros()) { + if (lhs.bit_count() == 0) { + return UBits(0, 0); + } + if (SLessThan(lhs, UBits(0, lhs.bit_count()))) { + // Divide by zero and lhs is negative. Return largest magnitude negative + // number: 0b1000...000. + return Concat({UBits(1, 1), UBits(0, lhs.bit_count() - 1)}); + } else { + // Divide by zero and lhs is non-negative. Return largest positive number: + // 0b0111...111. + return ZeroExtend(Bits::AllOnes(lhs.bit_count() - 1), lhs.bit_count()); + } + } + BigInt quotient = + BigInt::Div(BigInt::MakeSigned(lhs), BigInt::MakeSigned(rhs)); + return TruncateOrSignExtend(quotient.ToSignedBits(), lhs.bit_count()); +} + +bool UGreaterThanOrEqual(const Bits& lhs, const Bits& rhs) { + return !ULessThan(lhs, rhs); +} + +bool UGreaterThan(const Bits& lhs, const Bits& rhs) { + return !ULessThanOrEqual(lhs, rhs); +} + +bool ULessThanOrEqual(const Bits& lhs, const Bits& rhs) { + return (lhs == rhs) || ULessThan(lhs, rhs); +} + +bool ULessThan(const Bits& lhs, const Bits& rhs) { + return BigInt::LessThan(BigInt::MakeUnsigned(lhs), BigInt::MakeUnsigned(rhs)); +} + +bool UGreaterThanOrEqual(const Bits& lhs, int64 rhs) { + XLS_CHECK_GE(rhs, 0); + if (lhs.bit_count() < Bits::MinBitCountUnsigned(rhs)) { + // The number of bits in lhs can't hold the rhs. Trying to create bits + // object with the rhs value and lhs bit count below will CHECK fail. + return false; + } + return UGreaterThanOrEqual(lhs, UBits(rhs, lhs.bit_count())); +} + +bool UGreaterThan(const Bits& lhs, int64 rhs) { + XLS_CHECK_GE(rhs, 0); + if (lhs.bit_count() < Bits::MinBitCountUnsigned(rhs)) { + // The number of bits in lhs can't hold the rhs. Trying to create bits + // object with the rhs value and lhs bit count below will CHECK fail. + return false; + } + return UGreaterThan(lhs, UBits(rhs, lhs.bit_count())); +} + +bool ULessThanOrEqual(const Bits& lhs, int64 rhs) { + XLS_CHECK_GE(rhs, 0); + if (lhs.bit_count() < Bits::MinBitCountUnsigned(rhs)) { + // The number of bits in lhs can't hold the rhs. Trying to create bits + // object with the rhs value and lhs bit count below will CHECK fail. + return true; + } + return ULessThanOrEqual(lhs, UBits(rhs, lhs.bit_count())); +} + +bool ULessThan(const Bits& lhs, int64 rhs) { + XLS_CHECK_GE(rhs, 0); + if (lhs.bit_count() < Bits::MinBitCountUnsigned(rhs)) { + // The number of bits in lhs can't hold the rhs. Trying to create bits + // object with the rhs value and lhs bit count below will CHECK fail. + return true; + } + return ULessThan(lhs, UBits(rhs, lhs.bit_count())); +} + +bool SGreaterThanOrEqual(const Bits& lhs, const Bits& rhs) { + return !SLessThan(lhs, rhs); +} + +bool SGreaterThan(const Bits& lhs, const Bits& rhs) { + return !SLessThanOrEqual(lhs, rhs); +} + +bool SLessThanOrEqual(const Bits& lhs, const Bits& rhs) { + return (lhs == rhs) || SLessThan(lhs, rhs); +} + +bool SLessThan(const Bits& lhs, const Bits& rhs) { + if (lhs.bit_count() <= 64) { + XLS_CHECK_EQ(rhs.bit_count(), lhs.bit_count()); + return lhs.ToInt64().value() < rhs.ToInt64().value(); + } + return BigInt::LessThan(BigInt::MakeSigned(lhs), BigInt::MakeSigned(rhs)); +} + +bool SGreaterThanOrEqual(const Bits& lhs, int64 rhs) { + return SGreaterThanOrEqual(lhs, UBits(rhs, lhs.bit_count())); +} + +bool SGreaterThan(const Bits& lhs, int64 rhs) { + return SGreaterThan(lhs, UBits(rhs, lhs.bit_count())); +} + +bool SLessThanOrEqual(const Bits& lhs, int64 rhs) { + return SLessThanOrEqual(lhs, UBits(rhs, lhs.bit_count())); +} + +bool SLessThan(const Bits& lhs, int64 rhs) { + return SLessThan(lhs, UBits(rhs, lhs.bit_count())); +} + +Bits ZeroExtend(const Bits& bits, int64 new_bit_count) { + XLS_CHECK_GE(new_bit_count, 0); + XLS_CHECK_GE(new_bit_count, bits.bit_count()); + return Concat({UBits(0, new_bit_count - bits.bit_count()), bits}); +} + +Bits SignExtend(const Bits& bits, int64 new_bit_count) { + XLS_CHECK_GE(new_bit_count, 0); + XLS_CHECK_GE(new_bit_count, bits.bit_count()); + const int64 ext_width = new_bit_count - bits.bit_count(); + return Concat( + {bits.msb() ? Bits::AllOnes(ext_width) : Bits(ext_width), bits}); +} + +Bits Concat(absl::Span inputs) { + int64 new_bit_count = 0; + for (const Bits& bits : inputs) { + new_bit_count += bits.bit_count(); + } + // Iterate in reverse order because the first input becomes the + // most-significant bits. + BitsRope rope(new_bit_count); + for (int64 i = 0; i < inputs.size(); ++i) { + rope.push_back(inputs[inputs.size() - i - 1]); + } + return rope.Build(); +} + +Bits Negate(const Bits& bits) { + if (bits.bit_count() < 64) { + return UBits((-bits.ToInt64().value()) & Mask(bits.bit_count()), + bits.bit_count()); + } + Bits negated = BigInt::Negate(BigInt::MakeSigned(bits)).ToSignedBits(); + return TruncateOrSignExtend(negated, bits.bit_count()); +} + +Bits ShiftLeftLogical(const Bits& bits, int64 shift_amount) { + XLS_CHECK_GE(shift_amount, 0); + shift_amount = std::min(shift_amount, bits.bit_count()); + return Concat( + {bits.Slice(0, bits.bit_count() - shift_amount), UBits(0, shift_amount)}); +} + +Bits ShiftRightLogical(const Bits& bits, int64 shift_amount) { + XLS_CHECK_GE(shift_amount, 0); + shift_amount = std::min(shift_amount, bits.bit_count()); + return Concat({UBits(0, shift_amount), + bits.Slice(shift_amount, bits.bit_count() - shift_amount)}); +} + +Bits ShiftRightArith(const Bits& bits, int64 shift_amount) { + XLS_CHECK_GE(shift_amount, 0); + shift_amount = std::min(shift_amount, bits.bit_count()); + return Concat( + {bits.msb() ? Bits::AllOnes(shift_amount) : UBits(0, shift_amount), + bits.Slice(shift_amount, bits.bit_count() - shift_amount)}); +} + +Bits OneHotLsbToMsb(const Bits& bits) { + for (int64 i = 0; i < bits.bit_count(); ++i) { + if (bits.Get(i)) { + return Bits::PowerOfTwo(i, bits.bit_count() + 1); + } + } + return Bits::PowerOfTwo(bits.bit_count(), bits.bit_count() + 1); +} + +Bits OneHotMsbToLsb(const Bits& bits) { + for (int64 i = bits.bit_count() - 1; i >= 0; --i) { + if (bits.Get(i)) { + return Bits::PowerOfTwo(i, bits.bit_count() + 1); + } + } + return Bits::PowerOfTwo(bits.bit_count(), bits.bit_count() + 1); +} + +Bits Reverse(const Bits& bits) { + auto bits_vector = bits.ToBitVector(); + std::reverse(bits_vector.begin(), bits_vector.end()); + return Bits(bits_vector); +} + +} // namespace bits_ops + +Bits LogicalOpIdentity(Op op, int64 width) { + switch (op) { + case Op::kAnd: + case Op::kNand: + return Bits::AllOnes(width); + case Op::kOr: + case Op::kNor: + case Op::kXor: + return Bits(width); + default: + XLS_LOG(FATAL) << "NaryOpIdentity got non-nary op:" << OpToString(op); + } +} + +Bits DoLogicalOp(Op op, absl::Span operands) { + XLS_CHECK_GT(operands.size(), 0); + switch (op) { + case Op::kAnd: + return bits_ops::NaryAnd(operands); + case Op::kOr: + return bits_ops::NaryOr(operands); + case Op::kXor: + return bits_ops::NaryXor(operands); + case Op::kNand: + return bits_ops::NaryNand(operands); + case Op::kNor: + return bits_ops::NaryNor(operands); + default: + XLS_LOG(FATAL) << "DoNaryBitOp got non-nary op: " << OpToString(op); + } +} + +Bits operator&(const Bits& lhs, const Bits& rhs) { + return bits_ops::And(lhs, rhs); +} +Bits operator|(const Bits& lhs, const Bits& rhs) { + return bits_ops::Or(lhs, rhs); +} +Bits operator^(const Bits& lhs, const Bits& rhs) { + return bits_ops::Xor(lhs, rhs); +} +Bits operator~(const Bits& bits) { return bits_ops::Not(bits); } + +} // namespace xls diff --git a/xls/ir/bits_ops.h b/xls/ir/bits_ops.h new file mode 100644 index 0000000000..d38ea80f56 --- /dev/null +++ b/xls/ir/bits_ops.h @@ -0,0 +1,143 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_BITS_OPS_H_ +#define THIRD_PARTY_XLS_IR_BITS_OPS_H_ + +#include "xls/ir/bits.h" +#include "xls/ir/op.h" + +namespace xls { +namespace bits_ops { + +// Various bitwise operations. The width of the lhs and rhs must be equal, and +// the returned Bits object has the same width as the input. +Bits And(const Bits& lhs, const Bits& rhs); +Bits NaryAnd(absl::Span operands); +Bits Or(const Bits& lhs, const Bits& rhs); +Bits NaryOr(absl::Span operands); +Bits Xor(const Bits& lhs, const Bits& rhs); +Bits NaryXor(absl::Span operands); +Bits Not(const Bits& bits); +Bits Nand(const Bits& lhs, const Bits& rhs); +Bits NaryNand(absl::Span operands); +Bits Nor(const Bits& lhs, const Bits& rhs); +Bits NaryNor(absl::Span operands); + +// Reducing bitwise operations. +Bits AndReduce(const Bits& operand); +Bits OrReduce(const Bits& operand); +Bits XorReduce(const Bits& operand); + +// Various arithmetic operations. The width of the lhs and rhs must be equal, +// and the returned Bits object is truncated to the same width as the input. +Bits Add(const Bits& lhs, const Bits& rhs); +Bits Sub(const Bits& lhs, const Bits& rhs); + +// Signed/unsigned multiplication. The rhs and lhs can be different widths. The +// width of the result of the operation is the sum of the widths of the +// operands. +Bits SMul(const Bits& lhs, const Bits& rhs); +Bits UMul(const Bits& lhs, const Bits& rhs); + +// Performs an (un)signed divide with round toward zero. The width of the lhs +// and rhs must be equal, and the returned result is the same width as the +// inputs. For UDiv, if the rhs is zero the result is all ones. For SDiv, if the +// rhs is zero the result is the maximal positive/negative value depending upon +// whether the lhs is positive/negative. +Bits SDiv(const Bits& lhs, const Bits& rhs); +Bits UDiv(const Bits& lhs, const Bits& rhs); + +// Various unsigned comparison operations. +bool UGreaterThanOrEqual(const Bits& lhs, const Bits& rhs); +bool UGreaterThan(const Bits& lhs, const Bits& rhs); +bool ULessThanOrEqual(const Bits& lhs, const Bits& rhs); +bool ULessThan(const Bits& lhs, const Bits& rhs); + +// Overloads for unsigned comparisons against an int64. CHECK fails if 'rhs' if +// negative because this is an unsigned comparison. We do not use an uint64 to +// avoid surprising conversions. For example, with an uint64 argument, the +// following would be true: LessThan(Ubits(42, 16), -1). +bool UGreaterThanOrEqual(const Bits& lhs, int64 rhs); +bool UGreaterThan(const Bits& lhs, int64 rhs); +bool ULessThanOrEqual(const Bits& lhs, int64 rhs); +bool ULessThan(const Bits& lhs, int64 rhs); + +// Various signed comparison operations. +bool SGreaterThanOrEqual(const Bits& lhs, const Bits& rhs); +bool SGreaterThan(const Bits& lhs, const Bits& rhs); +bool SLessThanOrEqual(const Bits& lhs, const Bits& rhs); +bool SLessThan(const Bits& lhs, const Bits& rhs); + +bool SGreaterThanOrEqual(const Bits& lhs, int64 rhs); +bool SGreaterThan(const Bits& lhs, int64 rhs); +bool SLessThanOrEqual(const Bits& lhs, int64 rhs); +bool SLessThan(const Bits& lhs, int64 rhs); + +// Zero/sign extend 'bits' to the new bit count and return the result. +// +// Check-fails if new_bit_count is not >= bits.bit_count(). +Bits ZeroExtend(const Bits& bits, int64 new_bit_count); +Bits SignExtend(const Bits& bits, int64 new_bit_count); + +// Shift Left/Right Logical or Arithmetic (shift right only). The width of the +// returned Bits object is the same as the input. +Bits ShiftLeftLogical(const Bits& bits, int64 shift_amount); +Bits ShiftRightLogical(const Bits& bits, int64 shift_amount); +Bits ShiftRightArith(const Bits& bits, int64 shift_amount); + +// Performs a twos-complement negate. The width of the returned bits object is +// the same as the input. In case of negating the minimal negative number +// (e.g., bit pattern 0x800...) the value overflows (e.g. produces bit pattern +// 0x800...). +Bits Negate(const Bits& bits); + +// Concatenates the argument bits together. The zero-th index element in the +// span becomes the most significant bits in the returned Bits object. +Bits Concat(absl::Span inputs); + +// Performs an operation equivalent to the XLS IR Op::kOneHot operation. +Bits OneHotLsbToMsb(const Bits& bits); +Bits OneHotMsbToLsb(const Bits& bits); + +inline int64 CountLeadingOnes(const Bits& bits) { + return Not(bits).CountLeadingZeros(); +} +inline int64 CountTrailingOnes(const Bits& bits) { + return Not(bits).CountTrailingZeros(); +} + +// Returns a Bits object with the bits of the argument in reverse order. That +// is, most-significant bit becomes least-significant bit, etc. +Bits Reverse(const Bits& bits); + +} // namespace bits_ops + +// Returns the identity value of the given width for the given logical Op (e.g., +// Op::kAnd). +Bits LogicalOpIdentity(Op op, int64 width); + +// Returns the result of applying the given logical Op (e.g., Op::kAnd) to the +// operands. Must have at least one operand. +Bits DoLogicalOp(Op op, absl::Span operands); + +// Operator overloads. +Bits operator&(const Bits& lhs, const Bits& rhs); +Bits operator|(const Bits& lhs, const Bits& rhs); +Bits operator^(const Bits& lhs, const Bits& rhs); +Bits operator~(const Bits& bits); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_BITS_OPS_H_ diff --git a/xls/ir/bits_ops_test.cc b/xls/ir/bits_ops_test.cc new file mode 100644 index 0000000000..3855cd7515 --- /dev/null +++ b/xls/ir/bits_ops_test.cc @@ -0,0 +1,568 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/bits_ops.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/math_util.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/number_parser.h" +#include "xls/ir/value.h" + +namespace xls { +namespace { + +// Create a Bits of the given bit count with the prime number index bits set to +// one. +Bits PrimeBits(int64 bit_count) { + auto is_prime = [](int64 n) { + if (n < 2) { + return false; + } + for (int64 i = 2; i * i < n; ++i) { + if (n % i == 0) { + return false; + } + } + return true; + }; + + std::vector bytes(CeilOfRatio(bit_count, int64{8}), 0); + for (int64 i = 0; i < bit_count; ++i) { + if (is_prime(i)) { + bytes[bytes.size() - 1 - i / 8] |= 1 << (i % 8); + } + } + return Bits::FromBytes(bytes, bit_count); +} + +TEST(BitsOpsTest, LogicalOps) { + Bits empty_bits(0); + EXPECT_EQ(empty_bits, bits_ops::And(empty_bits, empty_bits)); + EXPECT_EQ(empty_bits, bits_ops::Or(empty_bits, empty_bits)); + EXPECT_EQ(empty_bits, bits_ops::Xor(empty_bits, empty_bits)); + EXPECT_EQ(empty_bits, bits_ops::Not(empty_bits)); + + Bits b0 = UBits(0, 1); + Bits b1 = UBits(1, 1); + + EXPECT_EQ(b0, bits_ops::And(b0, b1)); + EXPECT_EQ(b1, bits_ops::And(b1, b1)); + EXPECT_EQ(b0, bits_ops::Or(b0, b0)); + EXPECT_EQ(b1, bits_ops::Or(b0, b1)); + EXPECT_EQ(b1, bits_ops::Or(b1, b1)); + EXPECT_EQ(b1, bits_ops::Xor(b0, b1)); + EXPECT_EQ(b1, bits_ops::Xor(b1, b0)); + EXPECT_EQ(b0, bits_ops::Xor(b0, b0)); + EXPECT_EQ(b1, bits_ops::Not(b0)); + EXPECT_EQ(b0, bits_ops::Not(b1)); + + Bits deadbeef2 = UBits(0xdeadbeefdeadbeefULL, 64); + Bits fofo = UBits(0xf0f0f0f0f0f0f0f0ULL, 64); + EXPECT_EQ(UBits(0xf0f0f0f0f0f0f0fULL, 64), bits_ops::Not(fofo)); + EXPECT_EQ(UBits(0xe0d0e0f0e0d0e0fULL, 64), + bits_ops::And(deadbeef2, bits_ops::Not(fofo))); + EXPECT_EQ(UBits(0xfefdfefffefdfeffULL, 64), bits_ops::Or(deadbeef2, fofo)); + + Bits wide_bits = PrimeBits(12345); + EXPECT_EQ(wide_bits, bits_ops::And(wide_bits, wide_bits)); + EXPECT_EQ(wide_bits, bits_ops::Or(wide_bits, wide_bits)); + EXPECT_TRUE(bits_ops::Xor(wide_bits, wide_bits).IsAllZeros()); + EXPECT_TRUE(bits_ops::Xor(wide_bits, bits_ops::Not(wide_bits)).IsAllOnes()); +} + +TEST(BitsOpsTest, Concat) { + Bits empty_bits(0); + EXPECT_EQ(empty_bits, bits_ops::Concat({})); + EXPECT_EQ(empty_bits, bits_ops::Concat({empty_bits})); + EXPECT_EQ(empty_bits, bits_ops::Concat({empty_bits, empty_bits})); + + Bits b0 = UBits(0, 1); + Bits b1 = UBits(1, 1); + + EXPECT_EQ(bits_ops::Concat({b0, b1}), UBits(1, 2)); + EXPECT_EQ(bits_ops::Concat({empty_bits, b0, empty_bits, b1, empty_bits}), + UBits(1, 2)); + EXPECT_EQ(bits_ops::Concat({b1, b1, b0, b1, b0, b1}), UBits(0x35, 6)); + + EXPECT_EQ(bits_ops::Concat({UBits(0xab, 8), UBits(0xcd, 8), UBits(0xef, 8)}), + UBits(0xabcdef, 24)); + + Bits deadbeef2 = UBits(0xdeadbeefdeadbeefULL, 64); + Bits fofo = UBits(0xf0f0f0f0f0f0f0f0ULL, 64); + EXPECT_EQ( + bits_ops::Concat({deadbeef2, fofo}).ToString(FormatPreference::kHex), + "0xdead_beef_dead_beef_f0f0_f0f0_f0f0_f0f0"); +} + +TEST(BitsOpsTest, Add) { + EXPECT_EQ(bits_ops::Add(Bits(), Bits()), Bits()); + EXPECT_EQ(bits_ops::Add(UBits(23, 64), UBits(42, 64)), UBits(65, 64)); + + // Test overflow conditions. + EXPECT_EQ(bits_ops::Add(UBits(10, 4), UBits(13, 4)), UBits(7, 4)); + EXPECT_EQ(bits_ops::Add(UBits(0xffffffffffffffffUL, 64), UBits(1, 64)), + UBits(0, 64)); + + // Test wide values. + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_lhs, + ParseNumber("0x1fff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0042")); + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_rhs, + ParseNumber("0x1000_0000_0000_0000_0001_1234_4444_3333_0000_1234")); + EXPECT_EQ(bits_ops::Add(wide_lhs, wide_rhs).ToString(FormatPreference::kHex), + "0x1000_0000_0000_0000_0000_1234_4444_3333_0000_1276"); +} + +TEST(BitsOpsTest, Sub) { + EXPECT_EQ(bits_ops::Sub(Bits(), Bits()), Bits()); + EXPECT_EQ(bits_ops::Sub(UBits(55, 64), UBits(12, 64)), UBits(43, 64)); + + // Test overflow conditions. + EXPECT_EQ(bits_ops::Sub(UBits(9, 4), UBits(12, 4)), UBits(13, 4)); + EXPECT_EQ(bits_ops::Sub(UBits(0, 64), UBits(1, 64)), + UBits(0xffffffffffffffffUL, 64)); + EXPECT_EQ(bits_ops::Sub(SBits(-6, 4), SBits(5, 4)), SBits(5, 4)); + + // Test wide values. + { + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_lhs, + ParseNumber("0x1fff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0042")); + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_rhs, + ParseNumber("0x1000_0000_0000_0000_0001_1234_4444_3333_0000_1234")); + EXPECT_EQ( + bits_ops::Sub(wide_lhs, wide_rhs).ToString(FormatPreference::kHex), + "0xfff_ffff_ffff_ffff_fffd_edcb_bbbb_cccc_ffff_ee0e"); + } + { + // Test an underflow case. + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_lhs, + ParseNumber("0x1000_0000_0000_0000_0000_0000_0000_0000_0000")); + Bits wide_rhs = UBits(42, wide_lhs.bit_count()); + EXPECT_EQ( + bits_ops::Sub(wide_lhs, wide_rhs).ToString(FormatPreference::kHex), + "0xfff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffd6"); + } +} + +TEST(BitsOpsTest, UMul) { + EXPECT_EQ(bits_ops::UMul(Bits(), Bits()), Bits()); + EXPECT_EQ(bits_ops::UMul(UBits(100, 24), UBits(55, 22)), UBits(5500, 46)); + EXPECT_EQ(bits_ops::UMul(UBits(100, 64), UBits(55, 64)), UBits(5500, 128)); + EXPECT_EQ(bits_ops::UMul(UBits(100, 7), UBits(3, 2)), UBits(300, 9)); + + // Test wide values. + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_lhs, + ParseNumber("0x1fff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0042")); + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_rhs, + ParseNumber("0x1000_0000_0000_0000_0001_1234_4444_3333_0000_1234")); + EXPECT_EQ(bits_ops::UMul(wide_lhs, wide_rhs).ToString(FormatPreference::kHex), + "0x200_0000_0000_0000_0000_1246_8888_8666_6000_0249_8dcb_bbbb_cccc_" + "ffff_ee12_b179_9995_3326_0004_b168"); + + Bits result = bits_ops::UMul(Bits::AllOnes(65), Bits::AllOnes(65)); + EXPECT_EQ(result.bit_count(), 130); + EXPECT_EQ(result.ToString(FormatPreference::kHex), + "0x3_ffff_ffff_ffff_fffc_0000_0000_0000_0001"); +} + +TEST(BitsOpsTest, SMul) { + EXPECT_EQ(bits_ops::SMul(Bits(), Bits()), Bits()); + EXPECT_EQ(bits_ops::SMul(SBits(100, 64), SBits(55, 64)), SBits(5500, 128)); + EXPECT_EQ(bits_ops::SMul(SBits(100, 64), SBits(-3, 64)), SBits(-300, 128)); + EXPECT_EQ(bits_ops::SMul(SBits(50, 7), SBits(-1, 2)), SBits(-50, 9)); + EXPECT_EQ(bits_ops::SMul(SBits(-50, 7), SBits(-1, 2)), SBits(50, 9)); + + // Test wide values. + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_lhs, + ParseNumber("0x1fff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0042")); + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_rhs, + ParseNumber("0x1000_0000_0000_0000_0001_1234_4444_3333_0000_1234")); + EXPECT_EQ(bits_ops::SMul(wide_lhs, wide_rhs).ToString(FormatPreference::kHex), + "0xfff_ffff_ffff_ffff_fffa_cdcb_bbbb_cccc_ffff_ee12_b179_9995_3326_" + "0004_b168"); +} + +TEST(BitsOpsTest, UDiv) { + EXPECT_EQ(bits_ops::UDiv(UBits(100, 64), UBits(5, 64)), UBits(20, 64)); + EXPECT_EQ(bits_ops::UDiv(UBits(100, 32), UBits(7, 32)), UBits(14, 32)); + EXPECT_EQ(bits_ops::UDiv(UBits(0, 64), UBits(7, 64)), UBits(0, 64)); + + // Divide by zero. + EXPECT_EQ(bits_ops::UDiv(Bits(), Bits()), Bits()); + EXPECT_EQ(bits_ops::UDiv(UBits(10, 4), UBits(0, 4)), UBits(15, 4)); + EXPECT_EQ(bits_ops::UDiv(UBits(123456, 64), UBits(0, 64)), Bits::AllOnes(64)); + + // Test wide values. + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_lhs, + ParseNumber("0xffff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0000")); + XLS_ASSERT_OK_AND_ASSIGN(Bits wide_rhs, ParseNumber("0x1000_0000_0000_0000")); + EXPECT_EQ(bits_ops::UDiv(wide_lhs, + bits_ops::ZeroExtend(wide_rhs, wide_lhs.bit_count())) + .ToString(FormatPreference::kHex), + "0xf_ffff_ffff_ffff_ffff_fff0_0000"); +} + +TEST(BitsOpsTest, SDiv) { + EXPECT_EQ(bits_ops::SDiv(SBits(100, 64), SBits(5, 64)), SBits(20, 64)); + EXPECT_EQ(bits_ops::SDiv(SBits(100, 64), SBits(-5, 64)), SBits(-20, 64)); + EXPECT_EQ(bits_ops::SDiv(SBits(-100, 64), SBits(-5, 64)), SBits(20, 64)); + EXPECT_EQ(bits_ops::SDiv(SBits(-100, 64), SBits(5, 64)), SBits(-20, 64)); + + EXPECT_EQ(bits_ops::SDiv(SBits(100, 32), SBits(7, 32)), SBits(14, 32)); + EXPECT_EQ(bits_ops::SDiv(SBits(-100, 32), SBits(7, 32)), SBits(-14, 32)); + EXPECT_EQ(bits_ops::SDiv(SBits(100, 32), SBits(-7, 32)), SBits(-14, 32)); + EXPECT_EQ(bits_ops::SDiv(SBits(-100, 32), SBits(-7, 32)), SBits(14, 32)); + + EXPECT_EQ(bits_ops::SDiv(SBits(0, 64), SBits(7, 64)), SBits(0, 64)); + EXPECT_EQ(bits_ops::SDiv(SBits(0, 64), SBits(-7, 64)), SBits(0, 64)); + + // Divide by zero. + EXPECT_EQ(bits_ops::SDiv(Bits(), Bits()), Bits()); + EXPECT_EQ(bits_ops::SDiv(SBits(5, 4), SBits(0, 4)), SBits(7, 4)); + EXPECT_EQ(bits_ops::SDiv(SBits(-5, 4), SBits(0, 4)), SBits(-8, 4)); + EXPECT_EQ(bits_ops::SDiv(SBits(123456, 64), SBits(0, 64)), + SBits(std::numeric_limits::max(), 64)); + EXPECT_EQ(bits_ops::SDiv(SBits(-123456, 64), SBits(0, 64)), + SBits(std::numeric_limits::min(), 64)); + + // Test wide values. + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_lhs, + ParseNumber("0xffff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0000")); + XLS_ASSERT_OK_AND_ASSIGN(Bits wide_rhs, ParseNumber("0x1000_0000_0000_0000")); + EXPECT_EQ(bits_ops::SDiv(wide_lhs, + bits_ops::ZeroExtend(wide_rhs, wide_lhs.bit_count())) + .ToString(FormatPreference::kHex), + "0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_fff0_0000"); +} + +TEST(BitsOpsTest, Comparisons) { + Bits b42 = UBits(42, 64); + Bits b77 = UBits(77, 64); + + EXPECT_TRUE(bits_ops::UGreaterThanOrEqual(b42, b42)); + EXPECT_FALSE(bits_ops::UGreaterThanOrEqual(b42, b77)); + EXPECT_TRUE(bits_ops::UGreaterThanOrEqual(b77, b42)); + + EXPECT_FALSE(bits_ops::UGreaterThan(b42, b42)); + EXPECT_FALSE(bits_ops::UGreaterThan(b42, b77)); + EXPECT_TRUE(bits_ops::UGreaterThan(b77, b42)); + + EXPECT_TRUE(bits_ops::ULessThanOrEqual(b42, b42)); + EXPECT_TRUE(bits_ops::ULessThanOrEqual(b42, b77)); + EXPECT_FALSE(bits_ops::ULessThanOrEqual(b77, b42)); + + EXPECT_FALSE(bits_ops::ULessThan(b42, b42)); + EXPECT_TRUE(bits_ops::ULessThan(b42, b77)); + EXPECT_FALSE(bits_ops::ULessThan(b77, b42)); + + XLS_ASSERT_OK_AND_ASSIGN( + Bits huge, + ParseNumber( + "0x2fff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0000_1234_5555")); + XLS_ASSERT_OK_AND_ASSIGN( + Bits huger, + ParseNumber( + "0x3234_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0000_1111_3333")); + EXPECT_TRUE(bits_ops::UGreaterThanOrEqual(huger, huge)); + EXPECT_FALSE(bits_ops::ULessThan(huger, huge)); + EXPECT_TRUE(bits_ops::ULessThan(huge, huger)); +} + +TEST(BitsOpsTest, Int64UnsignedComparisons) { + Bits b42 = UBits(42, 64); + EXPECT_TRUE(bits_ops::UGreaterThanOrEqual(b42, 42)); + EXPECT_FALSE(bits_ops::UGreaterThanOrEqual(b42, 123)); + EXPECT_TRUE(bits_ops::UGreaterThanOrEqual(b42, 7)); + + EXPECT_FALSE(bits_ops::UGreaterThan(b42, 42)); + EXPECT_FALSE(bits_ops::UGreaterThan(b42, 100)); + EXPECT_TRUE(bits_ops::UGreaterThan(b42, 1)); + + EXPECT_TRUE(bits_ops::ULessThanOrEqual(b42, 42)); + EXPECT_TRUE(bits_ops::ULessThanOrEqual(b42, 77)); + EXPECT_FALSE(bits_ops::ULessThanOrEqual(b42, 33)); + + EXPECT_FALSE(bits_ops::ULessThan(b42, 42)); + EXPECT_TRUE(bits_ops::ULessThan(b42, 77)); + EXPECT_FALSE(bits_ops::ULessThan(b42, 2)); + + XLS_ASSERT_OK_AND_ASSIGN( + Bits huge, + ParseNumber( + "0x2fff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0000_1234_5555")); + EXPECT_TRUE(bits_ops::UGreaterThanOrEqual(huge, 0)); + EXPECT_TRUE( + bits_ops::UGreaterThanOrEqual(huge, std::numeric_limits::max())); + EXPECT_TRUE(bits_ops::UGreaterThan(huge, 1234567)); + EXPECT_FALSE( + bits_ops::ULessThanOrEqual(huge, std::numeric_limits::max())); + EXPECT_FALSE(bits_ops::ULessThanOrEqual(huge, 33)); +} + +TEST(BitsOpsTest, Int64SignedComparisons) { + Bits b42 = UBits(42, 64); + Bits minus42 = SBits(-42, 64); + EXPECT_TRUE(bits_ops::SGreaterThanOrEqual(b42, 42)); + EXPECT_TRUE(bits_ops::SGreaterThanOrEqual(minus42, -42)); + EXPECT_TRUE(bits_ops::SGreaterThanOrEqual(b42, minus42)); + EXPECT_FALSE(bits_ops::UGreaterThanOrEqual(b42, minus42)); + EXPECT_FALSE(bits_ops::SGreaterThanOrEqual(b42, 123)); + EXPECT_TRUE(bits_ops::SGreaterThanOrEqual(b42, 7)); + + EXPECT_FALSE(bits_ops::SGreaterThan(b42, 42)); + EXPECT_FALSE(bits_ops::SGreaterThan(b42, 100)); + EXPECT_TRUE(bits_ops::SGreaterThan(b42, -100)); + + EXPECT_TRUE(bits_ops::SLessThanOrEqual(b42, 42)); + EXPECT_TRUE(bits_ops::SLessThanOrEqual(b42, 77)); + EXPECT_FALSE(bits_ops::SLessThanOrEqual(b42, -33)); + EXPECT_TRUE(bits_ops::SLessThanOrEqual(minus42, -33)); + + EXPECT_FALSE(bits_ops::SLessThan(b42, 42)); + EXPECT_TRUE(bits_ops::SLessThan(b42, 77)); + EXPECT_FALSE(bits_ops::SLessThan(b42, -10000)); + EXPECT_TRUE(bits_ops::SLessThan(minus42, -10)); + + XLS_ASSERT_OK_AND_ASSIGN( + Bits huge_minus2, + ParseNumber("0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_fffe")); + XLS_ASSERT_OK_AND_ASSIGN( + Bits tmp, + ParseNumber("0x2fff_ffff_ffff_ffff_ffff_0000_0000_0000_0000_0000_1234")); + Bits huge = bits_ops::ZeroExtend(tmp, huge_minus2.bit_count()); + + EXPECT_TRUE(bits_ops::SGreaterThanOrEqual(huge, 0)); + EXPECT_FALSE(bits_ops::SGreaterThanOrEqual(huge_minus2, 0)); + EXPECT_TRUE(bits_ops::SGreaterThanOrEqual(huge, huge_minus2)); + EXPECT_TRUE( + bits_ops::SGreaterThanOrEqual(huge, std::numeric_limits::max())); + EXPECT_TRUE(bits_ops::SGreaterThan(huge, 1234567)); + EXPECT_FALSE(bits_ops::SGreaterThan(huge_minus2, 1234567)); + EXPECT_FALSE( + bits_ops::SLessThanOrEqual(huge, std::numeric_limits::max())); + EXPECT_TRUE(bits_ops::SLessThanOrEqual(huge_minus2, + std::numeric_limits::min())); + EXPECT_FALSE(bits_ops::SLessThanOrEqual(huge, 33)); + EXPECT_TRUE(bits_ops::SLessThanOrEqual(huge_minus2, 33)); +} + +TEST(BitsOpsTest, ZeroAndSignExtend) { + Bits empty_bits(0); + EXPECT_TRUE(bits_ops::ZeroExtend(empty_bits, 47).IsAllZeros()); + EXPECT_TRUE(bits_ops::SignExtend(empty_bits, 123).IsAllZeros()); + + Bits b0 = UBits(0, 1); + EXPECT_TRUE(bits_ops::ZeroExtend(b0, 2).IsAllZeros()); + EXPECT_TRUE(bits_ops::SignExtend(b0, 44).IsAllZeros()); + + Bits b1 = UBits(1, 1); + EXPECT_EQ(bits_ops::ZeroExtend(b1, 32), UBits(1, 32)); + EXPECT_EQ(bits_ops::SignExtend(b1, 47), SBits(-1, 47)); + + EXPECT_EQ(bits_ops::ZeroExtend(UBits(0x80, 8), 32), UBits(0x80, 32)); + EXPECT_EQ(bits_ops::SignExtend(UBits(0x80, 8), 32), UBits(0xffffff80UL, 32)); +} + +TEST(BitsOpsTest, ShiftLeftLogical) { + Bits b0 = UBits(0, 11); + EXPECT_EQ(bits_ops::ShiftLeftLogical(b0, 0), b0); + + Bits b1 = UBits(1, 16); + EXPECT_EQ(bits_ops::ShiftLeftLogical(b1, 1), UBits(2, 16)); + EXPECT_EQ(bits_ops::ShiftLeftLogical(b1, 2), UBits(4, 16)); + + Bits b2 = UBits(0x7FFF, 16); + EXPECT_EQ(bits_ops::ShiftLeftLogical(b2, 1), UBits(0xFFFE, 16)); + + Bits b3 = UBits(0x0010, 16); + EXPECT_EQ(bits_ops::ShiftLeftLogical(b3, 8), UBits(0x1000, 16)); + + Bits b4 = UBits(0x0001, 16); + EXPECT_EQ(bits_ops::ShiftLeftLogical(b4, 15), UBits(0x8000, 16)); + + Bits b5 = UBits(0xf, 4); + EXPECT_EQ(bits_ops::ShiftLeftLogical(b5, 4), UBits(0, 4)); +} + +TEST(BitsOpsTest, ShiftRightLogical) { + Bits b0 = UBits(0, 11); + EXPECT_EQ(bits_ops::ShiftRightLogical(b0, 0), UBits(0, 11)); + + Bits b1 = UBits(4, 16); + EXPECT_EQ(bits_ops::ShiftRightLogical(b1, 1), UBits(2, 16)); + EXPECT_EQ(bits_ops::ShiftRightLogical(b1, 2), UBits(1, 16)); + + Bits b2 = UBits(0xFFFE, 16); + EXPECT_EQ(bits_ops::ShiftRightLogical(b2, 1), UBits(0x7FFF, 16)); + + Bits b3 = UBits(0x1000, 16); + EXPECT_EQ(bits_ops::ShiftRightLogical(b3, 8), UBits(0x0010, 16)); + + Bits b4 = UBits(0x8000, 16); + EXPECT_EQ(bits_ops::ShiftRightLogical(b4, 15), UBits(0x0001, 16)); +} + +TEST(BitsOpsTest, ShiftRightArith) { + Bits b1 = UBits(0x8080, 16); + EXPECT_EQ(bits_ops::ShiftRightArith(b1, 1), UBits(0xc040, 16)); + + Bits b2 = UBits(0b10000100, 8); + EXPECT_EQ(bits_ops::ShiftRightArith(b2, 2), UBits(0b11100001, 8)); + + Bits b3 = UBits(0b11111111, 8); + EXPECT_EQ(bits_ops::ShiftRightArith(b3, 7), UBits(0b11111111, 8)); + + Bits b4 = UBits(0xF000000000000000, 64); + EXPECT_EQ(bits_ops::ShiftRightArith(b4, 4), UBits(0xFF00000000000000, 64)); + + Bits b5 = SBits(-1, 64); // All hexadecimal F's + EXPECT_EQ(bits_ops::ShiftRightArith(b5, 63), SBits(-1, 64)); + + // Shift by the full bit width. + Bits b6 = SBits(-1, 2); + Bits b6_shifted = bits_ops::ShiftRightArith(b6, 2); + EXPECT_EQ(b6_shifted, SBits(-1, 2)); +} + +TEST(BitsOpsTest, Negate) { + EXPECT_EQ(bits_ops::Negate(Bits(0)), Bits(0)); + EXPECT_EQ(bits_ops::Negate(UBits(0, 1)), UBits(0, 1)); + + // A single-bit 1 as twos-complement is -1. + EXPECT_EQ(bits_ops::Negate(UBits(1, 1)), UBits(1, 1)); + + EXPECT_EQ(bits_ops::Negate(SBits(-4, 3)), UBits(4, 3)); + EXPECT_EQ(bits_ops::Negate(UBits(42, 37)), SBits(-42, 37)); + EXPECT_EQ(bits_ops::Negate(UBits(std::numeric_limits::min(), 64)), + UBits(0x8000000000000000ULL, 64)); + EXPECT_EQ(bits_ops::Negate(UBits(0, 1234)), UBits(0, 1234)); + EXPECT_EQ(bits_ops::Negate(UBits(1, 1234)), SBits(-1, 1234)); +} + +TEST(BitsOpsTest, OneHot) { + EXPECT_EQ(bits_ops::OneHotLsbToMsb(Bits(0)), UBits(1, 1)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(Bits(0)), UBits(1, 1)); + + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0, 1)), UBits(0b10, 2)); + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(1, 1)), UBits(0b01, 2)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0, 1)), UBits(0b10, 2)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(1, 1)), UBits(0b01, 2)); + + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0b00, 2)), UBits(0b100, 3)); + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0b01, 2)), UBits(0b001, 3)); + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0b10, 2)), UBits(0b010, 3)); + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0b11, 2)), UBits(0b001, 3)); + + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0b00, 2)), UBits(0b100, 3)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0b01, 2)), UBits(0b001, 3)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0b10, 2)), UBits(0b010, 3)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0b11, 2)), UBits(0b010, 3)); + + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0b00010000, 8)), + UBits(0b000010000, 9)); + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0b00110010, 8)), + UBits(0b000000010, 9)); + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0b11111111, 8)), + UBits(0b000000001, 9)); + EXPECT_EQ(bits_ops::OneHotLsbToMsb(UBits(0b00000000, 8)), + UBits(0b100000000, 9)); + + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0b00010000, 8)), + UBits(0b000010000, 9)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0b00110010, 8)), + UBits(0b000100000, 9)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0b11111111, 8)), + UBits(0b010000000, 9)); + EXPECT_EQ(bits_ops::OneHotMsbToLsb(UBits(0b00000000, 8)), + UBits(0b100000000, 9)); +} + +TEST(BitsOpsTest, Nor3) { + std::vector, uint8>> cases = { + {{0, 0, 0}, 1}, // + {{0, 0, 1}, 0}, // + {{0, 1, 0}, 0}, // + {{0, 1, 1}, 0}, // + {{1, 0, 0}, 0}, // + {{1, 0, 1}, 0}, // + {{1, 1, 0}, 0}, // + {{1, 1, 1}, 0}, // + }; + for (auto test_case : cases) { + EXPECT_EQ(bits_ops::NaryNor({UBits(test_case.first[0], 1), + UBits(test_case.first[1], 1), + UBits(test_case.first[2], 1)}), + UBits(test_case.second, 1)); + } +} + +TEST(BitsOpsTest, Nand3) { + std::vector, uint8>> cases = { + {{0, 0, 0}, 1}, // + {{0, 0, 1}, 1}, // + {{0, 1, 0}, 1}, // + {{0, 1, 1}, 1}, // + {{1, 0, 0}, 1}, // + {{1, 0, 1}, 1}, // + {{1, 1, 0}, 1}, // + {{1, 1, 1}, 0}, // + }; + for (auto test_case : cases) { + EXPECT_EQ(bits_ops::NaryNand({UBits(test_case.first[0], 1), + UBits(test_case.first[1], 1), + UBits(test_case.first[2], 1)}), + UBits(test_case.second, 1)); + } +} + +TEST(BitsOpsTest, Reverse) { + EXPECT_EQ(bits_ops::Reverse(Bits()), Bits()); + EXPECT_EQ(bits_ops::Reverse(UBits(0, 1)), UBits(0, 1)); + EXPECT_EQ(bits_ops::Reverse(UBits(1, 1)), UBits(1, 1)); + EXPECT_EQ(bits_ops::Reverse(UBits(1, 100)), Bits::PowerOfTwo(99, 100)); + EXPECT_EQ(bits_ops::Reverse(UBits(0b111001, 6)), UBits(0b100111, 6)); + EXPECT_EQ(bits_ops::Reverse(UBits(0b111001, 10)), UBits(0b1001110000, 10)); +} + +TEST(BitsOpsTest, ReductionOps) { + EXPECT_EQ(bits_ops::AndReduce(UBits(0, 1)), UBits(0, 1)); + EXPECT_EQ(bits_ops::AndReduce(UBits(1, 1)), UBits(1, 1)); + EXPECT_EQ(bits_ops::AndReduce(Bits::AllOnes(128)), UBits(1, 1)); + EXPECT_EQ(bits_ops::AndReduce(UBits(128, 128)), UBits(0, 1)); + + EXPECT_EQ(bits_ops::OrReduce(UBits(0, 1)), UBits(0, 1)); + EXPECT_EQ(bits_ops::OrReduce(UBits(1, 1)), UBits(1, 1)); + EXPECT_EQ(bits_ops::OrReduce(Bits::AllOnes(128)), UBits(1, 1)); + EXPECT_EQ(bits_ops::OrReduce(UBits(128, 128)), UBits(1, 1)); + + EXPECT_EQ(bits_ops::XorReduce(UBits(0, 1)), UBits(0, 1)); + EXPECT_EQ(bits_ops::XorReduce(UBits(1, 1)), UBits(1, 1)); + EXPECT_EQ(bits_ops::XorReduce(Bits::AllOnes(128)), UBits(0, 1)); + EXPECT_EQ(bits_ops::XorReduce(UBits(127, 128)), UBits(1, 1)); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/bits_test.cc b/xls/ir/bits_test.cc new file mode 100644 index 0000000000..1023074546 --- /dev/null +++ b/xls/ir/bits_test.cc @@ -0,0 +1,627 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/bits.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/math_util.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/number_parser.h" +#include "xls/ir/value.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; +using status_testing::StatusIs; +using ::testing::ElementsAre; +using ::testing::HasSubstr; + +// Create a Bits of the given bit count with the prime number index bits set to +// one. +Bits PrimeBits(int64 bit_count) { + auto is_prime = [](int64 n) { + if (n < 2) { + return false; + } + for (int64 i = 2; i * i < n; ++i) { + if (n % i == 0) { + return false; + } + } + return true; + }; + + std::vector bytes(CeilOfRatio(bit_count, int64{8}), 0); + for (int64 i = 0; i < bit_count; ++i) { + if (is_prime(i)) { + bytes[bytes.size() - 1 - i / 8] |= 1 << (i % 8); + } + } + return Bits::FromBytes(bytes, bit_count); +} + +TEST(BitsTest, BitsConstructor) { + Bits empty(0); + EXPECT_THAT(empty.ToInt64(), IsOkAndHolds(0)); + EXPECT_THAT(empty.ToUint64(), IsOkAndHolds(0)); + + Bits b0(15); + EXPECT_THAT(b0.ToInt64(), IsOkAndHolds(0)); + EXPECT_THAT(b0.ToUint64(), IsOkAndHolds(0)); + + Bits b1(1234); + for (int64 i = 0; i < 1234; ++i) { + EXPECT_EQ(b1.Get(i), 0); + } +} + +TEST(BitsTest, BitsVectorConstructor) { + EXPECT_EQ(Bits::FromBytes({}, 0), Bits()); + + EXPECT_EQ(Bits::FromBytes({42}, 6), UBits(42, 6)); + EXPECT_EQ(Bits::FromBytes({42}, 8), UBits(42, 8)); + EXPECT_EQ(Bits::FromBytes({0, 0, 0, 0, 0, 0, 0, 0, 0, 42}, 80), + UBits(42, 80)); + EXPECT_EQ(Bits::FromBytes({0, 0, 0, 0, 0, 0, 0, 0, 0, 42}, 73), + UBits(42, 73)); + + EXPECT_EQ(Bits::FromBytes({0xde, 0xad, 0xbe, 0xef}, 32), + UBits(0xdeadbeefULL, 32)); + + EXPECT_EQ(Bits::FromBytes({0x1, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, + 0x90, 0xfe, 0xdc, 0xba}, + 89) + .ToString(FormatPreference::kHex), + "0x1ab_cdef_1234_5678_90fe_dcba"); +} + +TEST(BitsTest, BitsToBytes) { + EXPECT_TRUE(Bits().ToBytes().empty()); + EXPECT_THAT(UBits(42, 6).ToBytes(), ElementsAre(42)); + EXPECT_THAT(UBits(123, 16).ToBytes(), ElementsAre(0, 123)); + EXPECT_THAT(UBits(42, 80).ToBytes(), + ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 0, 42)); + EXPECT_THAT(UBits(42, 77).ToBytes(), + ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 0, 42)); + EXPECT_THAT(Bits::FromBytes({0x1, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, + 0x90, 0xfe, 0xdc, 0xba}, + 89) + .ToBytes(), + ElementsAre(0x1, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, + 0xfe, 0xdc, 0xba)); + + EXPECT_THAT( + Bits::AllOnes(65).ToBytes(), + ElementsAre(0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)); + { + Bits wide_bits = Bits::AllOnes(75); + std::vector bytes(CeilOfRatio(wide_bits.bit_count(), int64{8})); + wide_bits.ToBytes(absl::MakeSpan(bytes), /*big_endian=*/true); + EXPECT_THAT(bytes, ElementsAre(0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff)); + } + { + Bits wide_bits = Bits::AllOnes(75); + std::vector bytes(CeilOfRatio(wide_bits.bit_count(), int64{8})); + wide_bits.ToBytes(absl::MakeSpan(bytes), /*big_endian=*/false); + EXPECT_THAT(bytes, ElementsAre(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x07)); + } + + { + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_bits, + ParseNumber("0x4ff_ffff_0000_1111_2222_3333_4444_ffff_0000_cc31")); + std::vector bytes(CeilOfRatio(wide_bits.bit_count(), int64{8})); + wide_bits.ToBytes(absl::MakeSpan(bytes), /*big_endian=*/true); + EXPECT_THAT(bytes, ElementsAre(0x4, 0xff, 0xff, 0xff, 0x00, 0x00, 0x11, + 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, + 0xff, 0xff, 0x00, 0x00, 0xcc, 0x31)); + } + { + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_bits, + ParseNumber("0x4ff_ffff_0000_1111_2222_3333_4444_ffff_0000_cc31")); + std::vector bytes(CeilOfRatio(wide_bits.bit_count(), int64{8})); + wide_bits.ToBytes(absl::MakeSpan(bytes), /*big_endian=*/false); + EXPECT_THAT(bytes, ElementsAre(0x31, 0xcc, 0x00, 0x00, 0xff, 0xff, 0x44, + 0x44, 0x33, 0x33, 0x22, 0x22, 0x11, 0x11, + 0x00, 0x00, 0xff, 0xff, 0xff, 0x4)); + } +} + +TEST(BitsTest, Msb) { + EXPECT_EQ(Bits().msb(), 0); + EXPECT_EQ(UBits(1, 1).msb(), 1); + EXPECT_EQ(UBits(1, 2).msb(), 0); + EXPECT_EQ(UBits(0x80, 8).msb(), 1); + EXPECT_EQ(UBits(0x80, 800).msb(), 0); + EXPECT_EQ(SBits(-1, 800).msb(), 1); + EXPECT_EQ(SBits(-1, 48).msb(), 1); + EXPECT_EQ(SBits(-1, 63).msb(), 1); + EXPECT_EQ(SBits(-1, 64).msb(), 1); + EXPECT_EQ(SBits(-1, 65).msb(), 1); +} + +TEST(BitsTest, IsOne) { + EXPECT_FALSE(Bits().IsOne()); + EXPECT_TRUE(UBits(1, 1).IsOne()); + EXPECT_TRUE(UBits(1, 10).IsOne()); + EXPECT_TRUE(UBits(1, 10000000).IsOne()); + EXPECT_FALSE(UBits(0b10, 10).IsOne()); + EXPECT_FALSE(UBits(0b111, 16).IsOne()); +} + +TEST(BitsTest, PopCount) { + EXPECT_EQ(Bits().PopCount(), 0); + EXPECT_EQ(UBits(0b1, 1).PopCount(), 1); + EXPECT_EQ(UBits(0b1, 100).PopCount(), 1); + EXPECT_EQ(UBits(0b10101010101, 16).PopCount(), 6); + EXPECT_EQ(UBits(0, 10000).PopCount(), 0); + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_bits, + ParseNumber("0xffff_ffff_0000_1111_2222_3333_4444_ffff_0000_cccc")); + EXPECT_EQ(wide_bits.PopCount(), 76); +} + +TEST(BitsTest, CountLeandingZeros) { + EXPECT_EQ(Bits().CountLeadingZeros(), 0); + EXPECT_EQ(UBits(0b1, 1).CountLeadingZeros(), 0); + EXPECT_EQ(UBits(0b1, 100).CountLeadingZeros(), 99); + EXPECT_EQ(UBits(0b10101010101, 16).CountLeadingZeros(), 5); + EXPECT_EQ(UBits(0, 10000).CountLeadingZeros(), 10000); +} + +TEST(BitsTest, CountLeandingTrailingOnes) { + EXPECT_EQ(Bits().CountLeadingOnes(), 0); + EXPECT_EQ(UBits(0b1, 1).CountLeadingOnes(), 1); + EXPECT_EQ(UBits(0b1, 100).CountLeadingOnes(), 0); + EXPECT_EQ(UBits(0b11110000, 8).CountLeadingOnes(), 4); + EXPECT_EQ(UBits(0b11110000, 9).CountLeadingOnes(), 0); + + EXPECT_EQ(Bits().CountTrailingOnes(), 0); + EXPECT_EQ(UBits(0b1, 1).CountTrailingOnes(), 1); + EXPECT_EQ(UBits(0b1, 100).CountTrailingOnes(), 1); + EXPECT_EQ(UBits(0b11110000, 8).CountTrailingOnes(), 0); + EXPECT_EQ(UBits(0x3ff, 12345).CountTrailingOnes(), 10); + EXPECT_EQ(UBits(0x3ff0, 12345).CountTrailingOnes(), 0); +} + +TEST(BitsTest, FitsIn) { + EXPECT_TRUE(Bits().FitsInNBitsUnsigned(0)); + EXPECT_TRUE(Bits().FitsInNBitsUnsigned(1)); + EXPECT_TRUE(Bits().FitsInNBitsUnsigned(16)); + EXPECT_TRUE(Bits().FitsInNBitsSigned(0)); + EXPECT_TRUE(Bits().FitsInNBitsSigned(1)); + EXPECT_TRUE(Bits().FitsInNBitsSigned(16)); + + EXPECT_FALSE(UBits(1, 1).FitsInNBitsUnsigned(0)); + EXPECT_TRUE(UBits(1, 1).FitsInNBitsUnsigned(1)); + EXPECT_TRUE(UBits(1, 1).FitsInNBitsUnsigned(16)); + EXPECT_FALSE(UBits(1, 1).FitsInNBitsSigned(0)); + EXPECT_TRUE(UBits(1, 1).FitsInNBitsSigned(1)); + EXPECT_TRUE(UBits(1, 1).FitsInNBitsSigned(16)); + + EXPECT_FALSE(UBits(0xff, 8).FitsInNBitsUnsigned(1)); + EXPECT_FALSE(UBits(0xff, 8).FitsInNBitsUnsigned(4)); + EXPECT_TRUE(UBits(0xff, 8).FitsInNBitsUnsigned(8)); + EXPECT_TRUE(UBits(0xff, 8).FitsInNBitsUnsigned(16)); + // 0xff in 8-bits is -1 so it should fit in any signed width >= 1. + EXPECT_TRUE(UBits(0xff, 8).FitsInNBitsSigned(1)); + EXPECT_TRUE(UBits(0xff, 8).FitsInNBitsSigned(4)); + EXPECT_TRUE(UBits(0xff, 8).FitsInNBitsSigned(8)); + EXPECT_TRUE(UBits(0xff, 8).FitsInNBitsSigned(16)); + + EXPECT_FALSE(UBits(0xff, 16).FitsInNBitsUnsigned(1)); + EXPECT_FALSE(UBits(0xff, 16).FitsInNBitsUnsigned(4)); + EXPECT_TRUE(UBits(0xff, 16).FitsInNBitsUnsigned(8)); + EXPECT_TRUE(UBits(0xff, 16).FitsInNBitsUnsigned(16)); + EXPECT_FALSE(UBits(0xff, 16).FitsInNBitsSigned(1)); + EXPECT_FALSE(UBits(0xff, 16).FitsInNBitsSigned(4)); + EXPECT_FALSE(UBits(0xff, 16).FitsInNBitsSigned(8)); + EXPECT_TRUE(UBits(0xff, 16).FitsInNBitsSigned(16)); + + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_bits, + ParseNumber("0xffff_ffff_0000_1111_2222_3333_4444_ffff_0000_cccc")); + EXPECT_FALSE(wide_bits.FitsInNBitsUnsigned(64)); + EXPECT_FALSE(wide_bits.FitsInNBitsSigned(64)); + EXPECT_FALSE(wide_bits.FitsInUint64()); + EXPECT_FALSE(wide_bits.FitsInInt64()); + EXPECT_TRUE(wide_bits.FitsInNBitsUnsigned(160)); + EXPECT_TRUE(wide_bits.FitsInNBitsSigned(160)); + + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_minus_2, + ParseNumber( + "0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_fffe")); + EXPECT_FALSE(wide_minus_2.FitsInNBitsUnsigned(64)); + EXPECT_TRUE(wide_minus_2.FitsInNBitsSigned(64)); +} + +TEST(BitsTest, ToUint64OrInt64) { + EXPECT_THAT(Bits().ToUint64(), IsOkAndHolds(0)); + EXPECT_THAT(Bits().ToInt64(), IsOkAndHolds(0)); + + EXPECT_THAT(UBits(0xff, 8).ToUint64(), IsOkAndHolds(0xff)); + EXPECT_THAT(UBits(0xff, 8).ToInt64(), IsOkAndHolds(-1)); + + EXPECT_THAT(UBits(0xffffffffffffffffULL, 64).ToUint64(), + IsOkAndHolds(0xffffffffffffffffULL)); + EXPECT_THAT(UBits(0xffffffffffffffffULL, 64).ToInt64(), IsOkAndHolds(-1)); + + EXPECT_THAT(UBits(0xffffffff00000000ULL, 64).ToUint64(), + IsOkAndHolds(0xffffffff00000000ULL)); + EXPECT_THAT(UBits(0xffffffff00000000ULL, 64).ToInt64(), + IsOkAndHolds(-4294967296)); + + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_bits, + ParseNumber("0xffff_ffff_0000_1111_2222_3333_4444_ffff_0000_cccc")); + EXPECT_THAT( + wide_bits.ToUint64(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr( + "Bits value cannot be represented as an unsigned 64-bit value"))); + EXPECT_THAT( + wide_bits.ToInt64(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr( + "Bits value cannot be represented as a signed 64-bit value"))); + + XLS_ASSERT_OK_AND_ASSIGN( + Bits wide_minus_2, + ParseNumber( + "0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff_fffe")); + EXPECT_THAT( + wide_minus_2.ToUint64(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr( + "Bits value cannot be represented as an unsigned 64-bit value"))); + EXPECT_THAT(wide_minus_2.ToInt64(), IsOkAndHolds(-2)); +} + +TEST(BitsTest, ToString) { + Bits empty_bits(0); + EXPECT_EQ(empty_bits.ToString(FormatPreference::kDecimal), "0"); + EXPECT_EQ(empty_bits.ToString(FormatPreference::kHex), "0x0"); + EXPECT_EQ(empty_bits.ToString(FormatPreference::kBinary), "0b0"); + EXPECT_EQ(empty_bits.ToString(FormatPreference::kDecimal, + /*include_bit_count=*/true), + "0 [0 bits]"); + + Bits b1 = UBits(1, 1); + EXPECT_EQ(b1.ToString(FormatPreference::kDecimal), "1"); + EXPECT_EQ(b1.ToString(FormatPreference::kHex), "0x1"); + EXPECT_EQ(b1.ToString(FormatPreference::kBinary), "0b1"); + + EXPECT_EQ(UBits(1, 16).ToString(FormatPreference::kBinary), "0b1"); + EXPECT_EQ(UBits(1, 16).ToString(FormatPreference::kHex), "0x1"); + + Bits b42 = UBits(42, 7); + EXPECT_EQ(b42.ToString(FormatPreference::kDecimal), "42"); + EXPECT_EQ(b42.ToString(FormatPreference::kHex), "0x2a"); + EXPECT_EQ(b42.ToString(FormatPreference::kBinary), "0b10_1010"); + + Bits prime64 = PrimeBits(64); + EXPECT_EQ(prime64.ToString(FormatPreference::kDecimal), + "2892025783495830204"); + EXPECT_EQ(prime64.ToString(FormatPreference::kHex), "0x2822_8a20_a28a_2abc"); + EXPECT_EQ(prime64.ToString(FormatPreference::kBinary), + "0b10_1000_0010_0010_1000_1010_0010_0000_1010_0010_1000_1010_0010_" + "1010_1011_1100"); + + // Test widths wider than 64. Decimal output for wide bit counts is not + // supported. + EXPECT_EQ(PrimeBits(65).ToString(FormatPreference::kHex), + "0x2822_8a20_a28a_2abc"); + EXPECT_EQ(PrimeBits(65).ToString(FormatPreference::kBinary), + "0b10_1000_0010_0010_1000_1010_0010_0000_1010_0010_1000_1010_0010_" + "1010_1011_1100"); + + EXPECT_EQ(PrimeBits(96).ToString(FormatPreference::kHex), + "0x208_8288_2822_8a20_a28a_2abc"); + EXPECT_EQ(PrimeBits(96).ToString(FormatPreference::kBinary), + "0b10_0000_1000_1000_0010_1000_1000_0010_1000_0010_0010_1000_1010_" + "0010_0000_1010_0010_1000_1010_0010_1010_1011_1100"); +} + +TEST(BitsTest, ToRawString) { + Bits empty_bits(0); + EXPECT_EQ(empty_bits.ToRawDigits(FormatPreference::kDecimal), "0"); + EXPECT_EQ(empty_bits.ToRawDigits(FormatPreference::kHex), "0"); + EXPECT_EQ(empty_bits.ToRawDigits(FormatPreference::kBinary), "0"); + EXPECT_EQ(empty_bits.ToRawDigits(FormatPreference::kHex, + /*emit_leading_zeros=*/true), + "0"); + EXPECT_EQ(empty_bits.ToRawDigits(FormatPreference::kBinary, + /*emit_leading_zeros=*/true), + "0"); + + EXPECT_EQ(UBits(1, 16).ToRawDigits(FormatPreference::kBinary), "1"); + EXPECT_EQ(UBits(1, 16).ToRawDigits(FormatPreference::kHex), "1"); + EXPECT_EQ(UBits(1, 16).ToRawDigits(FormatPreference::kBinary, + /*emit_leading_zeros=*/true), + "0000_0000_0000_0001"); + EXPECT_EQ(UBits(1, 16).ToRawDigits(FormatPreference::kHex, + /*emit_leading_zeros=*/true), + "0001"); + + EXPECT_EQ(UBits(0x1b, 13).ToRawDigits(FormatPreference::kBinary), "1_1011"); + EXPECT_EQ(UBits(0x1b, 13).ToRawDigits(FormatPreference::kHex), "1b"); + EXPECT_EQ(UBits(0x1b, 13).ToRawDigits(FormatPreference::kBinary, + /*emit_leading_zeros=*/true), + "0_0000_0001_1011"); + EXPECT_EQ(UBits(0x1b, 13).ToRawDigits(FormatPreference::kHex, + /*emit_leading_zeros=*/true), + "001b"); +} + +TEST(BitsTest, UBitsFactory) { + Bits b0 = UBits(0, 1); + EXPECT_THAT(b0.ToInt64(), IsOkAndHolds(0)); + EXPECT_THAT(b0.ToUint64(), IsOkAndHolds(0)); + + Bits b1 = UBits(1, 1); + // 0b1 as a one-bit twos complement number is -1. + EXPECT_THAT(b1.ToInt64(), IsOkAndHolds(-1)); + EXPECT_THAT(b1.ToUint64(), IsOkAndHolds(1)); + + Bits b2 = UBits(0, 4); + EXPECT_THAT(b2.ToInt64(), IsOkAndHolds(0)); + EXPECT_THAT(b2.ToUint64(), IsOkAndHolds(0)); + + Bits b3 = UBits(0b1111, 4); + EXPECT_THAT(b3.ToInt64(), IsOkAndHolds(-1)); + EXPECT_THAT(b3.ToUint64(), IsOkAndHolds(15)); + + // Verify that 1 in the MSB constructs properly. + Bits b4 = UBits(1ull << 63, 64); + EXPECT_THAT(b4.ToInt64(), IsOkAndHolds(std::numeric_limits::min())); + EXPECT_THAT(b4.ToUint64(), IsOkAndHolds(0x8000000000000000ULL)); +} + +TEST(BitsTest, SBitsFactory) { + Bits b0 = SBits(-1, 1); + EXPECT_THAT(b0.ToInt64(), IsOkAndHolds(-1)); + EXPECT_THAT(b0.ToUint64(), IsOkAndHolds(1)); + + Bits b1 = SBits(-4, 3); + EXPECT_THAT(b1.ToInt64(), IsOkAndHolds(-4)); + EXPECT_THAT(b1.ToUint64(), IsOkAndHolds(4)); + + Bits b2 = SBits(-5, 7); + EXPECT_THAT(b2.ToInt64(), IsOkAndHolds(-5)); + EXPECT_THAT(b2.ToUint64(), IsOkAndHolds(123)); + + Bits b3 = SBits(7, 4); + EXPECT_THAT(b3.ToInt64(), IsOkAndHolds(7)); + EXPECT_THAT(b3.ToUint64(), IsOkAndHolds(7)); + + Bits b4 = SBits(123456, 64); + EXPECT_THAT(b4.ToInt64(), IsOkAndHolds(123456)); + EXPECT_THAT(b4.ToUint64(), IsOkAndHolds(123456)); + + Bits b5 = SBits(-987654321, 64); + EXPECT_THAT(b5.ToInt64(), IsOkAndHolds(-987654321)); + EXPECT_THAT(b5.ToUint64(), IsOkAndHolds(18446744072721897295ULL)); + + Bits b6 = SBits(std::numeric_limits::min(), 64); + EXPECT_THAT(b6.ToInt64(), IsOkAndHolds(std::numeric_limits::min())); + EXPECT_THAT( + b6.ToUint64(), + IsOkAndHolds(static_cast(std::numeric_limits::max()) + 1)); + + Bits b7 = SBits(std::numeric_limits::max(), 64); + EXPECT_THAT(b7.ToInt64(), IsOkAndHolds(std::numeric_limits::max())); + EXPECT_THAT(b7.ToUint64(), IsOkAndHolds(std::numeric_limits::max())); + + const int64 kNLargeBits = 345; + Bits b8 = SBits(-4, kNLargeBits); + for (int64 i = 0; i < kNLargeBits; ++i) { + // All except the two LSb's should be set. + if (i < 2) { + EXPECT_EQ(b8.Get(i), 0) << "Bit " << i << " should be 0: " << b8; + } else { + EXPECT_EQ(b8.Get(i), 1) << "Bit " << i << " should be 1: " << b8; + } + } + + Bits b9 = SBits(7, kNLargeBits); + for (int64 i = 0; i < kNLargeBits; ++i) { + // Only the three LSb's should be set. + if (i < 3) { + EXPECT_EQ(b9.Get(i), 1) << "Bit " << i << " should be 1: " << b9; + } else { + EXPECT_EQ(b9.Get(i), 0) << "Bit " << i << " should be 0: " << b9; + } + } +} + +TEST(BitsTest, PowerOfTwo) { + EXPECT_THAT(Bits::PowerOfTwo(0, 1).ToUint64(), IsOkAndHolds(1)); + EXPECT_THAT(Bits::PowerOfTwo(1, 5).ToUint64(), IsOkAndHolds(2)); + EXPECT_THAT(Bits::PowerOfTwo(6, 1024).ToUint64(), IsOkAndHolds(64)); + EXPECT_THAT(Bits::PowerOfTwo(63, 1024).ToUint64(), IsOkAndHolds(1ULL << 63)); + + Bits big_bits = Bits::PowerOfTwo(1234, 5000); + for (int64 i = 0; i < 5000; ++i) { + EXPECT_EQ(big_bits.Get(i), i == 1234); + } +} + +TEST(BitsTest, MinimumBits) { + EXPECT_EQ(Bits::MinBitCountUnsigned(0), 0); + EXPECT_EQ(Bits::MinBitCountSigned(0), 0); + + EXPECT_EQ(Bits::MinBitCountUnsigned(1), 1); + EXPECT_EQ(Bits::MinBitCountSigned(1), 2); + + EXPECT_EQ(Bits::MinBitCountUnsigned(2), 2); + EXPECT_EQ(Bits::MinBitCountSigned(2), 3); + + EXPECT_EQ(Bits::MinBitCountUnsigned(255), 8); + EXPECT_EQ(Bits::MinBitCountSigned(255), 9); + + EXPECT_EQ(Bits::MinBitCountUnsigned(std::numeric_limits::max()), 63); + EXPECT_EQ(Bits::MinBitCountSigned(std::numeric_limits::max()), 64); + + EXPECT_EQ(Bits::MinBitCountUnsigned(std::numeric_limits::max()), 64); + + EXPECT_EQ(Bits::MinBitCountSigned(-1), 1); + EXPECT_EQ(Bits::MinBitCountSigned(-2), 2); + EXPECT_EQ(Bits::MinBitCountSigned(-3), 3); + EXPECT_EQ(Bits::MinBitCountSigned(-4), 3); + EXPECT_EQ(Bits::MinBitCountSigned(-5), 4); + EXPECT_EQ(Bits::MinBitCountSigned(-128), 8); + EXPECT_EQ(Bits::MinBitCountSigned(-129), 9); + EXPECT_EQ(Bits::MinBitCountSigned(std::numeric_limits::min()), 64); +} + +TEST(BitsTest, Equality) { + EXPECT_TRUE(Bits(0) == Bits(0)); + EXPECT_FALSE(Bits(0) != Bits(0)); + + EXPECT_TRUE(UBits(0, 5) == UBits(0, 5)); + EXPECT_FALSE(UBits(0, 5) == UBits(0, 3)); + EXPECT_FALSE(UBits(3, 5) == UBits(0, 5)); + EXPECT_TRUE(UBits(123456, 444) == UBits(123456, 444)); + EXPECT_FALSE(UBits(123456, 444) == UBits(123456, 445)); + EXPECT_TRUE(PrimeBits(12345) == PrimeBits(12345)); + EXPECT_FALSE(PrimeBits(12345) == PrimeBits(12346)); +} + +TEST(BitsTest, AllZerosOrOnes) { + Bits empty_bits(0); + EXPECT_TRUE(empty_bits.IsAllZeros()); + EXPECT_TRUE(empty_bits.IsAllOnes()); + + Bits b0 = UBits(0, 1); + EXPECT_TRUE(b0.IsAllZeros()); + EXPECT_FALSE(b0.IsAllOnes()); + + Bits b1 = UBits(1, 1); + EXPECT_FALSE(b1.IsAllZeros()); + EXPECT_TRUE(b1.IsAllOnes()); + + EXPECT_TRUE(UBits(0xffff, 16).IsAllOnes()); + EXPECT_FALSE(UBits(0xffef, 16).IsAllOnes()); + + EXPECT_TRUE(UBits(0, 16).IsAllZeros()); + EXPECT_FALSE(UBits(0x800, 16).IsAllZeros()); + + EXPECT_TRUE(Bits(1234).IsAllZeros()); + EXPECT_FALSE(PrimeBits(1234).IsAllZeros()); + + EXPECT_TRUE(Bits::AllOnes(0).IsAllOnes()); + EXPECT_EQ(Bits::AllOnes(0).bit_count(), 0); + EXPECT_TRUE(Bits::AllOnes(1).IsAllOnes()); + EXPECT_EQ(Bits::AllOnes(1).bit_count(), 1); + + EXPECT_TRUE(Bits::AllOnes(32).IsAllOnes()); + EXPECT_EQ(Bits::AllOnes(32).bit_count(), 32); + EXPECT_TRUE(Bits::AllOnes(1234).IsAllOnes()); + EXPECT_EQ(Bits::AllOnes(1234).bit_count(), 1234); +} + +TEST(BitsTest, Slice) { + Bits empty_bits(0); + Bits b0 = UBits(0, 1); + Bits b1 = UBits(1, 1); + + EXPECT_EQ(b0, b0.Slice(0, 1)); + EXPECT_EQ(b1, b1.Slice(0, 1)); + EXPECT_EQ(empty_bits, b0.Slice(0, 0)); + + Bits deadbeef = UBits(0xdeadbeef12345678ULL, 64); + EXPECT_EQ(deadbeef, deadbeef.Slice(0, 64)); + EXPECT_EQ(UBits(0xdead, 16), deadbeef.Slice(48, 16)); + EXPECT_EQ(UBits(0x78, 8), deadbeef.Slice(0, 8)); + EXPECT_EQ(UBits(7716, 13), deadbeef.Slice(23, 13)); + + Bits big_prime = PrimeBits(12345); + EXPECT_EQ(big_prime, big_prime.Slice(0, big_prime.bit_count())); + // Random primes in some range: 2377 2381 2383 2389 2393 + // Slice out this range and verify the appropriate 5 bits are set. + EXPECT_EQ(big_prime.Slice(2377, 17).ToString(FormatPreference::kBinary), + "0b1_0001_0000_0101_0001"); +} + +TEST(BitsTest, ValueTest) { + Value b0 = Value(UBits(2, 4)); + EXPECT_EQ(b0.bits(), UBits(2, 4)); + + Value b1 = Value::Tuple({ + Value(UBits(2, 4)), + Value(UBits(100, 8)), + }); + absl::Span tuple_values = b1.elements(); + EXPECT_EQ(tuple_values[0].bits(), UBits(2, 4)); + EXPECT_EQ(tuple_values[1].bits(), UBits(100, 8)); +} + +TEST(BitsTest, ValueTupleEquality) { + Value b1 = Value::Tuple({ + Value(UBits(2, 4)), + Value(UBits(100, 8)), + }); + Value b2 = Value::Tuple({ + Value(UBits(2, 4)), + Value(UBits(100, 8)), + }); + EXPECT_EQ(b1, b2); + + // Different size. + Value b3 = Value::Tuple({Value(UBits(2, 4))}); + EXPECT_NE(b1, b3); + + // Different value. + Value b4 = Value::Tuple({ + Value(UBits(3, 4)), + Value(UBits(100, 8)), + }); + EXPECT_NE(b1, b4); + + // Different bitwidth. + Value b5 = Value::Tuple({ + Value(UBits(2, 4)), + Value(UBits(100, 7)), + }); + EXPECT_NE(b1, b5); +} + +TEST(BitsTest, ToBitVectorAndBack) { + EXPECT_TRUE(Bits().ToBitVector().empty()); + EXPECT_THAT(UBits(0, 1).ToBitVector(), ElementsAre(false)); + EXPECT_THAT(UBits(1, 1).ToBitVector(), ElementsAre(true)); + EXPECT_THAT( + UBits(0, 8).ToBitVector(), + ElementsAre(false, false, false, false, false, false, false, false)); + EXPECT_THAT(UBits(0b11001, 5).ToBitVector(), + ElementsAre(true, false, false, true, true)); + + EXPECT_EQ(Bits(Bits().ToBitVector()), Bits()); + EXPECT_EQ(Bits(UBits(0, 1).ToBitVector()), UBits(0, 1)); + EXPECT_EQ(Bits(UBits(1, 1).ToBitVector()), UBits(1, 1)); + EXPECT_EQ(Bits(UBits(0b11001, 5).ToBitVector()), UBits(0b11001, 5)); + EXPECT_EQ(Bits(UBits(0b11001, 1234).ToBitVector()), UBits(0b11001, 1234)); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/dfs_visitor.cc b/xls/ir/dfs_visitor.cc new file mode 100644 index 0000000000..0b704aab17 --- /dev/null +++ b/xls/ir/dfs_visitor.cc @@ -0,0 +1,217 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/dfs_visitor.h" + +namespace xls { + +absl::Status DfsVisitorWithDefault::HandleAdd(BinOp* add) { + return DefaultHandler(add); +} + +absl::Status DfsVisitorWithDefault::HandleAndReduce( + BitwiseReductionOp* and_reduce) { + return DefaultHandler(and_reduce); +} + +absl::Status DfsVisitorWithDefault::HandleOrReduce( + BitwiseReductionOp* or_reduce) { + return DefaultHandler(or_reduce); +} + +absl::Status DfsVisitorWithDefault::HandleXorReduce( + BitwiseReductionOp* xor_reduce) { + return DefaultHandler(xor_reduce); +} + +absl::Status DfsVisitorWithDefault::HandleNaryAnd(NaryOp* and_op) { + return DefaultHandler(and_op); +} + +absl::Status DfsVisitorWithDefault::HandleNaryNand(NaryOp* nand_op) { + return DefaultHandler(nand_op); +} + +absl::Status DfsVisitorWithDefault::HandleNaryNor(NaryOp* nor_op) { + return DefaultHandler(nor_op); +} + +absl::Status DfsVisitorWithDefault::HandleNaryOr(NaryOp* or_op) { + return DefaultHandler(or_op); +} + +absl::Status DfsVisitorWithDefault::HandleNaryXor(NaryOp* xor_op) { + return DefaultHandler(xor_op); +} + +absl::Status DfsVisitorWithDefault::HandleArray(Array* array) { + return DefaultHandler(array); +} + +absl::Status DfsVisitorWithDefault::HandleArrayIndex(ArrayIndex* index) { + return DefaultHandler(index); +} + +absl::Status DfsVisitorWithDefault::HandleBitSlice(BitSlice* bit_slice) { + return DefaultHandler(bit_slice); +} + +absl::Status DfsVisitorWithDefault::HandleConcat(Concat* concat) { + return DefaultHandler(concat); +} + +absl::Status DfsVisitorWithDefault::HandleCountedFor(CountedFor* counted_for) { + return DefaultHandler(counted_for); +} + +absl::Status DfsVisitorWithDefault::HandleDecode(Decode* decode) { + return DefaultHandler(decode); +} +absl::Status DfsVisitorWithDefault::HandleEncode(Encode* encode) { + return DefaultHandler(encode); +} + +absl::Status DfsVisitorWithDefault::HandleEq(CompareOp* eq) { + return DefaultHandler(eq); +} + +absl::Status DfsVisitorWithDefault::HandleIdentity(UnOp* identity) { + return DefaultHandler(identity); +} + +absl::Status DfsVisitorWithDefault::HandleInvoke(Invoke* invoke) { + return DefaultHandler(invoke); +} + +absl::Status DfsVisitorWithDefault::HandleLiteral(Literal* literal) { + return DefaultHandler(literal); +} + +absl::Status DfsVisitorWithDefault::HandleMap(Map* map) { + return DefaultHandler(map); +} + +absl::Status DfsVisitorWithDefault::HandleNe(CompareOp* ne) { + return DefaultHandler(ne); +} + +absl::Status DfsVisitorWithDefault::HandleNeg(UnOp* neg) { + return DefaultHandler(neg); +} + +absl::Status DfsVisitorWithDefault::HandleNot(UnOp* not_op) { + return DefaultHandler(not_op); +} + +absl::Status DfsVisitorWithDefault::HandleOneHot(OneHot* one_hot) { + return DefaultHandler(one_hot); +} + +absl::Status DfsVisitorWithDefault::HandleOneHotSel(OneHotSelect* sel) { + return DefaultHandler(sel); +} + +absl::Status DfsVisitorWithDefault::HandleParam(Param* param) { + return DefaultHandler(param); +} + +absl::Status DfsVisitorWithDefault::HandleReverse(UnOp* reverse) { + return DefaultHandler(reverse); +} + +absl::Status DfsVisitorWithDefault::HandleSDiv(BinOp* div) { + return DefaultHandler(div); +} + +absl::Status DfsVisitorWithDefault::HandleSGe(CompareOp* ge) { + return DefaultHandler(ge); +} + +absl::Status DfsVisitorWithDefault::HandleSGt(CompareOp* gt) { + return DefaultHandler(gt); +} + +absl::Status DfsVisitorWithDefault::HandleSLe(CompareOp* le) { + return DefaultHandler(le); +} + +absl::Status DfsVisitorWithDefault::HandleSLt(CompareOp* lt) { + return DefaultHandler(lt); +} + +absl::Status DfsVisitorWithDefault::HandleUMul(ArithOp* mul) { + return DefaultHandler(mul); +} + +absl::Status DfsVisitorWithDefault::HandleSel(Select* sel) { + return DefaultHandler(sel); +} + +absl::Status DfsVisitorWithDefault::HandleShll(BinOp* shll) { + return DefaultHandler(shll); +} + +absl::Status DfsVisitorWithDefault::HandleShra(BinOp* shra) { + return DefaultHandler(shra); +} + +absl::Status DfsVisitorWithDefault::HandleShrl(BinOp* shrl) { + return DefaultHandler(shrl); +} + +absl::Status DfsVisitorWithDefault::HandleSignExtend(ExtendOp* sign_ext) { + return DefaultHandler(sign_ext); +} + +absl::Status DfsVisitorWithDefault::HandleSMul(ArithOp* mul) { + return DefaultHandler(mul); +} + +absl::Status DfsVisitorWithDefault::HandleSub(BinOp* sub) { + return DefaultHandler(sub); +} + +absl::Status DfsVisitorWithDefault::HandleTuple(Tuple* tuple) { + return DefaultHandler(tuple); +} + +absl::Status DfsVisitorWithDefault::HandleTupleIndex(TupleIndex* index) { + return DefaultHandler(index); +} + +absl::Status DfsVisitorWithDefault::HandleUDiv(BinOp* div) { + return DefaultHandler(div); +} + +absl::Status DfsVisitorWithDefault::HandleUGe(CompareOp* ge) { + return DefaultHandler(ge); +} + +absl::Status DfsVisitorWithDefault::HandleUGt(CompareOp* gt) { + return DefaultHandler(gt); +} + +absl::Status DfsVisitorWithDefault::HandleULe(CompareOp* le) { + return DefaultHandler(le); +} + +absl::Status DfsVisitorWithDefault::HandleULt(CompareOp* lt) { + return DefaultHandler(lt); +} + +absl::Status DfsVisitorWithDefault::HandleZeroExtend(ExtendOp* zero_ext) { + return DefaultHandler(zero_ext); +} + +} // namespace xls diff --git a/xls/ir/dfs_visitor.h b/xls/ir/dfs_visitor.h new file mode 100644 index 0000000000..a5049f9ac8 --- /dev/null +++ b/xls/ir/dfs_visitor.h @@ -0,0 +1,163 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_DFS_VISITOR_H_ +#define THIRD_PARTY_XLS_IR_DFS_VISITOR_H_ + +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "xls/ir/node.h" +#include "xls/ir/nodes.h" + +namespace xls { + +// Abstract interface for a DFS post-order node visitor. See Node::Accept() and +// Function::Accept. Dispatches on Op. +class DfsVisitor { + public: + virtual ~DfsVisitor() = default; + + virtual absl::Status HandleAdd(BinOp* add) = 0; + virtual absl::Status HandleAndReduce(BitwiseReductionOp* and_reduce) = 0; + virtual absl::Status HandleArray(Array* array) = 0; + virtual absl::Status HandleArrayIndex(ArrayIndex* index) = 0; + virtual absl::Status HandleBitSlice(BitSlice* bit_slice) = 0; + virtual absl::Status HandleConcat(Concat* concat) = 0; + virtual absl::Status HandleCountedFor(CountedFor* counted_for) = 0; + virtual absl::Status HandleDecode(Decode* decode) = 0; + virtual absl::Status HandleEncode(Encode* encode) = 0; + virtual absl::Status HandleEq(CompareOp* eq) = 0; + virtual absl::Status HandleIdentity(UnOp* identity) = 0; + virtual absl::Status HandleInvoke(Invoke* invoke) = 0; + virtual absl::Status HandleLiteral(Literal* literal) = 0; + virtual absl::Status HandleMap(Map* map) = 0; + virtual absl::Status HandleNaryAnd(NaryOp* and_op) = 0; + virtual absl::Status HandleNaryNand(NaryOp* nand_op) = 0; + virtual absl::Status HandleNaryNor(NaryOp* nor_op) = 0; + virtual absl::Status HandleNaryOr(NaryOp* or_op) = 0; + virtual absl::Status HandleNaryXor(NaryOp* xor_op) = 0; + virtual absl::Status HandleNe(CompareOp* ne) = 0; + virtual absl::Status HandleNeg(UnOp* neg) = 0; + virtual absl::Status HandleNot(UnOp* not_op) = 0; + virtual absl::Status HandleOneHot(OneHot* one_hot) = 0; + virtual absl::Status HandleOneHotSel(OneHotSelect* sel) = 0; + virtual absl::Status HandleOrReduce(BitwiseReductionOp* or_reduce) = 0; + virtual absl::Status HandleParam(Param* param) = 0; + virtual absl::Status HandleReverse(UnOp* reverse) = 0; + virtual absl::Status HandleSDiv(BinOp* div) = 0; + virtual absl::Status HandleSGe(CompareOp* ge) = 0; + virtual absl::Status HandleSGt(CompareOp* gt) = 0; + virtual absl::Status HandleSLe(CompareOp* le) = 0; + virtual absl::Status HandleSLt(CompareOp* lt) = 0; + virtual absl::Status HandleSel(Select* sel) = 0; + virtual absl::Status HandleShll(BinOp* shll) = 0; + virtual absl::Status HandleShra(BinOp* shra) = 0; + virtual absl::Status HandleShrl(BinOp* shrl) = 0; + virtual absl::Status HandleSignExtend(ExtendOp* sign_ext) = 0; + virtual absl::Status HandleSMul(ArithOp* mul) = 0; + virtual absl::Status HandleSub(BinOp* sub) = 0; + virtual absl::Status HandleTuple(Tuple* tuple) = 0; + virtual absl::Status HandleTupleIndex(TupleIndex* index) = 0; + virtual absl::Status HandleUDiv(BinOp* div) = 0; + virtual absl::Status HandleUGe(CompareOp* ge) = 0; + virtual absl::Status HandleUGt(CompareOp* gt) = 0; + virtual absl::Status HandleULe(CompareOp* le) = 0; + virtual absl::Status HandleULt(CompareOp* lt) = 0; + virtual absl::Status HandleUMul(ArithOp* mul) = 0; + virtual absl::Status HandleXorReduce(BitwiseReductionOp* xor_reduce) = 0; + virtual absl::Status HandleZeroExtend(ExtendOp* zero_ext) = 0; + + // Returns true if the given node has been visited. + bool IsVisited(Node* node) const { return visited_.contains(node); } + + // Marks the given node as visited. + void MarkVisited(Node* node) { visited_.insert(node); } + + // Returns whether the given node is on path from the root of the traversal + // to the currently visited node. Used to identify cycles in the graph. + bool IsTraversing(Node* node) const { return traversing_.contains(node); } + + // Sets/unsets whether this node is being traversed through. + void SetTraversing(Node* node) { traversing_.insert(node); } + void UnsetTraversing(Node* node) { traversing_.erase(node); } + + private: + // Set of nodes which have been visited. + absl::flat_hash_set visited_; + + // Set of nodes which are being traversed through. + absl::flat_hash_set traversing_; +}; + +// Visitor with a default action. If the Handle method is not overridden +// in the derived class then DefaultHandler is called when visiting nodes of +// type . +class DfsVisitorWithDefault : public DfsVisitor { + public: + virtual absl::Status DefaultHandler(Node* node) = 0; + + absl::Status HandleAdd(BinOp* add) override; + absl::Status HandleAndReduce(BitwiseReductionOp* and_reduce) override; + absl::Status HandleArray(Array* array) override; + absl::Status HandleArrayIndex(ArrayIndex* index) override; + absl::Status HandleBitSlice(BitSlice* bit_slice) override; + absl::Status HandleConcat(Concat* concat) override; + absl::Status HandleCountedFor(CountedFor* counted_for) override; + absl::Status HandleDecode(Decode* decode) override; + absl::Status HandleEncode(Encode* encode) override; + absl::Status HandleEq(CompareOp* eq) override; + absl::Status HandleIdentity(UnOp* identity) override; + absl::Status HandleInvoke(Invoke* invoke) override; + absl::Status HandleLiteral(Literal* literal) override; + absl::Status HandleMap(Map* map) override; + absl::Status HandleNaryAnd(NaryOp* and_op) override; + absl::Status HandleNaryNand(NaryOp* and_op) override; + absl::Status HandleNaryNor(NaryOp* nor_op) override; + absl::Status HandleNaryOr(NaryOp* or_op) override; + absl::Status HandleNaryXor(NaryOp* xor_op) override; + absl::Status HandleNe(CompareOp* ne) override; + absl::Status HandleNeg(UnOp* neg) override; + absl::Status HandleNot(UnOp* not_op) override; + absl::Status HandleOneHot(OneHot* one_hot) override; + absl::Status HandleOneHotSel(OneHotSelect* sel) override; + absl::Status HandleOrReduce(BitwiseReductionOp* or_reduce) override; + absl::Status HandleParam(Param* param) override; + absl::Status HandleReverse(UnOp* reverse) override; + absl::Status HandleSDiv(BinOp* div) override; + absl::Status HandleSGe(CompareOp* ge) override; + absl::Status HandleSGt(CompareOp* gt) override; + absl::Status HandleSLe(CompareOp* le) override; + absl::Status HandleSLt(CompareOp* lt) override; + absl::Status HandleSel(Select* sel) override; + absl::Status HandleShll(BinOp* shll) override; + absl::Status HandleShra(BinOp* shra) override; + absl::Status HandleShrl(BinOp* shrl) override; + absl::Status HandleSignExtend(ExtendOp* sign_ext) override; + absl::Status HandleSMul(ArithOp* mul) override; + absl::Status HandleSub(BinOp* sub) override; + absl::Status HandleTuple(Tuple* tuple) override; + absl::Status HandleTupleIndex(TupleIndex* index) override; + absl::Status HandleUDiv(BinOp* div) override; + absl::Status HandleUGe(CompareOp* ge) override; + absl::Status HandleUGt(CompareOp* gt) override; + absl::Status HandleULe(CompareOp* le) override; + absl::Status HandleULt(CompareOp* lt) override; + absl::Status HandleUMul(ArithOp* mul) override; + absl::Status HandleXorReduce(BitwiseReductionOp* xor_reduce) override; + absl::Status HandleZeroExtend(ExtendOp* zero_ext) override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_DFS_VISITOR_H_ diff --git a/xls/ir/dfs_visitor_test.cc b/xls/ir/dfs_visitor_test.cc new file mode 100644 index 0000000000..944d4b0598 --- /dev/null +++ b/xls/ir/dfs_visitor_test.cc @@ -0,0 +1,168 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/dfs_visitor.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/node.h" +#include "xls/ir/nodes.h" +#include "xls/ir/package.h" + +namespace xls { +namespace { + +// A testing visitor which records which nodes it has visited. +class TestVisitor : public DfsVisitorWithDefault { + public: + absl::Status DefaultHandler(Node* node) override { + XLS_VLOG(1) << "Visiting " << node->GetName(); + visited_.push_back(node); + visited_set_.insert(node); + return absl::OkStatus(); + } + + // Returns the ordered set of visited nodes. + const std::vector& visited() const { return visited_; } + + int64 visited_count() const { return visited_.size(); } + + // Returns the total number of unique nodes visited. + int64 unique_visited_count() const { return visited_set_.size(); } + + private: + std::vector visited_; + absl::flat_hash_set visited_set_; +}; + +class DfsVisitorTest : public IrTestBase {}; + +TEST_F(DfsVisitorTest, VisitSingleNode) { + std::string input = R"( +fn single_node() -> bits[32] { + ret literal.1: bits[32] = literal(value=42) +})"; + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(input, p.get())); + + TestVisitor v; + EXPECT_FALSE(v.IsVisited(f->return_value())); + XLS_ASSERT_OK(f->return_value()->Accept(&v)); + EXPECT_TRUE(v.IsVisited(f->return_value())); + EXPECT_EQ(1, v.visited_count()); + EXPECT_THAT(v.visited(), ::testing::ElementsAre(f->return_value())); + + // Calling it again should not revisit the node. + XLS_ASSERT_OK(f->return_value()->Accept(&v)); + EXPECT_EQ(1, v.visited_count()); +} + +TEST_F(DfsVisitorTest, VisitGraph) { + std::string input = R"( +fn graph(p: bits[42], q: bits[42]) -> bits[42] { + and.1: bits[42] = and(p, q) + add.2: bits[42] = add(and.1, q) + ret sub.3: bits[42] = sub(add.2, add.2) +} +)"; + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(input, p.get())); + + { + // Visit from the root. + TestVisitor v; + XLS_ASSERT_OK(f->return_value()->Accept(&v)); + EXPECT_EQ(5, v.visited_count()); + EXPECT_EQ(5, v.unique_visited_count()); + EXPECT_EQ(FindNode("sub.3", f), v.visited().back()); + EXPECT_THAT(v.visited(), + ::testing::UnorderedElementsAre( + FindNode("sub.3", f), FindNode("add.2", f), + FindNode("and.1", f), FindNode("p", f), FindNode("q", f))); + } + + { + // Visit from an interior node. + TestVisitor v; + XLS_ASSERT_OK(FindNode("and.1", f)->Accept(&v)); + EXPECT_EQ(3, v.visited_count()); + EXPECT_THAT(v.visited(), + ::testing::UnorderedElementsAre( + FindNode("and.1", f), FindNode("p", f), FindNode("q", f))); + EXPECT_FALSE(v.IsVisited(FindNode("add.2", f))); + EXPECT_FALSE(v.IsVisited(FindNode("sub.3", f))); + } +} + +TEST_F(DfsVisitorTest, FunctionVisit) { + // The umul operation is dead. + std::string input = R"( +fn graph(p: bits[42], q: bits[42]) -> bits[42] { + and.1: bits[42] = and(p, q) + add.2: bits[42] = add(and.1, q) + umul.4: bits[42] = umul(add.2, p) + ret sub.3: bits[42] = sub(add.2, add.2) +} +)"; + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(input, p.get())); + + { + // Visit from the root should only visit 5 nodes, not the dead umul. + TestVisitor v; + XLS_ASSERT_OK(f->return_value()->Accept(&v)); + EXPECT_EQ(5, v.visited_count()); + EXPECT_FALSE(v.IsVisited(FindNode("umul.4", f))); + } + + { + // Calling Function::Accept should visit all nodes in the graph including + // the dead multiply. + TestVisitor v; + XLS_ASSERT_OK(f->Accept(&v)); + EXPECT_EQ(6, v.visited_count()); + EXPECT_TRUE(v.IsVisited(FindNode("umul.4", f))); + } +} + +TEST_F(DfsVisitorTest, GraphWithCycle) { + std::string input = R"( +fn graph(p: bits[42], q: bits[42]) -> bits[42] { + and.1: bits[42] = and(p, q) + add.2: bits[42] = add(and.1, q) + ret sub.3: bits[42] = sub(add.2, add.2) +} +)"; + // We don't want a VerifiedPackage because we are introducing a cycle. + auto p = absl::make_unique(TestName()); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(input, p.get())); + + // Introduce a cycle in the graph. + ASSERT_TRUE(FindNode("and.1", f) + ->ReplaceOperand(FindNode("p", f), FindNode("add.2", f))); + TestVisitor v; + EXPECT_THAT(std::string(f->Accept(&v).message()), + ::testing::HasSubstr(std::string("Cycle detected"))); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/fileno.h b/xls/ir/fileno.h new file mode 100644 index 0000000000..63e5dbaa59 --- /dev/null +++ b/xls/ir/fileno.h @@ -0,0 +1,32 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Define strong_ints used by source locators. + +#ifndef THIRD_PARTY_XLS_IR_FILENO_H_ +#define THIRD_PARTY_XLS_IR_FILENO_H_ + +#include "absl/strings/string_view.h" +#include "xls/common/integral_types.h" +#include "xls/common/strong_int.h" + +namespace xls { + +DEFINE_STRONG_INT_TYPE(Fileno, int32); +DEFINE_STRONG_INT_TYPE(Lineno, int32); +DEFINE_STRONG_INT_TYPE(Colno, int32); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_FILENO_H_ diff --git a/xls/ir/format_preference.cc b/xls/ir/format_preference.cc new file mode 100644 index 0000000000..029c22e8a9 --- /dev/null +++ b/xls/ir/format_preference.cc @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/format_preference.h" + +namespace xls { + +absl::string_view FormatPreferenceToString(FormatPreference preference) { + switch (preference) { + case FormatPreference::kDefault: + return "default"; + case FormatPreference::kBinary: + return "binary"; + case FormatPreference::kDecimal: + return "decimal"; + case FormatPreference::kHex: + return "hex"; + default: + return ""; + } +} + +} // namespace xls diff --git a/xls/ir/format_preference.h b/xls/ir/format_preference.h new file mode 100644 index 0000000000..84670aac70 --- /dev/null +++ b/xls/ir/format_preference.h @@ -0,0 +1,44 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_FORMAT_PREFERENCE_H_ +#define THIRD_PARTY_XLS_IR_FORMAT_PREFERENCE_H_ + +#include + +#include "absl/strings/string_view.h" + +namespace xls { + +// Explains what formatting technique should be used to convert a bit value into +// string form. +enum class FormatPreference { + // Default formatting is decimal for values which fit in 64 bits. Otherwise + // hexadecimal is used. + kDefault, + kBinary, + kDecimal, + kHex, +}; + +absl::string_view FormatPreferenceToString(FormatPreference preference); + +inline std::ostream& operator<<(std::ostream& os, FormatPreference preference) { + os << FormatPreferenceToString(preference); + return os; +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_FORMAT_PREFERENCE_H_ diff --git a/xls/ir/function.cc b/xls/ir/function.cc new file mode 100644 index 0000000000..5ef931262d --- /dev/null +++ b/xls/ir/function.cc @@ -0,0 +1,240 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/function.h" + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/node_iterator.h" +#include "xls/ir/package.h" + +using absl::StrAppend; + +namespace xls { + +std::string Function::DumpIr(bool recursive) const { + std::string nested_funcs = ""; + std::string res = "fn " + name() + "("; + std::vector param_strings; + for (Param* param : params_) { + param_strings.push_back(param->name() + ": " + + param->GetType()->ToString()); + } + StrAppend(&res, absl::StrJoin(param_strings, ", ")); + StrAppend(&res, ") -> "); + + if (return_value() != nullptr) { + StrAppend(&res, return_value()->GetType()->ToString()); + } + StrAppend(&res, " {\n"); + + for (Node* node : TopoSort(const_cast(this))) { + if (node->op() == Op::kParam && node == return_value()) { + absl::StrAppendFormat(&res, " ret param.%d: %s = param(name=%s)\n", + node->id(), node->GetType()->ToString(), + node->As()->name()); + continue; + } + if (node->op() == Op::kParam) { + continue; // Already accounted for in the signature. + } + if (recursive && (node->op() == Op::kCountedFor)) { + nested_funcs += node->As()->body()->DumpIr() + "\n"; + } + if (recursive && (node->op() == Op::kMap)) { + nested_funcs += node->As()->to_apply()->DumpIr() + "\n"; + } + if (recursive && (node->op() == Op::kInvoke)) { + nested_funcs += node->As()->to_apply()->DumpIr() + "\n"; + } + StrAppend(&res, " ", node == return_value() ? "ret " : "", + node->ToString(), "\n"); + } + + StrAppend(&res, "}\n"); + return nested_funcs + res; +} + +FunctionType* Function::GetType() { + std::vector arg_types; + for (Param* param : params()) { + arg_types.push_back(param->GetType()); + } + XLS_CHECK(return_value() != nullptr); + return package_->GetFunctionType(arg_types, return_value()->GetType()); +} + +xabsl::StatusOr Function::GetParamByName( + absl::string_view param_name) const { + for (Param* param : params()) { + if (param->name() == param_name) { + return param; + } + } + return absl::NotFoundError( + absl::StrFormat("Function '%s' does not have a paramater named '%s'", + name(), param_name)); +} + +xabsl::StatusOr Function::GetParamIndex(Param* param) const { + auto it = std::find(params_.begin(), params_.end(), param); + if (it == params_.end()) { + return absl::InvalidArgumentError( + "Given param is not a member of this function: " + param->ToString()); + } + return std::distance(params_.begin(), it); +} + +xabsl::StatusOr Function::GetNode(absl::string_view standard_node_name) { + for (Node* node : nodes()) { + if (node->GetName() == standard_node_name) { + return node; + } + } + for (auto& param : params()) { + if (param->As()->name() == standard_node_name) { + return param; + } + } + return absl::InvalidArgumentError( + absl::StrFormat("GetNode(%s) failed.", standard_node_name)); +} + +absl::Status Function::RemoveNode(Node* node, bool remove_param_ok) { + XLS_RET_CHECK(node->users().empty()); + XLS_RET_CHECK_NE(node, return_value()); + if (node->Is()) { + XLS_RET_CHECK(remove_param_ok) + << "Attempting to remove parameter when !remove_param_ok: " << *node; + } + std::vector unique_operands; + for (Node* operand : node->operands()) { + if (!absl::c_linear_search(unique_operands, operand)) { + unique_operands.push_back(operand); + } + } + for (Node* operand : unique_operands) { + operand->RemoveUser(node); + } + auto node_it = node_iterators_.find(node); + XLS_RET_CHECK(node_it != node_iterators_.end()); + nodes_.erase(node_it->second); + node_iterators_.erase(node_it); + if (remove_param_ok) { + params_.erase(std::remove(params_.begin(), params_.end(), node), + params_.end()); + } + + return absl::OkStatus(); +} + +absl::Status Function::Accept(DfsVisitor* visitor) { + for (Node* node : nodes()) { + if (node->users().empty()) { + XLS_RETURN_IF_ERROR(node->Accept(visitor)); + } + } + return absl::OkStatus(); +} + +xabsl::StatusOr Function::Clone(absl::string_view new_name) const { + absl::flat_hash_map original_to_clone; + Function* cloned_function = + package()->AddFunction(absl::make_unique(new_name, package())); + for (Node* node : TopoSort(const_cast(this))) { + std::vector cloned_operands; + for (Node* operand : node->operands()) { + cloned_operands.push_back(original_to_clone.at(operand)); + } + XLS_ASSIGN_OR_RETURN(original_to_clone[node], + node->Clone(cloned_operands, cloned_function)); + } + cloned_function->set_return_value(original_to_clone.at(return_value())); + return cloned_function; +} + +// Helper function for IsDefinitelyEqualTo. Recursively compares 'node' and +// 'other_node' and their operands using Node::IsDefinitelyEqualTo. +// 'matched_pairs' is used to memoize the result of the comparison. +static bool IsEqualRecurse( + const Node* node, const Node* other_node, + absl::flat_hash_map* matched_pairs) { + auto it = matched_pairs->find(node); + if (it != matched_pairs->end()) { + return it->second == other_node; + } + + if (!node->IsDefinitelyEqualTo(other_node)) { + XLS_VLOG(2) << absl::StrFormat( + "Function %s != %s: node %s != %s", node->function()->name(), + other_node->function()->name(), node->GetName(), other_node->GetName()); + return false; + } + + for (int64 i = 0; i < node->operand_count(); ++i) { + if (!IsEqualRecurse(node->operand(i), other_node->operand(i), + matched_pairs)) { + return false; + } + } + (*matched_pairs)[node] = other_node; + return true; +} + +bool Function::IsDefinitelyEqualTo(const Function* other) const { + if (this == other) { + XLS_VLOG(2) << absl::StrFormat("Function %s == %s: same pointer", name(), + other->name()); + return true; + } + + // Must have the types of parameters in the same order. + if (params().size() != other->params().size()) { + XLS_VLOG(2) << absl::StrFormat( + "Function %s != %s: different number of parameters (%d vs %d)", name(), + other->name(), params().size(), other->params().size()); + return false; + } + + absl::flat_hash_map matched_pairs; + for (int64 i = 0; i < params().size(); ++i) { + // All we care about is the type (not the name) of the parameter so don't + // use Param::IsDefinitelyEqualTo. + if (!param(i)->GetType()->IsEqualTo(other->param(i)->GetType())) { + XLS_VLOG(2) << absl::StrFormat( + "Function %s != %s: type of parameter %d not the same (%s vs %s)", + name(), other->name(), i, param(i)->GetType()->ToString(), + other->param(i)->GetType()->ToString()); + return false; + } + matched_pairs[param(i)] = other->param(i); + } + + bool result = + IsEqualRecurse(return_value(), other->return_value(), &matched_pairs); + XLS_VLOG_IF(2, result) << absl::StrFormat("Function %s is equal to %s", + name(), other->name()); + return result; +} + +std::ostream& operator<<(std::ostream& os, const Function& function) { + os << function.DumpIr(); + return os; +} + +} // namespace xls diff --git a/xls/ir/function.h b/xls/ir/function.h new file mode 100644 index 0000000000..a35db8badd --- /dev/null +++ b/xls/ir/function.h @@ -0,0 +1,158 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_FUNCTION_H_ +#define THIRD_PARTY_XLS_IR_FUNCTION_H_ + +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "xls/common/iterator_range.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/dfs_visitor.h" +#include "xls/ir/node.h" +#include "xls/ir/nodes.h" +#include "xls/ir/package.h" +#include "xls/ir/type.h" +#include "xls/ir/unwrapping_iterator.h" +#include "xls/ir/verifier.h" + +namespace xls { + +// Holds a set of nodes that represent an IR function: +// +// * Functions are composed out of nodes (that represent expressions). +// * Functions are owned by packages that contain them. +class Function { + private: + using NodeList = std::list>; + + public: + explicit Function(absl::string_view name, Package* package) + : name_(name), + qualified_name_(absl::StrCat(package->name(), "::", name_)), + package_(package) {} + + Package* package() const { return package_; } + const std::string& name() const { return name_; } + const std::string qualified_name() const { return qualified_name_; } + + // DumpIr emits the IR in a parsable, hierarchical text format. + // Parameter: + // 'recursive' if true, will dump counted-for body functions as well. + // This is only useful when dumping individual functions, and not packages. + std::string DumpIr(bool recursive = false) const; + + // Returns the node that serves as the return value of this function. + Node* return_value() const { return return_value_; } + void set_return_value(Node* n) { return_value_ = n; } + + FunctionType* GetType(); + + // Return Span of parameters. + absl::Span params() const { return params_; } + + // Return the parameter at the given index. + Param* param(int64 index) const { return params_.at(index); } + + // Return the parameter with the given name. + xabsl::StatusOr GetParamByName(absl::string_view param_name) const; + + xabsl::StatusOr GetParamIndex(Param* param) const; + + int64 node_count() const { return nodes_.size(); } + + // Expose Nodes, so that transformation passes can operate + // on this function. + xabsl::iterator_range> nodes() { + return xabsl::make_range(MakeUnwrappingIterator(nodes_.begin()), + MakeUnwrappingIterator(nodes_.end())); + } + + // Adds a node to the set owned by this function. + template + T* AddNode(std::unique_ptr n) { + if (n->template Is()) { + params_.push_back(n->template As()); + } + T* ptr = n.get(); + node_iterators_[ptr] = nodes_.insert(nodes_.end(), std::move(n)); + return ptr; + } + + // Creates a new node and adds it to the function. NodeT is the node subclass + // (e.g., 'Param') and the variadic args are the constructor arguments with + // the exception of the final Function* argument. This method verifies the + // newly constructed node after it is added to the function. Returns a pointer + // to the newly constructed node. + template + xabsl::StatusOr MakeNode(Args&&... args) { + NodeT* new_node = + AddNode(absl::make_unique(std::forward(args)..., this)); + XLS_RETURN_IF_ERROR(Verify(new_node)); + return new_node; + } + + // Find a node by it's name, as generated by DumpIr. + xabsl::StatusOr GetNode(absl::string_view standard_node_name); + + // Removes the node from the function. The node must have no users. + // + // If remove_param_ok is false and a parameter is given for removal, this + // method will return an error -- this is a sanity check against removing + // parameters and changing the type signature when that is not intended. + // + // Warning: if you remove a parameter node via this method (with + // remove_param_ok), you will change the function type signature. + absl::Status RemoveNode(Node* n, bool remove_param_ok = false); + + // Visit all nodes (including nodes not reachable from the root) in the + // function using the given visitor. + absl::Status Accept(DfsVisitor* visitor); + + // Creates a clone of the function with the new name 'new_name'. Function is + // owned by the same package. + xabsl::StatusOr Clone(absl::string_view new_name) const; + + // Returns true if analysis indicates that this function always produces the + // same value as 'other' when run with the same arguments. The analysis is + // conservative and false may be returned for some "equivalent" functions. + bool IsDefinitelyEqualTo(const Function* other) const; + + private: + Function(const Function& other) = delete; + void operator=(const Function& other) = delete; + + std::string name_; + std::string qualified_name_; + Package* package_; + + // Store Nodes in std::list as they can be added and removed arbitrarily and + // we want a stable iteration order. Keep a map from instruction pointer to + // location in the list for fast lookup. + NodeList nodes_; + absl::flat_hash_map node_iterators_; + + std::vector params_; + Node* return_value_ = nullptr; +}; + +std::ostream& operator<<(std::ostream& os, const Function& function); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_FUNCTION_H_ diff --git a/xls/ir/function_builder.cc b/xls/ir/function_builder.cc new file mode 100644 index 0000000000..d85082164b --- /dev/null +++ b/xls/ir/function_builder.cc @@ -0,0 +1,703 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/function_builder.h" + +#include "absl/strings/str_format.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/symbolized_stacktrace.h" +#include "xls/ir/package.h" + +namespace xls { + +using ::absl::StrFormat; + +BValue BValue::operator>>(BValue rhs) { return builder()->Shrl(*this, rhs); } +BValue BValue::operator<<(BValue rhs) { return builder()->Shll(*this, rhs); } +BValue BValue::operator|(BValue rhs) { return builder()->Or(*this, rhs); } +BValue BValue::operator^(BValue rhs) { return builder()->Xor(*this, rhs); } +BValue BValue::operator*(BValue rhs) { return builder()->UMul(*this, rhs); } +BValue BValue::operator-(BValue rhs) { return builder()->Subtract(*this, rhs); } +BValue BValue::operator+(BValue rhs) { return builder()->Add(*this, rhs); } +BValue BValue::operator-() { return builder()->Negate(*this); } + +BValue FunctionBuilder::SetError(std::string msg, + absl::optional loc) { + error_pending_ = true; + error_msg_ = msg; + if (loc.has_value()) { + error_loc_.emplace(loc.value()); + } + error_stacktrace_ = GetSymbolizedStackTraceAsString(); + return BValue(); +} + +BValue FunctionBuilder::Param(absl::string_view name, Type* type, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, name, type); +} + +BValue FunctionBuilder::Literal(Value value, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, value); +} + +BValue FunctionBuilder::Negate(BValue x, absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddUnOp(Op::kNeg, x, loc); +} + +BValue FunctionBuilder::Not(BValue x, absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + + return AddUnOp(Op::kNot, x, loc); +} + +BValue FunctionBuilder::Select(BValue selector, absl::Span cases, + absl::optional default_value, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector cases_nodes; + for (const BValue& bvalue : cases) { + XLS_CHECK_EQ(selector.builder(), bvalue.builder()); + cases_nodes.push_back(bvalue.node()); + } + absl::optional default_node = absl::nullopt; + if (default_value.has_value()) { + default_node = default_value->node(); + } + return AddNode(loc, selector.node(), cases_nodes, default_node); +} + +BValue FunctionBuilder::Select(BValue selector, BValue on_true, BValue on_false, + absl::optional loc) { + return Select(selector, {on_false, on_true}, /*default_value=*/absl::nullopt, + loc); +} + +BValue FunctionBuilder::OneHot(BValue input, LsbOrMsb priority, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, input.node(), priority); +} + +BValue FunctionBuilder::OneHotSelect(BValue selector, + absl::Span cases, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector cases_nodes; + for (const BValue& bvalue : cases) { + XLS_CHECK_EQ(selector.builder(), bvalue.builder()); + cases_nodes.push_back(bvalue.node()); + } + return AddNode(loc, selector.node(), cases_nodes); +} + +BValue FunctionBuilder::Clz(BValue x, absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + if (!x.GetType()->IsBits()) { + return SetError( + StrFormat("Count-leading-zeros argument must be of Bits type; is: %s", + x.GetType()->ToString()), + loc); + } + return ZeroExtend( + Encode(OneHot(Reverse(x, loc), /*priority=*/LsbOrMsb::kLsb, loc)), + x.BitCountOrDie()); +} + +BValue FunctionBuilder::Ctz(BValue x, absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + if (!x.GetType()->IsBits()) { + return SetError( + StrFormat("Count-leading-zeros argument must be of Bits type; is: %s", + x.GetType()->ToString()), + loc); + } + return ZeroExtend(Encode(OneHot(x, /*priority=*/LsbOrMsb::kLsb, loc)), + x.BitCountOrDie()); +} + +BValue FunctionBuilder::Match(BValue condition, absl::Span cases, + BValue default_value, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector boolean_cases; + for (const Case& cas : cases) { + boolean_cases.push_back(Case{Eq(condition, cas.clause, loc), cas.value}); + } + return MatchTrue(boolean_cases, default_value, loc); +} + +BValue FunctionBuilder::MatchTrue(absl::Span case_clauses, + absl::Span case_values, + BValue default_value, + absl::optional loc) { + if (case_clauses.size() != case_values.size()) { + return SetError( + StrFormat( + "Number of case clauses %d does not equal number of values (%d)", + case_clauses.size(), case_values.size()), + loc); + } + std::vector cases; + for (int64 i = 0; i < case_clauses.size(); ++i) { + cases.push_back(Case{case_clauses[i], case_values[i]}); + } + return MatchTrue(cases, default_value, loc); +} + +BValue FunctionBuilder::MatchTrue(absl::Span cases, + BValue default_value, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector selector_bits; + std::vector case_values; + for (int64 i = 0; i < cases.size(); ++i) { + XLS_CHECK_EQ(cases[i].clause.builder(), default_value.builder()); + XLS_CHECK_EQ(cases[i].value.builder(), default_value.builder()); + if (GetType(cases[i].clause) != package()->GetBitsType(1)) { + return SetError( + StrFormat("Selector %d must be a single-bit Bits type, is: %s", i, + GetType(cases[i].clause)->ToString()), + loc); + } + selector_bits.push_back(cases[i].clause); + case_values.push_back(cases[i].value); + } + case_values.push_back(default_value); + + // Reverse the order of the bits because bit index and indexing of concat + // elements are reversed. That is, the zero-th operand of concat becomes the + // most-significant part (highest index) of the result. + std::reverse(selector_bits.begin(), selector_bits.end()); + + BValue concat = Concat(selector_bits, loc); + BValue one_hot = OneHot(concat, /*priority=*/LsbOrMsb::kLsb, loc); + + return OneHotSelect(one_hot, case_values, loc); +} + +BValue FunctionBuilder::Tuple(absl::Span elements, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector nodes; + nodes.reserve(elements.size()); + for (const BValue& value : elements) { + nodes.push_back(value.node()); + } + return AddNode(loc, nodes); +} + +BValue FunctionBuilder::Array(absl::Span elements, + Type* element_type, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector nodes; + nodes.reserve(elements.size()); + for (const BValue& value : elements) { + nodes.push_back(value.node()); + if (GetType(value) != element_type) { + return SetError( + StrFormat("Element type %s does not match expected type: %s", + GetType(value)->ToString(), element_type->ToString()), + loc); + } + } + + return AddNode(loc, nodes, element_type); +} + +BValue FunctionBuilder::TupleIndex(BValue arg, int64 idx, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, arg.node(), idx); +} + +BValue FunctionBuilder::CountedFor(BValue init_value, int64 trip_count, + int64 stride, Function* body, + absl::Span invariant_args, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector invariant_arg_nodes; + for (const BValue& arg : invariant_args) { + invariant_arg_nodes.push_back(arg.node()); + } + return AddNode(loc, init_value.node(), invariant_arg_nodes, + trip_count, stride, body); +} + +BValue FunctionBuilder::Map(BValue operand, Function* to_apply, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, operand.node(), to_apply); +} + +BValue FunctionBuilder::Invoke(absl::Span args, + Function* to_apply, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector arg_nodes; + arg_nodes.reserve(args.size()); + for (const BValue& value : args) { + arg_nodes.push_back(value.node()); + } + return AddNode(loc, arg_nodes, to_apply); +} + +BValue FunctionBuilder::ArrayIndex(BValue arg, BValue idx, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + if (!arg.node()->GetType()->IsArray()) { + return SetError( + absl::StrFormat( + "Cannot array-index the node %s because it has non-array type %s", + arg.node()->ToString(), arg.node()->GetType()->ToString()), + loc); + } + return AddNode(loc, arg.node(), idx.node()); +} + +BValue FunctionBuilder::Reverse(BValue arg, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddUnOp(Op::kReverse, arg, loc); +} + +BValue FunctionBuilder::Identity(BValue var, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddUnOp(Op::kIdentity, var, loc); +} + +BValue FunctionBuilder::SignExtend(BValue arg, int64 new_bit_count, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, arg.node(), new_bit_count, Op::kSignExt); +} + +BValue FunctionBuilder::ZeroExtend(BValue arg, int64 new_bit_count, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, arg.node(), new_bit_count, Op::kZeroExt); +} + +BValue FunctionBuilder::BitSlice(BValue arg, int64 start, int64 width, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, arg.node(), start, width); +} + +BValue FunctionBuilder::Encode(BValue arg, absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, arg.node()); +} + +BValue FunctionBuilder::Decode(BValue arg, absl::optional width, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + if (!arg.GetType()->IsBits()) { + return SetError(StrFormat("Decode argument must be of Bits type; is: %s", + arg.GetType()->ToString()), + loc); + } + // The full output width ('width' not given) is an exponential function of the + // argument width. Set a limit of 16 bits on the argument width. + const int64 arg_width = arg.GetType()->AsBitsOrDie()->bit_count(); + if (!width.has_value() && arg_width > 16) { + return SetError( + StrFormat( + "Decode argument width be no greater than 32-bits; is %d bits", + arg_width), + loc); + } + return AddNode(loc, arg.node(), /*width=*/width.has_value() + ? *width + : (1LL << arg_width)); +} + +BValue FunctionBuilder::Shra(BValue operand, BValue amount, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBinOp(Op::kShra, operand, amount, loc); +} +BValue FunctionBuilder::Shrl(BValue operand, BValue amount, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBinOp(Op::kShrl, operand, amount, loc); +} +BValue FunctionBuilder::Shll(BValue operand, BValue amount, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBinOp(Op::kShll, operand, amount, loc); +} +BValue FunctionBuilder::Subtract(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBinOp(Op::kSub, lhs, rhs, loc); +} +BValue FunctionBuilder::Add(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBinOp(Op::kAdd, lhs, rhs, loc); +} +BValue FunctionBuilder::Or(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNaryOp(Op::kOr, std::vector{lhs, rhs}, loc); +} +BValue FunctionBuilder::Or(absl::Span operands, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNaryOp(Op::kOr, operands, loc); +} +BValue FunctionBuilder::Xor(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNaryOp(Op::kXor, std::vector{lhs, rhs}, loc); +} +BValue FunctionBuilder::And(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddNaryOp(Op::kAnd, std::vector{lhs, rhs}, loc); +} + +BValue FunctionBuilder::AndReduce(BValue operand, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBitwiseReductionOp(Op::kAndReduce, operand); +} + +BValue FunctionBuilder::OrReduce(BValue operand, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBitwiseReductionOp(Op::kOrReduce, operand); +} + +BValue FunctionBuilder::XorReduce(BValue operand, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBitwiseReductionOp(Op::kXorReduce, operand); +} + +BValue FunctionBuilder::SMul(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddArithOp(Op::kSMul, lhs, rhs, /*result_width=*/absl::nullopt, loc); +} +BValue FunctionBuilder::UMul(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddArithOp(Op::kUMul, lhs, rhs, /*result_width=*/absl::nullopt, loc); +} +BValue FunctionBuilder::SMul(BValue lhs, BValue rhs, int64 result_width, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddArithOp(Op::kSMul, lhs, rhs, result_width, loc); +} +BValue FunctionBuilder::UMul(BValue lhs, BValue rhs, int64 result_width, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddArithOp(Op::kUMul, lhs, rhs, result_width, loc); +} +BValue FunctionBuilder::UDiv(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddBinOp(Op::kUDiv, lhs, rhs, loc); +} +BValue FunctionBuilder::Eq(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kEq, lhs, rhs, loc); +} +BValue FunctionBuilder::Ne(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kNe, lhs, rhs, loc); +} +BValue FunctionBuilder::UGe(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kUGe, lhs, rhs, loc); +} +BValue FunctionBuilder::UGt(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kUGt, lhs, rhs, loc); +} +BValue FunctionBuilder::ULe(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kULe, lhs, rhs, loc); +} +BValue FunctionBuilder::ULt(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kULt, lhs, rhs, loc); +} +BValue FunctionBuilder::SLt(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kSLt, lhs, rhs, loc); +} +BValue FunctionBuilder::SLe(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kSLe, lhs, rhs, loc); +} +BValue FunctionBuilder::SGe(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kSGe, lhs, rhs, loc); +} +BValue FunctionBuilder::SGt(BValue lhs, BValue rhs, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + return AddCompareOp(Op::kSGt, lhs, rhs, loc); +} + +BValue FunctionBuilder::Concat(absl::Span operands, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector node_operands; + for (const BValue operand : operands) { + node_operands.push_back(operand.node()); + if (!operand.node()->GetType()->IsBits()) { + return SetError( + absl::StrFormat( + "Cannot concatenate node %s because it has non-bits type %s", + operand.node()->ToString(), + operand.node()->GetType()->ToString()), + loc); + } + } + return AddNode(loc, node_operands); +} + +xabsl::StatusOr FunctionBuilder::Build() { + if (function_ == nullptr) { + return absl::FailedPreconditionError( + "Cannot build function multiple times"); + } + if (function_->node_count() == 0) { + return absl::InvalidArgumentError("Function cannot be empty"); + } + XLS_RET_CHECK(last_node_ != nullptr); + return BuildWithReturnValue(BValue(last_node_, this)); +} + +xabsl::StatusOr FunctionBuilder::BuildWithReturnValue( + BValue return_value) { + if (ErrorPending()) { + std::string msg = error_msg_ + " "; + if (error_loc_.has_value()) { + absl::StrAppendFormat( + &msg, "File: %d, Line: %d, Col: %d", error_loc_->fileno().value(), + error_loc_->lineno().value(), error_loc_->colno().value()); + } + absl::StrAppend(&msg, "\nStack Trace:\n" + error_stacktrace_); + return absl::InvalidArgumentError("Could not build IR: " + msg); + } + XLS_RET_CHECK_EQ(return_value.builder(), this); + function_->set_return_value(return_value.node()); + return package()->AddFunction(std::move(function_)); +} + +BValue FunctionBuilder::AddArithOp(Op op, BValue lhs, BValue rhs, + absl::optional result_width, + absl::optional loc) { + XLS_CHECK_EQ(lhs.builder(), rhs.builder()); + if (ErrorPending()) { + return BValue(); + } + if (!lhs.GetType()->IsBits() || !rhs.GetType()->IsBits()) { + return SetError( + StrFormat("Arithmetic arguments must be of Bits type; is: %s and %s", + lhs.GetType()->ToString(), rhs.GetType()->ToString()), + loc); + } + int64 width; + if (result_width.has_value()) { + width = *result_width; + } else { + if (lhs.BitCountOrDie() != rhs.BitCountOrDie()) { + return SetError( + StrFormat("Arguments of arithmetic operation must be same width if " + "result width is not specified; is: %s and %s", + lhs.GetType()->ToString(), rhs.GetType()->ToString()), + loc); + } + width = lhs.BitCountOrDie(); + } + return AddNode(loc, lhs.node(), rhs.node(), width, op); +} + +BValue FunctionBuilder::AddUnOp(Op op, BValue x, + absl::optional loc) { + XLS_CHECK_EQ(this, x.builder()); + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, x.node(), op); +} + +BValue FunctionBuilder::AddBinOp(Op op, BValue lhs, BValue rhs, + absl::optional loc) { + XLS_CHECK_EQ(lhs.builder(), rhs.builder()); + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, lhs.node(), rhs.node(), op); +} + +BValue FunctionBuilder::AddCompareOp(Op op, BValue lhs, BValue rhs, + absl::optional loc) { + XLS_CHECK_EQ(lhs.builder(), rhs.builder()); + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, lhs.node(), rhs.node(), op); +} + +BValue FunctionBuilder::AddNaryOp(Op op, absl::Span args, + absl::optional loc) { + if (ErrorPending()) { + return BValue(); + } + std::vector nodes; + for (const BValue& bvalue : args) { + nodes.push_back(bvalue.node()); + } + return AddNode(loc, nodes, op); +} + +BValue FunctionBuilder::AddBitwiseReductionOp( + Op op, BValue arg, absl::optional loc) { + XLS_CHECK_EQ(this, arg.builder()); + if (ErrorPending()) { + return BValue(); + } + return AddNode(loc, arg.node(), op); +} + +} // namespace xls diff --git a/xls/ir/function_builder.h b/xls/ir/function_builder.h new file mode 100644 index 0000000000..2f0d9421b5 --- /dev/null +++ b/xls/ir/function_builder.h @@ -0,0 +1,427 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_FUNCTION_BUILDER_H_ +#define THIRD_PARTY_XLS_IR_FUNCTION_BUILDER_H_ + +#include "absl/strings/string_view.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/source_location.h" +#include "xls/ir/value.h" + +namespace xls { + +class FunctionBuilder; +class Node; + +// Represents a value for use in the function-definition building process, +// supports some basic C++ operations that have a natural correspondence to the +// staged versions. +class BValue { + public: + // The vacuous constructor lets us use values in vectors and as values that + // are subsequently initialized. Check valid() to ensure proper + // initialization. + BValue() : BValue(nullptr, nullptr) {} + + BValue(Node* node, FunctionBuilder* builder) + : node_(node), builder_(builder) {} + + FunctionBuilder* builder() const { return builder_; } + Node* node() const { return node_; } + Type* GetType() const { return node()->GetType(); } + int64 BitCountOrDie() const { return node()->BitCountOrDie(); } + absl::optional loc() const { return node_->loc(); } + std::string ToString() const { + return node_ == nullptr ? std::string("") : node_->ToString(); + } + + bool valid() const { + XLS_CHECK_EQ(node_ == nullptr, builder_ == nullptr); + return node_ != nullptr; + } + + BValue operator>>(BValue rhs); + BValue operator<<(BValue rhs); + BValue operator|(BValue rhs); + BValue operator-(BValue rhs); + BValue operator+(BValue rhs); + BValue operator*(BValue rhs); + BValue operator^(BValue rhs); + + // Note: unary negation. + BValue operator-(); + + private: + Node* node_; + FunctionBuilder* builder_; +}; + +// Builds up a function for subsequent HLS (optimization and conversion to RTL). +// +// Package p("my_package"); +// FunctionBuilder b("my_or_function_32b", &p); +// auto bits_32 = m.GetBitsType(32); +// auto x = b.Param("x", bits_32); +// auto y = b.Param("y", bits_32); +// XLS_ASSIGN_OR_RETURN(Function* f, b.BuildWithReturnValue(x | y)); +// +// Note that the BValues returned by builder routines are specific to that +// function, and attempt to use those BValues with FunctionBuilders that did not +// originate them will cause fatal errors. +class FunctionBuilder { + public: + explicit FunctionBuilder(absl::string_view name, Package* package) + : function_(absl::make_unique(std::string(name), package)), + error_pending_(false) {} + + const std::string& name() const { return function_->name(); } + + // Declares a parameter to the function being built of type "type". + BValue Param(absl::string_view name, Type* type, + absl::optional loc = absl::nullopt); + + // Shift right arithmetic. + BValue Shra(BValue operand, BValue amount, + absl::optional loc = absl::nullopt); + + // Shift right logical. + BValue Shrl(BValue operand, BValue amount, + absl::optional loc = absl::nullopt); + + // Shift left (logical). + BValue Shll(BValue operand, BValue amount, + absl::optional loc = absl::nullopt); + + // Bitwise or. + BValue Or(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + BValue Or(absl::Span operands, + absl::optional loc = absl::nullopt); + + // Bitwise xor. + BValue Xor(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + + // Bitwise and. + BValue And(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + + // Unary and-reduction. + BValue AndReduce(BValue operand, + absl::optional loc = absl::nullopt); + + // Unary or-reduction. + BValue OrReduce(BValue operand, + absl::optional loc = absl::nullopt); + + // Unary xor-reduction. + BValue XorReduce(BValue operand, + absl::optional loc = absl::nullopt); + + // Signed/unsigned multiply. Result width is the same as the operand + // width. Operand widths must be the same. + BValue UMul(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + BValue SMul(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + + // Signed/unsigned multiply with explicitly specified result width. Operand + // widths can be arbitrary. + BValue UMul(BValue lhs, BValue rhs, int64 result_width, + absl::optional loc = absl::nullopt); + BValue SMul(BValue lhs, BValue rhs, int64 result_width, + absl::optional loc = absl::nullopt); + + // Unsigned division. + BValue UDiv(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + + // Two's complement subtraction/addition. + BValue Subtract(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + BValue Add(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + + // Concatenates the operands (zero-th operand are the most significant bits in + // the result). + BValue Concat(absl::Span operands, + absl::optional loc = absl::nullopt); + + // Unsigned less-or-equal. + BValue ULe(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + // Unsigned less-than. + BValue ULt(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + // Unsigned greater-or-equal. + BValue UGe(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + // Unsigned greater-than. + BValue UGt(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + + // Signed less-or-equal. + BValue SLe(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + // Signed less-than. + BValue SLt(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + // Signed greater-or-equal. + BValue SGe(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + // Signed greater-than. + BValue SGt(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + + // Equal. + BValue Eq(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + // Not Equal. + BValue Ne(BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + + // Two's complement (arithmetic) negation. + BValue Negate(BValue x, absl::optional loc = absl::nullopt); + + // One's complement negation (bitwise not). + BValue Not(BValue x, absl::optional loc = absl::nullopt); + + // Turns a literal value into a handle that can be used in this function being + // built. + BValue Literal(Value value, + absl::optional loc = absl::nullopt); + BValue Literal(Bits bits, + absl::optional loc = absl::nullopt) { + return Literal(Value(bits), loc); + } + + // An n-ary select which selects among an arbitrary number of cases based on + // the selector value. If the number of cases is less than 2**selector_width + // then a default value must be specified. + BValue Select(BValue selector, absl::Span cases, + absl::optional default_value = absl::nullopt, + absl::optional loc = absl::nullopt); + + // An overload for binary select: selects on_true when selector is true, + // on_false otherwise. + // TODO(meheff): switch positions of on_true and on_false to match the + // ordering of the cases span in the n-ary select. + BValue Select(BValue selector, BValue on_true, BValue on_false, + absl::optional loc = absl::nullopt); + + // Creates a one-hot operation which generates a one-hot bits value from its + // inputs. + BValue OneHot(BValue input, LsbOrMsb priority, + absl::optional loc = absl::nullopt); + + // Creates a select operation which uses a one-hot selector (rather than a + // binary encoded selector as used in Select). + BValue OneHotSelect(BValue selector, absl::Span cases, + absl::optional loc = absl::nullopt); + + // Counts the number of leading zeros in the value 'x'. + // + // x must be of bits type. This function returns a value of the same type that + // it takes as an argument. + BValue Clz(BValue x, absl::optional loc = absl::nullopt); + + // Counts the number of trailing zeros in the value 'x'. + // + // x must be of bits type. This function returns a value of the same type that + // it takes as an argument. + BValue Ctz(BValue x, absl::optional loc = absl::nullopt); + + // Creates an match operation which compares 'condition' against each of the + // case clauses and produces the respective case value of the first match. If + // 'condition' matches no cases 'default_value' is produced. 'condition' and + // each case clause must be of the same type. 'default_value' and each case + // value must be of the same type. The match operation is composed of multiple + // IR nodes including one-hot and one-hot-select. + struct Case { + BValue clause; + BValue value; + }; + BValue Match(BValue condition, absl::Span cases, + BValue default_value, + absl::optional loc = absl::nullopt); + + // Creates a match operation which matches each of the case clauses against + // the single-bit literal one. Each case clause must be a single-bit Bits + // value. + BValue MatchTrue(absl::Span cases, BValue default_value, + absl::optional loc = absl::nullopt); + // Overload which is easier to expose to Python. + BValue MatchTrue(absl::Span case_clauses, + absl::Span case_values, BValue default_value, + absl::optional loc = absl::nullopt); + + // Creates a tuple of values. + // + // Note that "loc" would generally refer to the source location whether the + // tuple expression begins; e.g. in DSLX: + // + // let x = (1, 2, 3, 4) in ... + // ~~~~~~~~~~^ + // + // The arrow indicates the source location for the tuple expression. + BValue Tuple(absl::Span elements, + absl::optional loc = absl::nullopt); + + // Creates an array of values. Each value in element must be the same type + // which is given by element_type. + BValue Array(absl::Span elements, Type* element_type, + absl::optional loc = absl::nullopt); + + // Adds an tuple index expression. + BValue TupleIndex(BValue arg, int64 idx, + absl::optional loc = absl::nullopt); + + // Adds a "counted for-loop" to the computation, having a known-constant + // number of loop iterations. + // + // Args: + // trip_count: Number of iterations to execute "body". + // init_value: Of type "T", the starting value for the loop carry data. + // body: Of type "(u32, T) -> T": the body that is invoked on the loop carry + // data decorated with the iteration number, producing new loop carry data + // (of the same type) each iteration. + // invariant_args: Arguments that are passed to the body function in a + // loop-invariant fashion (for each of these arguments, the same value is + // passed as an argument on every trip and the value does not change). + // loc: Source location for this counted for-loop. + // + // Returns the value that results from this counted for loop after it has + // completed all of its trips. + BValue CountedFor(BValue init_value, int64 trip_count, int64 stride, + Function* body, + absl::Span invariant_args = {}, + absl::optional loc = absl::nullopt); + + // Adds a map to the computation. + // Applies the function to_apply to each element of the array-typed operand + // and returns the result as an array + // + // Args: + // operand: Of type "Array" containing N elements of type T. + // to_apply: Of type "T -> U". + // loc: Source location for this map. + // + // Returns a value of type "Array". + BValue Map(BValue operand, Function* to_apply, + absl::optional loc = absl::nullopt); + + // Adds a function call with parameters. + BValue Invoke(absl::Span args, Function* to_apply, + absl::optional loc = absl::nullopt); + + // Adds an array index expression. + BValue ArrayIndex(BValue arg, BValue idx, + absl::optional loc = absl::nullopt); + + // Reverses the order of the bits of the argument. + BValue Reverse(BValue arg, + absl::optional loc = absl::nullopt); + + // Adds an identity expression. + BValue Identity(BValue var, + absl::optional loc = absl::nullopt); + + // Sign-extends arg to the new_bit_count. + BValue SignExtend(BValue arg, int64 new_bit_count, + absl::optional loc = absl::nullopt); + + // Zero-extends arg to the new_bit_count. + BValue ZeroExtend(BValue arg, int64 new_bit_count, + absl::optional loc = absl::nullopt); + + // Extracts a slice from the bits-typed arg. 'start' is the first bit of the + // slice and is zero-indexed where zero is the LSb of arg. + BValue BitSlice(BValue arg, int64 start, int64 width, + absl::optional loc = absl::nullopt); + + // Binary encodes the n-bit input to a log_2(n) bit output. + BValue Encode(BValue arg, absl::optional loc = absl::nullopt); + + // Binary decodes the n-bit input to a one-hot output. 'width' can be at most + // 2**n where n is the bit width of the operand. If 'width' is not specified + // the output is 2**n bits wide. + BValue Decode(BValue arg, absl::optional width = absl::nullopt, + absl::optional loc = absl::nullopt); + + // Retrieves the type of "value" and returns it. + Type* GetType(BValue value) { return value.node()->GetType(); } + + // Adds the function internally being built-up by this builder to the package + // given at construction time, and returns a pointer to it (the function is + // subsequently owned by the package and this builder should be discarded). + // + // The return value of the function is the most recently added operation. + xabsl::StatusOr Build(); + + // Get access to currently built up function. + Function* function() { return function_.get(); } + + // Overload which enables explicitly setting the return value. + xabsl::StatusOr BuildWithReturnValue(BValue return_value); + + // Adds a Arith/UnOp/BinOp/CompareOp to the function. Exposed for + // programmatically adding these ops using with variable Op values. + BValue AddUnOp(Op op, BValue x, + absl::optional loc = absl::nullopt); + BValue AddBinOp(Op op, BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + BValue AddCompareOp(Op op, BValue lhs, BValue rhs, + absl::optional loc = absl::nullopt); + BValue AddNaryOp(Op op, absl::Span args, + absl::optional loc = absl::nullopt); + // If result width is not given the result width set to the width of the + // arguments lhs and rhs which must have the same width. + BValue AddArithOp(Op op, BValue lhs, BValue rhs, + absl::optional result_width, + absl::optional loc = absl::nullopt); + BValue AddBitwiseReductionOp( + Op op, BValue arg, absl::optional loc = absl::nullopt); + + Package* package() const { return function_->package(); } + + private: + BValue SetError(std::string msg, absl::optional loc); + bool ErrorPending() { return error_pending_; } + + // Constructs and adds a node to the function and returns a corresponding + // BValue. + template + BValue AddNode(Args&&... args) { + last_node_ = function_->AddNode( + absl::make_unique(std::forward(args)..., function_.get())); + return BValue(last_node_, this); + } + + // The most recently added node to the function. + Node* last_node_ = nullptr; + + // The function being built by this builder. + std::unique_ptr function_; + + bool error_pending_; + std::string error_msg_; + std::string error_stacktrace_; + absl::optional error_loc_; +}; + +} // namespace xls +#endif // THIRD_PARTY_XLS_IR_FUNCTION_BUILDER_H_ diff --git a/xls/ir/function_builder_test.cc b/xls/ir/function_builder_test.cc new file mode 100644 index 0000000000..67ed27f7b1 --- /dev/null +++ b/xls/ir/function_builder_test.cc @@ -0,0 +1,300 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/function_builder.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "xls/common/status/matchers.h" +#include "xls/examples/sample_packages.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/package.h" + +namespace m = ::xls::op_matchers; + +namespace xls { + +using status_testing::StatusIs; +using ::testing::AllOf; +using ::testing::HasSubstr; + +TEST(FunctionBuilderTest, SimpleSourceLocation) { + // Lineno/Colno are faked out here. + const Lineno lineno(7); + const Colno colno(11); + + Package p("p"); + SourceLocation loc = p.AddSourceLocation(__FILE__, lineno, colno); + FunctionBuilder b("f", &p); + + Type* bits_32 = p.GetBitsType(32); + auto x = b.Param("x", bits_32, loc); + + ASSERT_TRUE(x.loc().has_value()); + EXPECT_EQ(__FILE__ ":7", p.SourceLocationToString(*x.loc())); +} + +TEST(FunctionBuilderTest, CheckFilenameToFilenoLookup) { + const std::string filename("fake_file.cc"); + Package p("p"); + + // Verify two AddSourceLocation calls to the same filename result in correct + // filename lookups. + + SourceLocation loc0 = p.AddSourceLocation(filename, Lineno(7), Colno(11)); + EXPECT_EQ("fake_file.cc:7", p.SourceLocationToString(loc0)); + + SourceLocation loc1 = p.AddSourceLocation(filename, Lineno(8), Colno(12)); + EXPECT_EQ("fake_file.cc:8", p.SourceLocationToString(loc1)); +} + +TEST(FunctionBuilderTest, CheckUnknownFileToString) { + Package p("p"); + SourceLocation loc(Fileno(1), Lineno(7), Colno(11)); + EXPECT_EQ("UNKNOWN:7", p.SourceLocationToString(loc)); +} + +TEST(FunctionBuilderTest, EmptyFunctionTest) { + Package p("p"); + FunctionBuilder b("empty", &p); + EXPECT_THAT(b.Build().status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Function cannot be empty"))); +} + +TEST(FunctionBuilderTest, LessThanTest) { + Package p("p"); + FunctionBuilder b("lt", &p); + BitsType* type = p.GetBitsType(33); + b.ULt(b.Param("a", type), b.Param("b", type)); + XLS_ASSERT_OK_AND_ASSIGN(Function * func, b.Build()); + Node* lt = func->return_value(); + EXPECT_EQ(lt->op(), Op::kULt); + EXPECT_EQ(lt->GetType(), p.GetBitsType(1)); +} + +TEST(FunctionBuilderTest, NonRootReturnValue) { + Package p("p"); + FunctionBuilder b("lt", &p); + BitsType* type = p.GetBitsType(7); + BValue and_node = b.And(b.Param("a", type), b.Param("b", type)); + b.Negate(and_node); + XLS_ASSERT_OK_AND_ASSIGN(Function * func, b.BuildWithReturnValue(and_node)); + Node* return_value = func->return_value(); + EXPECT_EQ(return_value->op(), Op::kAnd); +} + +TEST(FunctionBuilderTest, LiteralTupleTest) { + Package p("p"); + FunctionBuilder b("literal_tuple", &p); + BValue literal_node = + b.Literal(Value::Tuple({Value(UBits(1, 2)), Value(UBits(3, 3))})); + XLS_ASSERT_OK_AND_ASSIGN(Function * func, + b.BuildWithReturnValue(literal_node)); + EXPECT_TRUE(func->GetType()->return_type()->IsTuple()); +} + +TEST(FunctionBuilderTest, LiteralArrayTest) { + Package p("p"); + FunctionBuilder b("literal_array", &p); + BValue literal_node = + b.Literal(Value::ArrayOrDie({Value(UBits(1, 2)), Value(UBits(3, 2))})); + XLS_ASSERT_OK_AND_ASSIGN(Function * func, + b.BuildWithReturnValue(literal_node)); + EXPECT_TRUE(func->GetType()->return_type()->IsArray()); +} + +TEST(FunctionBuilderTest, MapTest) { + Package p("p"); + const int kElementCount = 123; + BitsType* element_type = p.GetBitsType(42); + ArrayType* array_type = p.GetArrayType(kElementCount, element_type); + Function* to_apply; + { + FunctionBuilder b("to_apply", &p); + b.ULt(b.Param("element", element_type), + b.Literal(Value(UBits(10, element_type->bit_count())))); + XLS_ASSERT_OK_AND_ASSIGN(to_apply, b.Build()); + } + Function* top; + { + FunctionBuilder b("top", &p); + b.Map(b.Param("input", array_type), to_apply); + XLS_ASSERT_OK_AND_ASSIGN(top, b.Build()); + } + Node* map = top->return_value(); + EXPECT_EQ(map->op(), Op::kMap); + EXPECT_EQ(to_apply->return_value()->GetType(), p.GetBitsType(1)); + EXPECT_EQ(map->GetType(), + p.GetArrayType(kElementCount, to_apply->return_value()->GetType())); +} + +TEST(FunctionBuilderTest, Match) { + Package p("p"); + FunctionBuilder b("f", &p); + BitsType* cond_type = p.GetBitsType(8); + BitsType* value_type = p.GetBitsType(32); + BValue cond = b.Param("cond", cond_type); + BValue x = b.Param("x", value_type); + BValue y = b.Param("y", value_type); + BValue z = b.Param("z", value_type); + BValue the_default = b.Param("default", value_type); + b.Match(cond, + {{b.Literal(UBits(42, 8)), x}, + {b.Literal(UBits(123, 8)), y}, + {b.Literal(UBits(8, 8)), z}}, + the_default); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_EQ( + f->DumpIr(), + R"(fn f(cond: bits[8], x: bits[32], y: bits[32], z: bits[32], default: bits[32]) -> bits[32] { + literal.8: bits[8] = literal(value=8) + literal.7: bits[8] = literal(value=123) + literal.6: bits[8] = literal(value=42) + eq.11: bits[1] = eq(cond, literal.8) + eq.10: bits[1] = eq(cond, literal.7) + eq.9: bits[1] = eq(cond, literal.6) + concat.12: bits[3] = concat(eq.11, eq.10, eq.9) + one_hot.13: bits[4] = one_hot(concat.12, lsb_prio=true) + ret one_hot_sel.14: bits[32] = one_hot_sel(one_hot.13, cases=[x, y, z, default]) +} +)"); +} + +TEST(FunctionBuilderTest, MatchTrue) { + Package p("p"); + FunctionBuilder b("f", &p); + BitsType* pred_type = p.GetBitsType(1); + BitsType* value_type = p.GetBitsType(32); + BValue p0 = b.Param("p0", pred_type); + BValue p1 = b.Param("p1", pred_type); + BValue p2 = b.Param("p2", pred_type); + BValue x = b.Param("x", value_type); + BValue y = b.Param("y", value_type); + BValue z = b.Param("z", value_type); + BValue the_default = b.Param("default", value_type); + b.MatchTrue({{p0, x}, {p1, y}, {p2, z}}, the_default); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_EQ( + f->DumpIr(), + R"(fn f(p0: bits[1], p1: bits[1], p2: bits[1], x: bits[32], y: bits[32], z: bits[32], default: bits[32]) -> bits[32] { + concat.8: bits[3] = concat(p2, p1, p0) + one_hot.9: bits[4] = one_hot(concat.8, lsb_prio=true) + ret one_hot_sel.10: bits[32] = one_hot_sel(one_hot.9, cases=[x, y, z, default]) +} +)"); +} + +TEST(FunctionBuilderTest, ConcatTuples) { + Package p("p"); + FunctionBuilder b("f", &p); + BitsType* value_type = p.GetBitsType(32); + BValue x = b.Param("x", value_type); + BValue t = b.Tuple({x}); + b.Concat({t, t}); + EXPECT_THAT(b.Build(), status_testing::StatusIs( + absl::StatusCode::kInvalidArgument, + testing::HasSubstr("it has non-bits type"))); +} + +TEST(FunctionBuilderTest, ArrayIndexBits) { + Package p("p"); + FunctionBuilder b("f", &p); + BitsType* value_type = p.GetBitsType(32); + BValue x = b.Param("x", value_type); + b.ArrayIndex(x, x); + EXPECT_THAT(b.Build(), + status_testing::StatusIs( + absl::StatusCode::kInvalidArgument, + testing::HasSubstr("it has non-array type bits[32]"))); +} + +TEST(FunctionBuilderTest, FullWidthDecode) { + Package p("p"); + FunctionBuilder b("f", &p); + b.Decode(b.Param("x", p.GetBitsType(7))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_THAT(f->return_value(), + AllOf(m::Decode(m::Param()), m::Type("bits[128]"))); +} + +TEST(FunctionBuilderTest, NarrowedDecode) { + Package p("p"); + FunctionBuilder b("f", &p); + b.Decode(b.Param("x", p.GetBitsType(7)), /*width=*/42); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_THAT(f->return_value(), + AllOf(m::Decode(m::Param()), m::Type("bits[42]"))); +} + +TEST(FunctionBuilderTest, OneBitDecode) { + Package p("p"); + FunctionBuilder b("f", &p); + b.Decode(b.Param("x", p.GetBitsType(1))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_THAT(f->return_value(), + AllOf(m::Decode(m::Param()), m::Type("bits[2]"))); +} + +TEST(FunctionBuilderTest, EncodePowerOfTwo) { + Package p("p"); + FunctionBuilder b("f", &p); + b.Encode(b.Param("x", p.GetBitsType(256))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_THAT(f->return_value(), + AllOf(m::Encode(m::Param()), m::Type("bits[8]"))); +} + +TEST(FunctionBuilderTest, EncodeLessThanPowerOfTwo) { + Package p("p"); + FunctionBuilder b("f", &p); + b.Encode(b.Param("x", p.GetBitsType(255))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_THAT(f->return_value(), + AllOf(m::Encode(m::Param()), m::Type("bits[8]"))); +} + +TEST(FunctionBuilderTest, EncodeGreaterThanPowerOfTwo) { + Package p("p"); + FunctionBuilder b("f", &p); + b.Encode(b.Param("x", p.GetBitsType(257))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_THAT(f->return_value(), + AllOf(m::Encode(m::Param()), m::Type("bits[9]"))); +} + +TEST(FunctionBuilderTest, OneBitEncode) { + Package p("p"); + FunctionBuilder b("f", &p); + b.Encode(b.Param("x", p.GetBitsType(1))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.Build()); + EXPECT_THAT(f->return_value(), + AllOf(m::Encode(m::Param()), m::Type("bits[0]"))); +} + +TEST(FunctionBuilderTest, BuildTwiceFails) { + Package p("p"); + FunctionBuilder b("lt", &p); + BitsType* type = p.GetBitsType(33); + b.ULt(b.Param("a", type), b.Param("b", type)); + + XLS_EXPECT_OK(b.Build()); + xabsl::StatusOr result = b.Build(); + + EXPECT_THAT(result, StatusIs(absl::StatusCode::kFailedPrecondition, + HasSubstr("multiple times"))); +} + +} // namespace xls diff --git a/xls/ir/function_test.cc b/xls/ir/function_test.cc new file mode 100644 index 0000000000..0d177b66ed --- /dev/null +++ b/xls/ir/function_test.cc @@ -0,0 +1,170 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/function.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_test_base.h" + +namespace xls { +namespace { + +using status_testing::StatusIs; +using ::testing::ElementsAre; +using ::testing::HasSubstr; + +class FunctionTest : public IrTestBase {}; + +TEST_F(FunctionTest, CloneSimpleFunction) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * func, ParseFunction(R"( +fn add(x: bits[32], y: bits[32]) -> bits[32] { +ret add.3: bits[32] = add(x, y) +} +)", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(Function * func_clone, func->Clone("foobar")); + EXPECT_EQ(func_clone->name(), "foobar"); + EXPECT_EQ(func_clone->node_count(), 3); + EXPECT_EQ(func_clone->return_value()->op(), Op::kAdd); +} + +TEST_F(FunctionTest, DumpIrWhenParamIsRetval) { + auto p = CreatePackage(); + FunctionBuilder b("f", p.get()); + auto x = b.Param("x", p->GetBitsType(32)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, b.BuildWithReturnValue(x)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[32]) -> bits[32] { + ret param.1: bits[32] = param(name=x) +} +)"); +} + +TEST_F(FunctionTest, CloneFunctionWithDeadNodes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * func, ParseFunction(R"( +fn add(x: bits[32], y: bits[32]) -> bits[32] { + add.1: bits[32] = add(x, y) + sub.2: bits[32] = sub(x, y) + literal.3: bits[32] = literal(value=42) + umul.4: bits[32] = umul(add.1, literal.3) + ret neg.5: bits[32] = neg(sub.2) +} +)", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(Function * func_clone, func->Clone("foobar")); + + EXPECT_EQ(func->node_count(), 7); + EXPECT_EQ(func_clone->node_count(), 7); +} + +TEST_F(FunctionTest, IsDefinitelyEqualTo) { + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackage(R"( +package function_is_equal + +fn f1(x: bits[32], y: bits[32]) -> bits[32] { + add.1: bits[32] = add(x, y) + sub.2: bits[32] = sub(x, y) + literal.3: bits[32] = literal(value=42) + ret umul.4: bits[32] = umul(add.1, literal.3) +} + +fn same_as_f1(x: bits[32], y: bits[32]) -> bits[32] { + add.5: bits[32] = add(x, y) + sub.6: bits[32] = sub(x, y) + literal.7: bits[32] = literal(value=42) + ret umul.8: bits[32] = umul(add.5, literal.7) +} + +fn same_as_f1_different_order(x: bits[32], y: bits[32]) -> bits[32] { + literal.37: bits[32] = literal(value=42) + sub.36: bits[32] = sub(x, y) + add.35: bits[32] = add(x, y) + ret umul.38: bits[32] = umul(add.35, literal.37) +} + +fn extra_parameter(x: bits[32], y: bits[32], z: bits[32]) -> bits[32] { + add.9: bits[32] = add(x, y) + sub.10: bits[32] = sub(x, y) + literal.11: bits[32] = literal(value=42) + ret umul.12: bits[32] = umul(add.9, literal.11) +} + +fn different_types(x: bits[16], y: bits[16]) -> bits[16] { + add.21: bits[16] = add(x, y) + sub.22: bits[16] = sub(x, y) + literal.23: bits[16] = literal(value=42) + ret umul.24: bits[16] = umul(add.21, literal.23) +} +)")); + + auto functions_equal = [&](absl::string_view a, absl::string_view b) { + return FindFunction(a, p.get()) + ->IsDefinitelyEqualTo(FindFunction(b, p.get())); + }; + + EXPECT_TRUE(functions_equal("f1", "f1")); + EXPECT_TRUE(functions_equal("f1", "same_as_f1")); + EXPECT_TRUE(functions_equal("f1", "same_as_f1_different_order")); + EXPECT_FALSE(functions_equal("f1", "extra_parameter")); + EXPECT_FALSE(functions_equal("f1", "different_types")); +} + +TEST_F(FunctionTest, RemoveParam) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * func, ParseFunction(R"( +fn id(x: bits[32], y: bits[32]) -> bits[32] { + ret param.1: bits[32] = param(name=x) +} +)", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(Param * y, func->GetParamByName("y")); + EXPECT_THAT(func->RemoveNode(y, /*remove_param_ok=*/false), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Attempting to remove parameter"))); + Type* u32 = p->GetBitsType(32); + FunctionType* orig = p->GetFunctionType({u32, u32}, u32); + FunctionType* updated = p->GetFunctionType({u32}, u32); + EXPECT_NE(orig, updated); + EXPECT_EQ(func->GetType(), orig); + EXPECT_THAT(func->params(), + ElementsAre(FindNode("x", func), FindNode("y", func))); + XLS_EXPECT_OK(func->RemoveNode(y, /*remove_param_ok=*/true)); + EXPECT_THAT(func->params(), ElementsAre(FindNode("x", func))); + EXPECT_EQ(func->GetType(), updated); +} + +TEST_F(FunctionTest, MakeInvalidNode) { + Package p(TestName()); + XLS_ASSERT_OK_AND_ASSIGN(Function * func, ParseFunction(R"( +fn id(x: bits[16], y: bits[32]) -> bits[16] { + ret param.1: bits[16] = param(name=x) +} +)", + &p)); + + EXPECT_THAT( + func->MakeNode( + FindNode("x", &p)->loc(), + std::vector{FindNode("x", &p), FindNode("y", &p)}, Op::kXor), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Type of operand 1 (bits[32] via y) does not " + "match type of xor"))); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/ir_evaluator_test.cc b/xls/ir/ir_evaluator_test.cc new file mode 100644 index 0000000000..d63d6f108e --- /dev/null +++ b/xls/ir/ir_evaluator_test.cc @@ -0,0 +1,2039 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_evaluator_test.h" + +#include "absl/strings/substitute.h" +#include "xls/common/logging/logging.h" +#include "xls/common/math_util.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/keyword_args.h" + +namespace xls { + +using status_testing::IsOkAndHolds; +using status_testing::StatusIs; +using ::testing::HasSubstr; + +using ArgMap = absl::flat_hash_map; + +TEST_P(IrEvaluatorTest, InterpretIdentity) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn get_identity(x: bits[8]) -> bits[8] { + ret identity.1: bits[8] = identity(x) + } + )")); + + IrEvaluatorTestParam param = GetParam(); + EXPECT_THAT(param.evaluator(function, {Value(UBits(2, 8))}), + IsOkAndHolds(Value(UBits(2, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretIdentityTuple) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn get_identity(x: (bits[5], bits[32])) -> (bits[5], bits[32]) { + ret identity.1: (bits[5], bits[32]) = identity(x) + } + )")); + + // Use a big ol' value so data dumps might contain helpful patterns. + const Value x = + Value::Tuple({Value(UBits(3, 5)), Value(UBits(2051583859, 32))}); + IrEvaluatorTestParam param = GetParam(); + EXPECT_THAT(param.evaluator(function, {x}), IsOkAndHolds(x)); +} + +TEST_P(IrEvaluatorTest, InterpretIdentityArrayOfTuple) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn get_identity(x: (bits[8], bits[32])[8]) -> (bits[8], bits[32])[8] { + ret identity.1: (bits[8], bits[32])[8] = identity(x) + } + )")); + + const Value t = Value::Tuple({Value(UBits(2, 8)), Value(UBits(7, 32))}); + std::vector v(8, t); + XLS_ASSERT_OK_AND_ASSIGN(const Value a, Value::Array(v)); + EXPECT_THAT(GetParam().evaluator(function, {a}), IsOkAndHolds(a)); +} + +TEST_P(IrEvaluatorTest, InterpretNegPositiveValue) { + // Positive values should become negative. + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, get_neg_function(&package)); + + EXPECT_THAT(GetParam().evaluator(function, {Value(UBits(1, 4))}), + IsOkAndHolds(Value(SBits(-1, 4)))); +} + +TEST_P(IrEvaluatorTest, InterpretNegNegativeValue) { + // Negative values should become positive. + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, get_neg_function(&package)); + EXPECT_THAT(GetParam().evaluator(function, {Value(SBits(-1, 4))}), + IsOkAndHolds(Value(UBits(1, 4)))); +} + +TEST_P(IrEvaluatorTest, InterpretNegZeroValue) { + // Zero should stay zero. + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, get_neg_function(&package)); + EXPECT_THAT(GetParam().evaluator(function, {Value(UBits(0, 4))}), + IsOkAndHolds(Value(UBits(0, 4)))); +} + +TEST_P(IrEvaluatorTest, InterpretNegMaxPositiveValue) { + // Test the maximum positive 2s-complement value that fits in four bits. + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, get_neg_function(&package)); + EXPECT_THAT(GetParam().evaluator(function, {Value(UBits(7, 4))}), + IsOkAndHolds(Value(UBits(0b1001, 4)))); +} + +TEST_P(IrEvaluatorTest, InterpretNegMaxMinValue) { + // Max minimum 2s-complement value that fits in four bits. + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, get_neg_function(&package)); + EXPECT_THAT(GetParam().evaluator(function, {Value(SBits(-8, 4))}), + IsOkAndHolds(Value(UBits(0b1000, 4)))); +} + +TEST_P(IrEvaluatorTest, InterpretNot) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn invert_bits(a: bits[4]) -> bits[4] { + ret not.1: bits[4] = not(a) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, {Value(UBits(0b1111, 4))}), + IsOkAndHolds(Value(UBits(0b0000, 4)))); + EXPECT_THAT(GetParam().evaluator(function, {Value(UBits(0b0000, 4))}), + IsOkAndHolds(Value(UBits(0b1111, 4)))); + EXPECT_THAT(GetParam().evaluator(function, {Value(UBits(0b1010, 4))}), + IsOkAndHolds(Value(UBits(0b0101, 4)))); +} + +TEST_P(IrEvaluatorTest, InterpretSixAndThree) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn six_or_three() -> bits[8] { + literal.1: bits[8] = literal(value=6) + literal.2: bits[8] = literal(value=3) + ret and.3: bits[8] = and(literal.1, literal.2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(2, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretOr) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn six_or_three() -> bits[8] { + literal.1: bits[8] = literal(value=6) + literal.2: bits[8] = literal(value=3) + ret or.3: bits[8] = or(literal.1, literal.2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(0b111, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretXor) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn six_xor_three() -> bits[8] { + literal.1: bits[8] = literal(value=6) + literal.2: bits[8] = literal(value=3) + ret xor.3: bits[8] = xor(literal.1, literal.2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(0b101, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretNaryXor) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn six_xor_three_xor_one() -> bits[8] { + literal.1: bits[8] = literal(value=6) + literal.2: bits[8] = literal(value=3) + literal.3: bits[8] = literal(value=1) + ret xor.4: bits[8] = xor(literal.1, literal.2, literal.3) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(0b100, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretNaryOr) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn f() -> bits[8] { + literal.1: bits[8] = literal(value=4) + literal.2: bits[8] = literal(value=3) + literal.3: bits[8] = literal(value=1) + ret or.4: bits[8] = or(literal.1, literal.2, literal.3) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(0b111, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretNaryNor) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn f() -> bits[8] { + literal.1: bits[8] = literal(value=4) + literal.2: bits[8] = literal(value=2) + literal.3: bits[8] = literal(value=0) + ret or.4: bits[8] = nor(literal.1, literal.2, literal.3) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(0b11111001, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretNaryAnd) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn f() -> bits[8] { + literal.1: bits[8] = literal(value=6) + literal.2: bits[8] = literal(value=3) + literal.3: bits[8] = literal(value=2) + ret and.4: bits[8] = and(literal.1, literal.2, literal.3) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(0b010, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretNaryNand) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn f() -> bits[8] { + literal.1: bits[8] = literal(value=6) + literal.2: bits[8] = literal(value=3) + literal.3: bits[8] = literal(value=2) + ret and.4: bits[8] = nand(literal.1, literal.2, literal.3) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(0b11111101, 8)))); +} + +absl::Status RunZeroExtendTest(const IrEvaluatorTestParam& param, + int64 input_size, int64 output_size) { + constexpr absl::string_view ir_text = R"( + package test + + fn zero_extend() -> bits[$0] { + literal.1: bits[$1] = literal(value=3) + ret zero_ext.2: bits[$0] = zero_ext(literal.1, new_bit_count=$0) + } + )"; + + std::string formatted_ir = absl::Substitute(ir_text, output_size, input_size); + XLS_ASSIGN_OR_RETURN(auto package, Parser::ParsePackage(formatted_ir)); + XLS_ASSIGN_OR_RETURN(Function * function, package->EntryFunction()); + + EXPECT_THAT(param.evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(3, output_size)))); + + return absl::OkStatus(); +} + +TEST_P(IrEvaluatorTest, InterpretZeroExtend) { + XLS_ASSERT_OK(RunZeroExtendTest(GetParam(), 3, 8)); + XLS_ASSERT_OK(RunZeroExtendTest(GetParam(), 3, 1024)); + XLS_ASSERT_OK(RunZeroExtendTest(GetParam(), 1024, 1025)); +} + +absl::Status RunSignExtendTest(const IrEvaluatorTestParam& param, + const Bits& input, int new_bit_count) { + constexpr absl::string_view ir_text = R"( + package test + + fn concatenate() -> bits[$0] { + literal.1: bits[$1] = literal(value=$2) + ret sign_ext.2: bits[$0] = sign_ext(literal.1, new_bit_count=$0) + } + )"; + + std::string formatted_text = absl::Substitute( + ir_text, new_bit_count, input.bit_count(), input.ToString()); + XLS_ASSIGN_OR_RETURN(auto package, Parser::ParsePackage(formatted_text)); + XLS_ASSIGN_OR_RETURN(Function * function, package->EntryFunction()); + Value expected(bits_ops::SignExtend(input, new_bit_count)); + EXPECT_THAT(param.evaluator(function, {}), IsOkAndHolds(expected)); + return absl::OkStatus(); +} + +TEST_P(IrEvaluatorTest, InterpretSignExtend) { + XLS_ASSERT_OK(RunSignExtendTest(GetParam(), UBits(0b11, 2), 8)); + XLS_ASSERT_OK(RunSignExtendTest(GetParam(), UBits(0b01, 2), 8)); + XLS_ASSERT_OK(RunSignExtendTest(GetParam(), UBits(0b11, 57), 1023)); + XLS_ASSERT_OK(RunSignExtendTest(GetParam(), UBits(0b11, 64), 128)); + XLS_ASSERT_OK(RunSignExtendTest(GetParam(), UBits(0b01, 64), 128)); +} + +TEST_P(IrEvaluatorTest, InterpretTwoAddTwo) { + Package package("my_package"); + // Big values to help debugging, again. + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn two_add_two() -> bits[32] { + literal.1: bits[32] = literal(value=127) + literal.2: bits[32] = literal(value=255) + ret add.3: bits[32] = add(literal.1, literal.2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(382, 32)))); +} + +TEST_P(IrEvaluatorTest, InterpretMaxAddTwo) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn max_add_two() -> bits[3] { + literal.1: bits[3] = literal(value=7) + literal.2: bits[3] = literal(value=2) + ret add.3: bits[3] = add(literal.1, literal.2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(1, 3)))); +} + +TEST_P(IrEvaluatorTest, InterpretEq) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn equal(a: bits[32], b: bits[32]) -> bits[1] { + ret eq.1: bits[1] = eq(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(1, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a < b + args = {{"a", Value(UBits(1, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a == b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretULt) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn less_than(a: bits[32], b: bits[32]) -> bits[1] { + ret lt.1: bits[1] = ult(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(1, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a < b + args = {{"a", Value(UBits(1, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); + + // a == b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretULe) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn less_than_or_equal(a: bits[32], b: bits[32]) -> bits[1] { + ret le.1: bits[1] = ule(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(1, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a < b + args = {{"a", Value(UBits(1, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); + + // a == b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretUGt) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn greater_than(a: bits[32], b: bits[32]) -> bits[1] { + ret gt.1: bits[1] = ugt(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(1, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); + + // a < b + args = {{"a", Value(UBits(1, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a == b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretUGe) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn greater_than_or_equal(a: bits[32], b: bits[32]) -> bits[1] { + ret ge.1: bits[1] = uge(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(1, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); + + // a < b + args = {{"a", Value(UBits(1, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a == b + args = {{"a", Value(UBits(4, 32))}, {"b", Value(UBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretSLt) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn less_than(a: bits[32], b: bits[32]) -> bits[1] { + ret lt.1: bits[1] = slt(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(SBits(4, 32))}, {"b", Value(SBits(1, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a < b + args = {{"a", Value(SBits(-1, 32))}, {"b", Value(SBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); + + // a == b + args = {{"a", Value(SBits(-4, 32))}, {"b", Value(SBits(-4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretSLe) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn less_than_or_equal(a: bits[32], b: bits[32]) -> bits[1] { + ret le.1: bits[1] = sle(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(SBits(-1, 32))}, {"b", Value(SBits(-4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a < b + args = {{"a", Value(SBits(-1, 32))}, {"b", Value(SBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); + + // a == b + args = {{"a", Value(SBits(4, 32))}, {"b", Value(SBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretSGt) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn greater_than(a: bits[32], b: bits[32]) -> bits[1] { + ret gt.1: bits[1] = sgt(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(SBits(-123, 32))}, {"b", Value(SBits(-442, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); + + // a < b + args = {{"a", Value(SBits(-1, 32))}, {"b", Value(SBits(400, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a == b + args = {{"a", Value(SBits(4, 32))}, {"b", Value(SBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretSGe) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn greater_than_or_equal(a: bits[32], b: bits[32]) -> bits[1] { + ret ge.1: bits[1] = sge(a, b) + } + )")); + + absl::flat_hash_map args; + + // a > b + args = {{"a", Value(SBits(4, 32))}, {"b", Value(SBits(-1, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); + + // a < b + args = {{"a", Value(SBits(-10, 32))}, {"b", Value(SBits(4, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0, 1)))); + + // a == b + args = {{"a", Value(SBits(400, 32))}, {"b", Value(SBits(400, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 1)))); +} + +TEST_P(IrEvaluatorTest, InterpretTwoMulThree) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn two_umul_three() -> bits[32] { + literal.1: bits[32] = literal(value=2) + literal.2: bits[32] = literal(value=3) + ret umul.3: bits[32] = umul(literal.1, literal.2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(6, 32)))); +} + +TEST_P(IrEvaluatorTest, InterpretMaxMulTwo) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn max_plus_two() -> bits[3] { + literal.1: bits[3] = literal(value=7) + literal.2: bits[3] = literal(value=2) + ret add.3: bits[3] = umul(literal.1, literal.2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(6, 3)))); +} + +TEST_P(IrEvaluatorTest, MixedWidthMultiplication) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn mixed_mul(x: bits[7], y: bits[3]) -> bits[5] { + ret umul.1: bits[5] = umul(x, y) + } + )")); + + EXPECT_THAT(Run(function, {0, 0}), IsOkAndHolds(0)); + EXPECT_THAT(Run(function, {127, 7}), IsOkAndHolds(25)); + EXPECT_THAT(Run(function, {3, 5}), IsOkAndHolds(15)); +} + +TEST_P(IrEvaluatorTest, MixedWidthMultiplicationExtraWideResult) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn mixed_mul(x: bits[5], y: bits[4]) -> bits[20] { + ret umul.1: bits[20] = umul(x, y) + } + )")); + + EXPECT_THAT(Run(function, {0, 0}), IsOkAndHolds(0)); + EXPECT_THAT(Run(function, {31, 15}), IsOkAndHolds(465)); + EXPECT_THAT(Run(function, {11, 5}), IsOkAndHolds(55)); +} + +TEST_P(IrEvaluatorTest, MixedWidthMultiplicationExhaustive) { + // Exhaustively check all bit width and value combinations of a mixed-width + // unsigned multiply up to a small constant bit width. + constexpr absl::string_view ir_text = R"( + fn mixed_mul(x: bits[$0], y: bits[$1]) -> bits[$2] { + ret umul.1: bits[$2] = umul(x, y) + } + )"; + + const int64 kMaxWidth = 3; + for (int x_width = 1; x_width <= kMaxWidth; ++x_width) { + for (int y_width = 1; y_width <= kMaxWidth; ++y_width) { + for (int result_width = 1; result_width <= kMaxWidth; ++result_width) { + std::string formatted_ir = + absl::Substitute(ir_text, x_width, y_width, result_width); + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, formatted_ir)); + for (int x = 0; x < 1 << x_width; ++x) { + for (int y = 0; y < 1 << y_width; ++y) { + Bits x_bits = UBits(x, x_width); + Bits y_bits = UBits(y, y_width); + // The expected result width may be narrower or wider than the + // result produced by bits_ops::Umul so just sign extend to a wide + // value then slice it to size. + Bits expected = bits_ops::ZeroExtend(bits_ops::UMul(x_bits, y_bits), + 2 * kMaxWidth) + .Slice(0, result_width); + EXPECT_THAT(RunBits(function, {x_bits, y_bits}), + IsOkAndHolds(expected)) + << absl::StreamFormat("umul(bits[%d]: %s, bits[%d]: %s)", + x_width, x_bits.ToString(), y_width, + y_bits.ToString()); + } + } + } + } + } +} + +TEST_P(IrEvaluatorTest, MixedWidthSignedMultiplication) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn mixed_smul(x: bits[7], y: bits[3]) -> bits[6] { + ret smul.1: bits[6] = smul(x, y) + } + )")); + + EXPECT_THAT(RunBits(function, {SBits(0, 7), SBits(0, 3)}), + IsOkAndHolds(SBits(0, 6))); + EXPECT_THAT(RunBits(function, {SBits(-5, 7), SBits(-2, 3)}), + IsOkAndHolds(SBits(10, 6))); + EXPECT_THAT(RunBits(function, {SBits(10, 7), SBits(-2, 3)}), + IsOkAndHolds(SBits(-20, 6))); + EXPECT_THAT(RunBits(function, {SBits(-50, 7), SBits(-2, 3)}), + IsOkAndHolds(SBits(-28, 6))); +} + +TEST_P(IrEvaluatorTest, MixedWidthSignedMultiplicationExhaustive) { + // Exhaustively check all bit width and value combinations of a mixed-width + // unsigned multiply up to a small constant bit width. + constexpr absl::string_view ir_text = R"( + fn mixed_mul(x: bits[$0], y: bits[$1]) -> bits[$2] { + ret smul.1: bits[$2] = smul(x, y) + } + )"; + + const int64 kMaxWidth = 3; + for (int x_width = 1; x_width <= kMaxWidth; ++x_width) { + for (int y_width = 1; y_width <= kMaxWidth; ++y_width) { + for (int result_width = 1; result_width <= kMaxWidth; ++result_width) { + std::string formatted_ir = + absl::Substitute(ir_text, x_width, y_width, result_width); + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, formatted_ir)); + for (int x = 0; x < 1 << x_width; ++x) { + for (int y = 0; y < 1 << y_width; ++y) { + Bits x_bits = UBits(x, x_width); + Bits y_bits = UBits(y, y_width); + // The expected result width may be narrower or wider than the + // result produced by bits_ops::Umul so just sign extend to a wide + // value then slice it to size. + Bits expected = bits_ops::SignExtend(bits_ops::SMul(x_bits, y_bits), + 2 * kMaxWidth) + .Slice(0, result_width); + EXPECT_THAT(RunBits(function, {x_bits, y_bits}), + IsOkAndHolds(expected)) + << absl::StreamFormat("smul(bits[%d]: %s, bits[%d]: %s)", + x_width, x_bits.ToString(), y_width, + y_bits.ToString()); + } + } + } + } + } +} + +TEST_P(IrEvaluatorTest, InterpretUDiv) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_left_logical(a: bits[8], b: bits[8]) -> bits[8] { + ret shll.1: bits[8] = udiv(a, b) + } + )")); + + absl::flat_hash_map args; + + args = {{"a", Value(UBits(33, 8))}, {"b", Value(UBits(11, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(3, 8)))); + + // Should round to zero. + args = {{"a", Value(UBits(4, 8))}, {"b", Value(UBits(3, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 8)))); + + // Divide by zero. + args = {{"a", Value(UBits(123, 8))}, {"b", Value(UBits(0, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0xff, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretSDiv) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn signed_div(a: bits[8], b: bits[8]) -> bits[8] { + ret shll.1: bits[8] = sdiv(a, b) + } + )")); + + absl::flat_hash_map args; + + args = {{"a", Value(SBits(33, 8))}, {"b", Value(SBits(-11, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(SBits(-3, 8)))); + + // Should round to zero. + args = {{"a", Value(SBits(4, 8))}, {"b", Value(SBits(3, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(SBits(1, 8)))); + args = {{"a", Value(SBits(4, 8))}, {"b", Value(SBits(-3, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(SBits(-1, 8)))); + + // Divide by zero. + args = {{"a", Value(SBits(123, 8))}, {"b", Value(SBits(0, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(SBits(127, 8)))); + args = {{"a", Value(SBits(-10, 8))}, {"b", Value(SBits(0, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(SBits(-128, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretShll) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_left_logical(a: bits[8], b: bits[8]) -> bits[8] { + ret shll.1: bits[8] = shll(a, b) + } + )")); + + absl::flat_hash_map args; + + args = {{"a", Value(UBits(4, 8))}, {"b", Value(UBits(3, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(32, 8)))); + + // No shift. + args = {{"a", Value(UBits(4, 8))}, {"b", Value(UBits(0, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(4, 8)))); + + // Max shift amount, does not wrap. + args = {{"a", Value(UBits(0b11111111, 8))}, {"b", Value(UBits(7, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0b10000000, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretShllNarrowShiftAmount) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_left_logical(a: bits[8], b: bits[3]) -> bits[8] { + ret shll.1: bits[8] = shll(a, b) + } + )")); + + absl::flat_hash_map args; + + args = {{"a", Value(UBits(4, 8))}, {"b", Value(UBits(3, 3))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(32, 8)))); + + // No shift. + args = {{"a", Value(UBits(4, 8))}, {"b", Value(UBits(0, 3))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(4, 8)))); + + // Max shift amount, does not wrap. + args = {{"a", Value(UBits(0b11111111, 8))}, {"b", Value(UBits(7, 3))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0b10000000, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretShll64) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_left_logical(a: bits[64], b: bits[64]) -> bits[64] { + ret shll.1: bits[64] = shll(a, b) + } + )")); + + absl::flat_hash_map args = {{"a", Value(UBits(1, 64))}, + {"b", Value(UBits(63, 64))}}; + XLS_ASSERT_OK_AND_ASSIGN(auto result, + GetParam().kwargs_evaluator(function, args)); + EXPECT_EQ(result.bits(), UBits(0x8000000000000000, 64)); +} + +TEST_P(IrEvaluatorTest, InterpretShrl) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_right_logical(a: bits[8], b: bits[8]) -> bits[8] { + ret shrl.1: bits[8] = shrl(a, b) + } + )")); + + absl::flat_hash_map args; + + args = {{"a", Value(UBits(0b00000100, 8))}, {"b", Value(UBits(2, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 8)))); + + // No shift. + args = {{"a", Value(UBits(0b00000100, 8))}, {"b", Value(UBits(0, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(4, 8)))); + + // Max shift amount, does not wrap. + args = {{"a", Value(UBits(0b11111111, 8))}, {"b", Value(UBits(7, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0b00000001, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretShrlBits64) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_right_logical(a: bits[64], b: bits[64]) -> bits[64] { + ret shrl.1: bits[64] = shrl(a, b) + } + )")); + + // Max shift amount is bit_count() - 1; should be one bit left over. + absl::flat_hash_map args = {{"a", Value(SBits(-1, 64))}, + {"b", Value(UBits(63, 64))}}; + XLS_ASSERT_OK_AND_ASSIGN(auto result, + GetParam().kwargs_evaluator(function, args)); + EXPECT_EQ(result.bits(), UBits(1, 64)); +} + +TEST_P(IrEvaluatorTest, InterpretShra) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_right_arithmetic(a: bits[8], b: bits[8]) -> bits[8] { + ret shra.1: bits[8] = shra(a, b) + } + )")); + + absl::flat_hash_map args; + + // Zero padding from left. + args = {{"a", Value(UBits(0b00000100, 8))}, {"b", Value(UBits(2, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 8)))); + + // Ones padding from left. + args = {{"a", Value(UBits(0b10000100, 8))}, {"b", Value(UBits(2, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0b11100001, 8)))); + + // No shift. + args = {{"a", Value(UBits(4, 8))}, {"b", Value(UBits(0, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(4, 8)))); + + // Max shift amount, does not wrap. + args = {{"a", Value(UBits(0b11111111, 8))}, {"b", Value(UBits(7, 8))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0b11111111, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretShraNarrowShiftAmount) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_right_arithmetic(a: bits[8], b: bits[2]) -> bits[8] { + ret shra.1: bits[8] = shra(a, b) + } + )")); + + absl::flat_hash_map args; + + // Zero padding from left. + args = {{"a", Value(UBits(0b00000100, 8))}, {"b", Value(UBits(2, 2))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(1, 8)))); + + // Ones padding from left. + args = {{"a", Value(UBits(0b10000100, 8))}, {"b", Value(UBits(2, 2))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0b11100001, 8)))); + + // No shift. + args = {{"a", Value(UBits(4, 8))}, {"b", Value(UBits(0, 2))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(4, 8)))); + + // Max shift amount, does not wrap. + args = {{"a", Value(UBits(0b11111111, 8))}, {"b", Value(UBits(3, 2))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0b11111111, 8)))); +} + +TEST_P(IrEvaluatorTest, InterpretShraBits64) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn shift_right_arithmetic(a: bits[64], b: bits[64]) -> bits[64] { + ret shra.1: bits[64] = shra(a, b) + } + )")); + + // Max shift amount is bit_count() - 1; should be all one's after shift. + absl::flat_hash_map args = {{"a", Value(SBits(-1, 64))}, + {"b", Value(UBits(63, 64))}}; + XLS_ASSERT_OK_AND_ASSIGN(auto result, + GetParam().kwargs_evaluator(function, args)); + EXPECT_EQ(result.bits(), SBits(-1, 64)); + + args = {{"a", Value(UBits(0xF000000000000000ULL, 64))}, + {"b", Value(UBits(4, 64))}}; + XLS_ASSERT_OK_AND_ASSIGN(result, GetParam().kwargs_evaluator(function, args)); + EXPECT_EQ(result.bits(), UBits(0xFF00000000000000ULL, 64)); +} + +TEST_P(IrEvaluatorTest, InterpretFourSubOne) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn sub(a: bits[32], b: bits[32]) -> bits[32] { + ret sub.3: bits[32] = sub(a, b) + } + )")); + + absl::flat_hash_map args = {{"a", Value(UBits(4, 32))}, + {"b", Value(UBits(1, 32))}}; + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(3, 32)))); +} + +absl::Status RunConcatTest(const IrEvaluatorTestParam& param, + const ArgMap& kwargs) { + constexpr absl::string_view ir_text = R"( + package test + + fn concatenate(a: bits[$0], b: bits[$1]) -> bits[$2] { + ret concat.1: bits[$2] = concat(a, b) + } + )"; + + std::string bytes_str = "0x"; + std::vector bytes; + + Value a = kwargs.at("a"); + Value b = kwargs.at("b"); + int64 a_width = a.GetFlatBitCount(); + int64 b_width = b.GetFlatBitCount(); + std::string formatted_text = + absl::Substitute(ir_text, a_width, b_width, a_width + b_width); + XLS_ASSIGN_OR_RETURN(auto package, Parser::ParsePackage(formatted_text)); + XLS_ASSIGN_OR_RETURN(Function * function, package->EntryFunction()); + + Value expected(bits_ops::Concat({a.bits(), b.bits()})); + EXPECT_THAT(param.kwargs_evaluator(function, kwargs), IsOkAndHolds(expected)); + return absl::OkStatus(); +} + +TEST_P(IrEvaluatorTest, InterpretConcat) { + ArgMap args = {{"a", Value(UBits(1, 8))}, {"b", Value(UBits(4, 8))}}; + XLS_ASSERT_OK(RunConcatTest(GetParam(), args)); + + args = {{"a", Value(UBits(4, 8))}, {"b", Value(UBits(1, 8))}}; + XLS_ASSERT_OK(RunConcatTest(GetParam(), args)); + + args = {{"a", Value(SBits(-1, 8))}, {"b", Value(SBits(-1, 8))}}; + XLS_ASSERT_OK(RunConcatTest(GetParam(), args)); + + args = {{"a", Value(SBits(-1, 128))}, {"b", Value(SBits(-1, 128))}}; + XLS_ASSERT_OK(RunConcatTest(GetParam(), args)); + + args = {{"a", Value(SBits(512, 513))}, {"b", Value(SBits(511, 513))}}; + XLS_ASSERT_OK(RunConcatTest(GetParam(), args)); + + args = {{"a", Value(SBits(512, 1025))}, {"b", Value(SBits(511, 1025))}}; + XLS_ASSERT_OK(RunConcatTest(GetParam(), args)); +} + +TEST_P(IrEvaluatorTest, InterpretOneHot) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn one_hot(x: bits[3]) -> bits[4] { + ret one_hot.1: bits[4] = one_hot(x, lsb_prio=true) + } + )")); + + struct Example { + Bits input; + Bits output; + }; + std::vector examples = { + {UBits(0b000, 3), UBits(0b1000, 4)}, {UBits(0b001, 3), UBits(0b0001, 4)}, + {UBits(0b010, 3), UBits(0b0010, 4)}, {UBits(0b011, 3), UBits(0b0001, 4)}, + {UBits(0b100, 3), UBits(0b0100, 4)}, {UBits(0b101, 3), UBits(0b0001, 4)}, + {UBits(0b110, 3), UBits(0b0010, 4)}, {UBits(0b111, 3), UBits(0b0001, 4)}, + }; + + for (const auto& example : examples) { + XLS_VLOG(2) << "input: " + << example.input.ToString(FormatPreference::kBinary, true) + << " expected: " + << example.output.ToString(FormatPreference::kBinary, true); + EXPECT_THAT(GetParam().evaluator(function, {Value(example.input)}), + IsOkAndHolds(Value(example.output))); + } +} + +TEST_P(IrEvaluatorTest, InterpretOneHotMsbPrio) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn one_hot(x: bits[3]) -> bits[4] { + ret one_hot.1: bits[4] = one_hot(x, lsb_prio=false) + } + )")); + + struct Example { + Bits input; + Bits output; + }; + std::vector examples = { + {UBits(0b000, 3), UBits(0b1000, 4)}, {UBits(0b001, 3), UBits(0b0001, 4)}, + {UBits(0b010, 3), UBits(0b0010, 4)}, {UBits(0b011, 3), UBits(0b0010, 4)}, + {UBits(0b100, 3), UBits(0b0100, 4)}, {UBits(0b101, 3), UBits(0b0100, 4)}, + {UBits(0b110, 3), UBits(0b0100, 4)}, {UBits(0b111, 3), UBits(0b0100, 4)}, + }; + + for (const auto& example : examples) { + XLS_VLOG(2) << "input: " + << example.input.ToString(FormatPreference::kBinary, true) + << " expected: " + << example.output.ToString(FormatPreference::kBinary, true); + EXPECT_THAT(GetParam().evaluator(function, {Value(example.input)}), + IsOkAndHolds(Value(example.output))); + } +} + +TEST_P(IrEvaluatorTest, InterpretOneBitOneHot) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn one_hot(x: bits[1]) -> bits[2] { + ret one_hot.1: bits[2] = one_hot(x, lsb_prio=true) + } + )")); + struct Example { + Bits input; + Bits output; + }; + std::vector examples = { + {UBits(0, 1), UBits(2, 2)}, + {UBits(1, 1), UBits(1, 2)}, + }; + for (const auto& example : examples) { + EXPECT_THAT(GetParam().evaluator(function, {Value(example.input)}), + IsOkAndHolds(Value(example.output))); + } +} + +TEST_P(IrEvaluatorTest, InterpretOneHotSelect) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn one_hot(p: bits[3], x: bits[8], y: bits[8], z: bits[8]) -> bits[8] { + ret one_hot_sel.1: bits[8] = one_hot_sel(p, cases=[x, y, z]) + } + )")); + absl::flat_hash_map args_map = { + {"x", Value(UBits(0b00001111, 8))}, + {"y", Value(UBits(0b00110011, 8))}, + {"z", Value(UBits(0b01010101, 8))}}; + + struct Example { + Bits p; + Bits output; + }; + + std::vector examples = { + {UBits(0b000, 3), UBits(0b00000000, 8)}, + {UBits(0b001, 3), UBits(0b00001111, 8)}, + {UBits(0b010, 3), UBits(0b00110011, 8)}, + {UBits(0b011, 3), UBits(0b00111111, 8)}, + {UBits(0b100, 3), UBits(0b01010101, 8)}, + {UBits(0b101, 3), UBits(0b01011111, 8)}, + {UBits(0b110, 3), UBits(0b01110111, 8)}, + {UBits(0b111, 3), UBits(0b01111111, 8)}, + }; + + for (const auto& example : examples) { + std::vector args = {Value(example.p)}; + args.push_back(args_map["x"]); + args.push_back(args_map["y"]); + args.push_back(args_map["z"]); + EXPECT_THAT(GetParam().evaluator(function, args), + IsOkAndHolds(Value(example.output))); + } +} + +TEST_P(IrEvaluatorTest, InterpretOneHotSelectNestedTuple) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn one_hot(p: bits[2], x: (bits[3], (bits[8])), y: (bits[3], (bits[8]))) -> (bits[3], (bits[8])) { + ret one_hot_sel.1: (bits[3], (bits[8])) = one_hot_sel(p, cases=[x, y]) + } + )")); + absl::flat_hash_map args = { + {"x", AsValue("(bits[3]:0b101, (bits[8]:0b11001100))")}, + {"y", AsValue("(bits[3]:0b001, (bits[8]:0b00001111))")}}; + + args["p"] = AsValue("bits[2]: 0b00"); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(AsValue("(bits[3]:0b000, (bits[8]:0b00000000))"))); + args["p"] = AsValue("bits[2]: 0b01"); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(AsValue("(bits[3]:0b101, (bits[8]:0b11001100))"))); + args["p"] = AsValue("bits[2]: 0b10"); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(AsValue("(bits[3]:0b001, (bits[8]:0b00001111))"))); + args["p"] = AsValue("bits[2]: 0b11"); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(AsValue("(bits[3]:0b101, (bits[8]:0b11001111))"))); +} + +TEST_P(IrEvaluatorTest, InterpretOneHotSelectArray) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn one_hot(p: bits[2], x: bits[4][3], y: bits[4][3]) -> bits[4][3] { + ret one_hot_sel.1: bits[4][3] = one_hot_sel(p, cases=[x, y]) + } + )")); + absl::flat_hash_map args = { + {"x", AsValue("[bits[4]:0b0001, bits[4]:0b0010, bits[4]:0b0100]")}, + {"y", AsValue("[bits[4]:0b1100, bits[4]:0b0101, bits[4]:0b1110]")}}; + + args["p"] = AsValue("bits[2]: 0b00"); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds( + AsValue("[bits[4]:0b0000, bits[4]:0b0000, bits[4]:0b0000]"))); + args["p"] = AsValue("bits[2]: 0b01"); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds( + AsValue("[bits[4]:0b0001, bits[4]:0b0010, bits[4]:0b0100]"))); + args["p"] = AsValue("bits[2]: 0b10"); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds( + AsValue("[bits[4]:0b1100, bits[4]:0b0101, bits[4]:0b1110]"))); + args["p"] = AsValue("bits[2]: 0b11"); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds( + AsValue("[bits[4]:0b1101, bits[4]:0b0111, bits[4]:0b1110]"))); +} + +TEST_P(IrEvaluatorTest, InterpretBinarySel) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn select(cond: bits[1], if_true: bits[8], if_false: bits[8]) -> bits[8] { + ret sel.1: bits[8] = sel(cond, cases=[if_false, if_true]) + } + )")); + + absl::flat_hash_map args = { + {"if_true", Value(UBits(0xA, 8))}, {"if_false", Value(UBits(0xB, 8))}}; + + args["cond"] = Value(UBits(1, 1)); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0xA, 8)))); + + args["cond"] = Value(UBits(0, 1)); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(0xB, 8)))); +} + +TEST_P(IrEvaluatorTest, Interpret4WaySel) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn select(p: bits[2], w:bits[8], x: bits[8], y: bits[8], z:bits[8]) -> bits[8] { + ret sel.1: bits[8] = sel(p, cases=[w, x, y, z]) + } + )")); + + auto u2 = [](int64 i) { return Value(UBits(i, 2)); }; + auto u8 = [](int64 i) { return Value(UBits(i, 8)); }; + absl::flat_hash_map args = { + {"w", u8(0xa)}, {"x", u8(0xb)}, {"y", u8(0xc)}, {"z", u8(0xd)}}; + args["p"] = u2(0); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u8(0xa))); + args["p"] = u2(1); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u8(0xb))); + args["p"] = u2(2); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u8(0xc))); + args["p"] = u2(3); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u8(0xd))); +} + +TEST_P(IrEvaluatorTest, InterpretManyWaySelWithDefault) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn select(p: bits[1024], w:bits[32], x: bits[32], y: bits[32]) -> bits[32] { + literal.1: bits[32] = literal(value=0xdefa17) + ret sel.2: bits[32] = sel(p, cases=[w, x, y], default=literal.1) + } + )")); + + auto u1024 = [](int64 i) { return Value(UBits(i, 1024)); }; + auto u32 = [](int64 i) { return Value(UBits(i, 32)); }; + absl::flat_hash_map args = { + {"w", u32(0xa)}, {"x", u32(0xb)}, {"y", u32(0xc)}}; + args["p"] = u1024(0); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u32(0xa))); + args["p"] = u1024(1); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u32(0xb))); + args["p"] = u1024(2); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u32(0xc))); + args["p"] = u1024(3); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u32(0xdefa17))); + XLS_ASSERT_OK_AND_ASSIGN( + args["p"], + Parser::ParseTypedValue( + "bits[1024]:0xabcd_1111_cccc_1234_ffff_eeee_3333_7777_0000_9876")); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(u32(0xdefa17))); +} + +TEST_P(IrEvaluatorTest, InterpretMap) { + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(R"( + package SimpleMap + + fn to_apply(element: bits[16]) -> bits[1] { + literal.2: bits[16] = literal(value=2) + ret lt.3: bits[1] = ult(element, literal.2) + } + + fn main(input: bits[16][2]) -> bits[1][2] { + ret map.5: bits[1][2] = map(input, to_apply=to_apply) + } + )")); + + XLS_ASSERT_OK(Verify(package.get())); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN( + auto input_array, + Value::Array({Value(UBits(1, 16)), Value(UBits(2, 16))})); + XLS_ASSERT_OK_AND_ASSIGN(Value result, + GetParam().evaluator(function, {input_array})); + EXPECT_EQ(result.elements().at(0), Value(UBits(1, 1))); + EXPECT_EQ(result.elements().at(1), Value(UBits(0, 1))); +} + +TEST_P(IrEvaluatorTest, InterpretTwoLevelInvoke) { + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(R"( + package SimpleMap + + fn create_tuple(element: bits[16]) -> (bits[1], bits[16]) { + literal.2: bits[16] = literal(value=2) + lt.3: bits[1] = ult(element, literal.2) + ret tuple.4: (bits[1], bits[16]) = tuple(lt.3, element) + } + + fn nest_tuple(element: (bits[1], bits[16])) -> ((bits[1], bits[1]), bits[16]) { + tuple_index.5: bits[1] = tuple_index(element, index=0) + tuple_index.6: bits[16] = tuple_index(element, index=1) + tuple.7: (bits[1], bits[1]) = tuple(tuple_index.5, tuple_index.5) + ret tuple.8: ((bits[1], bits[1]), bits[16]) = tuple(tuple.7, tuple_index.6) + } + + fn to_apply(element: bits[16]) -> ((bits[1], bits[1]), bits[16], bits[1]) { + invoke.9: (bits[1], bits[16]) = invoke(element, to_apply=create_tuple) + invoke.10: ((bits[1], bits[1]), bits[16]) = invoke(invoke.9, to_apply=nest_tuple) + tuple_index.11: (bits[1], bits[1]) = tuple_index(invoke.10, index=0) + tuple_index.12: bits[16] = tuple_index(invoke.10, index=1) + tuple_index.13: bits[1] = tuple_index(tuple_index.11, index=0) + ret tuple.14: ((bits[1], bits[1]), bits[16], bits[1]) = tuple(tuple_index.11, tuple_index.12, tuple_index.13) + } + + fn main(input: bits[16][2]) -> ((bits[1], bits[1]), bits[16], bits[1])[2] { + ret map.15: ((bits[1], bits[1]), bits[16], bits[1])[2] = map(input, to_apply=to_apply) + } + )")); + + XLS_ASSERT_OK(Verify(package.get())); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN( + auto input_array, + Value::Array({Value(UBits(1, 16)), Value(UBits(2, 16))})); + XLS_ASSERT_OK_AND_ASSIGN(Value result, + GetParam().evaluator(function, {input_array})); + + Value expected = + Value::Tuple({Value::Tuple({Value(UBits(1, 1)), Value(UBits(1, 1))}), + Value(UBits(1, 16)), Value(UBits(1, 1))}); + + EXPECT_EQ(result.elements().at(0), expected); + + expected = + Value::Tuple({Value::Tuple({Value(UBits(0, 1)), Value(UBits(0, 1))}), + Value(UBits(2, 16)), Value(UBits(0, 1))}); + EXPECT_EQ(result.elements().at(1), expected); +} + +absl::Status RunReverseTest(const IrEvaluatorTestParam& param, + const Bits& bits) { + constexpr absl::string_view ir_text = R"( + package test + + fn main(x: bits[$0]) -> bits[$0] { + ret reverse.1: bits[$0] = reverse(x) + } + )"; + + std::string formatted_ir = absl::Substitute(ir_text, bits.bit_count()); + XLS_ASSIGN_OR_RETURN(auto package, Parser::ParsePackage(formatted_ir)); + XLS_ASSIGN_OR_RETURN(Function * function, package->EntryFunction()); + + Value expected(bits_ops::Reverse(bits)); + EXPECT_THAT(param.evaluator(function, {Value(bits)}), IsOkAndHolds(expected)); + + return absl::OkStatus(); +} +TEST_P(IrEvaluatorTest, InterpretReverse) { + XLS_ASSERT_OK(RunReverseTest(GetParam(), UBits(0, 1))); + XLS_ASSERT_OK(RunReverseTest(GetParam(), UBits(1, 1))); + XLS_ASSERT_OK(RunReverseTest(GetParam(), UBits(0b10000000, 8))); + XLS_ASSERT_OK(RunReverseTest(GetParam(), UBits(0b11000101, 8))); + XLS_ASSERT_OK(RunReverseTest(GetParam(), UBits(0b11000101, 9))); + XLS_ASSERT_OK(RunReverseTest(GetParam(), UBits(0b011000101, 9))); + XLS_ASSERT_OK(RunReverseTest(GetParam(), UBits(0b011000101, 65))); + XLS_ASSERT_OK(RunReverseTest(GetParam(), UBits(0b011000101, 129))); +} + +TEST_P(IrEvaluatorTest, InterpretCountedFor) { + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(R"( + package test + + fn body(iv: bits[3], y: bits[11]) -> bits[11] { + zero_ext.1: bits[11] = zero_ext(iv, new_bit_count=11) + ret add.3: bits[11] = add(zero_ext.1, y) + } + + fn main() -> bits[11] { + literal.4: bits[11] = literal(value=0) + ret counted_for.5: bits[11] = counted_for(literal.4, trip_count=7, stride=1, body=body) + } + )")); + XLS_ASSERT_OK(Verify(package.get())); + + // Expected execution behavior: + // initial_value = 0, trip_count = 7, stride = 1 + // iteration 0: body(iv = 0, x = 0) -> 0 + // iteration 1: body(iv = 1, x = 0) -> 1 + // iteration 2: body(iv = 2, x = 1) -> 3 + // iteration 3: body(iv = 3, x = 3) -> 6 + // iteration 4: body(iv = 4, x = 6) -> 10 + // iteration 5: body(iv = 5, x = 10) -> 15 + // iteration 6: body(iv = 6, x = 15) -> 21 + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(21, 11)))); +} + +TEST_P(IrEvaluatorTest, InterpretCountedForStride2) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package test + + fn body(x: bits[11], y: bits[11]) -> bits[11] { + ret add.3: bits[11] = add(x, y) + } + + fn main() -> bits[11] { + literal.4: bits[11] = literal(value=0) + ret counted_for.5: bits[11] = counted_for(literal.4, trip_count=7, stride=2, body=body) + } + )")); + + // TODO(dmlockhart): is this the stride of 2 behavior we want? + // Expected execution behavior: + // initial_value = 0, trip_count = 7, stride = 2 + // iteration 0: body(x = 0, y = 0) -> 0 + // iteration 1: body(x = 0, y = 2) -> 2 + // iteration 2: body(x = 2, y = 4) -> 6 + // iteration 3: body(x = 6, y = 6) -> 12 + // iteration 4: body(x = 12, y = 8) -> 20 + // iteration 5: body(x = 20, y = 10) -> 30 + // iteration 6: body(x = 30, y = 12) -> 42 + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(42, 11)))); +} + +TEST_P(IrEvaluatorTest, InterpretCountedForStaticArgs) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( +package test + +fn body(x: bits[32], y: bits[32], z: bits[32], unused: bits[32]) -> bits[32] { + add.3: bits[32] = add(x, y) + ret shll.4: bits[32] = shll(add.3, z) +} + +fn main() -> bits[32] { + literal.0: bits[32] = literal(value=0) + literal.1: bits[32] = literal(value=1) + ret counted_for.5: bits[32] = counted_for(literal.0, trip_count=4, body=body, invariant_args=[literal.1, literal.0]) +} +)")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + // The body function executed via counted_for above should be equivalent to + // the following: + int64 expected = 0; + for (int64 i = 0; i < 4; ++i) { + expected += i; + expected <<= 1; + } + + // Expected execution behavior: + // initial_value = 0, trip_count = 4, stride = 1, invariant_args=[1,0] + // iteration 0: body(x = 0, y = 0, z = 1, unused = 0) -> 0 + // iteration 1: body(x = 0, y = 1, z = 1, unused = 0) -> 2 + // iteration 2: body(x = 2, y = 2, z = 1, unused = 0) -> 8 + // iteration 3: body(x = 8, y = 3, z = 1, unused = 0) -> 11 + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(expected, 32)))); +} + +TEST_P(IrEvaluatorTest, InterpretTuple) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn make_tuple(x: bits[32]) -> (bits[32], bits[64], bits[128]) { + zero_ext.1: bits[64] = zero_ext(x, new_bit_count=64) + zero_ext.2: bits[128] = zero_ext(x, new_bit_count=128) + ret tuple.3: (bits[32], bits[64], bits[128]) = tuple(x, zero_ext.1, zero_ext.2) + } + )")); + + absl::flat_hash_map args = {{"x", Value(UBits(4, 32))}}; + + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value::Tuple({ + Value(UBits(4, 32)), + Value(UBits(4, 64)), + Value(UBits(4, 128)), + }))); +} + +TEST_P(IrEvaluatorTest, InterpretTupleLiteral) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn tuple_literal() -> (bits[8], bits[8], bits[8]) { + literal.1: bits[8] = literal(value=3) + literal.2: bits[8] = literal(value=2) + literal.3: bits[8] = literal(value=1) + ret tuple.4: (bits[8], bits[8], bits[8]) = tuple(literal.1, literal.2, + literal.3) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value::Tuple({ + Value(UBits(3, 8)), + Value(UBits(2, 8)), + Value(UBits(1, 8)), + }))); +} + +TEST_P(IrEvaluatorTest, InterpretTupleIndexReturnsBits) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn tuple_index() -> bits[33] { + literal.1: bits[32] = literal(value=5) + literal.2: bits[33] = literal(value=123) + tuple.3: (bits[32], bits[33]) = tuple(literal.1, literal.2) + ret tuple_index.4: bits[33] = tuple_index(tuple.3, index=1) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(123, 33)))); +} + +TEST_P(IrEvaluatorTest, InterpretTupleIndexReturnsTuple) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn tuple_index() -> (bits[33], bits[34]) { + literal.1: bits[32] = literal(value=5) + literal.2: bits[33] = literal(value=123) + literal.3: bits[34] = literal(value=7) + tuple.4: (bits[33], bits[34]) = tuple(literal.2, literal.3) + tuple.5: (bits[32], (bits[33], bits[34])) = tuple(literal.1, tuple.4) + ret tuple_index.6: (bits[33], bits[34]) = tuple_index(tuple.5, index=1) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value::Tuple({ + Value(UBits(123, 33)), + Value(UBits(7, 34)), + }))); +} + +TEST_P(IrEvaluatorTest, InterpretTupleIndexOfLiteralReturnsTuple) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn tuple_index() -> (bits[37], bits[38]) { + literal.1: (bits[32][3], bits[33], bits[34], (bits[35], bits[36], (bits[37], bits[38]))) = literal(value=([0, 1, 2], 3, 4, (5, 6, (7, 8)))) + tuple_index.2: (bits[35], bits[36], (bits[37], bits[38])) = tuple_index(literal.1, index=3) + ret tuple_index.3: (bits[37], bits[38]) = tuple_index(tuple_index.2, index=2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value::Tuple({ + Value(UBits(7, 37)), + Value(UBits(8, 38)), + }))); +} + +TEST_P(IrEvaluatorTest, InterpretArrayLiteral) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn array_literal() -> bits[32][3] { + ret literal.1: bits[32][3] = literal(value=[0, 1, 2]) + } + )")); + + // Note: Array constructor returns a StatusOr, so we need to get at the value. + XLS_ASSERT_OK_AND_ASSIGN(Value array_value, Value::Array({ + Value(UBits(0, 32)), + Value(UBits(1, 32)), + Value(UBits(2, 32)), + })); + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(array_value)); +} + +TEST_P(IrEvaluatorTest, InterpretArrayIndex) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn array_index() -> bits[32] { + literal.1: bits[32][2] = literal(value=[5, 123]) + literal.2: bits[3] = literal(value=0) + literal.3: bits[3] = literal(value=1) + array_index.4: bits[32] = array_index(literal.1, literal.2) + ret array_index.5: bits[32] = array_index(literal.1, literal.3) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(123, 32)))); +} + +TEST_P(IrEvaluatorTest, InterpretArrayOfArrayIndex) { + Package package("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + ParseAndGetFunction(&package, R"( + fn array_index() -> bits[32] { + literal.1: bits[32][2][2] = literal(value=[[1, 2], [3, 4]]) + literal.2: bits[3] = literal(value=1) + literal.3: bits[32][2] = array_index(literal.1, literal.2) + ret array_index.4: bits[32] = array_index(literal.3, literal.2) + } + )")); + + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(4, 32)))); +} + +TEST_P(IrEvaluatorTest, InterpretInvokeZeroArgs) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( +package test + +fn val() -> bits[32] { + ret literal.1: bits[32] = literal(value=42) +} + +fn main() -> bits[32] { + ret invoke.5: bits[32] = invoke(to_apply=val) +} +)")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(42, 32)))); +} + +TEST_P(IrEvaluatorTest, InterpretInvokeMultipleArgs) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( +package test + +fn add_wrapper(x: bits[32], y: bits[32]) -> bits[32] { + ret add.4: bits[32] = add(x, y) +} + +fn main() -> bits[32] { + literal.0: bits[32] = literal(value=2) + literal.1: bits[32] = literal(value=3) + ret invoke.5: bits[32] = invoke(literal.0, literal.1, to_apply=add_wrapper) +} +)")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(5, 32)))); +} + +TEST_P(IrEvaluatorTest, InterpretInvokeMultipleArgsDepth2) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( +package test + +fn add_wrapper(x: bits[32], y: bits[32]) -> bits[32] { + ret add.4: bits[32] = add(x, y) +} + +fn middleman(x: bits[32], y: bits[32]) -> bits[32] { + ret invoke.2: bits[32] = invoke(x, y, to_apply=add_wrapper) +} + +fn main() -> bits[32] { + literal.0: bits[32] = literal(value=2) + literal.1: bits[32] = literal(value=3) + ret invoke.5: bits[32] = invoke(literal.0, literal.1, to_apply=middleman) +} +)")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(GetParam().evaluator(function, /*args=*/{}), + IsOkAndHolds(Value(UBits(5, 32)))); +} + +TEST_P(IrEvaluatorTest, WideAdd) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package wide_add + + fn main(a: bits[128], b: bits[128]) -> bits[128] { + ret add.1: bits[128] = add(a, b) + } + )")); + absl::flat_hash_map args = { + {"a", Value(UBits(42, 128))}, {"b", Value(UBits(123, 128))}}; + + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(GetParam().kwargs_evaluator(function, args), + IsOkAndHolds(Value(UBits(165, 128)))); +} + +TEST_P(IrEvaluatorTest, WideNegate) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package wide_negate + + fn main(a: bits[128]) -> bits[128] { + ret add.1: bits[128] = neg(a) + } + )")); + absl::flat_hash_map args = { + {"a", Value(UBits(0x42, 128))}}; + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN(Value result, + GetParam().kwargs_evaluator(function, args)); + EXPECT_EQ(result.bits().ToString(FormatPreference::kHex), + "0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffbe"); +} + +TEST_P(IrEvaluatorTest, WideLogicOperator) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package wide_add + + fn main(a: bits[128], b: bits[128]) -> bits[128] { + xor.1: bits[128] = xor(a, b) + ret not.2: bits[128] = not(xor.1) + } + )")); + // Build the 128-bit arguments by concating together 64-bit values. The + // 64-bit half-arguments are palindromes, so the result should be similarly + // structured. + absl::flat_hash_map args = { + {"a", Value(bits_ops::Concat({UBits(0xdeadbeeffeebdaedULL, 64), + UBits(0x1234567887654321ULL, 64)}))}, + {"b", Value(bits_ops::Concat({UBits(0xf0f0f0f00f0f0f0fULL, 64), + UBits(0x5a5a5a5aa5a5a5a5ULL, 64)}))}}; + + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN(Value result, + GetParam().kwargs_evaluator(function, args)); + EXPECT_EQ(result.bits().ToString(FormatPreference::kHex), + "0xd1a2_b1e0_0e1b_2a1d_b791_f3dd_dd3f_197b"); +} + +TEST_P(IrEvaluatorTest, OptimizedParamReturn) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package test + + fn main(x0: bits[1], x1: bits[2]) -> bits[1] { + ret param.1: bits[1] = param(name=x0) + } + )")); + absl::flat_hash_map args = { + {"x0", Value(UBits(1, 1))}, + {"x1", Value(UBits(0, 2))}, + }; + + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN(Value result, + GetParam().kwargs_evaluator(function, args)); + EXPECT_EQ(result.bits().ToString(FormatPreference::kBinary), "0b1"); +} + +TEST_P(IrEvaluatorTest, ArrayOperation) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package test + + fn main(x0: bits[16], x1: bits[16]) -> bits[16][2] { + ret array.1: bits[16][2] = array(x0, x1) + } + )")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN( + Value result, + GetParam().kwargs_evaluator(function, {{"x0", Value(UBits(34, 16))}, + {"x1", Value(UBits(56, 16))}})); + EXPECT_EQ(result, + Value::ArrayOrDie({Value(UBits(34, 16)), Value(UBits(56, 16))})); +} + +TEST_P(IrEvaluatorTest, Decode) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package test + + fn main(x: bits[3]) -> bits[8] { + ret decode.1: bits[8] = decode(x, width=8) + } + )")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(Run(function, {0}), IsOkAndHolds(1)); + EXPECT_THAT(Run(function, {1}), IsOkAndHolds(2)); + EXPECT_THAT(Run(function, {2}), IsOkAndHolds(4)); + EXPECT_THAT(Run(function, {3}), IsOkAndHolds(8)); + EXPECT_THAT(Run(function, {4}), IsOkAndHolds(16)); + EXPECT_THAT(Run(function, {5}), IsOkAndHolds(32)); + EXPECT_THAT(Run(function, {6}), IsOkAndHolds(64)); + EXPECT_THAT(Run(function, {7}), IsOkAndHolds(128)); +} + +TEST_P(IrEvaluatorTest, NarrowedDecode) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package test + + fn main(x: bits[3]) -> bits[5] { + ret decode.1: bits[5] = decode(x, width=5) + } + )")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(Run(function, {0}), IsOkAndHolds(1)); + EXPECT_THAT(Run(function, {1}), IsOkAndHolds(2)); + EXPECT_THAT(Run(function, {2}), IsOkAndHolds(4)); + EXPECT_THAT(Run(function, {3}), IsOkAndHolds(8)); + EXPECT_THAT(Run(function, {4}), IsOkAndHolds(16)); + EXPECT_THAT(Run(function, {5}), IsOkAndHolds(0)); + EXPECT_THAT(Run(function, {6}), IsOkAndHolds(0)); + EXPECT_THAT(Run(function, {7}), IsOkAndHolds(0)); +} + +TEST_P(IrEvaluatorTest, Encode) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package test + + fn main(x: bits[5]) -> bits[3] { + ret encode.1: bits[3] = encode(x) + } + )")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT(Run(function, {0}), IsOkAndHolds(0)); + + // Explicitly test all the one-hot values. + EXPECT_THAT(Run(function, {1}), IsOkAndHolds(0)); + EXPECT_THAT(Run(function, {2}), IsOkAndHolds(1)); + EXPECT_THAT(Run(function, {4}), IsOkAndHolds(2)); + EXPECT_THAT(Run(function, {8}), IsOkAndHolds(3)); + EXPECT_THAT(Run(function, {16}), IsOkAndHolds(4)); + + // Test a few random non-one-hot values. + EXPECT_THAT(Run(function, {3}), IsOkAndHolds(1)); + EXPECT_THAT(Run(function, {18}), IsOkAndHolds(5)); + + // Test all values in a loop. + for (uint64 i = 0; i < 31; ++i) { + uint64 expected = 0; + for (int64 j = 0; j < 5; ++j) { + if (i & (1 << j)) { + expected |= j; + } + } + EXPECT_THAT(Run(function, {i}), IsOkAndHolds(expected)); + } +} + +TEST_P(IrEvaluatorTest, RunMismatchedType) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package test + + fn main(x: bits[16]) -> bits[16] { + ret param.2: bits[16] = param(name=x) + } + )")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + EXPECT_THAT( + GetParam().evaluator(function, {Value(UBits(42, 17))}), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Got argument bits[17]:42 for parameter 0 which is " + "not of type bits[16]"))); +} + +absl::Status RunBitSliceTest(const IrEvaluatorTestParam& param, + int64 literal_width, int64 slice_start, + int64 slice_width) { + constexpr absl::string_view ir_text = R"( + package test + + fn main() -> bits[$0] { + literal.1: bits[$1] = literal(value=$2) + ret bit_slice.2: bits[$0] = bit_slice(literal.1, start=$3, width=$0) + } + )"; + + std::string bytes_str = "0x"; + std::vector bytes; + for (int i = 0; i < CeilOfRatio(literal_width, static_cast(CHAR_BIT)); + i++) { + absl::StrAppend(&bytes_str, absl::Hex(i % 256, absl::kZeroPad2)); + bytes.push_back(i % 256); + } + + std::string formatted_ir = absl::Substitute( + ir_text, slice_width, literal_width, bytes_str, slice_start); + XLS_ASSIGN_OR_RETURN(auto package, Parser::ParsePackage(formatted_ir)); + XLS_ASSIGN_OR_RETURN(Function * function, package->EntryFunction()); + + Value expected( + Bits::FromBytes(bytes, literal_width).Slice(slice_start, slice_width)); + EXPECT_THAT(param.evaluator(function, {}), IsOkAndHolds(expected)); + + return absl::OkStatus(); +} + +TEST_P(IrEvaluatorTest, BitSlice) { + XLS_ASSERT_OK(RunBitSliceTest(GetParam(), 27, 9, 3)); + XLS_ASSERT_OK(RunBitSliceTest(GetParam(), 32, 9, 3)); + XLS_ASSERT_OK(RunBitSliceTest(GetParam(), 64, 15, 27)); + XLS_ASSERT_OK(RunBitSliceTest(GetParam(), 128, 24, 50)); + XLS_ASSERT_OK(RunBitSliceTest(GetParam(), 1024, 747, 32)); + XLS_ASSERT_OK(RunBitSliceTest(GetParam(), 65536, 8192, 32768)); +} + +// Test driven by b/148608161. +TEST_P(IrEvaluatorTest, FunnyShapedArrays) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(R"( + package test + + fn main() -> bits[20][2] { + ret literal.1: bits[20][2] = literal(value=[0xabcde, 0x12345]) + } + )")); + XLS_ASSERT_OK_AND_ASSIGN(Function * function, package->EntryFunction()); + Value expected = + Value::ArrayOrDie({Value(UBits(0xabcde, 20)), Value(UBits(0x12345, 20))}); + EXPECT_THAT(GetParam().evaluator(function, {}), IsOkAndHolds(expected)); +} + +absl::Status RunBitwiseReduceTest( + const IrEvaluatorTestParam& param, const std::string& reduce_op, + std::function generate_expected, const Bits& bits) { + constexpr absl::string_view ir_text = R"( + package test + + fn main(x: bits[$1]) -> bits[1] { + ret $0.1: bits[1] = $0(x) + } + )"; + + std::string formatted_ir = + absl::Substitute(ir_text, reduce_op, bits.bit_count()); + XLS_ASSIGN_OR_RETURN(auto package, Parser::ParsePackage(formatted_ir)); + XLS_ASSIGN_OR_RETURN(Function * function, package->EntryFunction()); + + EXPECT_THAT(param.evaluator(function, {Value(bits)}), + IsOkAndHolds(generate_expected(bits))); + return absl::OkStatus(); +} + +TEST_P(IrEvaluatorTest, InterpretAndReduce) { + auto gen_expected = [](const Bits& bits) { + return Value(bits_ops::AndReduce(bits)); + }; + std::vector test_cases = { + UBits(0, 1), UBits(1, 1), UBits(0xFF, 8), + UBits(0x7FF, 11), UBits(0x0FF, 11), UBits(0x1FFFFFFFF, 33), + Bits::AllOnes(31), Bits::AllOnes(63), Bits::AllOnes(127), + Bits::AllOnes(255), + }; + for (const auto& test_case : test_cases) { + XLS_ASSERT_OK(RunBitwiseReduceTest(GetParam(), "and_reduce", gen_expected, + test_case)); + } +} + +TEST_P(IrEvaluatorTest, InterpretOrReduce) { + auto gen_expected = [](const Bits& bits) { + return Value(bits_ops::OrReduce(bits)); + }; + std::vector test_cases = { + UBits(0, 1), UBits(1, 1), UBits(0xFF, 8), + UBits(0x7FF, 11), UBits(0x0FF, 11), UBits(0x1FFFFFFFF, 33), + Bits::AllOnes(255), Bits::AllOnes(256), Bits::AllOnes(257), + Bits::AllOnes(2048), + }; + for (const auto& test_case : test_cases) { + XLS_ASSERT_OK( + RunBitwiseReduceTest(GetParam(), "or_reduce", gen_expected, test_case)); + } +} + +TEST_P(IrEvaluatorTest, InterpretXorReduce) { + auto gen_expected = [](const Bits& bits) { + return Value(bits_ops::XorReduce(bits)); + }; + std::vector test_cases = { + UBits(0, 1), UBits(1, 1), UBits(0xFF, 8), + UBits(0x7FF, 11), UBits(0x0FF, 11), UBits(0x1FFFFFFFF, 33), + Bits::AllOnes(255), Bits::AllOnes(256), Bits::AllOnes(257), + Bits::AllOnes(2048), + }; + for (const auto& test_case : test_cases) { + XLS_ASSERT_OK(RunBitwiseReduceTest(GetParam(), "xor_reduce", gen_expected, + test_case)); + } +} + +} // namespace xls diff --git a/xls/ir/ir_evaluator_test.h b/xls/ir/ir_evaluator_test.h new file mode 100644 index 0000000000..5d11d15432 --- /dev/null +++ b/xls/ir/ir_evaluator_test.h @@ -0,0 +1,116 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_IR_EVALUATOR_TEST_H_ +#define THIRD_PARTY_XLS_IR_IR_EVALUATOR_TEST_H_ + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/ir_parser.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" +#include "xls/ir/verifier.h" + +namespace xls { + +// Simple holder struct to contain the per-evaluator data needed to run these +// tests. +struct IrEvaluatorTestParam { + // Function to perform evaluation of the specified program with the given + // [positional] args. + using EvaluatorFnT = std::function( + Function* function, const std::vector& args)>; + + // Function to perform evaluation of the specified program with the given + // keyword args. + using KwargsEvaluatorFnT = std::function( + Function* function, + const absl::flat_hash_map& kwargs)>; + + IrEvaluatorTestParam(EvaluatorFnT evaluator_in, + KwargsEvaluatorFnT kwargs_evaluator_in) + : evaluator(std::move(evaluator_in)), + kwargs_evaluator(std::move(kwargs_evaluator_in)) {} + + // Function to execute a function and return a Value. + EvaluatorFnT evaluator; + + // Function to execute a function w/keyword args and return a Value. + KwargsEvaluatorFnT kwargs_evaluator; +}; + +// Public face of the suite of tests to run against IR evaluators +// (IrInterpreter, LlvmIrJit). Users should instantiate with an +// INSTANTIATE_TEST_SUITE_P macro; see llvm_ir_jit_test.cc for an example. +class IrEvaluatorTest + : public IrTestBase, + public testing::WithParamInterface { + protected: + Value AsValue(absl::string_view input_string) { + return Parser::ParseTypedValue(input_string).value(); + } + + xabsl::StatusOr ParseAndGetFunction(Package* package, + absl::string_view program) { + XLS_ASSIGN_OR_RETURN(Function * function, + Parser::ParseFunction(program, package)); + XLS_VLOG(1) << "Dumped:\n" << function->DumpIr(); + return function; + } + + xabsl::StatusOr get_neg_function(Package* package) { + return ParseAndGetFunction(package, R"( + fn negate_value(a: bits[4]) -> bits[4] { + ret neg.1: bits[4] = neg(a) + } + )"); + } + + // Runs the given function with uint64s as input. Converts to/from Values + // under the hood. All arguments and result must be bits-typed. + xabsl::StatusOr Run(Function* f, absl::Span args) { + std::vector value_args; + for (int64 i = 0; i < args.size(); ++i) { + XLS_RET_CHECK(f->param(i)->GetType()->IsBits()); + value_args.push_back(Value(UBits(args[i], f->param(i)->BitCountOrDie()))); + } + XLS_ASSIGN_OR_RETURN(Value value_result, + GetParam().evaluator(f, value_args)); + XLS_RET_CHECK(value_result.IsBits()); + return value_result.bits().ToUint64(); + } + + // Runs the given function with Bits as input. Converts to/from Values under + // the hood. All arguments and result must be bits-typed. + xabsl::StatusOr RunBits(Function* f, absl::Span args) { + std::vector value_args; + for (int64 i = 0; i < args.size(); ++i) { + value_args.push_back(Value(args[i])); + } + XLS_ASSIGN_OR_RETURN(Value value_result, + GetParam().evaluator(f, value_args)); + XLS_RET_CHECK(value_result.IsBits()); + return value_result.bits(); + } +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_IR_EVALUATOR_TEST_H_ diff --git a/xls/ir/ir_interpreter.cc b/xls/ir/ir_interpreter.cc new file mode 100644 index 0000000000..bb8cf0765e --- /dev/null +++ b/xls/ir/ir_interpreter.cc @@ -0,0 +1,659 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_interpreter.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/dfs_visitor.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_interpreter_stats.h" +#include "xls/ir/keyword_args.h" +#include "xls/ir/node_iterator.h" +#include "xls/ir/package.h" + +namespace xls { +namespace ir_interpreter { + +// A visitor for traversing and evaluating a Function. +class InterpreterVisitor : public DfsVisitor { + public: + // Runs the visitor on the given function. 'args' are the argument values + // indexed by parameter name. + static xabsl::StatusOr Run(Function* function, + absl::Span args, + InterpreterStats* stats) { + if (args.size() != function->params().size()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Function %s wants %d arguments, got %d.", function->name(), + function->params().size(), args.size())); + } + for (int64 argno = 0; argno < args.size(); ++argno) { + Param* param = function->param(argno); + const Value& value = args[argno]; + Type* param_type = param->GetType(); + Type* value_type = function->package()->GetTypeForValue(value); + if (value_type != param_type) { + return absl::InvalidArgumentError(absl::StrFormat( + "Got argument %s for parameter %d which is not of type %s", + value.ToString(), argno, param_type->ToString())); + } + } + InterpreterVisitor visitor(args, stats); + XLS_RETURN_IF_ERROR(function->return_value()->Accept(&visitor)); + return visitor.ResolveAsValue(function->return_value()); + } + + static xabsl::StatusOr EvaluateNodeWithLiteralOperands(Node* node) { + InterpreterVisitor visitor({}, /*stats=*/nullptr); + XLS_RETURN_IF_ERROR(node->Accept(&visitor)); + return visitor.ResolveAsValue(node); + } + + static xabsl::StatusOr EvaluateNode( + Node* node, absl::Span operand_values) { + XLS_RET_CHECK_EQ(node->operand_count(), operand_values.size()); + InterpreterVisitor visitor({}, /*stats=*/nullptr); + for (int64 i = 0; i < operand_values.size(); ++i) { + visitor.node_values_[node->operand(i)] = *operand_values[i]; + } + XLS_RETURN_IF_ERROR(node->VisitSingleNode(&visitor)); + return visitor.ResolveAsValue(node); + } + + absl::Status HandleAdd(BinOp* add) override { + return SetBitsResult(add, bits_ops::Add(ResolveAsBits(add->operand(0)), + ResolveAsBits(add->operand(1)))); + } + + absl::Status HandleAndReduce(BitwiseReductionOp* and_reduce) override { + Bits operand = ResolveAsBits(and_reduce->operand(0)); + return SetBitsResult(and_reduce, bits_ops::AndReduce(operand)); + } + + absl::Status HandleOrReduce(BitwiseReductionOp* or_reduce) override { + Bits operand = ResolveAsBits(or_reduce->operand(0)); + return SetBitsResult(or_reduce, bits_ops::OrReduce(operand)); + } + + absl::Status HandleXorReduce(BitwiseReductionOp* xor_reduce) override { + Bits operand = ResolveAsBits(xor_reduce->operand(0)); + return SetBitsResult(xor_reduce, bits_ops::XorReduce(operand)); + } + + absl::Status HandleNaryAnd(NaryOp* and_op) override { + Bits accum = ResolveAsBits(and_op->operand(0)); + for (Node* operand : and_op->operands().subspan(1)) { + accum = bits_ops::And(accum, ResolveAsBits(operand)); + } + return SetBitsResult(and_op, accum); + } + + absl::Status HandleNaryNand(NaryOp* nand_op) override { + std::vector operands = ResolveAsBits(nand_op->operands()); + Bits accum = bits_ops::NaryNand(operands); + return SetBitsResult(nand_op, accum); + } + + absl::Status HandleNaryNor(NaryOp* nor_op) override { + std::vector operands = ResolveAsBits(nor_op->operands()); + Bits accum = bits_ops::NaryNor(operands); + return SetBitsResult(nor_op, accum); + } + + absl::Status HandleNaryOr(NaryOp* or_op) override { + std::vector operands = ResolveAsBits(or_op->operands()); + Bits accum = bits_ops::NaryOr(operands); + return SetBitsResult(or_op, accum); + } + + absl::Status HandleNaryXor(NaryOp* xor_op) override { + std::vector operands = ResolveAsBits(xor_op->operands()); + Bits accum = bits_ops::NaryXor(operands); + return SetBitsResult(xor_op, accum); + } + + absl::Status HandleArray(Array* array) override { + std::vector operand_values; + for (Node* operand : array->operands()) { + operand_values.push_back(ResolveAsValue(operand)); + } + XLS_ASSIGN_OR_RETURN(Value result, Value::Array(operand_values)); + return SetValueResult(array, result); + } + + absl::Status HandleBitSlice(BitSlice* bit_slice) override { + return SetBitsResult(bit_slice, + ResolveAsBits(bit_slice->operand(0)) + .Slice(bit_slice->start(), bit_slice->width())); + } + + absl::Status HandleConcat(Concat* concat) override { + std::vector operand_values; + for (Node* operand : concat->operands()) { + operand_values.push_back(ResolveAsBits(operand)); + } + return SetBitsResult(concat, bits_ops::Concat(operand_values)); + } + + absl::Status HandleCountedFor(CountedFor* counted_for) override { + Function* body = counted_for->body(); + std::vector invariant_args; + // Set the loop invariant args (from the second operand on up). + for (int64 i = 1; i < counted_for->operand_count(); ++i) { + // The n-th operand of the counted for actually feeds the (n+1)-th + // parameter of the body as the first two are the induction variable and + // the loop state. + invariant_args.push_back(ResolveAsValue(counted_for->operand(i))); + } + Value loop_state = ResolveAsValue(counted_for->operand(0)); + BitsType* arg0_type = body->param(0)->GetType()->AsBitsOrDie(); + // For each iteration of counted_for, update the induction variable and loop + // state arguments (params 0 and 1) and recursively call the interpreter + // Run() on the body function -- the new accumulator value is the return + // value of interpreting body. + for (int64 i = 0, iv = 0; i < counted_for->trip_count(); + ++i, iv += counted_for->stride()) { + std::vector args_for_body = { + Value(UBits(iv, arg0_type->bit_count())), loop_state}; + for (const auto& value : invariant_args) { + args_for_body.push_back(value); + } + XLS_ASSIGN_OR_RETURN(loop_state, Run(body, args_for_body, stats_)); + } + return SetValueResult(counted_for, loop_state); + } + + absl::Status HandleDecode(Decode* decode) override { + XLS_ASSIGN_OR_RETURN(int64 input_value, + ResolveAsBits(decode->operand(0)).ToUint64()); + if (input_value < decode->BitCountOrDie()) { + return SetBitsResult(decode, + Bits::PowerOfTwo(/*set_bit_index=*/input_value, + decode->BitCountOrDie())); + } else { + return SetBitsResult(decode, Bits(decode->BitCountOrDie())); + } + } + + absl::Status HandleEncode(Encode* encode) override { + const Bits& input = ResolveAsBits(encode->operand(0)); + Bits result(encode->BitCountOrDie()); + for (int64 i = 0; i < input.bit_count(); ++i) { + if (input.Get(i)) { + result = bits_ops::Or(result, UBits(i, encode->BitCountOrDie())); + } + } + return SetBitsResult(encode, result); + } + + absl::Status HandleUDiv(BinOp* div) override { + return SetBitsResult(div, bits_ops::UDiv(ResolveAsBits(div->operand(0)), + ResolveAsBits(div->operand(1)))); + } + + absl::Status HandleSDiv(BinOp* div) override { + return SetBitsResult(div, bits_ops::SDiv(ResolveAsBits(div->operand(0)), + ResolveAsBits(div->operand(1)))); + } + + absl::Status HandleEq(CompareOp* eq) override { + XLS_RETURN_IF_ERROR(VerifyAllBitsTypes(eq)); + return SetUint64Result( + eq, ResolveAsBits(eq->operand(0)) == ResolveAsBits(eq->operand(1))); + } + + absl::Status HandleSGe(CompareOp* ge) override { + return SetUint64Result( + ge, bits_ops::SGreaterThanOrEqual(ResolveAsBits(ge->operand(0)), + ResolveAsBits(ge->operand(1)))); + } + + absl::Status HandleSGt(CompareOp* gt) override { + return SetUint64Result( + gt, bits_ops::SGreaterThan(ResolveAsBits(gt->operand(0)), + ResolveAsBits(gt->operand(1)))); + } + + absl::Status HandleUGe(CompareOp* ge) override { + return SetUint64Result( + ge, bits_ops::UGreaterThanOrEqual(ResolveAsBits(ge->operand(0)), + ResolveAsBits(ge->operand(1)))); + } + + absl::Status HandleUGt(CompareOp* gt) override { + return SetUint64Result( + gt, bits_ops::UGreaterThan(ResolveAsBits(gt->operand(0)), + ResolveAsBits(gt->operand(1)))); + } + + absl::Status HandleIdentity(UnOp* identity) override { + return SetValueResult(identity, ResolveAsValue(identity->operand(0))); + } + + absl::Status HandleArrayIndex(ArrayIndex* index) override { + const Value& input_array = ResolveAsValue(index->operand(0)); + // Out-of-bounds accesses are clamped to the highest index. + // TODO(meheff): Figure out what the right thing to do here is including + // potentially making the behavior an option. + uint64 i = + ResolveAsBoundedUint64(index->operand(1), input_array.size() - 1); + return SetValueResult(index, input_array.elements().at(i)); + } + + absl::Status HandleInvoke(Invoke* invoke) override { + Function* to_apply = invoke->to_apply(); + std::vector args; + for (int64 i = 0; i < to_apply->params().size(); ++i) { + args.push_back(ResolveAsValue(invoke->operand(i))); + } + XLS_ASSIGN_OR_RETURN(Value result, Run(to_apply, args, stats_)); + return SetValueResult(invoke, result); + } + + absl::Status HandleLiteral(Literal* literal) override { + return SetValueResult(literal, literal->value()); + } + + absl::Status HandleULe(CompareOp* le) override { + return SetUint64Result( + le, bits_ops::ULessThanOrEqual(ResolveAsBits(le->operand(0)), + ResolveAsBits(le->operand(1)))); + } + + absl::Status HandleSLt(CompareOp* lt) override { + return SetUint64Result(lt, + bits_ops::SLessThan(ResolveAsBits(lt->operand(0)), + ResolveAsBits(lt->operand(1)))); + } + + absl::Status HandleSLe(CompareOp* le) override { + return SetUint64Result( + le, bits_ops::SLessThanOrEqual(ResolveAsBits(le->operand(0)), + ResolveAsBits(le->operand(1)))); + } + + absl::Status HandleULt(CompareOp* lt) override { + return SetUint64Result(lt, + bits_ops::ULessThan(ResolveAsBits(lt->operand(0)), + ResolveAsBits(lt->operand(1)))); + } + + absl::Status HandleMap(Map* map) override { + Function* to_apply = map->to_apply(); + std::vector results; + for (const Value& operand_element : + ResolveAsValue(map->operand(0)).elements()) { + XLS_ASSIGN_OR_RETURN(Value result, + Run(to_apply, {operand_element}, stats_)); + results.push_back(result); + } + XLS_ASSIGN_OR_RETURN(Value result_array, Value::Array(results)); + return SetValueResult(map, result_array); + } + + absl::Status HandleSMul(ArithOp* mul) override { + const int64 mul_width = mul->BitCountOrDie(); + Bits result = bits_ops::SMul(ResolveAsBits(mul->operand(0)), + ResolveAsBits(mul->operand(1))); + if (result.bit_count() > mul_width) { + return SetBitsResult(mul, result.Slice(0, mul_width)); + } else if (result.bit_count() < mul_width) { + return SetBitsResult(mul, bits_ops::SignExtend(result, mul_width)); + } + return SetBitsResult(mul, result); + } + + absl::Status HandleUMul(ArithOp* mul) override { + const int64 mul_width = mul->BitCountOrDie(); + Bits result = bits_ops::UMul(ResolveAsBits(mul->operand(0)), + ResolveAsBits(mul->operand(1))); + if (result.bit_count() > mul_width) { + return SetBitsResult(mul, result.Slice(0, mul_width)); + } else if (result.bit_count() < mul_width) { + return SetBitsResult(mul, bits_ops::ZeroExtend(result, mul_width)); + } + return SetBitsResult(mul, result); + } + + absl::Status HandleNe(CompareOp* ne) override { + XLS_RETURN_IF_ERROR(VerifyAllBitsTypes(ne)); + return SetUint64Result( + ne, ResolveAsBits(ne->operand(0)) != ResolveAsBits(ne->operand(1))); + } + + absl::Status HandleNeg(UnOp* neg) override { + return SetBitsResult(neg, bits_ops::Negate(ResolveAsBits(neg->operand(0)))); + } + + absl::Status HandleNot(UnOp* not_op) override { + return SetBitsResult(not_op, + bits_ops::Not(ResolveAsBits(not_op->operand(0)))); + } + + absl::Status HandleOneHot(OneHot* one_hot) override { + int64 output_width = one_hot->BitCountOrDie(); + const Bits& input = ResolveAsBits(one_hot->operand(0)); + const int64 input_width = input.bit_count(); + for (int64 i = 0; i < input.bit_count(); ++i) { + int64 index = + one_hot->priority() == LsbOrMsb::kLsb ? i : input_width - i - 1; + if (ResolveAsBits(one_hot->operand(0)).Get(index)) { + auto one = UBits(1, /*bit_count=*/output_width); + return SetBitsResult(one_hot, bits_ops::ShiftLeftLogical(one, index)); + } + } + // No bits of the operand are set so assert the msb of the output indicating + // the default value. + return SetBitsResult( + one_hot, + bits_ops::ShiftLeftLogical(UBits(1, output_width), output_width - 1)); + } + + absl::Status HandleOneHotSel(OneHotSelect* sel) override { + const Bits& selector = ResolveAsBits(sel->selector()); + std::vector activated_inputs; + for (int64 i = 0; i < selector.bit_count(); ++i) { + if (selector.Get(i)) { + activated_inputs.push_back(&ResolveAsValue(sel->cases()[i])); + } + } + XLS_ASSIGN_OR_RETURN(Value result, + DeepOr(sel->GetType(), activated_inputs)); + return SetValueResult(sel, result); + } + + absl::Status HandleParam(Param* param) override { + XLS_ASSIGN_OR_RETURN(int64 index, param->function()->GetParamIndex(param)); + if (index >= args_.size()) { + return absl::InternalError(absl::StrFormat( + "Parameter %s at index %d does not exist in args (of length %d)", + param->ToString(), index, args_.size())); + } + return SetValueResult(param, args_[index]); + } + + absl::Status HandleReverse(UnOp* reverse) override { + return SetBitsResult(reverse, + bits_ops::Reverse(ResolveAsBits(reverse->operand(0)))); + } + + absl::Status HandleSel(Select* sel) override { + Bits selector = ResolveAsBits(sel->selector()); + if (bits_ops::UGreaterThan( + selector, UBits(sel->cases().size() - 1, selector.bit_count()))) { + XLS_RET_CHECK(sel->default_value().has_value()); + return SetValueResult(sel, ResolveAsValue(*sel->default_value())); + } + XLS_ASSIGN_OR_RETURN(uint64 i, selector.ToUint64()); + return SetValueResult(sel, ResolveAsValue(sel->cases()[i])); + } + + static int64 GetBitCountOrDie(Node* n) { return n->BitCountOrDie(); } + + absl::Status HandleShll(BinOp* shll) override { + const Bits& input = ResolveAsBits(shll->operand(0)); + const int64 shift_amt = + ResolveAsBoundedUint64(shll->operand(1), input.bit_count()); + return SetBitsResult(shll, bits_ops::ShiftLeftLogical(input, shift_amt)); + } + + absl::Status HandleShra(BinOp* shra) override { + const Bits& input = ResolveAsBits(shra->operand(0)); + const int64 shift_amt = + ResolveAsBoundedUint64(shra->operand(1), input.bit_count()); + return SetBitsResult(shra, bits_ops::ShiftRightArith(input, shift_amt)); + } + + absl::Status HandleShrl(BinOp* shrl) override { + const Bits& input = ResolveAsBits(shrl->operand(0)); + const int64 shift_amt = + ResolveAsBoundedUint64(shrl->operand(1), input.bit_count()); + return SetBitsResult(shrl, bits_ops::ShiftRightLogical(input, shift_amt)); + } + + absl::Status HandleSignExtend(ExtendOp* sign_ext) override { + return SetBitsResult( + sign_ext, bits_ops::SignExtend(ResolveAsBits(sign_ext->operand(0)), + sign_ext->new_bit_count())); + } + + absl::Status HandleSub(BinOp* sub) override { + return SetBitsResult(sub, bits_ops::Sub(ResolveAsBits(sub->operand(0)), + ResolveAsBits(sub->operand(1)))); + } + + absl::Status HandleTuple(Tuple* tuple) override { + std::vector tuple_values; + for (Node* operand : tuple->operands()) { + tuple_values.push_back(ResolveAsValue(operand)); + } + return SetValueResult(tuple, Value::Tuple(tuple_values)); + } + + absl::Status HandleTupleIndex(TupleIndex* index) override { + int64 tuple_index = index->As()->index(); + return SetValueResult( + index, ResolveAsValue(index->operand(0)).elements().at(tuple_index)); + } + + absl::Status HandleZeroExtend(ExtendOp* zero_ext) override { + return SetBitsResult( + zero_ext, bits_ops::ZeroExtend(ResolveAsBits(zero_ext->operand(0)), + zero_ext->new_bit_count())); + } + + private: + InterpreterVisitor(absl::Span args, InterpreterStats* stats) + : stats_(stats), args_(args) {} + + // Verifies that the width of the given node and all of its operands are less + // than or equal to 64 bits. Also returns an error if an operand or the node + // are not Bits type. + absl::Status VerifyFitsIn64Bits(Node* node) { + XLS_RETURN_IF_ERROR(VerifyAllBitsTypes(node)); + for (Node* operand : node->operands()) { + if (operand->BitCountOrDie() > 64) { + return absl::UnimplementedError( + absl::StrFormat("Interpreter does not support operation '%s' with " + "operands of more than 64-bits: %s", + OpToString(node->op()), node->ToString())); + } + } + if (node->BitCountOrDie() > 64) { + return absl::UnimplementedError(absl::StrFormat( + "Interpreter does not support operation '%s' with more " + "than 64-bits: %s", + OpToString(node->op()), node->ToString())); + } + return absl::OkStatus(); + } + + // Returns an error if the given node or any of its operands are not Bits + // types. + absl::Status VerifyAllBitsTypes(Node* node) { + for (Node* operand : node->operands()) { + if (!operand->GetType()->IsBits()) { + return absl::UnimplementedError( + absl::StrFormat("Interpreter does not support operation '%s' with " + "non-bits type operand", + OpToString(node->op()))); + } + } + if (!node->GetType()->IsBits()) { + return absl::UnimplementedError( + absl::StrFormat("Interpreter does not support operation '%s' with " + "non-bits type operand", + OpToString(node->op()))); + } + return absl::OkStatus(); + } + + // Returns the previously evaluated value of 'node' as a bits type. CHECK + // fails if it is not bits. + const Bits& ResolveAsBits(Node* node) { return node_values_.at(node).bits(); } + + std::vector ResolveAsBits(absl::Span nodes) { + std::vector results; + for (Node* node : nodes) { + results.push_back(ResolveAsBits(node)); + } + return results; + } + + // Returns the previously evaluated value of 'node' as a uint64. If the value + // is greater than 'upper_limit' then 'upper_limit' is returned. + uint64 ResolveAsBoundedUint64(Node* node, uint64 upper_limit) { + const Bits& bits = ResolveAsBits(node); + if (Bits::MinBitCountUnsigned(upper_limit) <= bits.bit_count() && + bits_ops::UGreaterThan(bits, UBits(upper_limit, bits.bit_count()))) { + return upper_limit; + } + // Necessarily the bits value fits in a uint64 so the value() call is safe. + return bits.ToUint64().value(); + } + + // Returns the previously evaluated value of 'node' as a Value. + const Value& ResolveAsValue(Node* node) { return node_values_.at(node); } + + // Sets the evaluated value for 'node' to the given uint64 value. Returns an + // error if 'node' is not a bits type or the result does not fit in the type. + absl::Status SetUint64Result(Node* node, uint64 result) { + XLS_RET_CHECK(node->GetType()->IsBits()); + XLS_RET_CHECK_GE(node->BitCountOrDie(), Bits::MinBitCountUnsigned(result)); + return SetValueResult(node, Value(UBits(result, node->BitCountOrDie()))); + } + + // Sets the evaluated value for 'node' to the given bits value. Returns an + // error if 'node' is not a bits type. + absl::Status SetBitsResult(Node* node, const Bits& result) { + XLS_RET_CHECK(node->GetType()->IsBits()); + XLS_RET_CHECK_EQ(node->BitCountOrDie(), result.bit_count()); + if (stats_ != nullptr) { + stats_->NoteNodeBits(node->ToString(), result); + } + return SetValueResult(node, Value(result)); + } + + // Sets the evaluated value for 'node' to the given Value. 'value' must be + // passed in by value (ha!) because a use case is passing in a previously + // evaluated value and inserting a into flat_hash_map (done below) invalidates + // all references to Values in the map. + absl::Status SetValueResult(Node* node, Value result) { + XLS_VLOG(4) << absl::StreamFormat("%s operands:", node->GetName()); + for (int64 i = 0; i < node->operand_count(); ++i) { + XLS_VLOG(4) << absl::StreamFormat( + " operand %d (%s): %s", i, node->operand(i)->GetName(), + ResolveAsValue(node->operand(i)).ToString()); + } + XLS_VLOG(3) << absl::StreamFormat("Result of %s: %s", node->ToString(), + result.ToString()); + XLS_RET_CHECK(!node_values_.contains(node)); + node_values_[node] = result; + return absl::OkStatus(); + } + + // Performs a logical OR of the given inputs. If 'inputs' is a not a Bits type + // (ie, tuple or array) the element a recursively traversed and the Bits-typed + // leaves are OR-ed. + xabsl::StatusOr DeepOr(Type* input_type, + absl::Span inputs) { + if (input_type->IsBits()) { + Bits result(input_type->AsBitsOrDie()->bit_count()); + for (const Value* input : inputs) { + result = bits_ops::Or(result, input->bits()); + } + return Value(result); + } + + auto input_elements = [&](int64 i) { + std::vector values; + for (int64 j = 0; j < inputs.size(); ++j) { + values.push_back(&inputs[j]->elements()[i]); + } + return values; + }; + + if (input_type->IsArray()) { + Type* element_type = input_type->AsArrayOrDie()->element_type(); + std::vector elements; + for (int64 i = 0; i < input_type->AsArrayOrDie()->size(); ++i) { + XLS_ASSIGN_OR_RETURN(Value element, + DeepOr(element_type, input_elements(i))); + elements.push_back(element); + } + return Value::Array(elements); + } + + XLS_RET_CHECK(input_type->IsTuple()); + std::vector elements; + for (int64 i = 0; i < input_type->AsTupleOrDie()->size(); ++i) { + XLS_ASSIGN_OR_RETURN(Value element, + DeepOr(input_type->AsTupleOrDie()->element_type(i), + input_elements(i))); + elements.push_back(element); + } + return Value::Tuple(elements); + } + + // Statistics on interpreter execution. May be nullptr. + InterpreterStats* stats_; + + // The arguments to the Function being evaluated indexed by parameter name. + absl::Span args_; + + // The evaluated values for the nodes in the Function. + absl::flat_hash_map node_values_; +}; + +xabsl::StatusOr Run(Function* function, absl::Span args, + InterpreterStats* stats) { + XLS_VLOG(3) << "Function:"; + XLS_VLOG_LINES(3, function->DumpIr()); + XLS_ASSIGN_OR_RETURN(Value result, + InterpreterVisitor::Run(function, args, stats)); + XLS_VLOG(2) << "Result = " << result; + return result; +} + +xabsl::StatusOr RunKwargs( + Function* function, const absl::flat_hash_map& args, + InterpreterStats* stats) { + XLS_VLOG(2) << "Interpreting function " << function->name() + << " with arguments:"; + XLS_ASSIGN_OR_RETURN(std::vector positional_args, + KeywordArgsToPositional(*function, args)); + return Run(function, positional_args, stats); +} + +xabsl::StatusOr EvaluateNodeWithLiteralOperands(Node* node) { + XLS_RET_CHECK_GT(node->operand_count(), 0); + XLS_RET_CHECK(std::all_of(node->operands().begin(), node->operands().end(), + [](Node* o) { return o->Is(); })); + return InterpreterVisitor::EvaluateNodeWithLiteralOperands(node); +} + +xabsl::StatusOr EvaluateNode( + Node* node, absl::Span operand_values) { + return InterpreterVisitor::EvaluateNode(node, operand_values); +} + +} // namespace ir_interpreter +} // namespace xls diff --git a/xls/ir/ir_interpreter.h b/xls/ir/ir_interpreter.h new file mode 100644 index 0000000000..89d52d99ed --- /dev/null +++ b/xls/ir/ir_interpreter.h @@ -0,0 +1,49 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_IR_INTERPRETER_H_ +#define THIRD_PARTY_XLS_IR_IR_INTERPRETER_H_ + +#include "absl/types/span.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_interpreter_stats.h" + +namespace xls { +namespace ir_interpreter { + +// Executes the function and returns the calculated output(s) (i.e. perform +// behavioral simulation) by interpreting each node in the IR graph. +xabsl::StatusOr RunKwargs( + Function* function, const absl::flat_hash_map& args, + InterpreterStats* stats = nullptr); + +// As above, but with positional arguments. +xabsl::StatusOr Run(Function* function, absl::Span args, + InterpreterStats* stats = nullptr); + +// Evaluates the given node. All operands of the nodes must be literal which are +// used in the evaluation. +xabsl::StatusOr EvaluateNodeWithLiteralOperands(Node* node); + +// Evaluates the given nodes using the given operand values. +xabsl::StatusOr EvaluateNode( + Node* node, absl::Span operand_values); + +} // namespace ir_interpreter +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_IR_INTERPRETER_H_ diff --git a/xls/ir/ir_interpreter_stats.cc b/xls/ir/ir_interpreter_stats.cc new file mode 100644 index 0000000000..4237e7a91e --- /dev/null +++ b/xls/ir/ir_interpreter_stats.cc @@ -0,0 +1,52 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_interpreter_stats.h" + +namespace xls { + +std::string InterpreterStats::ToNodeReport() const { + std::string result; + for (const auto& item : value_profile_) { + if (ternary_ops::AllUnknown(item.second.value())) { + continue; + } + absl::StrAppendFormat(&result, " %s: %s\n", item.first, + ToString(item.second.value())); + } + return result; +} + +std::string InterpreterStats::ToReport() const { + absl::MutexLock lock(&mutex_); + auto percent = [](int64 value, int64 all) -> double { + if (all == 0) { + return 100.0; + } + return static_cast(value) / all * 100.0; + }; + return absl::StrFormat( + R"(Interpreter stats report: +shll: %d + zero: %d (%.2f%%) + overlarge: %d (%.2f%%) + in-range: %d (%.2f%%) +)", + all_shlls_, zero_shlls_, percent(zero_shlls_, all_shlls_), + overlarge_shlls_, percent(overlarge_shlls_, all_shlls_), + in_range_shlls(), percent(in_range_shlls(), all_shlls_)) + + ToNodeReport(); +} + +} // namespace xls diff --git a/xls/ir/ir_interpreter_stats.h b/xls/ir/ir_interpreter_stats.h new file mode 100644 index 0000000000..b9f0c57d7d --- /dev/null +++ b/xls/ir/ir_interpreter_stats.h @@ -0,0 +1,93 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_IR_INTERPRETER_STATS_H_ +#define THIRD_PARTY_XLS_IR_IR_INTERPRETER_STATS_H_ + +#include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" +#include "absl/strings/str_format.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/optional.h" +#include "xls/common/integral_types.h" +#include "xls/ir/node.h" +#include "xls/ir/ternary.h" + +namespace xls { + +// Note: as of now this is more of a "performance counter" dumb-struct sort of +// class, where the determination of when/where to note things is inline in the +// IR interpreter itself. +class InterpreterStats { + public: + void NoteShllAmountForBitCount(int64 amount, int64 bit_count) { + absl::MutexLock lock(&mutex_); + all_shlls_ += 1; + overlarge_shlls_ += amount >= bit_count; + zero_shlls_ += amount == 0; + } + + // Notes the bits result for a given node (as determined by the interpreter) + // -- the values that have consistent bits are recorded via the "Meet" + // operator. + void NoteNodeBits(std::string node_string, const Bits& bits) { + absl::MutexLock lock(&mutex_); + auto& lattice = value_profile_[node_string]; + Meet(bits, &lattice); + } + + // Returns a multi-line report string suitable for, e.g. XLS_LOG_LINES'ing. + std::string ToReport() const; + + private: + int64 in_range_shlls() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + return all_shlls_ - overlarge_shlls_ - zero_shlls_; + } + + // Meets the observed "bits" value against the seen-so-far "lattice" of values + // -- e.g. if the seen-so-far value is 0 and "bits" contains a 1 in that bit + // position, the value will go to "bottom" (unknown = X). + // + // A nullopt in the lattice means no value has yet been observed for the given + // node. + void Meet(const Bits& bits, absl::optional* lattice) { + if (lattice->has_value()) { + XLS_CHECK_EQ(bits.bit_count(), lattice->value().size()); + *lattice = ternary_ops::Equals(ternary_ops::BitsToTernary(bits), + lattice->value()); + } else { + *lattice = ternary_ops::BitsToTernary(bits); + } + } + + // Returns a string that represents the nodes with consistent bit values. + std::string ToNodeReport() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable absl::Mutex mutex_; + + // When absl::nullopt, the value is "top" in the lattice (i.e. no info is + // available). + // + // When the ternary value is present, kUnknown is "bottom" in the lattice + // (conflicting info). + absl::flat_hash_map> value_profile_ + ABSL_GUARDED_BY(mutex_); + int64 overlarge_shlls_ ABSL_GUARDED_BY(mutex_) = 0; + int64 zero_shlls_ ABSL_GUARDED_BY(mutex_) = 0; + int64 all_shlls_ ABSL_GUARDED_BY(mutex_) = 0; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_IR_INTERPRETER_STATS_H_ diff --git a/xls/ir/ir_interpreter_test.cc b/xls/ir/ir_interpreter_test.cc new file mode 100644 index 0000000000..59d3d5878e --- /dev/null +++ b/xls/ir/ir_interpreter_test.cc @@ -0,0 +1,75 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_interpreter.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/ir_evaluator_test.h" +#include "xls/ir/ir_parser.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" +#include "xls/ir/verifier.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +INSTANTIATE_TEST_SUITE_P( + IrInterpreterTest, IrEvaluatorTest, + testing::Values(IrEvaluatorTestParam( + [](Function* function, const std::vector& args) { + return ir_interpreter::Run(function, args); + }, + [](Function* function, + const absl::flat_hash_map& kwargs) { + return ir_interpreter::RunKwargs(function, kwargs); + }))); + +// Fixture for IrInterpreter-only tests (i.e., those that aren't common to all +// IR evaluators). +class IrInterpreterOnlyTest : public IrTestBase {}; + +TEST_F(IrInterpreterOnlyTest, EvaluateNode) { + Package package("my_package"); + std::string fn_text = R"( + fn f(x: bits[4]) -> bits[4] { + literal.1: bits[4] = literal(value=6) + literal.2: bits[4] = literal(value=3) + and.3: bits[4] = and(literal.1, x) + ret or.4: bits[4] = or(literal.2, and.3) + } + )"; + + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + Parser::ParseFunction(fn_text, &package)); + + Value a = Value(UBits(0b0011, 4)); + Value b = Value(UBits(0b1010, 4)); + EXPECT_THAT( + ir_interpreter::EvaluateNode(FindNode("and.3", function), {&a, &b}), + IsOkAndHolds(Value(UBits(0b0010, 4)))); + EXPECT_THAT( + ir_interpreter::EvaluateNode(FindNode("or.4", function), {&a, &b}), + IsOkAndHolds(Value(UBits(0b1011, 4)))); + EXPECT_THAT(ir_interpreter::EvaluateNode(FindNode("literal.1", function), {}), + IsOkAndHolds(Value(UBits(6, 4)))); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/ir_matcher.cc b/xls/ir/ir_matcher.cc new file mode 100644 index 0000000000..0dc3399307 --- /dev/null +++ b/xls/ir/ir_matcher.cc @@ -0,0 +1,198 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_matcher.h" + +#include "gmock/gmock.h" +#include "xls/ir/nodes.h" + +namespace xls { +namespace op_matchers { + +bool NodeMatcher::MatchAndExplain( + const Node* node, ::testing::MatchResultListener* listener) const { + if (!node) { + return false; + } + *listener << node->ToString(); + if (node->op() != op_) { + *listener << " has incorrect op, expected: " << OpToString(op_); + return false; + } + + // If no operands are specified, then matching stops here. + if (operands_.empty()) { + return true; + } + const auto& operands = node->operands(); + if (operands.size() != operands_.size()) { + *listener << " has too " + << (operands.size() > operands_.size() ? "many" : "few") + << " operands (got " << operands.size() << ", want " + << operands_.size() << ")"; + return false; + } + for (int64 index = 0; index < operands.size(); index++) { + ::testing::StringMatchResultListener inner_listener; + if (!operands_[index].MatchAndExplain(operands[index], &inner_listener)) { + if (listener->IsInterested()) { + *listener << "\noperand " << index << ":\n\t" + << operands[index]->ToString() + << "\ndoesn't match expected:\n\t"; + operands_[index].DescribeTo(listener->stream()); + std::string explanation = inner_listener.str(); + if (!explanation.empty()) { + *listener << ", " << explanation; + } + } + return false; + } + } + return true; +} + +void NodeMatcher::DescribeTo(::std::ostream* os) const { + *os << op_; + if (!operands_.empty()) { + *os << "("; + for (int i = 0; i < operands_.size(); i++) { + if (i > 0) { + *os << ", "; + } + operands_[i].DescribeTo(os); + } + *os << ")"; + } +} + +bool TypeMatcher::MatchAndExplain( + const Node* node, ::testing::MatchResultListener* listener) const { + if (type_str_ == node->GetType()->ToString()) { + return true; + } + *listener << node->ToString() + << " has incorrect type, expected: " << type_str_; + return false; +} + +void TypeMatcher::DescribeTo(std::ostream* os) const { *os << type_str_; } + +bool ParamMatcher::MatchAndExplain( + const Node* node, ::testing::MatchResultListener* listener) const { + if (!NodeMatcher::MatchAndExplain(node, listener)) { + return false; + } + if (name_.has_value() && node->GetName() != *name_) { + *listener << " has incorrect name, expected: " << *name_; + return false; + } + return true; +} + +bool BitSliceMatcher::MatchAndExplain( + const Node* node, ::testing::MatchResultListener* listener) const { + if (!NodeMatcher::MatchAndExplain(node, listener)) { + return false; + } + if (start_.has_value() && node->As<::xls::BitSlice>()->start() != *start_) { + *listener << " has incorrect start, expected: " << *start_; + return false; + } + if (width_.has_value() && node->As<::xls::BitSlice>()->width() != *width_) { + *listener << " has incorrect width, expected: " << *width_; + return false; + } + return true; +} + +bool LiteralMatcher::MatchAndExplain( + const Node* node, ::testing::MatchResultListener* listener) const { + if (!NodeMatcher::MatchAndExplain(node, listener)) { + return false; + } + const Value& literal_value = node->As<::xls::Literal>()->value(); + if (value_.has_value() || uint64_value_.has_value()) { + Value expected; + if (value_.has_value()) { + expected = *value_; + } else { + // The int64 expected value does not carry width information, so create a + // value object with width matching the literal. + if (!literal_value.IsBits() || Bits::MinBitCountUnsigned(*uint64_value_) > + literal_value.bits().bit_count()) { + // Literal value isn't a Bits value or it is too narrow to hold the + // expected value. Just create a 64-bit Bits value for the error + // message. + expected = Value(UBits(*uint64_value_, 64)); + } else { + expected = + Value(UBits(*uint64_value_, literal_value.bits().bit_count())); + } + } + if (literal_value != expected) { + *listener << " has value " << literal_value.ToString(format_) + << ", expected: " << expected.ToString(format_); + return false; + } + } + + return true; +} + +bool SelectMatcher::MatchAndExplain( + const Node* node, ::testing::MatchResultListener* listener) const { + if (!NodeMatcher::MatchAndExplain(node, listener)) { + return false; + } + if (default_value_.has_value() && + !node->As<::xls::Select>()->default_value().has_value()) { + *listener << " has no default value, expected: " << (*default_value_); + return false; + } + if (!default_value_.has_value() && + node->As<::xls::Select>()->default_value().has_value()) { + *listener << " has default value, expected no default value"; + return false; + } + return true; +} + +bool OneHotMatcher::MatchAndExplain( + const Node* node, ::testing::MatchResultListener* listener) const { + if (!NodeMatcher::MatchAndExplain(node, listener)) { + return false; + } + if (priority_.has_value() && + *priority_ != node->As<::xls::OneHot>()->priority()) { + *listener << " has incorrect priority, expected: lsb_prio=" + << (*priority_ == LsbOrMsb::kLsb ? "true" : "false"); + return false; + } + return true; +} + +bool TupleIndexMatcher::MatchAndExplain( + const Node* node, ::testing::MatchResultListener* listener) const { + if (!NodeMatcher::MatchAndExplain(node, listener)) { + return false; + } + if (index_.has_value() && *index_ != node->As<::xls::TupleIndex>()->index()) { + *listener << " has incorrect index, expected: " << *index_; + return false; + } + return true; +} + +} // namespace op_matchers +} // namespace xls diff --git a/xls/ir/ir_matcher.h b/xls/ir/ir_matcher.h new file mode 100644 index 0000000000..f0099a6ad2 --- /dev/null +++ b/xls/ir/ir_matcher.h @@ -0,0 +1,405 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_IR_MATCHER_H_ +#define THIRD_PARTY_XLS_IR_IR_MATCHER_H_ + +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "xls/ir/format_preference.h" +#include "xls/ir/ir_parser.h" +#include "xls/ir/lsb_or_msb.h" +#include "xls/ir/node.h" +#include "xls/ir/op.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" + +namespace xls { +namespace op_matchers { + +// Implements matching over XLS IR. Enables easy pattern matching of XLS +// expressions in tests. +// +// Example usage which EXPECTs the return value of the function 'f' to be a +// BitSlice of a parameter with the given start and width values: +// +// EXPECT_THAT(f->return_value(), +// m::BitSlice(m::Param(), /*start=*/3, /*width=*/8)); + +// Base class for matchers. Only checks the op and then recursively checks the +// operands. +class NodeMatcher : public ::testing::MatcherInterface { + public: + NodeMatcher(Op op, std::vector<::testing::Matcher> operands) + : op_(op), operands_(operands) {} + + bool MatchAndExplain(const Node* node, + ::testing::MatchResultListener* listener) const override; + + void DescribeTo(::std::ostream* os) const override; + + private: + Op op_; + std::vector<::testing::Matcher> operands_; +}; + +// Class for matching XLS Types. Example usage: +// +// EXPECT_THAT(foo, m::Type("bits[32]")); +// EXPECT_THAT(foo, m::Type(package->GetBitsType(32))); +class TypeMatcher : public ::testing::MatcherInterface { + public: + explicit TypeMatcher(absl::string_view type_str) : type_str_(type_str) {} + + bool MatchAndExplain(const Node* node, + ::testing::MatchResultListener* listener) const override; + void DescribeTo(std::ostream* os) const override; + + private: + std::string type_str_; +}; + +inline ::testing::Matcher Type(const Type* type) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::TypeMatcher(type->ToString())); +} + +inline ::testing::Matcher Type(const char* type_str) { + return ::testing::MakeMatcher(new ::xls::op_matchers::TypeMatcher(type_str)); +} + +// Node* matchers for ops which have no metadata beyond Op, type, and operands. +#define NODE_MATCHER(op) \ + template \ + ::testing::Matcher op(M... operands) { \ + return ::testing::MakeMatcher( \ + new ::xls::op_matchers::NodeMatcher(::xls::Op::k##op, {operands...})); \ + } +NODE_MATCHER(Add); +NODE_MATCHER(And); +NODE_MATCHER(Array); +NODE_MATCHER(ArrayIndex); +NODE_MATCHER(Concat); +NODE_MATCHER(Decode); +NODE_MATCHER(Encode); +NODE_MATCHER(Eq); +NODE_MATCHER(Identity); +NODE_MATCHER(Nand); +NODE_MATCHER(Ne); +NODE_MATCHER(Neg); +NODE_MATCHER(Nor); +NODE_MATCHER(Not); +NODE_MATCHER(Or); +NODE_MATCHER(Reverse); +NODE_MATCHER(SDiv); +NODE_MATCHER(SGe); +NODE_MATCHER(SGt); +NODE_MATCHER(SLe); +NODE_MATCHER(SLt); +NODE_MATCHER(SMul); +NODE_MATCHER(Sel); +NODE_MATCHER(Shll); +NODE_MATCHER(Shra); +NODE_MATCHER(Shrl); +NODE_MATCHER(SignExt); +NODE_MATCHER(Sub); +NODE_MATCHER(Tuple); +NODE_MATCHER(UDiv); +NODE_MATCHER(UGe); +NODE_MATCHER(UGt); +NODE_MATCHER(ULe); +NODE_MATCHER(ULt); +NODE_MATCHER(UMul); +NODE_MATCHER(Xor); +NODE_MATCHER(ZeroExt); + +// TODO(meheff): The following ops should have custom matchers defined as they +// have additional metadata. +NODE_MATCHER(CountedFor); +NODE_MATCHER(Invoke); +NODE_MATCHER(Map); +#undef NODE_MATCHER + +// Ops which have metadata beyond the Op, type, and the operands (e.g., Literals +// whice have values) require their own subclass of NodeMatcher. Below are the +// definitions of these classes. + +// Param matcher. Matches parameter name only. Supported forms: +// +// EXPECT_THAT(x, m::Param()); +// EXPECT_THAT(x, m::Param("x")); +class ParamMatcher : public NodeMatcher { + public: + explicit ParamMatcher(absl::optional name) + : NodeMatcher(Op::kParam, /*operands=*/{}), name_(name) {} + + bool MatchAndExplain(const Node* node, + ::testing::MatchResultListener* listener) const override; + + private: + absl::optional name_; +}; + +inline ::testing::Matcher Param( + absl::optional name) { + return ::testing::MakeMatcher(new ::xls::op_matchers::ParamMatcher(name)); +} + +inline ::testing::Matcher Param() { + return ::testing::MakeMatcher( + new ::xls::op_matchers::NodeMatcher(Op::kParam, {})); +} + +// BitSlice matcher. Supported forms: +// +// EXPECT_THAT(foo, op::BitSlice()); +// EXPECT_THAT(foo, op::BitSlice(op::Param())); +// EXPECT_THAT(foo, op::BitSlice(/*start=*/7, /*width=*/8)); +// EXPECT_THAT(foo, op::BitSlice(/*operand=*/op::Param(), /*start=*/7, +// /*width=*/8)); +class BitSliceMatcher : public NodeMatcher { + public: + BitSliceMatcher(::testing::Matcher operand, + absl::optional start, absl::optional width) + : NodeMatcher(Op::kBitSlice, {operand}), start_(start), width_(width) {} + BitSliceMatcher(absl::optional start, absl::optional width) + : NodeMatcher(Op::kBitSlice, {}), start_(start), width_(width) {} + + bool MatchAndExplain(const Node* node, + ::testing::MatchResultListener* listener) const override; + + private: + absl::optional start_; + absl::optional width_; +}; + +inline ::testing::Matcher BitSlice() { + return ::testing::MakeMatcher( + new ::xls::op_matchers::BitSliceMatcher(absl::nullopt, absl::nullopt)); +} + +inline ::testing::Matcher BitSlice( + ::testing::Matcher operand) { + return ::testing::MakeMatcher(new ::xls::op_matchers::BitSliceMatcher( + operand, absl::nullopt, absl::nullopt)); +} + +inline ::testing::Matcher BitSlice( + ::testing::Matcher operand, int64 start, int64 width) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::BitSliceMatcher(operand, start, width)); +} + +inline ::testing::Matcher BitSlice(int64 start, + int64 width) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::BitSliceMatcher(start, width)); +} + +// Literal matcher. Supported forms: +// +// EXPECT_THAT(foo, op::Literal()); +// EXPECT_THAT(foo, op::Literal(Value(UBits(7, 8)))); +// EXPECT_THAT(foo, op::Literal(UBits(7, 8))); +// EXPECT_THAT(foo, op::Literal(42)); +// EXPECT_THAT(foo, op::Literal("bits[8]:7")); +// EXPECT_THAT(foo, op::Literal("bits[8]:0x7")); +// EXPECT_THAT(foo, op::Literal("bits[8]:0b111")); +class LiteralMatcher : public NodeMatcher { + public: + explicit LiteralMatcher(FormatPreference format = FormatPreference::kDefault) + : NodeMatcher(Op::kLiteral, {}), format_(format) {} + explicit LiteralMatcher(absl::optional value, + FormatPreference format = FormatPreference::kDefault) + : NodeMatcher(Op::kLiteral, {}), value_(value), format_(format) {} + explicit LiteralMatcher(absl::optional value, + FormatPreference format = FormatPreference::kDefault) + : NodeMatcher(Op::kLiteral, {}), uint64_value_(value), format_(format) {} + + bool MatchAndExplain(const Node* node, + ::testing::MatchResultListener* listener) const override; + + private: + // At most one of the optional data members has a value. + absl::optional value_; + absl::optional uint64_value_; + FormatPreference format_; +}; + +inline ::testing::Matcher Literal() { + return ::testing::MakeMatcher(new ::xls::op_matchers::LiteralMatcher()); +} + +inline ::testing::Matcher Literal(const Value& value) { + return ::testing::MakeMatcher(new ::xls::op_matchers::LiteralMatcher(value)); +} + +inline ::testing::Matcher Literal(const Bits& bits) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::LiteralMatcher(Value(bits))); +} +inline ::testing::Matcher Literal(uint64 value) { + return ::testing::MakeMatcher(new ::xls::op_matchers::LiteralMatcher(value)); +} + +inline ::testing::Matcher Literal( + absl::string_view value_str) { + Value value = Parser::ParseTypedValue(value_str).value(); + FormatPreference format = FormatPreference::kDefault; + if (value_str.find("0b") != std::string::npos) { + format = FormatPreference::kBinary; + } else if (value_str.find("0x") != std::string::npos) { + format = FormatPreference::kHex; + } + return ::testing::MakeMatcher( + new ::xls::op_matchers::LiteralMatcher(value, format)); +} + +// OneHot matcher. Supported forms: +// +// EXPECT_THAT(foo, op::OneHot()); +// EXPECT_THAT(foo, op::OneHot(op::Param())); +// EXPECT_THAT(foo, op::OneHot(/*priority=*/LsbOrMsb::kLsb)); +// EXPECT_THAT(foo, op::OneHot(op::Param(), /*priority=*/LsbOrMsb::kLsb)); +class OneHotMatcher : public NodeMatcher { + public: + explicit OneHotMatcher(::testing::Matcher operand, + absl::optional priority = absl::nullopt) + : NodeMatcher(Op::kOneHot, {operand}), priority_(priority) {} + explicit OneHotMatcher(absl::optional priority = absl::nullopt) + : NodeMatcher(Op::kOneHot, {}), priority_(priority) {} + + bool MatchAndExplain(const Node* node, + ::testing::MatchResultListener* listener) const override; + + private: + absl::optional priority_; +}; + +inline ::testing::Matcher OneHot( + absl::optional priority = absl::nullopt) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::OneHotMatcher(priority)); +} + +inline ::testing::Matcher OneHot( + ::testing::Matcher operand, + absl::optional priority = absl::nullopt) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::OneHotMatcher(operand, priority)); +} + +// Select matcher. Supported forms: +// +// EXPECT_THAT(foo, op::Select()); +// EXPECT_THAT(foo, op::Select(op::Param(), /*cases=*/{op::Xor(), op::And}); +// EXPECT_THAT(foo, op::Select(op::Param(), +// /*cases=*/{op::Xor(), op::And}, +// /*default_value=*/op::Literal())); +class SelectMatcher : public NodeMatcher { + public: + SelectMatcher(::testing::Matcher selector, + std::vector<::testing::Matcher> cases, + absl::optional<::testing::Matcher> default_value) + : NodeMatcher(Op::kSel, + [&]() { + std::vector<::testing::Matcher> operands; + operands.push_back(selector); + operands.insert(operands.end(), cases.begin(), + cases.end()); + if (default_value.has_value()) { + operands.push_back(*default_value); + } + return operands; + }()), + default_value_(default_value) {} + + bool MatchAndExplain(const Node* node, + ::testing::MatchResultListener* listener) const override; + + private: + absl::optional<::testing::Matcher> default_value_; +}; + +inline ::testing::Matcher Select( + ::testing::Matcher selector, + std::vector<::testing::Matcher> cases, + absl::optional<::testing::Matcher> default_value = + absl::nullopt) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::SelectMatcher(selector, cases, default_value)); +} + +inline ::testing::Matcher Select() { + return ::testing::MakeMatcher( + new ::xls::op_matchers::NodeMatcher(Op::kSel, {})); +} + +// OneHotSelect matcher. Supported forms: +// +// EXPECT_THAT(foo, op::OneHotSelect()); +// EXPECT_THAT(foo, op::OneHotSelect(op::Param(), +// /*cases=*/{op::Xor(), op::And}); +inline ::testing::Matcher OneHotSelect( + ::testing::Matcher selector, + std::vector<::testing::Matcher> cases) { + std::vector<::testing::Matcher> operands; + operands.push_back(selector); + operands.insert(operands.end(), cases.begin(), cases.end()); + return ::testing::MakeMatcher( + new ::xls::op_matchers::NodeMatcher(Op::kOneHotSel, operands)); +} + +inline ::testing::Matcher OneHotSelect() { + return ::testing::MakeMatcher( + new ::xls::op_matchers::NodeMatcher(Op::kOneHotSel, {})); +} + +// TupleIndex matcher. Supported forms: +// +// EXPECT_THAT(foo, op::TupleIndex()); +// EXPECT_THAT(foo, op::TupleIndex(/*index=*/42)); +class TupleIndexMatcher : public NodeMatcher { + public: + explicit TupleIndexMatcher(::testing::Matcher operand, + absl::optional index = absl::nullopt) + : NodeMatcher(Op::kTupleIndex, {operand}), index_(index) {} + explicit TupleIndexMatcher(absl::optional index = absl::nullopt) + : NodeMatcher(Op::kTupleIndex, {}), index_(index) {} + + bool MatchAndExplain(const Node* node, + ::testing::MatchResultListener* listener) const override; + + private: + absl::optional index_; +}; + +inline ::testing::Matcher TupleIndex( + absl::optional index = absl::nullopt) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::TupleIndexMatcher(index)); +} + +inline ::testing::Matcher TupleIndex( + ::testing::Matcher operand, + absl::optional index = absl::nullopt) { + return ::testing::MakeMatcher( + new ::xls::op_matchers::TupleIndexMatcher(operand, index)); +} + +} // namespace op_matchers +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_IR_MATCHER_H_ diff --git a/xls/ir/ir_matcher_test.cc b/xls/ir/ir_matcher_test.cc new file mode 100644 index 0000000000..aad727e4b9 --- /dev/null +++ b/xls/ir/ir_matcher_test.cc @@ -0,0 +1,263 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_matcher.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/package.h" + +namespace m = xls::op_matchers; + +namespace xls { +namespace { + +using ::testing::_; +using ::testing::AllOf; +using ::testing::Eq; + +template +std::string Explain(const T& t, const M& m) { + ::testing::StringMatchResultListener listener; + EXPECT_THAT(t, ::testing::Not(m)); // For the error message. + EXPECT_FALSE(m.MatchAndExplain(t, &listener)); + return listener.str(); +} + +TEST(IrMatchersTest, Basic) { + Package p("p"); + FunctionBuilder fb("f", &p); + + auto x = fb.Param("x", p.GetBitsType(32)); + auto y = fb.Param("y", p.GetBitsType(32)); + // Ensure we get the expected order of node evaluation by breaking out + // statements, so that the ordinals are stable across compilers (C++ does not + // define argument evaluation order). + auto lhs = fb.Subtract(x, y); + auto rhs = fb.Not(x); + fb.Add(lhs, rhs); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(x.node(), m::Param()); + EXPECT_THAT(x.node(), m::Param("x")); + EXPECT_THAT(x.node(), m::Type("bits[32]")); + EXPECT_THAT(x.node(), AllOf(m::Param("x"), m::Type("bits[32]"))); + + EXPECT_THAT(y.node(), m::Param()); + EXPECT_THAT(y.node(), m::Param("y")); + EXPECT_THAT(y.node(), m::Type("bits[32]")); + + EXPECT_THAT(f->return_value(), m::Add()); + EXPECT_THAT(f->return_value(), m::Add(m::Sub(), m::Not())); + EXPECT_THAT(f->return_value(), + m::Add(m::Sub(m::Param(), m::Param()), m::Not(m::Param()))); + EXPECT_THAT(f->return_value(), m::Add(m::Sub(m::Param("x"), m::Param("y")), + m::Not(m::Param("x")))); + EXPECT_THAT(f->return_value(), m::Add(m::Sub(_, m::Param("y")), _)); + + EXPECT_THAT(Explain(x.node(), m::Param("z")), + Eq("x: bits[32] = param(x) has incorrect name, expected: z")); + EXPECT_THAT( + Explain(y.node(), m::Type("bits[123]")), + Eq("y: bits[32] = param(y) has incorrect type, expected: bits[123]")); + EXPECT_THAT(Explain(y.node(), m::Add()), + Eq("y: bits[32] = param(y) has incorrect op, expected: add")); + + EXPECT_THAT(Explain(f->return_value(), m::Add(m::Param())), + Eq("add.5: bits[32] = add(sub.3, not.4) has too many operands " + "(got 2, want 1)")); + EXPECT_THAT(Explain(f->return_value(), m::Add(m::Add(), _)), + Eq("add.5: bits[32] = add(sub.3, not.4)\noperand 0:\n\tsub.3: " + "bits[32] = sub(x, y)\ndoesn't match expected:\n\tadd, sub.3: " + "bits[32] = sub(x, y) has incorrect op, expected: add")); +} + +TEST(IrMatchersTest, BitSlice) { + Package p("p"); + FunctionBuilder fb("f", &p); + + auto x = fb.Param("x", p.GetBitsType(32)); + fb.BitSlice(x, /*start=*/7, /*width=*/9); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(f->return_value(), m::BitSlice()); + EXPECT_THAT(f->return_value(), m::BitSlice(/*start=*/7, /*width=*/9)); + EXPECT_THAT(f->return_value(), + m::BitSlice(m::Param(), /*start=*/7, /*width=*/9)); + EXPECT_THAT(f->return_value(), + m::BitSlice(m::Param("x"), /*start=*/7, /*width=*/9)); + + EXPECT_THAT( + Explain(f->return_value(), m::BitSlice(/*start=*/7, /*width=*/42)), + Eq("bit_slice.2: bits[9] = bit_slice(x, start=7, width=9) has incorrect " + "width, expected: 42")); + EXPECT_THAT( + Explain(f->return_value(), m::BitSlice(/*start=*/123, /*width=*/9)), + Eq("bit_slice.2: bits[9] = bit_slice(x, start=7, width=9) has incorrect " + "start, expected: 123")); +} + +TEST(IrMatchersTest, Literal) { + Package p("p"); + FunctionBuilder fb("f", &p); + + auto x = fb.Literal(Value(UBits(0b1001, 4))); + auto y = fb.Literal(Value(UBits(12345678, 32))); + XLS_ASSERT_OK(fb.Build().status()); + + EXPECT_THAT(x.node(), m::Literal()); + EXPECT_THAT(x.node(), m::Literal("bits[4]: 0b1001")); + EXPECT_THAT(x.node(), m::Literal(UBits(9, 4))); + EXPECT_THAT(x.node(), m::Literal(Value(UBits(9, 4)))); + EXPECT_THAT(x.node(), m::Literal(9)); + + EXPECT_THAT(y.node(), m::Literal()); + EXPECT_THAT(y.node(), m::Literal("bits[32]: 12345678")); + EXPECT_THAT(y.node(), m::Literal(UBits(12345678, 32))); + EXPECT_THAT(y.node(), m::Literal(Value(UBits(12345678, 32)))); + EXPECT_THAT(y.node(), m::Literal(12345678)); + + EXPECT_THAT(Explain(x.node(), m::Literal(Value(UBits(9, 123)))), + Eq("literal.1: bits[4] = literal(value=9) has value bits[4]:9, " + "expected: bits[123]:0x9")); + EXPECT_THAT(Explain(x.node(), m::Literal(42)), + Eq("literal.1: bits[4] = literal(value=9) has value bits[4]:9, " + "expected: bits[64]:42")); + + // When passing in a string for value matching, verify that the number format + // of the input string is used in the error message. + EXPECT_THAT(Explain(x.node(), m::Literal("bits[4]: 0b1111")), + Eq("literal.1: bits[4] = literal(value=9) has value " + "bits[4]:0b1001, expected: bits[4]:0b1111")); + EXPECT_THAT(Explain(x.node(), m::Literal("bits[4]: 0xf")), + Eq("literal.1: bits[4] = literal(value=9) has value bits[4]:0x9, " + "expected: bits[4]:0xf")); + EXPECT_THAT(Explain(x.node(), m::Literal("bits[4]: 12")), + Eq("literal.1: bits[4] = literal(value=9) has value bits[4]:9, " + "expected: bits[4]:12")); +} + +TEST(IrMatchersTest, OneHotSelect) { + Package p("p"); + FunctionBuilder fb("f", &p); + + auto pred = fb.Param("pred", p.GetBitsType(2)); + auto x = fb.Param("x", p.GetBitsType(32)); + auto y = fb.Param("y", p.GetBitsType(32)); + fb.OneHotSelect(pred, {x, y}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(f->return_value(), m::OneHotSelect()); + EXPECT_THAT( + f->return_value(), + m::OneHotSelect(m::Param("pred"), {m::Param("x"), m::Param("y")})); +} + +TEST(IrMatchersTest, Select) { + Package p("p"); + FunctionBuilder fb("f", &p); + + auto pred = fb.Param("pred", p.GetBitsType(1)); + auto x = fb.Param("x", p.GetBitsType(32)); + auto y = fb.Param("y", p.GetBitsType(32)); + fb.Select(pred, {x, y}, /*default_value=*/absl::nullopt); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(f->return_value(), m::Select()); + EXPECT_THAT(f->return_value(), + m::Select(m::Param("pred"), {m::Param("x"), m::Param("y")})); + EXPECT_THAT(f->return_value(), + m::Select(m::Param("pred"), {m::Param("x"), m::Param("y")}, + /*default_value=*/absl::nullopt)); + + EXPECT_THAT( + Explain(f->return_value(), m::Select(m::Param("pred"), {m::Param("x")}, + /*default_value=*/m::Param("y"))), + Eq("sel.4: bits[32] = sel(pred, cases=[x, y]) has no default value, " + "expected: param")); +} + +TEST(IrMatchersTest, SelectWithDefault) { + Package p("p"); + FunctionBuilder fb("f", &p); + + auto pred = fb.Param("pred", p.GetBitsType(2)); + auto x = fb.Param("x", p.GetBitsType(32)); + auto y = fb.Param("y", p.GetBitsType(32)); + auto z = fb.Param("z", p.GetBitsType(32)); + fb.Select(pred, {x, y}, /*default_value=*/z); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(f->return_value(), m::Select()); + EXPECT_THAT(f->return_value(), + m::Select(m::Param("pred"), {m::Param("x"), m::Param("y")}, + /*default_value=*/m::Param("z"))); + + EXPECT_THAT( + Explain(f->return_value(), + m::Select(m::Param("pred"), + {m::Param("x"), m::Param("y"), m::Param("z")})), + Eq("sel.5: bits[32] = sel(pred, cases=[x, y], default=z) has default " + "value, expected no default value")); +} + +TEST(IrMatchersTest, OneHot) { + Package p("p"); + FunctionBuilder fb("f", &p); + + auto x = fb.Param("x", p.GetBitsType(32)); + auto oh0 = fb.OneHot(x, LsbOrMsb::kLsb); + auto oh1 = fb.OneHot(oh0, LsbOrMsb::kMsb); + XLS_ASSERT_OK(fb.Build().status()); + + EXPECT_THAT(oh0.node(), m::OneHot()); + EXPECT_THAT(oh0.node(), m::OneHot(LsbOrMsb::kLsb)); + EXPECT_THAT(oh1.node(), m::OneHot(m::OneHot(), LsbOrMsb::kMsb)); + + EXPECT_THAT(Explain(oh0.node(), m::OneHot(LsbOrMsb::kMsb)), + Eq("one_hot.2: bits[33] = one_hot(x, lsb_prio=true) has " + "incorrect priority, expected: lsb_prio=false")); + EXPECT_THAT(Explain(oh1.node(), m::OneHot(LsbOrMsb::kLsb)), + Eq("one_hot.3: bits[34] = one_hot(one_hot.2, lsb_prio=false) has " + "incorrect priority, expected: lsb_prio=true")); +} + +TEST(IrMatchersTest, TupleIndex) { + Package p("p"); + FunctionBuilder fb("f", &p); + + auto x = fb.Param("x", p.GetTupleType({p.GetBitsType(32), p.GetBitsType(7)})); + auto elem0 = fb.TupleIndex(x, 0); + auto elem1 = fb.TupleIndex(x, 1); + XLS_ASSERT_OK(fb.Build().status()); + EXPECT_THAT(elem0.node(), m::TupleIndex()); + EXPECT_THAT(elem0.node(), m::TupleIndex(absl::optional(0))); + EXPECT_THAT(elem0.node(), m::TupleIndex(m::Param(), 0)); + + EXPECT_THAT(elem1.node(), m::TupleIndex()); + EXPECT_THAT(elem1.node(), m::TupleIndex(1)); + EXPECT_THAT(elem1.node(), m::TupleIndex(m::Param(), 1)); + + EXPECT_THAT(Explain(elem0.node(), m::TupleIndex(1)), + Eq("tuple_index.2: bits[32] = tuple_index(x, index=0) has " + "incorrect index, expected: 1")); + EXPECT_THAT(Explain(elem1.node(), m::TupleIndex(400)), + Eq("tuple_index.3: bits[7] = tuple_index(x, index=1) has " + "incorrect index, expected: 400")); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/ir_parser.cc b/xls/ir/ir_parser.cc new file mode 100644 index 0000000000..00485a30f0 --- /dev/null +++ b/xls/ir/ir_parser.cc @@ -0,0 +1,930 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_parser.h" + +#include "absl/status/status.h" +#include "absl/strings/str_split.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/visitor.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/node.h" +#include "xls/ir/nodes.h" +#include "xls/ir/number_parser.h" +#include "xls/ir/op.h" +#include "xls/ir/verifier.h" + +namespace xls { + +xabsl::StatusOr Parser::ParseBitsTypeAndReturnWidth() { + XLS_ASSIGN_OR_RETURN(Token peek, scanner_.PeekToken()); + XLS_RETURN_IF_ERROR(scanner_.DropKeywordOrError("bits")); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kBracketOpen)); + XLS_ASSIGN_OR_RETURN(int64 bit_count, ParseInt64()); + if (bit_count < 0) { + return absl::InvalidArgumentError(absl::StrFormat( + "Only positive bit counts are permitted for bits types; found %d @ %s", + bit_count, peek.pos().ToHumanString())); + } + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kBracketClose)); + return bit_count; +} + +xabsl::StatusOr Parser::ParseBitsType(Package* package) { + XLS_ASSIGN_OR_RETURN(int64 bit_count, ParseBitsTypeAndReturnWidth()); + return package->GetBitsType(bit_count); +} + +xabsl::StatusOr Parser::ParseType(Package* package) { + Type* type; + if (scanner_.PeekTokenIs(TokenType::kParenOpen)) { + XLS_ASSIGN_OR_RETURN(type, ParseTupleType(package)); + } else { + XLS_ASSIGN_OR_RETURN(type, ParseBitsType(package)); + } + while (scanner_.TryDropToken(TokenType::kBracketOpen)) { + // Array type. + XLS_ASSIGN_OR_RETURN(int64 size, ParseInt64()); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kBracketClose)); + type = package->GetArrayType(size, type); + } + return type; +} + +// Abstraction holding the value of a keyword argument. +template +struct KeywordValue { + bool is_optional; + T value; + absl::optional optional_value; + + // Sets the value of this object to the value contains in 'value_status' or + // returns an error if value_status is an error value. + absl::Status SetOrReturn(xabsl::StatusOr value_status) { + if (is_optional) { + XLS_ASSIGN_OR_RETURN(optional_value, value_status); + } else { + XLS_ASSIGN_OR_RETURN(value, value_status); + } + return absl::OkStatus(); + } +}; + +// Variant which gathers all the possible keyword argument types. New +// keywords arguments which require a new type should be added here. +using KeywordVariant = + absl::variant, KeywordValue, + KeywordValue, KeywordValue>, + KeywordValue, KeywordValue, + KeywordValue>; + +// Abstraction for parsing the arguments of a node. The arguments include +// positional and keyword arguments. The positional arguments are exclusively +// the operands of the node. The keyword argument are the attributes such as +// counted_for loop stride, map function name, etc. Like python, the positional +// arguments are ordered and must be listed first. The keyword arguments may be +// listed in any order. Example: +// +// operation.1: bits[32] = operation(x, y, z, foo=bar, baz=7) +// +// Here, x, y, and z are the positional arguments. foo and baz are the keyword +// arguments. +class ArgParser { + public: + ArgParser(const absl::flat_hash_map& name_to_bvalue, + Type* node_type, Parser* parser) + : name_to_bvalue_(name_to_bvalue), + node_type_(node_type), + parser_(parser) {} + + // Adds a mandatory keyword to the parser. After calling ArgParser::Run the + // value pointed to by the returned pointer is the keyword argument value. + template + T* AddKeywordArg(std::string key) { + mandatory_keywords_.insert(key); + auto pair = keywords_.emplace( + key, absl::make_unique(KeywordValue())); + XLS_CHECK(pair.second); + auto& keyword_value = absl::get>(*pair.first->second); + keyword_value.is_optional = false; + // Return a pointer into the KeywordValue which will be filled in when Run + // is called. + return &keyword_value.value; + } + + // Adds an optional keyword to the parser. After calling ArgParser::Run the + // absl::optional pointed to by the returned pointer will (optionally) contain + // the keyword argument value. + template + absl::optional* AddOptionalKeywordArg(absl::string_view key) { + auto pair = keywords_.emplace( + key, absl::make_unique(KeywordValue())); + XLS_CHECK(pair.second); + auto& keyword_value = absl::get>(*keywords_.at(key)); + keyword_value.is_optional = true; + // Return a pointer into the KeywordValue which will be filled in when Run + // is called. + return &keyword_value.optional_value; + } + + template + T* AddOptionalKeywordArg(absl::string_view key, T default_value) { + auto pair = keywords_.emplace( + key, absl::make_unique(KeywordValue())); + XLS_CHECK(pair.second); + auto& keyword_value = absl::get>(*keywords_.at(key)); + keyword_value.optional_value = default_value; + keyword_value.is_optional = true; + // Return a pointer into the KeywordValue which may be filled in when Run is + // called; however, if it is not filled, will remain the default value + // provided. + return &keyword_value.optional_value.value(); + } + + // Runs the argument parser. 'arity' is the expected number of operands + // (positional arguments). Returns the BValues of the operands. + static constexpr int64 kVariadic = -1; + xabsl::StatusOr> Run(int64 arity) { + absl::flat_hash_set seen_keywords; + std::vector operands; + XLS_ASSIGN_OR_RETURN(Token open_paren, parser_->scanner_.PopTokenOrError( + TokenType::kParenOpen)); + if (!parser_->scanner_.PeekTokenIs(TokenType::kParenClose)) { + // Variable indicating whether we are parsing the keywords or still + // parsing the positional arguments. + bool parsing_keywords = false; + do { + XLS_ASSIGN_OR_RETURN(Token name, parser_->scanner_.PopTokenOrError( + TokenType::kIdent, "argument")); + if (!parsing_keywords && + parser_->scanner_.PeekTokenIs(TokenType::kEquals)) { + parsing_keywords = true; + } + if (parsing_keywords) { + XLS_RETURN_IF_ERROR( + parser_->scanner_.DropTokenOrError(TokenType::kEquals)); + if (seen_keywords.contains(name.value())) { + return absl::InvalidArgumentError( + absl::StrFormat("Duplicate keyword argument '%s' @ %s", + name.value(), name.pos().ToHumanString())); + } else if (keywords_.contains(name.value())) { + XLS_RETURN_IF_ERROR(ParseKeywordArg(name.value())); + } else { + return absl::InvalidArgumentError( + absl::StrFormat("Invalid keyword @ %s: %s", + name.pos().ToHumanString(), name.value())); + } + seen_keywords.insert(name.value()); + } else { + if (!name_to_bvalue_.contains(name.value())) { + return absl::InvalidArgumentError(absl::StrFormat( + "Referred to a name @ %s that was not previously " + "defined: \"%s\"", + name.pos().ToHumanString(), name.value())); + } + operands.push_back(name_to_bvalue_.at(name.value())); + } + } while (parser_->scanner_.TryDropToken(TokenType::kComma)); + } + XLS_RETURN_IF_ERROR( + parser_->scanner_.DropTokenOrError(TokenType::kParenClose)); + + // Verify all mandatory keywords are present. + for (const std::string& key : mandatory_keywords_) { + if (!seen_keywords.contains(key)) { + return absl::InvalidArgumentError( + absl::StrFormat("Mandatory keyword argument '%s' not found @ %s", + key, open_paren.pos().ToHumanString())); + } + } + + // Verify the arity is as expected. + if (arity != kVariadic && operands.size() != arity) { + return absl::InvalidArgumentError( + absl::StrFormat("Expected %d operands, got %d @ %s", arity, + operands.size(), open_paren.pos().ToHumanString())); + } + return operands; + } + + private: + // Parses the keyword argument with the given key. The expected type of the + // keyword argument value is determined by the template parameter type T used + // when Add(Optional)KeywordArgument was called. + absl::Status ParseKeywordArg(absl::string_view key) { + KeywordVariant& keyword_variant = *keywords_.at(key); + return absl::visit( + Visitor{[&](KeywordValue& v) { + return v.SetOrReturn(parser_->ParseBool()); + }, + [&](KeywordValue& v) { + return v.SetOrReturn(parser_->ParseInt64()); + }, + [&](KeywordValue& v) { + return v.SetOrReturn(parser_->ParseIdentifierString()); + }, + [&](KeywordValue& v) { + return v.SetOrReturn(parser_->ParseValueInternal(node_type_)); + }, + [&](KeywordValue& v) { + return v.SetOrReturn( + parser_->ParseIdentifierValue(name_to_bvalue_)); + }, + [&](KeywordValue>& v) { + return v.SetOrReturn(parser_->ParseNameList(name_to_bvalue_)); + }, + [&](KeywordValue& v) { + return v.SetOrReturn(parser_->ParseSourceLocation()); + }}, + keyword_variant); + return absl::OkStatus(); + } + + const absl::flat_hash_map& name_to_bvalue_; + Type* node_type_; + Parser* parser_; + + absl::flat_hash_set mandatory_keywords_; + absl::flat_hash_map> keywords_; +}; + +xabsl::StatusOr Parser::ParseInt64() { + XLS_ASSIGN_OR_RETURN(Token literal, + scanner_.PopTokenOrError(TokenType::kLiteral)); + return literal.GetValueInt64(); +} + +xabsl::StatusOr Parser::ParseBool() { + XLS_ASSIGN_OR_RETURN(Token literal, + scanner_.PopTokenOrError(TokenType::kLiteral)); + return literal.GetValueBool(); +} + +xabsl::StatusOr Parser::ParseIdentifierString(TokenPos* pos) { + XLS_ASSIGN_OR_RETURN(Token token, + scanner_.PopTokenOrError(TokenType::kIdent)); + if (pos != nullptr) { + *pos = token.pos(); + } + return token.value(); +} + +xabsl::StatusOr Parser::ParseIdentifierValue( + const absl::flat_hash_map& name_to_value) { + TokenPos start_pos; + XLS_ASSIGN_OR_RETURN(std::string identifier, + ParseIdentifierString(&start_pos)); + auto it = name_to_value.find(identifier); + if (it == name_to_value.end()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Referred to a name @ %s that was not previously defined: \"%s\"", + start_pos.ToHumanString(), identifier)); + } + return it->second; +} + +xabsl::StatusOr Parser::ParseValueInternal(absl::optional type) { + XLS_ASSIGN_OR_RETURN(Token peek, scanner_.PeekToken()); + const TokenPos start_pos = peek.pos(); + TypeKind type_kind; + int64 bit_count = 0; + if (type.has_value()) { + type_kind = type.value()->kind(); + bit_count = + type.value()->IsBits() ? type.value()->AsBitsOrDie()->bit_count() : 0; + } else { + if (scanner_.PeekTokenIs(TokenType::kKeyword)) { + type_kind = TypeKind::kBits; + XLS_ASSIGN_OR_RETURN(bit_count, ParseBitsTypeAndReturnWidth()); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kColon)); + } else if (scanner_.PeekTokenIs(TokenType::kBracketOpen)) { + type_kind = TypeKind::kArray; + } else { + type_kind = TypeKind::kTuple; + } + } + + if (bit_count < 0) { + return absl::InvalidArgumentError(absl::StrFormat( + "Invalid bit count: %d @ %s", bit_count, start_pos.ToHumanString())); + } + + if (type_kind == TypeKind::kBits) { + XLS_ASSIGN_OR_RETURN(Token literal, + scanner_.PopTokenOrError(TokenType::kLiteral)); + XLS_ASSIGN_OR_RETURN(Bits bits_value, literal.GetValueBits()); + if (bits_value.bit_count() > bit_count) { + return absl::InvalidArgumentError(absl::StrFormat( + "Value %s is not representable in %d bits @ %s", literal.value(), + bit_count, literal.pos().ToHumanString())); + } + XLS_ASSIGN_OR_RETURN(bool is_negative, literal.IsNegative()); + if (is_negative) { + return Value(bits_ops::SignExtend(bits_value, bit_count)); + } else { + return Value(bits_ops::ZeroExtend(bits_value, bit_count)); + } + } + if (type_kind == TypeKind::kArray) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kBracketOpen)); + std::vector values; + while (true) { + if (scanner_.TryDropToken(TokenType::kBracketClose)) { + break; + } + if (!values.empty()) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kComma, + "',' in array literal")); + } + absl::optional element_type = absl::nullopt; + if (type.has_value()) { + element_type = type.value()->AsArrayOrDie()->element_type(); + } + XLS_ASSIGN_OR_RETURN(Value element_value, + ParseValueInternal(element_type)); + values.push_back(std::move(element_value)); + } + return Value::Array(values); + } + if (type_kind == TypeKind::kTuple) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kParenOpen)); + std::vector values; + while (true) { + if (scanner_.TryDropToken(TokenType::kParenClose)) { + break; + } + if (!values.empty()) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kComma, + "',' in tuple literal")); + } + absl::optional element_type = absl::nullopt; + if (type.has_value()) { + element_type = + type.value()->AsTupleOrDie()->element_type(values.size()); + } + XLS_ASSIGN_OR_RETURN(Value element_value, + ParseValueInternal(element_type)); + values.push_back(std::move(element_value)); + } + return Value::Tuple(values); + } + return absl::InvalidArgumentError( + absl::StrFormat("Unsupported type %s", TypeKindToString(type_kind))); +} + +xabsl::StatusOr> Parser::ParseNameList( + const absl::flat_hash_map& name_to_value) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kBracketOpen)); + std::vector result; + bool must_end = false; + while (true) { + if (must_end) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kBracketClose)); + break; + } + if (scanner_.TryDropToken(TokenType::kBracketClose)) { + break; + } + XLS_ASSIGN_OR_RETURN(BValue value, ParseIdentifierValue(name_to_value)); + result.push_back(value); + must_end = !scanner_.TryDropToken(TokenType::kComma); + } + return result; +} + +xabsl::StatusOr Parser::ParseSourceLocation() { + XLS_ASSIGN_OR_RETURN(Token fileno, + scanner_.PopTokenOrError(TokenType::kLiteral)); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kComma)); + XLS_ASSIGN_OR_RETURN(Token lineno, + scanner_.PopTokenOrError(TokenType::kLiteral)); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kComma)); + XLS_ASSIGN_OR_RETURN(Token colno, + scanner_.PopTokenOrError(TokenType::kLiteral)); + XLS_ASSIGN_OR_RETURN(int64 fileno_value, fileno.GetValueInt64()); + XLS_ASSIGN_OR_RETURN(int64 lineno_value, lineno.GetValueInt64()); + XLS_ASSIGN_OR_RETURN(int64 colno_value, colno.GetValueInt64()); + return SourceLocation(Fileno(fileno_value), Lineno(lineno_value), + Colno(colno_value)); +} + +xabsl::StatusOr Parser::BuildBinaryOrUnaryOp( + Op op, FunctionBuilder* fb, absl::optional* loc, + ArgParser* arg_parser) { + std::vector operands; + + if (IsOpClass(op)) { + XLS_ASSIGN_OR_RETURN(operands, arg_parser->Run(2)); + return fb->AddBinOp(op, operands[0], operands[1], *loc); + } + + if (IsOpClass(op)) { + XLS_ASSIGN_OR_RETURN(operands, arg_parser->Run(1)); + return fb->AddUnOp(op, operands[0], *loc); + } + + if (IsOpClass(op)) { + XLS_ASSIGN_OR_RETURN(operands, arg_parser->Run(2)); + return fb->AddCompareOp(op, operands[0], operands[1], *loc); + } + + if (IsOpClass(op)) { + XLS_ASSIGN_OR_RETURN(operands, arg_parser->Run(ArgParser::kVariadic)); + return fb->AddNaryOp(op, operands, *loc); + } + + if (IsOpClass(op)) { + XLS_ASSIGN_OR_RETURN(operands, arg_parser->Run(1)); + return fb->AddBitwiseReductionOp(op, operands[0], *loc); + } + + return absl::InvalidArgumentError(absl::StrFormat( + "Invalid operation name for IR parsing: \"%s\"", OpToString(op))); +} + +namespace { + +// GetLocalNode finds function-local BValues by name. +xabsl::StatusOr GetLocalNode( + std::string name, absl::flat_hash_map* name_to_value) { + auto it = name_to_value->find(name); + if (it == name_to_value->end()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Referred to a name that was not previously defined: %s", name)); + } + return it->second.node(); +} + +} // namespace + +xabsl::StatusOr Parser::ParseFunctionBody( + FunctionBuilder* fb, + absl::flat_hash_map* name_to_value, Package* package) { + BValue last_created; + BValue return_value; + while (!scanner_.PeekTokenIs(TokenType::kCurlClose)) { + bool saw_ret = scanner_.TryDropKeyword("ret"); + + // : = op(...) + XLS_ASSIGN_OR_RETURN( + Token output_name, + scanner_.PopTokenOrError(TokenType::kIdent, "node output name")); + if (saw_ret && !scanner_.PeekTokenIs(TokenType::kColon)) { + XLS_ASSIGN_OR_RETURN(Node * ret, + GetLocalNode(output_name.value(), name_to_value)); + fb->function()->set_return_value(ret); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError( + TokenType::kCurlClose, "'}' at end of function body")); + return BValue(ret, fb); + } + + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kColon)); + XLS_ASSIGN_OR_RETURN(Type * type, ParseType(package)); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kEquals)); + XLS_ASSIGN_OR_RETURN(Token op_token, scanner_.PopTokenOrError( + TokenType::kIdent, "operator")); + + XLS_ASSIGN_OR_RETURN(Op op, StringToOp(op_token.value())); + + ArgParser arg_parser(*name_to_value, type, this); + absl::optional* loc = + arg_parser.AddOptionalKeywordArg("pos"); + BValue bvalue; + + std::vector operands; + switch (op) { + case Op::kBitSlice: { + int64* start = arg_parser.AddKeywordArg("start"); + int64* width = arg_parser.AddKeywordArg("width"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + bvalue = fb->BitSlice(operands[0], *start, *width, *loc); + break; + } + case Op::kConcat: { + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(ArgParser::kVariadic)); + bvalue = fb->Concat(operands, *loc); + break; + } + case Op::kLiteral: { + Value* value = arg_parser.AddKeywordArg("value"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/0)); + bvalue = fb->Literal(*value, *loc); + break; + } + case Op::kMap: { + std::string* to_apply_name = + arg_parser.AddKeywordArg("to_apply"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + XLS_ASSIGN_OR_RETURN(Function * to_apply, + package->GetFunction(*to_apply_name)); + bvalue = fb->Map(operands[0], to_apply, *loc); + break; + } + case Op::kParam: { + // TODO(meheff): Params should not appear in the body of the + // function. This is currently required because we have no way of + // returning a param value otherwise. + std::string* param_name = arg_parser.AddKeywordArg("name"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/0)); + auto it = name_to_value->find(*param_name); + if (it == name_to_value->end()) { + return absl::InvalidArgumentError( + absl::StrFormat("Referred to parameter name that hadn't yet been " + "defined: %s @ %s", + *param_name, op_token.pos().ToHumanString())); + } + bvalue = it->second; + break; + } + case Op::kCountedFor: { + int64* trip_count = arg_parser.AddKeywordArg("trip_count"); + int64* stride = arg_parser.AddOptionalKeywordArg("stride", 1); + std::string* body_name = arg_parser.AddKeywordArg("body"); + std::vector* invariant_args = + arg_parser.AddOptionalKeywordArg>( + "invariant_args", /*default_value=*/{}); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + XLS_ASSIGN_OR_RETURN(Function * body, package->GetFunction(*body_name)); + bvalue = fb->CountedFor(operands[0], *trip_count, *stride, body, + *invariant_args, *loc); + break; + } + case Op::kOneHot: { + bool* lsb_prio = arg_parser.AddKeywordArg("lsb_prio"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + bvalue = fb->OneHot(operands[0], + *lsb_prio ? LsbOrMsb::kLsb : LsbOrMsb::kMsb, *loc); + break; + } + case Op::kOneHotSel: { + std::vector* case_args = + arg_parser.AddKeywordArg>("cases"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + if (case_args->empty()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Expected at least 1 case @ %s", op_token.pos().ToHumanString())); + } + bvalue = fb->OneHotSelect(operands[0], *case_args, *loc); + break; + } + case Op::kSel: { + std::vector* case_args = + arg_parser.AddKeywordArg>("cases"); + std::optional* default_value = + arg_parser.AddOptionalKeywordArg("default"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + if (case_args->empty()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Expected at least 1 case @ %s", op_token.pos().ToHumanString())); + } + bvalue = fb->Select(operands[0], *case_args, *default_value, *loc); + break; + } + case Op::kTuple: { + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(ArgParser::kVariadic)); + bvalue = fb->Tuple(operands, *loc); + break; + } + case Op::kArray: { + if (!type->IsArray()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Expected array type @ %s", op_token.pos().ToHumanString())); + } + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(ArgParser::kVariadic)); + bvalue = + fb->Array(operands, type->AsArrayOrDie()->element_type(), *loc); + break; + } + case Op::kTupleIndex: { + int64* index = arg_parser.AddKeywordArg("index"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + if (!operands[0].GetType()->IsTuple()) { + return absl::InvalidArgumentError( + absl::StrFormat("tuple_index operand is not a tuple; got %s @ %s", + operands[0].GetType()->ToString(), + op_token.pos().ToHumanString())); + } + bvalue = fb->TupleIndex(operands[0], *index, *loc); + break; + } + case Op::kArrayIndex: { + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/2)); + if (!operands[0].GetType()->IsArray()) { + return absl::InvalidArgumentError(absl::StrFormat( + "array_index operand is not an array; got %s @ %s", + operands[0].GetType()->ToString(), + op_token.pos().ToHumanString())); + } + bvalue = fb->ArrayIndex(operands[0], operands[1], *loc); + break; + } + case Op::kInvoke: { + std::string* to_apply_name = + arg_parser.AddKeywordArg("to_apply"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(ArgParser::kVariadic)); + XLS_ASSIGN_OR_RETURN(Function * to_apply, + package->GetFunction(*to_apply_name)); + bvalue = fb->Invoke(operands, to_apply, *loc); + break; + } + case Op::kZeroExt: + case Op::kSignExt: { + int64* new_bit_count = arg_parser.AddKeywordArg("new_bit_count"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + if (type->IsBits() && + type->AsBitsOrDie()->bit_count() != *new_bit_count) { + return absl::InvalidArgumentError( + absl::StrFormat("Extend op has an annotated type %s that differs " + "from its new_bit_count annotation %d.", + type->ToString(), *new_bit_count)); + } + bvalue = op == Op::kZeroExt + ? fb->ZeroExtend(operands[0], *new_bit_count, *loc) + : fb->SignExtend(operands[0], *new_bit_count, *loc); + break; + } + case Op::kEncode: { + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + bvalue = fb->Encode(operands[0], *loc); + break; + } + case Op::kDecode: { + int64* width = arg_parser.AddKeywordArg("width"); + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/1)); + if (type->IsBits() && type->AsBitsOrDie()->bit_count() != *width) { + return absl::InvalidArgumentError( + absl::StrFormat("Decode op has an annotated type %s that differs " + "from its width annotation %d.", + type->ToString(), *width)); + } + bvalue = fb->Decode(operands[0], *width, *loc); + break; + } + case Op::kSMul: + case Op::kUMul: { + XLS_ASSIGN_OR_RETURN(operands, arg_parser.Run(/*arity=*/2)); + bvalue = fb->AddArithOp(op, operands[0], operands[1], + type->AsBitsOrDie()->bit_count(), *loc); + break; + } + default: + XLS_ASSIGN_OR_RETURN(bvalue, + BuildBinaryOrUnaryOp(op, fb, loc, &arg_parser)); + } + + // Verify name is unique + if (name_to_value->contains(output_name.value())) { + return absl::InvalidArgumentError( + absl::StrFormat("Name '%s' has already been defined @ %s", + output_name.value(), op_token.pos().ToHumanString())); + } + (*name_to_value)[output_name.value()] = bvalue; + + // Verify that the type of the newly constructed node matches the parsed + // type. + if (type != bvalue.node()->GetType()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Declared type %s does not match expected type %s @ %s", + type->ToString(), bvalue.GetType()->ToString(), + op_token.pos().ToHumanString())); + } + + last_created = bvalue; + + // If the name in the IR dump suggested an ID, we use it directly. + auto get_suggested_id = + [](absl::string_view name) -> absl::optional { + std::vector pieces = absl::StrSplit(name, '.'); + if (pieces.empty()) { + return absl::nullopt; + } + int64 result; + if (absl::SimpleAtoi(pieces.back(), &result)) { + return result; + } + return absl::nullopt; + }; + + if (absl::optional suggested_id = + get_suggested_id(output_name.value())) { + last_created.node()->set_id(suggested_id.value()); + } + + if (saw_ret) { + return_value = last_created; + } + } + + if (!return_value.valid()) { + return_value = last_created; + } + + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kCurlClose, + "'}' at end of function body")); + return return_value; +} + +xabsl::StatusOr Parser::ParseTupleType(Package* package) { + std::vector types; + scanner_.PopToken(); + if (!scanner_.PeekTokenIs(TokenType::kParenClose)) { + do { + XLS_ASSIGN_OR_RETURN(Type * type, ParseType(package)); + types.push_back(type); + } while (scanner_.TryDropToken(TokenType::kComma)); + } + if (!scanner_.PeekTokenIs(TokenType::kParenClose)) { + return absl::InvalidArgumentError( + absl::StrFormat("Expected ')' to terminate tuple type; found %s", + scanner_.PopToken().value())); + } + scanner_.PopToken(); + return package->GetTupleType(types); +} + +xabsl::StatusOr, Type*>> +Parser::ParseSignature(absl::flat_hash_map* name_to_value, + Package* package) { + XLS_ASSIGN_OR_RETURN( + Token name, scanner_.PopTokenOrError(TokenType::kIdent, "function name")); + auto fb = absl::make_unique(name.value(), package); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kParenOpen, + "'(' in function parameters")); + + bool must_end = false; + while (true) { + if (must_end || scanner_.PeekTokenIs(TokenType::kParenClose)) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError( + TokenType::kParenClose, "')' in function parameters")); + break; + } + XLS_ASSIGN_OR_RETURN(Token param_name, + scanner_.PopTokenOrError(TokenType::kIdent)); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kColon)); + XLS_ASSIGN_OR_RETURN(Type * type, ParseType(package)); + (*name_to_value)[param_name.value()] = fb->Param(param_name.value(), type); + must_end = !scanner_.TryDropToken(TokenType::kComma); + } + + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kRightArrow, + "'->' in function signature")); + XLS_ASSIGN_OR_RETURN(Type * return_type, ParseType(package)); + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kCurlOpen, + "start of function body")); + return std::pair, Type*>{std::move(fb), + return_type}; +} + +xabsl::StatusOr Parser::ParsePackageName() { + XLS_RETURN_IF_ERROR(scanner_.DropKeywordOrError("package")); + XLS_ASSIGN_OR_RETURN( + Token package_name, + scanner_.PopTokenOrError(TokenType::kIdent, "package name")); + return package_name.value(); +} + +xabsl::StatusOr Parser::ParseFunction(Package* package) { + if (AtEof()) { + return absl::InvalidArgumentError("Could not parse function; at EOF."); + } + XLS_RETURN_IF_ERROR(scanner_.DropKeywordOrError("fn")); + + absl::flat_hash_map name_to_value; + XLS_ASSIGN_OR_RETURN(auto function_data, + ParseSignature(&name_to_value, package)); + FunctionBuilder* fb = function_data.first.get(); + + XLS_ASSIGN_OR_RETURN(BValue return_value, + ParseFunctionBody(fb, &name_to_value, package)); + + if (return_value.node()->GetType() != function_data.second) { + return absl::InvalidArgumentError(absl::StrFormat( + "Type of return value %s does not match declared function return type " + "%s", + return_value.node()->GetType()->ToString(), + function_data.second->ToString())); + } + + // TODO(leary): 2019-02-19 Could be an empty function body, need to decide + // what to do for those. Accept that the return value can be null and handle + // everywhere? + return fb->BuildWithReturnValue(return_value); +} + +xabsl::StatusOr Parser::ParseFunctionType(Package* package) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kParenOpen)); + std::vector parameter_types; + bool must_end = false; + while (true) { + if (must_end) { + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError( + TokenType::kParenClose, + "expected end of function-type parameter list")); + break; + } + if (scanner_.TryDropToken(TokenType::kParenClose)) { + break; + } + XLS_ASSIGN_OR_RETURN(Type * type, ParseType(package)); + parameter_types.push_back(type); + must_end = !scanner_.TryDropToken(TokenType::kComma); + } + + XLS_RETURN_IF_ERROR(scanner_.DropTokenOrError(TokenType::kRightArrow)); + XLS_ASSIGN_OR_RETURN(Type * return_type, ParseType(package)); + + return package->GetFunctionType(parameter_types, return_type); +} + +/* static */ xabsl::StatusOr Parser::ParseFunctionType( + absl::string_view input_string, Package* package) { + XLS_ASSIGN_OR_RETURN(auto scanner, Scanner::Create(input_string)); + Parser p(std::move(scanner)); + return p.ParseFunctionType(package); +} + +/* static */ xabsl::StatusOr Parser::ParseType( + absl::string_view input_string, Package* package) { + XLS_ASSIGN_OR_RETURN(auto scanner, Scanner::Create(input_string)); + Parser p(std::move(scanner)); + return p.ParseType(package); +} + +// Verifies the given package. Replaces InternalError status codes with +// InvalidArgument status code which is more appropriate for the parser. +static absl::Status VerifyPackage(Package* package) { + absl::Status status = Verify(package); + if (!status.ok() && status.code() == absl::StatusCode::kInternal) { + return absl::InvalidArgumentError(status.message()); + } + return status; +} + +/* static */ +xabsl::StatusOr Parser::ParseFunction(absl::string_view input_string, + Package* package) { + XLS_ASSIGN_OR_RETURN(auto scanner, Scanner::Create(input_string)); + Parser p(std::move(scanner)); + XLS_ASSIGN_OR_RETURN(Function * function, p.ParseFunction(package)); + + // Verify the whole package because the addition of the function may break + // package-scoped invariants (eg, duplicate function name). + XLS_RETURN_IF_ERROR(VerifyPackage(package)); + return function; +} + +/* static */ +xabsl::StatusOr> Parser::ParsePackage( + absl::string_view input_string, + absl::optional filename) { + XLS_ASSIGN_OR_RETURN(std::unique_ptr package, + ParsePackageNoVerify(input_string, filename)); + XLS_RETURN_IF_ERROR(VerifyPackage(package.get())); + return package; +} + +/* static */ +xabsl::StatusOr> Parser::ParsePackageWithEntry( + absl::string_view input_string, absl::string_view entry, + absl::optional filename) { + XLS_ASSIGN_OR_RETURN(std::unique_ptr package, + ParsePackageNoVerify(input_string, filename, entry)); + XLS_RETURN_IF_ERROR(VerifyPackage(package.get())); + return package; +} + +/* static */ +xabsl::StatusOr> Parser::ParsePackageNoVerify( + absl::string_view input_string, absl::optional filename, + absl::optional entry) { + return ParseDerivedPackageNoVerify(input_string, filename, entry); +} + +/* static */ +xabsl::StatusOr Parser::ParseValue(absl::string_view input_string, + Type* expected_type) { + XLS_ASSIGN_OR_RETURN(auto scanner, Scanner::Create(input_string)); + Parser p(std::move(scanner)); + return p.ParseValueInternal(expected_type); +} + +/* static */ +xabsl::StatusOr Parser::ParseTypedValue(absl::string_view input_string) { + XLS_ASSIGN_OR_RETURN(auto scanner, Scanner::Create(input_string)); + Parser p(std::move(scanner)); + return p.ParseValueInternal(/*expected_type=*/absl::nullopt); +} + +} // namespace xls diff --git a/xls/ir/ir_parser.h b/xls/ir/ir_parser.h new file mode 100644 index 0000000000..9f3477bb08 --- /dev/null +++ b/xls/ir/ir_parser.h @@ -0,0 +1,231 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The IR parser allows to build an IR from reading in and +// parsing textual IR. +// +// This is convenience functionality, great for debugging and +// construction of small test cases, it can be used by other +// front-ends to target XLS without having to fully link to it. + +#ifndef THIRD_PARTY_XLS_IR_IR_PARSER_H_ +#define THIRD_PARTY_XLS_IR_IR_PARSER_H_ + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_scanner.h" +#include "xls/ir/node.h" +#include "xls/ir/nodes.h" +#include "xls/ir/package.h" +#include "xls/ir/source_location.h" + +namespace xls { + +class ArgParser; + +class Parser { + public: + // Parses the given input string as a package. + static xabsl::StatusOr> ParsePackage( + absl::string_view input_string, + absl::optional filename = absl::nullopt); + + // As above, but sets the entry function to be the given name in the returned + // package. + static xabsl::StatusOr> ParsePackageWithEntry( + absl::string_view input_string, absl::string_view entry, + absl::optional filename = absl::nullopt); + + // Parse the input_string as a function into the given package. + static xabsl::StatusOr ParseFunction( + absl::string_view input_string, Package* package); + + // Parse the input_string as a function type into the given package. + static xabsl::StatusOr ParseFunctionType( + absl::string_view input_string, Package* package); + + // Parse the input_string as a type into the given package. + static xabsl::StatusOr ParseType(absl::string_view input_string, + Package* package); + + // Parses the given input string as a package skipping verification. This + // should only be used in tests when malformed IR is desired. + static xabsl::StatusOr> ParsePackageNoVerify( + absl::string_view input_string, + absl::optional filename = absl::nullopt, + absl::optional entry = absl::nullopt); + + // As above but creates a package of type PackageT where PackageT must be + // type derived from Package. + template + static xabsl::StatusOr> ParseDerivedPackageNoVerify( + absl::string_view input_string, + absl::optional filename = absl::nullopt, + absl::optional entry = absl::nullopt); + + // Parses a literal value that should be of type "expected_type" and returns + // it. + static xabsl::StatusOr ParseValue(absl::string_view input_string, + Type* expected_type); + + // Parses a value with embedded type information, specifically 'bits[xx]:' + // substrings indicating the width of literal values. Value::ToString emits + // strings of this form. Examples of strings parsable with this method: + // bits[32]:0x42 + // (bits[7]:0, bits[8]:1) + // [bits[2]:1, bits[2]:2, bits[2]:3] + static xabsl::StatusOr ParseTypedValue(absl::string_view input_string); + + private: + friend class ArgParser; + + explicit Parser(Scanner scanner) : scanner_(scanner) {} + + // Parse starting from a single function. + xabsl::StatusOr ParseFunction(Package* package); + + // Parse starting from a function type. + xabsl::StatusOr ParseFunctionType(Package* package); + + // A thin convenience function which parses a single boolean literal. + xabsl::StatusOr ParseBool(); + + // A thin convenience function which parses a single int64 number. + xabsl::StatusOr ParseInt64(); + + // A thin convenience function which parses a single identifier string. + xabsl::StatusOr ParseIdentifierString(TokenPos* pos = nullptr); + + // Convenience function that parses an identifier and resolve it to a value, + // or returns a status error if it cannot. + xabsl::StatusOr ParseIdentifierValue( + const absl::flat_hash_map& name_to_value); + + // Parses a Value. Supports bits, array, and tuple types as well as their + // nested variants. If expected_type is not given, the input string should + // have embedded 'bits' types indicating the width of bits values as produced + // by Value::ToString. For example: "(bits[32]:0x23, bits[0]:0x1)". If + // expected_type is given, the string should NOT have embedded bits types as + // produced by Value::ToHumanString. For example: "(0x23, 0x1)". + xabsl::StatusOr ParseValueInternal( + absl::optional expected_type); + + // Parses a comma-delimited list of names surrounded by brackets; e.g. + // + // "[foo, bar, baz]" + // + // Where the foo, bar, and baz identifiers are resolved via name_to_value. + // + // Returns an error if the parse fails or if any of the names cannot be + // resolved via name_to_value. + xabsl::StatusOr> ParseNameList( + const absl::flat_hash_map& name_to_value); + + // Parses a source location. + // TODO(meheff): Currently the source location is a sequence of three + // comma-separated numbers. Encapsulating the numbers in braces or something + // would make the output less ambiguous. Example: + // "and(x,y,pos={1,2,3},foo=bar)" vs "and(x,y,pos=1,2,3,foo=bar)" + xabsl::StatusOr ParseSourceLocation(); + + // Parse type specifications. + xabsl::StatusOr ParseType(Package* package); + + // Parse a tuple type (which can contain nested tuples). + xabsl::StatusOr ParseTupleType(Package* package); + + // Parse a bits type. + xabsl::StatusOr ParseBitsType(Package* package); + + // Parses a bits types and returns the width. + xabsl::StatusOr ParseBitsTypeAndReturnWidth(); + + // Builds a binary or unary BValue with the given Op using the given + // FunctionBuilder and arg parser. + xabsl::StatusOr BuildBinaryOrUnaryOp( + Op op, FunctionBuilder* fb, absl::optional* loc, + ArgParser* arg_parser); + + // Parses the line-statements in the body of a function. + xabsl::StatusOr ParseFunctionBody( + FunctionBuilder* fb, + absl::flat_hash_map* name_to_value, + Package* package); + + // Parses a full function signature, starting after the 'fn' keyword. + // + // Returns the newly created function builder and the annotated return type + // (may be nullptr) after the opening brace has been popped. + // + // Note: FunctionBuilder must be unique_ptr because it is referred to by + // pointer in BValue types. + xabsl::StatusOr, Type*>> + ParseSignature(absl::flat_hash_map* name_to_value, + Package* package); + + // Pops the package name out of the scanner, of the form: + // + // "package" + // + // And returns the name. + xabsl::StatusOr ParsePackageName(); + + bool AtEof() const { return scanner_.AtEof(); } + + Scanner scanner_; +}; + +/* static */ +template +xabsl::StatusOr> Parser::ParseDerivedPackageNoVerify( + absl::string_view input_string, absl::optional filename, + absl::optional entry) { + XLS_ASSIGN_OR_RETURN(auto scanner, Scanner::Create(input_string)); + Parser parser(std::move(scanner)); + + XLS_ASSIGN_OR_RETURN(std::string package_name, parser.ParsePackageName()); + + auto package = absl::make_unique(package_name, entry); + while (!parser.AtEof()) { + XLS_RETURN_IF_ERROR(parser.ParseFunction(package.get()).status()) + << "@ " << (filename.has_value() ? filename.value() : ""); + } + + // Ensure that, if there were explicit node ID hints in the input IR text, + // that the package's next ID doesn't collide with anything. + int64 max_id_seen = -1; + for (auto& function : package->functions()) { + for (Node* node : function->nodes()) { + max_id_seen = std::max(max_id_seen, node->id()); + } + } + package->set_next_node_id(max_id_seen + 1); + + // Verify the given entry function exists in the package. + if (entry.has_value()) { + XLS_RETURN_IF_ERROR(package->GetFunction(*entry).status()); + } + return package; +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_IR_PARSER_H_ diff --git a/xls/ir/ir_parser_test.cc b/xls/ir/ir_parser_test.cc new file mode 100644 index 0000000000..e2911117b3 --- /dev/null +++ b/xls/ir/ir_parser_test.cc @@ -0,0 +1,1091 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/strings/substitute.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/number_parser.h" + +namespace xls { + +using status_testing::StatusIs; +using ::testing::HasSubstr; + +// EXPECTS that the two given strings are similar modulo extra whitespace. +void ExpectStringsSimilar(absl::string_view a, absl::string_view b) { + std::string a_string(a); + std::string b_string(b); + + // After dumping remove any extra leading, trailing, and consecutive internal + // whitespace verify that strings are the same. + absl::RemoveExtraAsciiWhitespace(&a_string); + absl::RemoveExtraAsciiWhitespace(&b_string); + + EXPECT_EQ(a_string, b_string); +} + +// Parses the given string as a function, dumps the IR and compares that the +// dumped string and input string are the same modulo whitespace. +void ParseFunctionAndCheckDump(absl::string_view in) { + Package p("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(auto function, Parser::ParseFunction(in, &p)); + ExpectStringsSimilar(function->DumpIr(), in); +} + +// Parses the given string as a package, dumps the IR and compares that the +// dumped string and input string are the same modulo whitespace. +void ParsePackageAndCheckDump(absl::string_view in) { + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(in)); + ExpectStringsSimilar(package->DumpIr(), in); +} + +TEST(IrParserTest, ParseBitsLiteral) { + ParseFunctionAndCheckDump(R"(fn f() -> bits[37] { + ret literal.1: bits[37] = literal(value=42) +})"); +} + +TEST(IrParserTest, ParseWideLiteral) { + ParseFunctionAndCheckDump(R"(fn f() -> bits[96] { + ret literal.1: bits[96] = literal(value=0xaaaa_bbbb_1234_5678_90ab_cdef) +})"); +} + +TEST(IrParserTest, ParseVariousBitsLiterals) { + const char tmplate[] = R"(fn f() -> bits[$0] { + ret literal.1: bits[$0] = literal(value=$1) +})"; + struct TestCase { + int64 width; + std::string literal; + Bits expected; + }; + for (const TestCase& test_case : + {TestCase{1, "-1", UBits(1, 1)}, TestCase{8, "-1", UBits(0xff, 8)}, + TestCase{8, "-128", UBits(0x80, 8)}, + TestCase{32, "0xffffffff", UBits(0xffffffffULL, 32)}, + TestCase{32, "-0x80000000", UBits(0x80000000ULL, 32)}, + TestCase{32, "0x80000000", UBits(0x80000000ULL, 32)}}) { + Package p("my_package"); + XLS_ASSERT_OK_AND_ASSIGN( + auto function, + Parser::ParseFunction( + absl::Substitute(tmplate, test_case.width, test_case.literal), &p)); + EXPECT_EQ(function->return_value()->As()->value().bits(), + test_case.expected); + } +} + +TEST(IrParserTest, ParseTupleLiterals) { + std::string text = R"(fn f() -> (bits[16], bits[96]) { + ret literal.1: (bits[16], bits[96]) = literal(value=(1234, 0xdeadbeefdeadbeefdeadbeef)) +})"; + Package p("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(auto function, Parser::ParseFunction(text, &p)); + Bits deadbeef = UBits(0xdeadbeefULL, 32); + EXPECT_EQ( + function->return_value()->As()->value(), + Value::Tuple({Value(UBits(1234, 16)), + Value(bits_ops::Concat({deadbeef, deadbeef, deadbeef}))})); +} + +TEST(IrParserTest, ParseVariousLiteralsTooFewBits) { + const char tmplate[] = R"(fn f() -> bits[$0] { + ret literal.1: bits[$0] = literal(value=$1) +})"; + struct TestCase { + int64 width; + std::string literal; + }; + for (const TestCase& test_case : + {TestCase{1, "-2"}, TestCase{3, "42"}, TestCase{3, "-5"}, + TestCase{8, "-129"}, TestCase{64, "0x1_ffff_ffff_ffff_ffff"}, + TestCase{32, "-0x80000001"}}) { + Package p("my_package"); + EXPECT_THAT( + Parser::ParseFunction( + absl::Substitute(tmplate, test_case.width, test_case.literal), &p) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("is not representable"))); + } +} + +TEST(IrParserTest, DuplicateKeywordArgs) { + Package p("my_package"); + const std::string input = + R"(fn f() -> bits[37] { + ret literal.1: bits[37] = literal(value=42, value=123) +})"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Duplicate keyword argument 'value'"))); +} + +TEST(IrParserTest, WrongDeclaredNodeType) { + Package p("my_package"); + const std::string input = + R"( +fn less_than(a: bits[32], b: bits[32]) -> bits[1] { + ret ult.3: bits[32] = ult(a, b) +})"; + EXPECT_THAT( + Parser::ParseFunction(input, &p).status(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr( + "Declared type bits[32] does not match expected type bits[1]"))); +} + +TEST(IrParserTest, WrongFunctionReturnType) { + Package p("my_package"); + const std::string input = + R"( +fn less_than(a: bits[32], b: bits[32]) -> bits[32] { + ret ult.3: bits[1] = ult(a, b) +})"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Type of return value bits[1] does not match " + "declared function return type bits[32]"))); +} + +TEST(IrParserTest, MissingMandatoryKeyword) { + Package p("my_package"); + const std::string input = + R"(fn f() -> bits[37] { + ret literal.1: bits[37] = literal() +})"; + EXPECT_THAT( + Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Mandatory keyword argument 'value' not found"))); +} + +TEST(IrParserTest, ParsePosition) { + ParseFunctionAndCheckDump( + R"( +fn f(x: bits[42], y: bits[42]) -> bits[42] { + ret and.1: bits[42] = and(x, y, pos=0,1,3) +} +)"); +} + +TEST(IrParserTest, UndefinedOperand) { + Package p("my_package"); + std::string input = + R"( +fn f(x: bits[42]) -> bits[42] { + ret and.1: bits[42] = and(x, z) +} +)"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("was not previously defined: \"z\""))); +} + +TEST(IrParserTest, InvalidOp) { + Package p("my_package"); + std::string input = + R"( +fn f(x: bits[42]) -> bits[42] { + ret foo_op.1: bits[42] = foo_op(x, z) +} +)"; + EXPECT_THAT( + Parser::ParseFunction(input, &p).status(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Unknown operation for string-to-op conversion: foo_op"))); +} + +TEST(IrParserTest, PositionalArgumentAfterKeywordArgument) { + Package p("my_package"); + std::string input = + R"( +fn f(x: bits[42], y: bits[42]) -> bits[42] { + ret and.1: bits[42] = and(x, pos=0,1,3, y) +} +)"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected token of type \"=\""))); +} + +TEST(IrParserTest, ExtraOperands) { + Package p("my_package"); + const std::string input = + R"( +fn f(x: bits[42], y: bits[42], z: bits[42]) -> bits[42] { + ret add.1: bits[42] = add(x, y, z) +} +})"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected 2 operands, got 3"))); +} + +TEST(IrParserTest, TooFewOperands) { + Package p("my_package"); + const std::string input = + R"( +fn f(x: bits[42]) -> bits[42] { + ret add.1: bits[42] = add(x) +} +})"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected 2 operands, got 1"))); +} + +TEST(IrParserTest, DuplicateName) { + Package p("my_package"); + const std::string input = + R"( +fn f(x: bits[42]) -> bits[42] { + and.1: bits[42] = and(x, x) + and.1: bits[42] = and(and.1, and.1) +} +})"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Name 'and.1' has already been defined"))); +} + +TEST(IrParserTest, ParseNode) { + ParseFunctionAndCheckDump( + R"( +fn f() -> bits[32] { + literal.1: bits[32] = literal(value=3, pos=0,3,11) + ret sub.2: bits[32] = sub(literal.1, literal.1) +})"); +} + +TEST(IrParserTest, ParseFunction) { + ParseFunctionAndCheckDump( + R"( +fn simple_arith(a: bits[32], b: bits[32]) -> bits[32] { + ret sub.3: bits[32] = sub(a, b) +})"); +} + +TEST(IrParserTest, ParseULessThan) { + ParseFunctionAndCheckDump( + R"( +fn less_than(a: bits[32], b: bits[32]) -> bits[1] { + ret ult.3: bits[1] = ult(a, b) +})"); +} + +TEST(IrParserTest, ParseSLessThan) { + ParseFunctionAndCheckDump( + R"( +fn less_than(a: bits[32], b: bits[32]) -> bits[1] { + ret slt.3: bits[1] = slt(a, b) +})"); +} + +TEST(IrParserTest, ParseTwoPlusTwo) { + std::string program = R"( +fn two_plus_two() -> bits[32] { + literal.1: bits[32] = literal(value=2) + literal.2: bits[32] = literal(value=2) + ret add.3: bits[32] = add(literal.1, literal.2) +} +)"; + ParseFunctionAndCheckDump(program); +} + +TEST(IrParserTest, ParseTwoPlusThreeCustomIdentifiers) { + std::string program = R"( +fn two_plus_two() -> bits[32] { + x.1: bits[32] = literal(value=2) + y.2: bits[32] = literal(value=3) + ret z.3: bits[32] = add(x.1, y.2) +} +)"; + Package p("my_package"); + XLS_ASSERT_OK_AND_ASSIGN(auto function, Parser::ParseFunction(program, &p)); + // The nodes are given canonical names when we dump because we don't note the + // original names. + ExpectStringsSimilar(function->DumpIr(), R"( +fn two_plus_two() -> bits[32] { + literal.1: bits[32] = literal(value=2) + literal.2: bits[32] = literal(value=3) + ret add.3: bits[32] = add(literal.1, literal.2) +})"); +} + +TEST(IrParserTest, CountedFor) { + std::string program = R"( +package CountedFor + +fn body(x: bits[11], y: bits[11]) -> bits[11] { + ret add.3: bits[11] = add(x, y) +} + +fn main() -> bits[11] { + literal.4: bits[11] = literal(value=0) + ret counted_for.5: bits[11] = counted_for(literal.4, trip_count=7, stride=1, body=body) +} +)"; + ParsePackageAndCheckDump(program); +} + +TEST(IrParserTest, CountedForMissingBody) { + std::string program = R"( +package CountedForMissingBody + +fn body(x: bits[11], y: bits[11]) -> bits[11] { + ret add.3: bits[11] = add(x, y) +} + +fn main() -> bits[11] { + literal.4: bits[11] = literal(value=0) + ret counted_for.5: bits[11] = counted_for(literal.4, trip_count=7, stride=1) +} +)"; + EXPECT_THAT( + Parser::ParsePackage(program).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Mandatory keyword argument 'body' not found"))); +} + +TEST(IrParserTest, CountedForInvariantArgs) { + std::string program = R"( +package CountedFor + +fn body(x: bits[11], y: bits[11]) -> bits[11] { + ret add.3: bits[11] = add(x, y) +} + +fn main() -> bits[11] { + literal.4: bits[11] = literal(value=0) + literal.5: bits[11] = literal(value=1) + ret counted_for.6: bits[11] = counted_for(literal.4, trip_count=7, stride=1, body=body, invariant_args=[literal.5]) +} +)"; + ParsePackageAndCheckDump(program); +} + +TEST(IrParserTest, ParseBitSlice) { + std::string input = R"( +fn bitslice(x: bits[32]) -> bits[14] { + ret bit_slice.1: bits[14] = bit_slice(x, start=7, width=14) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseArray) { + std::string input = R"( +fn array_and_array(x: bits[32], y: bits[32], z: bits[32]) -> bits[32][3] { + ret array.1: bits[32][3] = array(x, y, z) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseReverse) { + std::string input = R"( +fn reverse(x: bits[32]) -> bits[32] { + ret reverse.1: bits[32] = reverse(x) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseArrayOfTuples) { + std::string input = R"( +fn array_and_array(x: (bits[32], bits[1]), y: (bits[32], bits[1])) -> (bits[32], bits[1])[3] { + ret array.1: (bits[32], bits[1])[3] = array(x, y, x) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseNestedBitsArrayIndex) { + std::string input = R"( +fn array_and_array(p: bits[2][5][4], q: bits[32]) -> bits[2][5] { + ret array_index.1: bits[2][5] = array_index(p, q) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, DifferentWidthMultiplies) { + std::string input = R"( +fn multiply(x: bits[32], y: bits[7]) -> bits[42] { + ret umul.1: bits[42] = umul(x, y) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, EmptyBitsBounds) { + Package p("my_package"); + std::string input = R"(fn f() -> bits[] { + ret literal.1: bits[] = literal(value=0) +})"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected token of type \"literal\""))); +} + +TEST(IrParserTest, ParseSingleEmptyPackage) { + std::string input = R"(package EmptyPackage)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(input)); + EXPECT_EQ(package->name(), "EmptyPackage"); + EXPECT_EQ(0, package->functions().size()); + + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParseSingleFunctionPackage) { + std::string input = R"(package SingleFunctionPackage + +fn two_plus_two() -> bits[32] { + literal.1: bits[32] = literal(value=2) + literal.2: bits[32] = literal(value=2) + ret add.3: bits[32] = add(literal.1, literal.2) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(input)); + EXPECT_EQ(package->name(), "SingleFunctionPackage"); + EXPECT_EQ(1, package->functions().size()); + Function* func = package->functions().front().get(); + EXPECT_EQ(func->name(), "two_plus_two"); + + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParseMultiFunctionPackage) { + std::string input = R"(package MultiFunctionPackage + +fn two_plus_two() -> bits[32] { + literal.1: bits[32] = literal(value=2) + literal.2: bits[32] = literal(value=2) + ret add.3: bits[32] = add(literal.1, literal.2) +} + +fn seven_and_five() -> bits[32] { + literal.4: bits[32] = literal(value=7) + literal.5: bits[32] = literal(value=5) + ret and.6: bits[32] = and(literal.4, literal.5) +} +)"; + + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + Parser::ParsePackage(input)); + EXPECT_EQ(package->name(), "MultiFunctionPackage"); + EXPECT_EQ(2, package->functions().size()); + EXPECT_EQ(package->functions()[0]->name(), "two_plus_two"); + EXPECT_EQ(package->functions()[1]->name(), "seven_and_five"); + + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParsePackageWithError) { + std::string input = R"(package MultiFunctionPackage + +Garbage +)"; + EXPECT_THAT(Parser::ParsePackage(input).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected 'fn' keyword"))); +} + +TEST(IrParserTest, ParseEmptyStringAsPackage) { + EXPECT_THAT(Parser::ParsePackage("").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected token, but found EOF"))); +} + +TEST(IrParserTest, ParsePackageWithMissingPackageLine) { + std::string input = R"(fn two_plus_two() -> bits[32] { + literal.1: bits[32] = literal(value=2) + literal.2: bits[32] = literal(value=2) + ret add.3: bits[32] = add(literal.1, literal.2) +} +)"; + absl::Status status = Parser::ParsePackage(input).status(); + EXPECT_THAT(Parser::ParsePackage(input).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected 'package' keyword"))); +} + +TEST(IrParserTest, ParseBinaryConcat) { + std::string input = R"(package p +fn concat_wrapper(x: bits[31], y: bits[1]) -> bits[32] { + ret concat.1: bits[32] = concat(x, y) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(input)); + ASSERT_EQ(1, p->functions().size()); + std::unique_ptr& f = p->functions()[0]; + EXPECT_EQ(f->return_value()->op(), Op::kConcat); + EXPECT_FALSE(f->return_value()->Is()); + EXPECT_TRUE(f->return_value()->Is()); + EXPECT_EQ(p->GetBitsType(32), f->return_value()->GetType()); +} + +TEST(IrParserTest, ParseNaryConcat) { + std::string input = R"(package p +fn concat_wrapper(x: bits[31], y: bits[1]) -> bits[95] { + ret concat.1: bits[95] = concat(x, y, x, x, y) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(input)); + ASSERT_EQ(1, p->functions().size()); + std::unique_ptr& f = p->functions()[0]; + EXPECT_EQ(f->return_value()->op(), Op::kConcat); + EXPECT_TRUE(f->return_value()->Is()); + EXPECT_EQ(p->GetBitsType(95), f->return_value()->GetType()); +} + +TEST(IrParserTest, ParseMap) { + std::string input = R"( +package SimpleMap + +fn to_apply(element: bits[42]) -> bits[1] { + literal.2: bits[42] = literal(value=10) + ret ult.3: bits[1] = ult(element, literal.2) +} + +fn top(input: bits[42][123]) -> bits[1][123] { + ret map.5: bits[1][123] = map(input, to_apply=to_apply) +} +)"; + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParseBinarySel) { + const std::string input = R"( +package ParseSel + +fn sel_wrapper(x: bits[1], y: bits[32], z: bits[32]) -> bits[32] { + ret sel.1: bits[32] = sel(x, cases=[y, z]) +} + )"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr pkg, + Parser::ParsePackage(input)); + ASSERT_EQ(pkg->functions().size(), 1); + Function& f = *(pkg->functions()[0]); + EXPECT_EQ(f.return_value()->op(), Op::kSel); + EXPECT_FALSE(f.return_value()->Is()); + EXPECT_TRUE(f.return_value()->Is()); + EXPECT_EQ(f.return_value()->GetType(), pkg->GetBitsType(32)); + + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParseOneHotLsbPriority) { + const std::string input = R"( +package ParseOneHot + +fn sel_wrapper(x: bits[42]) -> bits[43] { + ret one_hot.1: bits[43] = one_hot(x, lsb_prio=true) +} + )"; + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParseOneHotMsbPriority) { + const std::string input = R"( +package ParseOneHot + +fn sel_wrapper(x: bits[42]) -> bits[43] { + ret one_hot.1: bits[43] = one_hot(x, lsb_prio=false) +} + )"; + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParseOneHotSelect) { + const std::string input = R"( +package ParseOneHotSel + +fn sel_wrapper(p: bits[3], x: bits[32], y: bits[32], z: bits[32]) -> bits[32] { + ret one_hot_sel.1: bits[32] = one_hot_sel(p, cases=[x, y, z]) +} + )"; + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParseParamReturn) { + std::string input = R"( +fn simple_neg(x: bits[2]) -> bits[2] { + ret x: bits[2] = param(name=x) +} +)"; + Package p("my_package"); + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Name 'x' has already been defined"))); +} + +TEST(IrParserTest, ParseInvoke) { + const std::string input = R"(package foobar + +fn bar(x: bits[32], y: bits[32]) -> bits[32] { + ret add.1: bits[32] = add(x, y) +} + +fn foo(x: bits[32]) -> bits[32] { + literal.2: bits[32] = literal(value=5) + ret invoke.3: bits[32] = invoke(x, literal.2, to_apply=bar) +} +)"; + ParsePackageAndCheckDump(input); +} + +TEST(IrParserTest, ParseArrayIndex) { + const std::string input = R"( +fn foo(x: bits[32][6]) -> bits[32] { + literal.1: bits[32] = literal(value=5) + ret array_index.2: bits[32] = array_index(x, literal.1) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseTupleIndex) { + const std::string input = R"( +fn foo(x: bits[42]) -> bits[33] { + literal.1: bits[32] = literal(value=5) + literal.2: bits[33] = literal(value=123) + tuple.3: (bits[42], bits[32], bits[33]) = tuple(x, literal.1, literal.2) + ret tuple_index.4: bits[33] = tuple_index(tuple.3, index=2) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseIdentity) { + const std::string input = R"( +fn foo(x: bits[32]) -> bits[32] { + ret identity.2: bits[32] = identity(x) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseUnsignedInequalities) { + std::string program = R"( +fn parse_inequalities() -> bits[1] { + literal.1: bits[32] = literal(value=2) + literal.2: bits[32] = literal(value=2) + uge.3: bits[1] = uge(literal.1, literal.2) + ugt.4: bits[1] = ugt(literal.1, literal.2) + ule.5: bits[1] = ule(literal.1, literal.2) + ult.6: bits[1] = ult(literal.1, literal.2) + ret eq.7: bits[1] = eq(literal.1, literal.2) +} +)"; + ParseFunctionAndCheckDump(program); +} + +TEST(IrParserTest, ParseSignedInequalities) { + std::string program = R"( +fn parse_inequalities() -> bits[1] { + literal.1: bits[32] = literal(value=2) + literal.2: bits[32] = literal(value=2) + sge.3: bits[1] = sge(literal.1, literal.2) + sgt.4: bits[1] = sgt(literal.1, literal.2) + sle.5: bits[1] = sle(literal.1, literal.2) + slt.6: bits[1] = slt(literal.1, literal.2) + ret eq.7: bits[1] = eq(literal.1, literal.2) +} +)"; + ParseFunctionAndCheckDump(program); +} + +TEST(IrParserTest, StandAloneRet) { + const std::string input = R"(package foobar + +fn foo(x: bits[32]) -> bits[32] { + identity.2: bits[32] = identity(x) + ret identity.2 +} +)"; + + const std::string expected_output = R"(package foobar + +fn foo(x: bits[32]) -> bits[32] { + ret identity.2: bits[32] = identity(x) +} +)"; + + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr pkg, + Parser::ParsePackage(input)); + ASSERT_EQ(pkg->functions().size(), 1); + std::string dumped_ir = pkg->DumpIr(); + EXPECT_EQ(dumped_ir, expected_output); +} + +TEST(IrParserTest, ParseEndOfLineComment) { + const std::string input = R"(// top comment +package foobar +// a comment + +fn foo(x: bits[32]) -> bits[32] { // another comment + ret identity.2: bits[32] = identity(x) // yep, another one + +// comment + +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr pkg, + Parser::ParsePackage(input)); + ASSERT_EQ(pkg->functions().size(), 1); +} + +TEST(IrParserTest, ParseTupleType) { + const std::string input = R"( + package foobar + + fn foo(x: bits[32]) -> (bits[32], bits[32]) { + ret tuple.1: (bits[32], bits[32]) = tuple(x, x) + } + )"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr pkg, + Parser::ParsePackage(input)); + ASSERT_EQ(pkg->functions().size(), 1); + Type* t = pkg->functions()[0]->return_value()->GetType(); + EXPECT_TRUE(t->IsTuple()); + EXPECT_EQ(t->AsTupleOrDie()->size(), 2); + EXPECT_TRUE(t->AsTupleOrDie()->element_type(0)->IsBits()); + EXPECT_TRUE(t->AsTupleOrDie()->element_type(1)->IsBits()); +} + +TEST(IrParserTest, ParseEmptyTuple) { + const std::string input = R"( + package foobar + + fn foo(x: bits[32]) -> () { + ret tuple.1: () = tuple() + } + )"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr pkg, + Parser::ParsePackage(input)); + ASSERT_EQ(pkg->functions().size(), 1); + Type* t = pkg->functions()[0]->return_value()->GetType(); + EXPECT_TRUE(t->IsTuple()); + EXPECT_EQ(t->AsTupleOrDie()->size(), 0); +} + +TEST(IrParserTest, ParseNestedTuple) { + const std::string input = R"( + package foobar + + fn foo(x: bits[32]) -> ((bits[32], bits[32]), bits[32]) { + tuple.1: (bits[32], bits[32]) = tuple(x, x) + tuple.2: ((bits[32], bits[32]), bits[32]) = tuple(tuple.1, x) + ret tuple.2 + } + )"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr pkg, + Parser::ParsePackage(input)); + ASSERT_EQ(pkg->functions().size(), 1); + Type* t = pkg->functions()[0]->return_value()->GetType(); + EXPECT_TRUE(t->IsTuple()); + EXPECT_EQ(t->AsTupleOrDie()->size(), 2); + EXPECT_TRUE(t->AsTupleOrDie()->element_type(0)->IsTuple()); + EXPECT_EQ(t->AsTupleOrDie()->element_type(0)->AsTupleOrDie()->size(), 2); + EXPECT_TRUE(t->AsTupleOrDie()->element_type(1)->IsBits()); +} + +TEST(IrParserTest, ParseArrayLiterals) { + const std::string input = R"( +fn foo(x: bits[32]) -> bits[32] { + literal.1: bits[32][2] = literal(value=[0, 1]) + literal.2: bits[3] = literal(value=1) + ret array_index.3: bits[32] = array_index(literal.1, literal.2) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseNestedArrayLiterals) { + const std::string input = R"( +fn foo() -> bits[32][2][3][1] { + ret literal.1: bits[32][2][3][1] = literal(value=[[[0, 1], [2, 3], [4, 5]]]) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseArrayLiteralWithInsufficientBits) { + Package p("my_package"); + const std::string input = R"( +fn foo() -> bits[7][2] { + ret literal.1: bits[7][2] = literal(value=[0, 12345]) +} +)"; + EXPECT_THAT( + Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Value 12345 is not representable in 7 bits"))); +} + +TEST(IrParserTest, ReturnArrayLiteral) { + const std::string input = R"( +package foobar + +fn foo(x: bits[32]) -> bits[32][2] { + ret literal.1: bits[32][2] = literal(value=[0, 1]) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr pkg, + Parser::ParsePackage(input)); + ASSERT_EQ(pkg->functions().size(), 1); + Type* t = pkg->functions()[0]->return_value()->GetType(); + EXPECT_TRUE(t->IsArray()); +} + +TEST(IrParserTest, ReturnArrayOfTuplesLiteral) { + const std::string input = R"( +package foobar + +fn foo() -> (bits[32], bits[3])[2] { + ret literal.1: (bits[32], bits[3])[2] = literal(value=[(2, 2), (0, 1)]) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr pkg, + Parser::ParsePackage(input)); + ASSERT_EQ(pkg->functions().size(), 1); + Type* t = pkg->functions()[0]->return_value()->GetType(); + EXPECT_TRUE(t->IsArray()); +} + +TEST(IrParserTest, ArrayValueInBitsLiteral) { + Package p("my_package"); + const std::string input = R"( +fn foo() -> bits[42] { + ret literal.1: bits[42] = literal(value=[0, 123]) +} +)"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected token of type \"literal\""))); +} + +TEST(IrParserTest, BitsValueInArrayLiteral) { + Package p("my_package"); + const std::string input = R"( +fn foo() -> bits[7][42] { + ret literal.1: bits[7][42] = literal(value=123]) +} +)"; + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected token of type \"[\""))); +} + +TEST(IrParserTest, ParseTupleLiteral) { + const std::string input = R"( +fn foo() -> (bits[32][2], bits[1]) { + ret literal.1: (bits[32][2], bits[1]) = literal(value=([123, 456], 0)) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseNestedTupleLiteral) { + const std::string input = R"( +fn foo() -> (bits[32][2], bits[1], (), (bits[44])) { + ret literal.1: (bits[32][2], bits[1], (), (bits[44])) = literal(value=([123, 456], 0, (), (10))) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseNaryXor) { + const std::string input = R"( +fn foo(x: bits[8]) -> bits[8] { + ret xor.2: bits[8] = xor(x, x, x, x) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseExtendOps) { + const std::string input = R"( +fn foo(x: bits[8]) -> bits[32] { + zero_ext.1: bits[32] = zero_ext(x, new_bit_count=32) + sign_ext.2: bits[32] = sign_ext(x, new_bit_count=32) + ret xor.3: bits[32] = xor(zero_ext.1, sign_ext.2) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseInconsistentExtendOp) { + const std::string input = R"( +fn foo(x: bits[8]) -> bits[32] { + ret zero_ext.1: bits[33] = zero_ext(x, new_bit_count=32) +} +)"; + Package p("my_package"); + EXPECT_THAT( + Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("differs from its new_bit_count annotation 32"))); +} + +TEST(IrParserTest, ParseDecode) { + const std::string input = R"( +fn foo(x: bits[8]) -> bits[256] { + decode.1: bits[42] = decode(x, width=42) + ret decode.2: bits[256] = decode(x, width=256) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ParseEncode) { + const std::string input = R"( +fn foo(x: bits[16]) -> bits[4] { + ret encode.1: bits[4] = encode(x) +} +)"; + ParseFunctionAndCheckDump(input); +} + +TEST(IrParserTest, ArrayIndexOfTuple) { + const std::string input = R"( +fn foo(x: (bits[8])) -> bits[32] { + literal.1: bits[32] = literal(value=0) + ret array_index.2: bits[8] = array_index(x, literal.1) +} +)"; + Package p("my_package"); + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("array_index operand is not an array"))); +} + +TEST(IrParserTest, TupleIndexOfArray) { + const std::string input = R"( +fn foo(x: bits[8][5]) -> bits[8] { + ret tuple_index.1: bits[8] = tuple_index(x, index=0) +} +)"; + Package p("my_package"); + EXPECT_THAT(Parser::ParseFunction(input, &p).status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("tuple_index operand is not a tuple"))); +} + +TEST(IrParserTest, NicerErrorOnEmptyString) { + const std::string input = ""; + EXPECT_THAT( + Parser::ParsePackage(input).status(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr( + "Expected keyword 'package': Expected token, but found EOF."))); +} + +TEST(IrParserTest, ParsesComplexValue) { + const std::string input = "(0xf00, [0xba5, 0xba7], [0])"; + Package p("test_package"); + auto* u32 = p.GetBitsType(32); + auto* u12 = p.GetBitsType(12); + auto* u1 = p.GetBitsType(1); + auto* array_1xu1 = p.GetArrayType(1, u1); + auto* array_2xu12 = p.GetArrayType(2, u12); + auto* overall = p.GetTupleType({u32, array_2xu12, array_1xu1}); + XLS_ASSERT_OK_AND_ASSIGN(Value v, Parser::ParseValue(input, overall)); + Value expected = Value::Tuple({ + Value(UBits(0xf00, /*bit_count=*/32)), + Value::ArrayOrDie({ + Value(UBits(0xba5, /*bit_count=*/12)), + Value(UBits(0xba7, /*bit_count=*/12)), + }), + Value::ArrayOrDie({Value(UBits(0, /*bit_count=*/1))}), + }); + EXPECT_EQ(expected, v); +} + +TEST(IrParserTest, ParsesComplexValueWithEmbeddedTypes) { + const std::string input = + "(bits[32]:0xf00, [bits[12]:0xba5, bits[12]:0xba7], [bits[1]:0])"; + XLS_ASSERT_OK_AND_ASSIGN(Value v, Parser::ParseTypedValue(input)); + Value expected = Value::Tuple({ + Value(UBits(0xf00, /*bit_count=*/32)), + Value::ArrayOrDie({ + Value(UBits(0xba5, /*bit_count=*/12)), + Value(UBits(0xba7, /*bit_count=*/12)), + }), + Value::ArrayOrDie({Value(UBits(0, /*bit_count=*/1))}), + }); + EXPECT_EQ(expected, v); +} + +// TODO(leary): 2019-08-01 Figure out if we want to reify the type into the +// empty array Value. +TEST(IrParserTest, DISABLED_ParsesEmptyArray) { + const std::string input = "[]"; + Package p("test_package"); + auto* u1 = p.GetBitsType(1); + auto* array_0xu1 = p.GetArrayType(0, u1); + XLS_ASSERT_OK_AND_ASSIGN(Value v, Parser::ParseValue(input, array_0xu1)); + Value expected = Value::ArrayOrDie({}); + EXPECT_EQ(expected, v); +} + +TEST(IrParserTest, BigOrdinalAnnotation) { + std::string program = R"( +package test + +fn main() -> bits[1] { + ret literal.1000: bits[1] = literal(value=0) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(program)); + EXPECT_GT(package->next_node_id(), 1000); +} + +} // namespace xls diff --git a/xls/ir/ir_scanner.cc b/xls/ir/ir_scanner.cc new file mode 100644 index 0000000000..d19b513e1f --- /dev/null +++ b/xls/ir/ir_scanner.cc @@ -0,0 +1,344 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_scanner.h" + +#include + +#include "absl/strings/ascii.h" +#include "absl/strings/str_format.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/number_parser.h" + +namespace xls { + +std::string TokenTypeToString(TokenType token_type) { + switch (token_type) { + case TokenType::kIdent: + return "ident"; + case TokenType::kKeyword: + return "keyword"; + case TokenType::kLiteral: + return "literal"; + case TokenType::kMinus: + return "-"; + case TokenType::kAdd: + return "+"; + case TokenType::kColon: + return ":"; + case TokenType::kGt: + return ">"; + case TokenType::kLt: + return "<"; + case TokenType::kDot: + return "."; + case TokenType::kComma: + return ","; + case TokenType::kEquals: + return "="; + case TokenType::kCurlOpen: + return "{"; + case TokenType::kCurlClose: + return "}"; + case TokenType::kBracketOpen: + return "["; + case TokenType::kBracketClose: + return "]"; + case TokenType::kParenOpen: + return "("; + case TokenType::kParenClose: + return ")"; + case TokenType::kRightArrow: + return "->"; + } + return absl::StrCat("TokenType(", static_cast(token_type), ")"); +} + +std::string TokenPos::ToHumanString() const { + return absl::StrFormat("%d:%d", lineno + 1, colno + 1); +} + +xabsl::StatusOr Token::IsNegative() const { + if (type() != TokenType::kLiteral) { + return absl::InternalError("Can only get sign for literal tokens."); + } + std::pair pair; + XLS_ASSIGN_OR_RETURN(pair, GetSignAndMagnitude(value())); + return pair.first; +} + +xabsl::StatusOr Token::GetValueBits() const { + if (type() != TokenType::kLiteral) { + return absl::InternalError( + "Can only get value as integer for literal tokens."); + } + return ParseNumber(value()); +} + +xabsl::StatusOr Token::GetValueInt64() const { + if (type() != TokenType::kLiteral) { + return absl::InternalError( + "Can only get value as integer for literal tokens."); + } + return ParseNumberAsInt64(value()); +} + +xabsl::StatusOr Token::GetValueBool() const { + if (type() != TokenType::kLiteral) { + return absl::InternalError( + "Can only get value as integer for literal tokens."); + } + return ParseNumberAsBool(value()); +} + +std::string Token::ToString() const { + return absl::StrFormat("Token(\"%s\", value=\"%s\") @ %s", + TokenTypeToString(type_), value_, + pos_.ToHumanString()); +} + +xabsl::StatusOr> TokenizeString(absl::string_view str) { + int lineno = 0; + int colno = 0; + + auto in_bounds = [&str](int64 index) -> bool { return index < str.size(); }; + + // Returns the first index greater than 'index' in 'str' with a non-whitespace + // character. Updates source location information. + auto eat_white_space = [&](absl::string_view str, int64 index) -> int64 { + while (in_bounds(index) && absl::ascii_isspace(str[index])) { + char c = str[index]; + if (c == ' ') { + ++colno; + } + if (c == '\t') { + colno += 4; + } + if (c == '\n') { + colno = 1; + ++lineno; + } + index++; + } + return index; + }; + + // In case of end-of-line comments, return the first index after + // the comment-terminating end-of-line character. Otherwise return + // the originally-given index. + auto eat_end_of_line_comment = [&](absl::string_view str, + int64 index) -> int64 { + if (in_bounds(index + 1) && str[index] == '/' && str[index + 1] == '/') { + index += 2; + while (in_bounds(index) && str[index] != '\n') { + index++; + } + } + return index; + }; + + auto drop_whitespace_and_comments = [&](absl::string_view str, + int64 index) -> int64 { + int64 old_index = -1; + while (old_index != index) { + old_index = index; + index = eat_white_space(str, index); + index = eat_end_of_line_comment(str, index); + } + return index; + }; + + std::vector tokens; + int64 index = 0; // Running index into the input string. + while (in_bounds(index)) { + index = drop_whitespace_and_comments(str, index); + if (!in_bounds(index)) { + break; + } + int64 token_start_index = index; + + // Literal numbers can decimal, binary (eg, 0b0101) or hexadecimal (eg, + // 0xbeef) so capture all alphanumeric characters after the initial digit. + // Literal numbers can also contain '_'s after the first character which are + // used to improve readability (example: '0xabcd_ef00'). + if (isdigit(str[index]) || (str[index] == '-' && in_bounds(index + 1) && + isdigit(str[index + 1]))) { + while ((token_start_index == index && str[index] == '-') || + (in_bounds(index) && + (absl::ascii_isalnum(str[index]) || str[index] == '_'))) { + index++; + } + const int64 token_len = index - token_start_index; + absl::string_view value = str.substr(token_start_index, token_len); + tokens.push_back(Token(TokenType::kLiteral, value, lineno, colno)); + colno += token_len; + continue; + } + if (isalpha(str[index]) || str[index] == '_') { + std::string res = ""; + while (isalpha(str[index]) || str[index] == '_' || str[index] == '.' || + isdigit(str[index])) { + res.append(1, str[index++]); + } + tokens.push_back(Token::MakeIdentOrKeyword(res, lineno, colno)); + colno += (index - token_start_index); + continue; + } + + // Look for multi-character tokens. + if (str[index] == '-' && in_bounds(index + 1) && str[index + 1] == '>') { + tokens.push_back(Token(TokenType::kRightArrow, "->", lineno, colno)); + index += 2; + colno += 2; + continue; + } + + // Handle single-character tokens. + TokenType token_type; + const char c = str[index]; + switch (c) { + case '-': + token_type = TokenType::kMinus; + break; + case '+': + token_type = TokenType::kAdd; + break; + case '.': + token_type = TokenType::kDot; + break; + case ':': + token_type = TokenType::kColon; + break; + case ',': + token_type = TokenType::kComma; + break; + case '=': + token_type = TokenType::kEquals; + break; + case '[': + token_type = TokenType::kBracketOpen; + break; + case ']': + token_type = TokenType::kBracketClose; + break; + case '{': + token_type = TokenType::kCurlOpen; + break; + case '}': + token_type = TokenType::kCurlClose; + break; + case '(': + token_type = TokenType::kParenOpen; + break; + case ')': + token_type = TokenType::kParenClose; + break; + case '>': + token_type = TokenType::kGt; + break; + case '<': + token_type = TokenType::kLt; + break; + default: + std::string char_str = absl::ascii_iscntrl(c) + ? absl::StrFormat("\\x%02x", c) + : std::string(1, c); + XLS_LOG(ERROR) << "IR text with error: " << str; + return absl::InvalidArgumentError( + absl::StrFormat("Invalid character in IR text \"%s\" @ %s", + char_str, TokenPos{lineno, colno}.ToHumanString())); + } + tokens.push_back(Token(token_type, lineno, colno)); + index++; + colno++; + } + return tokens; +} + +xabsl::StatusOr Scanner::Create(absl::string_view text) { + XLS_ASSIGN_OR_RETURN(auto tokens, TokenizeString(text)); + return Scanner(std::move(tokens)); +} + +xabsl::StatusOr Scanner::PeekToken() const { + if (AtEof()) { + return absl::InvalidArgumentError("Expected token, but found EOF."); + } + return tokens_[token_idx_]; +} + +xabsl::StatusOr Scanner::PopTokenOrError(absl::string_view context) { + if (AtEof()) { + std::string context_str = + context.empty() ? std::string("") : absl::StrCat(" in ", context); + return absl::InvalidArgumentError("Expected token" + context_str + + ", but found EOF."); + } + return PopToken(); +} + +bool Scanner::TryDropToken(TokenType target) { + if (PeekTokenIs(target)) { + PopToken(); + return true; + } + return false; +} + +absl::Status Scanner::DropTokenOrError(TokenType target, + absl::string_view context) { + if (AtEof()) { + std::string context_str = + context.empty() ? std::string("") : absl::StrCat(" in ", context); + return absl::InvalidArgumentError( + absl::StrFormat("Expected token of type %s%s; found EOF.", + TokenTypeToString(target), context_str)); + } + XLS_ASSIGN_OR_RETURN(Token dropped, PopTokenOrError(target, context)); + (void)dropped; + return absl::OkStatus(); +} + +xabsl::StatusOr Scanner::PopTokenOrError(TokenType target, + absl::string_view context) { + XLS_ASSIGN_OR_RETURN(Token token, PopTokenOrError()); + if (token.type() != target) { + std::string context_str = + context.empty() ? std::string("") : absl::StrCat(" in ", context); + return absl::InvalidArgumentError( + absl::StrFormat("Expected token of type \"%s\"%s @ %s, but found: %s", + TokenTypeToString(target), context_str, + token.pos().ToHumanString(), token.ToString())); + } + return token; +} + +absl::Status Scanner::DropKeywordOrError(absl::string_view keyword) { + xabsl::StatusOr popped_status = PopTokenOrError(); + if (!popped_status.ok()) { + return absl::InvalidArgumentError( + absl::StrFormat("Expected keyword '%s': %s", keyword, + popped_status.status().message())); + } + XLS_ASSIGN_OR_RETURN(Token popped, popped_status); + if (popped.type() == TokenType::kKeyword && keyword == popped.value()) { + return absl::OkStatus(); + } + return absl::InvalidArgumentError( + absl::StrFormat("Expected '%s' keyword; got: %s @ %s", keyword, + popped.ToString(), popped.pos().ToHumanString())); +} + +} // namespace xls diff --git a/xls/ir/ir_scanner.h b/xls/ir/ir_scanner.h new file mode 100644 index 0000000000..9fd26c70dc --- /dev/null +++ b/xls/ir/ir_scanner.h @@ -0,0 +1,201 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_IR_SCANNER_H_ +#define THIRD_PARTY_XLS_IR_IR_SCANNER_H_ + +#include + +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "xls/common/integral_types.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" + +namespace xls { + +enum class TokenType { + kIdent, + kKeyword, + kLiteral, + kMinus, + kAdd, + kColon, + kGt, + kLt, + kDot, + kComma, + kCurlOpen, + kCurlClose, + kBracketOpen, + kBracketClose, + kParenOpen, + kParenClose, + kRightArrow, + kEquals, +}; + +std::string TokenTypeToString(TokenType token_type); + +struct TokenPos { + int64 lineno; + int64 colno; + + // Humans think of line 0 column 0 as "1:1" in text editors, typically. + std::string ToHumanString() const; +}; + +class Token { + public: + // Returns the (singleton) set of keyword strings. + static const absl::flat_hash_set& GetKeywords() { + static auto* keywords = + new absl::flat_hash_set{"fn", "bits", "ret", "package"}; + return *keywords; + } + + // Helper factory, returns a token of kKeyword type if "value" is a keyword + // string, and a token of kIdent type otherwise. + static Token MakeIdentOrKeyword(absl::string_view value, int64 lineno, + int64 colno) { + TokenType type = + GetKeywords().contains(value) ? TokenType::kKeyword : TokenType::kIdent; + if (value == "true" || value == "false") { + type = TokenType::kLiteral; + } + return Token(type, value, lineno, colno); + } + + Token(TokenType type, int64 lineno, int64 colno) + : type_(type), pos_({lineno, colno}) {} + + Token(TokenType type, absl::string_view value, int64 lineno, int64 colno) + : type_(type), value_(value), pos_({lineno, colno}) {} + + TokenType type() const { return type_; } + const std::string& value() const { return value_; } + const TokenPos& pos() const { return pos_; } + + // Returns the token as a (u)int64 value. Token must be a literal. The + // expected string representation is the same as with + // ParseNumberAsBits. Returns an error if the number does not fit in a + // (u)int64. + xabsl::StatusOr GetValueInt64() const; + + // Returns the token as a bool value. Token must be a literal. Returns an + // error if the number does not fit in a bool. + xabsl::StatusOr GetValueBool() const; + + // Returns the token as a Bits value. Token must be a literal. The + // expected string representation is the same as with ParseNumberAsBits. + xabsl::StatusOr GetValueBits() const; + + // Returns whether the token is a negative value. Token must be a literal. + xabsl::StatusOr IsNegative() const; + + std::string ToString() const; + + private: + TokenType type_; + std::string value_; + TokenPos pos_; +}; + +inline std::ostream& operator<<(std::ostream& os, const Token& token) { + os << token.ToString(); + return os; +} + +// Tokenizes the given string and returns the tokens. It maintains precise +// source location information. Right now this is a eager implementation - it +// tokenizes the whole input. This can be easily changed later to a more demand +// driven tokenization. +xabsl::StatusOr> TokenizeString(absl::string_view str); + +class Scanner { + public: + static xabsl::StatusOr Create(absl::string_view text); + + // Peeks at the next token in the token stream, or returns an error if we're + // at EOF and no more tokens are available. + xabsl::StatusOr PeekToken() const; + + // Return the current token. + const Token& PeekTokenOrDie() const { + XLS_CHECK(!AtEof()); + return tokens_[token_idx_]; + } + + // Helper that makes sure we don't peek past EOF. + bool PeekTokenIs(TokenType target) const { + return !AtEof() && PeekTokenOrDie().type() == target; + } + + // Pop the current token, advance token pointer to next token. + Token PopToken() { + XLS_VLOG(3) << "Popping token: " << tokens_[token_idx_]; + return tokens_.at(token_idx_++); + } + + // Same as PopToken() but returns a status error if we are at EOF (in which + // case a token cannot be popped). + xabsl::StatusOr PopTokenOrError(absl::string_view context = ""); + + // As above, but the caller must ensure we are not possibly at EOF: if we are, + // then the program will CHECK-fail. + void DropTokenOrDie() { XLS_CHECK_OK(PopTokenOrError().status()); } + + // Attempts to drop a token with type "target" from the token stream, and + // returns true if it is possible to do so; otherwise, returns false. + // + // Note: This function is "EOF safe": trying to drop a token at EOF is ok. + bool TryDropToken(TokenType target); + + bool TryDropKeyword(absl::string_view which) { + if (PeekTokenIs(TokenType::kKeyword) && PeekTokenOrDie().value() == which) { + DropTokenOrDie(); + return true; + } + return false; + } + + // As with PopTokenOrError, but also supplies an error if the token is not of + // type "target". + xabsl::StatusOr PopTokenOrError(TokenType target, + absl::string_view context = ""); + + // Wrapper around PopTokenOrError(target) above that can be used with + // XLS_RETURN_IF_ERROR. + absl::Status DropTokenOrError(TokenType target, + absl::string_view context = ""); + + // Pop a keyword token with keyword (payload) "keyword". + // + // Returns an absl::Status error if we cannot. + absl::Status DropKeywordOrError(absl::string_view keyword); + + // Check if more tokens are available. + bool AtEof() const { return token_idx_ >= tokens_.size(); } + + private: + explicit Scanner(std::vector tokens) : tokens_(tokens) {} + + int64 token_idx_ = 0; + std::vector tokens_; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_IR_SCANNER_H_ diff --git a/xls/ir/ir_scanner_test.cc b/xls/ir/ir_scanner_test.cc new file mode 100644 index 0000000000..c28ea40ed1 --- /dev/null +++ b/xls/ir/ir_scanner_test.cc @@ -0,0 +1,77 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_scanner.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "xls/common/status/matchers.h" + +namespace xls { +namespace { + +using status_testing::StatusIs; + +TEST(IrScannerTest, TokenizeWhitespaceString) { + XLS_ASSERT_OK_AND_ASSIGN(std::vector tokens, + TokenizeString(" \n\t")); + EXPECT_EQ(0, tokens.size()); +} + +TEST(IrScannerTest, TokenizeEmptyString) { + XLS_ASSERT_OK_AND_ASSIGN(std::vector tokens, TokenizeString("")); + EXPECT_EQ(0, tokens.size()); +} + +TEST(IrScannerTest, TokenizeEmptyStringWithMinComment) { + XLS_ASSERT_OK_AND_ASSIGN(std::vector tokens, TokenizeString("//")); + EXPECT_EQ(0, tokens.size()); +} + +TEST(IrScannerTest, TokenizeEmptyStringWithComment) { + XLS_ASSERT_OK_AND_ASSIGN(std::vector tokens, + TokenizeString("// comment")); + EXPECT_EQ(0, tokens.size()); +} + +TEST(IrScannerTest, TokenizeStringWithComment) { + XLS_ASSERT_OK_AND_ASSIGN(std::vector tokens, + TokenizeString(R"(fn n( // comment)")); + EXPECT_EQ(3, tokens.size()); +} + +TEST(IrScannerTest, TokenizeInvalidCharacter) { + { + auto tokens_status = TokenizeString("$"); + EXPECT_FALSE(tokens_status.ok()); + EXPECT_THAT( + tokens_status.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr("Invalid character in IR text \"$\""))); + } + { + auto tokens_status = TokenizeString("\x07"); + EXPECT_FALSE(tokens_status.ok()); + EXPECT_THAT(tokens_status.status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr( + "Invalid character in IR text \"\\x07\""))); + } +} + +} // namespace +} // namespace xls diff --git a/xls/ir/ir_test_base.cc b/xls/ir/ir_test_base.cc new file mode 100644 index 0000000000..d376ea6892 --- /dev/null +++ b/xls/ir/ir_test_base.cc @@ -0,0 +1,226 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_test_base.h" + +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "xls/codegen/combinational_generator.h" +#include "xls/codegen/module_signature.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/delay_model/delay_estimator.h" +#include "xls/delay_model/delay_estimators.h" +#include "xls/ir/ir_interpreter.h" +#include "xls/ir/ir_parser.h" +#include "xls/ir/value_test_util.h" +#include "xls/ir/verifier.h" +#include "xls/passes/standard_pipeline.h" +#include "xls/scheduling/pipeline_schedule.h" +#include "xls/simulation/module_simulator.h" +#include "xls/simulation/verilog_simulators.h" + +namespace xls { + +VerifiedPackage::~VerifiedPackage() { + absl::Status status = Verify(this); + if (!status.ok()) { + ADD_FAILURE() << absl::StrFormat( + "IR verifier failed on package %s during destruction: %s", name(), + status.message()); + } +} + +xabsl::StatusOr> IrTestBase::ParsePackage( + absl::string_view text) { + XLS_ASSIGN_OR_RETURN(std::unique_ptr package, + Parser::ParseDerivedPackageNoVerify( + text, absl::nullopt)); + XLS_RETURN_IF_ERROR(Verify(package.get())); + return std::move(package); +} + +xabsl::StatusOr> IrTestBase::ParsePackageNoVerify( + absl::string_view text) { + XLS_ASSIGN_OR_RETURN(std::unique_ptr package, + Parser::ParsePackageNoVerify(text)); + return std::move(package); +} + +xabsl::StatusOr IrTestBase::ParseFunction(absl::string_view text, + Package* package) { + return Parser::ParseFunction(text, package); +} + +Node* IrTestBase::FindNode(absl::string_view name, Package* package) { + for (auto& function : package->functions()) { + for (Node* node : function->nodes()) { + if (node->GetName() == name) { + return node; + } + } + } + XLS_LOG(FATAL) << "No node named " << name << " in package:\n" << *package; +} + +Node* IrTestBase::FindNode(absl::string_view name, Function* function) { + for (Node* node : function->nodes()) { + if (node->GetName() == name) { + return node; + } + } + XLS_LOG(FATAL) << "No node named " << name << " in function:\n" << *function; +} + +Function* IrTestBase::FindFunction(absl::string_view name, Package* package) { + for (auto& function : package->functions()) { + if (function->name() == name) { + return function.get(); + } + } + XLS_LOG(FATAL) << "No function named " << name << " in package:\n" + << *package; +} + +void IrTestBase::RunAndExpectEq( + const absl::flat_hash_map& args, uint64 expected, + absl::string_view package_text, xabsl::SourceLocation loc) { + // Emit the filename/line of the test code in any failure message. The + // location is captured as a default argument to RunAndExpectEq. + testing::ScopedTrace trace(loc.file_name(), loc.line(), + "RunAndExpectEq failed"); + XLS_VLOG(3) << "Package text:\n" << package_text; + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + ParsePackage(package_text)); + absl::flat_hash_map arg_values; + XLS_ASSERT_OK_AND_ASSIGN(arg_values, UInt64ArgsToValues(args, package.get())); + XLS_ASSERT_OK_AND_ASSIGN(Value expected_value, + UInt64ResultToValue(expected, package.get())); + + RunAndExpectEq(arg_values, expected_value, std::move(package)); +} + +void IrTestBase::RunAndExpectEq( + const absl::flat_hash_map& args, Bits expected, + absl::string_view package_text, xabsl::SourceLocation loc) { + // Emit the filename/line of the test code in any failure message. The + // location is captured as a default argument to RunAndExpectEq. + testing::ScopedTrace trace(loc.file_name(), loc.line(), + "RunAndExpectEq failed"); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + ParsePackage(package_text)); + absl::flat_hash_map args_as_values; + for (const auto& pair : args) { + args_as_values[pair.first] = Value(pair.second); + } + RunAndExpectEq(args_as_values, Value(expected), std::move(package)); +} + +void IrTestBase::RunAndExpectEq( + const absl::flat_hash_map& args, Value expected, + absl::string_view package_text, xabsl::SourceLocation loc) { + // Emit the filename/line of the test code in any failure message. The + // location is captured as a default argument to RunAndExpectEq. + testing::ScopedTrace trace(loc.file_name(), loc.line(), + "RunAndExpectEq failed"); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr package, + ParsePackage(package_text)); + RunAndExpectEq(args, expected, std::move(package)); +} + +xabsl::StatusOr> +IrTestBase::UInt64ArgsToValues( + const absl::flat_hash_map& args, Package* package) { + absl::flat_hash_map value_args; + for (const auto& pair : args) { + const std::string& param_name = pair.first; + const uint64 arg_value = pair.second; + XLS_ASSIGN_OR_RETURN(Function * entry, package->EntryFunction()); + XLS_ASSIGN_OR_RETURN(Param * param, entry->GetParamByName(pair.first)); + Type* type = param->GetType(); + XLS_RET_CHECK(type->IsBits()) + << absl::StrFormat("Parameter '%s' is not a bits type: %s", + param->name(), type->ToString()); + XLS_RET_CHECK_GE(type->AsBitsOrDie()->bit_count(), + Bits::MinBitCountUnsigned(arg_value)) + << absl::StrFormat( + "Argument value %d for parameter '%s' does not fit in type %s", + arg_value, param->name(), type->ToString()); + value_args[param_name] = + Value(UBits(arg_value, type->AsBitsOrDie()->bit_count())); + } + + return value_args; +} + +xabsl::StatusOr IrTestBase::UInt64ResultToValue(uint64 value, + Package* package) { + XLS_ASSIGN_OR_RETURN(Function * entry, package->EntryFunction()); + Type* return_type = entry->return_value()->GetType(); + XLS_RET_CHECK(return_type->IsBits()) << absl::StrFormat( + "Return value of function not a bits type: %s", return_type->ToString()); + XLS_RET_CHECK_GE(return_type->AsBitsOrDie()->bit_count(), + Bits::MinBitCountUnsigned(value)) + << absl::StrFormat("Value %d does not fit in return type %s", value, + return_type->ToString()); + return Value(UBits(value, return_type->AsBitsOrDie()->bit_count())); +} + +void IrTestBase::RunAndExpectEq( + const absl::flat_hash_map& args, const Value& expected, + std::unique_ptr&& package) { + // Run interpreter on unoptimized IR. + { + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN(Value actual, + ir_interpreter::RunKwargs(entry, args)); + ASSERT_TRUE(ValuesEqual(expected, actual)) + << "(interpreted unoptimized IR)"; + } + + // Run main pipeline. + XLS_ASSERT_OK(RunStandardPassPipeline(package.get())); + + // Run interpreter on optimized IR. + { + XLS_ASSERT_OK_AND_ASSIGN(Function * main, package->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN(Value actual, + ir_interpreter::RunKwargs(main, args)); + ASSERT_TRUE(ValuesEqual(expected, actual)) << "(interpreted optimized IR)"; + } + + // Emit Verilog with combinational generator and run with ModuleSimulator. + { + ASSERT_EQ(package->functions().size(), 1); + XLS_ASSERT_OK_AND_ASSIGN(Function * main, package->EntryFunction()); + + XLS_ASSERT_OK_AND_ASSIGN( + verilog::ModuleGeneratorResult result, + verilog::ToCombinationalModuleText(main, /*use_system_verilog=*/false)); + + absl::flat_hash_map arg_set; + for (const auto& pair : args) { + arg_set.insert(pair); + } + XLS_VLOG(3) << "Verilog text:\n" << result.verilog_text; + verilog::ModuleSimulator simulator(result.signature, result.verilog_text, + &verilog::GetDefaultVerilogSimulator()); + XLS_ASSERT_OK_AND_ASSIGN(Value actual, simulator.Run(arg_set)); + ASSERT_TRUE(ValuesEqual(expected, actual)) << "(Verilog simulation)"; + } +} + +} // namespace xls diff --git a/xls/ir/ir_test_base.h b/xls/ir/ir_test_base.h new file mode 100644 index 0000000000..eaf60a809b --- /dev/null +++ b/xls/ir/ir_test_base.h @@ -0,0 +1,118 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_IR_TEST_BASE_H_ +#define THIRD_PARTY_XLS_IR_IR_TEST_BASE_H_ + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/source_location.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/node.h" +#include "xls/ir/package.h" +#include "xls/ir/verifier.h" + +namespace xls { + +// A package which verifies itself upon destruction. This is useful to verify +// that any transformations done during the test were valid without having to +// explicitly call the verifier. +class VerifiedPackage : public Package { + public: + explicit VerifiedPackage(absl::string_view name, + absl::optional entry) + : Package(name, entry) {} + ~VerifiedPackage() override; +}; + +// A test base class with convenience functions for IR tests. +class IrTestBase : public ::testing::Test { + protected: + IrTestBase() {} + + static std::string TestName() { + return ::testing::UnitTest::GetInstance()->current_test_info()->name(); + } + + // Creates an empty package with a name equal to TestName(). + static std::unique_ptr CreatePackage() { + return absl::make_unique(TestName(), absl::nullopt); + } + + // Parses the given text as a package and replaces the owned packaged with the + // result. Package can be accessed by calling "package()". + static xabsl::StatusOr> ParsePackage( + absl::string_view text); + + // As above but skips IR verification and returns an ordinary Package.. + static xabsl::StatusOr> ParsePackageNoVerify( + absl::string_view text); + + // Parse the input_string as a function into the given package. + xabsl::StatusOr ParseFunction(absl::string_view text, + Package* package); + + // Finds and returns the node in the given package (function) with the given + // name. Dies if no such node exists. + static Node* FindNode(absl::string_view name, Package* package); + static Node* FindNode(absl::string_view name, Function* function); + + // Finds and returns the node in the given package with the given name. Dies + // if no such node exists. + static Function* FindFunction(absl::string_view name, Package* package); + + // Runs the given package (passed as IR text) and EXPECTs the result to equal + // 'expected'. Runs the package in several ways: + // (1) unoptimized IR through the interpreter. + // (2) optimized IR through the interpreter. + // (3) pipeline generator emitted Verilog through a Verilog simulator. + static void RunAndExpectEq( + const absl::flat_hash_map& args, uint64 expected, + absl::string_view package_text, + xabsl::SourceLocation loc = xabsl::SourceLocation::current()); + + // Overload which takes Bits as arguments and the expected result. + static void RunAndExpectEq( + const absl::flat_hash_map& args, Bits expected, + absl::string_view package_text, + xabsl::SourceLocation loc = xabsl::SourceLocation::current()); + + // Overload which takes Values as arguments and the expected result. + static void RunAndExpectEq( + const absl::flat_hash_map& args, Value expected, + absl::string_view package_text, + xabsl::SourceLocation loc = xabsl::SourceLocation::current()); + + private: + // Helper for RunAndExpectEq which accepts arguments and expectation as Values + // and takes a std::unique_ptr. + static void RunAndExpectEq( + const absl::flat_hash_map& args, + const Value& expected, std::unique_ptr&& package); + + // Converts the given map of uint64 arguments into a map of Value argument + // with the appropriate bit widths as determined by the package. + static xabsl::StatusOr> + UInt64ArgsToValues(const absl::flat_hash_map& args, + Package* package); + + // Converts the uint64 result to a Value with the appropriate bit widths as + // determined by the package return value. + static xabsl::StatusOr UInt64ResultToValue(uint64 value, + Package* package); +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_IR_TEST_BASE_H_ diff --git a/xls/ir/ir_test_base_test.cc b/xls/ir/ir_test_base_test.cc new file mode 100644 index 0000000000..28472a3081 --- /dev/null +++ b/xls/ir/ir_test_base_test.cc @@ -0,0 +1,85 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_test_base.h" + +#include "gmock/gmock.h" +#include "gtest/gtest-spi.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "xls/common/status/matchers.h" + +namespace xls { +namespace { + +class IrTestBaseTest : public IrTestBase { + protected: + static constexpr char kTestPackage[] = R"( +package test_package + +fn main(p: bits[8], q: bits[8]) -> bits[8] { + add.1: bits[8] = add(p, q) + ret add.2: bits[8] = add(add.1, q) +} +)"; + + // Creates an invalid VerifiedPackage, then destroys it. + void DestructInvalidVerifiedPackage() { + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + ParsePackage(kTestPackage)); + // Set an id to a duplicate value. + FindNode("add.2", p.get())->set_id(1); + } +}; + +TEST_F(IrTestBaseTest, DestructInvalidVerifiedPackage) { + EXPECT_NONFATAL_FAILURE( + DestructInvalidVerifiedPackage(), + "verifier failed on package test_package during destruction"); +} + +TEST_F(IrTestBaseTest, ValidVerifiedPackageSucceeds) { + // Verify that a valid VerifiedPackage does not raise any errors during + // destruction. The package is created then immediately dropped. + XLS_ASSERT_OK(ParsePackage(kTestPackage).status()); +} + +TEST_F(IrTestBaseTest, RunAndExpectEqRightValue) { + RunAndExpectEq({{"p", 3}, {"q", 10}}, 23, kTestPackage); +} + +TEST_F(IrTestBaseTest, RunAndExpectEqWrongValue) { + EXPECT_FATAL_FAILURE(RunAndExpectEq({{"p", 3}, {"q", 10}}, 55, kTestPackage), + "bits[8]:55 != bits[8]:23"); +} + +TEST_F(IrTestBaseTest, RunAndExpectEqArgDoesNotFit) { + EXPECT_FATAL_FAILURE( + RunAndExpectEq({{"p", 12345}, {"q", 10}}, 23, kTestPackage), + "Argument value 12345 for parameter 'p' does not fit in type bits[8]"); +} + +TEST_F(IrTestBaseTest, RunAndExpectEqExpectedResultDoesNotFit) { + EXPECT_FATAL_FAILURE( + RunAndExpectEq({{"p", 3}, {"q", 10}}, 12345, kTestPackage), + "Value 12345 does not fit in return type bits[8]"); +} + +TEST_F(IrTestBaseTest, RunAndExpectEqMissingArg) { + EXPECT_FATAL_FAILURE(RunAndExpectEq({{"p", 3}}, 10, kTestPackage), + "Missing argument 'q'"); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/keyword_args.cc b/xls/ir/keyword_args.cc new file mode 100644 index 0000000000..f8e45701e9 --- /dev/null +++ b/xls/ir/keyword_args.cc @@ -0,0 +1,51 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/keyword_args.h" + +#include "absl/strings/str_format.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" + +namespace xls { + +xabsl::StatusOr> KeywordArgsToPositional( + const Function& function, + const absl::flat_hash_map& kwargs) { + XLS_VLOG(2) << "Interpreting function " << function.name() + << " with arguments:"; + + // Make nice error messages with the name in the error message if a kwarg is + // missing. + for (Param* param : function.params()) { + if (!kwargs.contains(param->name())) { + return absl::InvalidArgumentError(absl::StrFormat( + "Missing argument '%s' to invocation of function '%s'", param->name(), + function.name())); + } + } + + std::vector positional_args; + positional_args.resize(kwargs.size()); + for (const auto& pair : kwargs) { + XLS_VLOG(2) << " " << pair.first << " = " << pair.second; + XLS_ASSIGN_OR_RETURN(Param * param, function.GetParamByName(pair.first)); + XLS_ASSIGN_OR_RETURN(int64 param_index, function.GetParamIndex(param)); + positional_args.at(param_index) = pair.second; + } + + return positional_args; +} + +} // namespace xls diff --git a/xls/ir/keyword_args.h b/xls/ir/keyword_args.h new file mode 100644 index 0000000000..0c178fbf72 --- /dev/null +++ b/xls/ir/keyword_args.h @@ -0,0 +1,35 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_KEYWORD_ARGS_H_ +#define THIRD_PARTY_XLS_IR_KEYWORD_ARGS_H_ + +#include +#include + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/value.h" + +namespace xls { + +// Converts the given set of keyword args for the given function into a vector +// of positional arguments. +xabsl::StatusOr> KeywordArgsToPositional( + const Function& function, + const absl::flat_hash_map& kwargs); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_KEYWORD_ARGS_H_ diff --git a/xls/ir/llvm_ir_jit.cc b/xls/ir/llvm_ir_jit.cc new file mode 100644 index 0000000000..c0b16e2f78 --- /dev/null +++ b/xls/ir/llvm_ir_jit.cc @@ -0,0 +1,1228 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/llvm_ir_jit.h" + +#include +#include + +#include "absl/flags/flag.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/types/span.h" +#include "llvm-c/Target.h" +#include "llvm/Analysis/TargetLibraryInfo.h" +#include "llvm/Analysis/TargetTransformInfo.h" +#include "llvm/ExecutionEngine/ExecutionEngine.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" +#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" +#include "llvm/ExecutionEngine/Orc/IRTransformLayer.h" +#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" +#include "llvm/ExecutionEngine/Orc/Layer.h" +#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h" +#include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/CodeGen.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/IPO/PassManagerBuilder.h" +#include "xls/codegen/vast.h" +#include "xls/common/integral_types.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/logging/vlog_is_on.h" +#include "xls/common/math_util.h" +#include "xls/common/status/ret_check.h" +#include "xls/ir/dfs_visitor.h" +#include "xls/ir/keyword_args.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" +#include "xls/ir/value_helpers.h" + +namespace xls { +namespace { + +// Convenience alias for XLS type => LLVM type mapping used as a cache. +using TypeCache = absl::flat_hash_map; + +constexpr int64 kCharBit = CHAR_BIT; + +// Visitor to construct LLVM IR for each encountered XLS IR node. Based on +// DfsVisitorWithDefault to highlight any unhandled IR nodes. +class BuilderVisitor : public DfsVisitorWithDefault { + public: + // llvm_entry_function is the function being used to enter "LLVM space", not + // the entry function to the XLS Package. It's necessary to know for parameter + // handling (whether or not we handle params as normal LLVM values or values + // read from an input char buffer). + explicit BuilderVisitor(llvm::Module* module, llvm::IRBuilder<>* builder, + absl::Span params, + absl::optional llvm_entry_function, + LlvmTypeConverter* type_converter) + : module_(module), + context_(&module_->getContext()), + builder_(builder), + return_value_(nullptr), + type_converter_(type_converter), + llvm_entry_function_(llvm_entry_function) { + for (int i = 0; i < params.size(); ++i) { + int64 start = i == 0 ? 0 : arg_indices_[i - 1].second + 1; + int64 end = + start + type_converter->GetTypeByteSize(*params[i]->GetType()) - 1; + arg_indices_.push_back({start, end}); + } + } + + absl::Status DefaultHandler(Node* node) override { + return absl::UnimplementedError( + absl::StrCat("Unhandled node: ", node->ToString())); + } + + absl::Status HandleAdd(BinOp* binop) override { return HandleBinOp(binop); } + + absl::Status HandleAndReduce(BitwiseReductionOp* op) override { + // AND-reduce is equivalent to checking if every bit is set in the input. + llvm::Value* operand = node_map_.at(op->operand(0)); + llvm::IntegerType* operand_type = + llvm::cast(operand->getType()); + llvm::Value* eq = builder_->CreateICmpEQ( + operand, llvm::ConstantInt::get(operand_type, operand_type->getMask())); + return StoreResult(op, eq); + } + + absl::Status HandleArray(Array* array) override { + llvm::Type* array_type = + type_converter_->ConvertToLlvmType(*array->GetType()); + + llvm::Value* result = CreateTypedZeroValue(array_type); + for (uint32 i = 0; i < array->size(); ++i) { + result = builder_->CreateInsertValue( + result, node_map_.at(array->operand(i)), {i}); + } + + return StoreResult(array, result); + } + + absl::Status HandleArrayIndex(ArrayIndex* index) override { + // Get the pointer to the element of interest, then load it. Easy peasy. + llvm::Value* array = node_map_.at(index->operand(0)); + llvm::Value* index_value = node_map_.at(index->operand(1)); + int index_width = index_value->getType()->getIntegerBitWidth(); + + // Our IR does not use negative indices, so we add a + // zero MSb to prevent LLVM from interpreting this as such. + std::vector gep_indices = { + llvm::ConstantInt::get(llvm::Type::getInt64Ty(*context_), 0), + builder_->CreateZExt( + index_value, llvm::IntegerType::get(*context_, index_width + 1))}; + + // Ideally, we'd use IRBuilder::CreateExtractValue here, but that requires + // constant indices. Since there's no other way to extract a value from an + // aggregate, we're left with storing the value in a temporary alloca and + // using that pointer to extract the value. + llvm::AllocaInst* alloca; + if (!array_storage_.contains(array)) { + alloca = builder_->CreateAlloca(array->getType()); + builder_->CreateStore(array, alloca); + array_storage_[array] = alloca; + } else { + alloca = array_storage_[array]; + } + + llvm::Value* gep = builder_->CreateGEP(alloca, gep_indices); + return StoreResult(index, builder_->CreateLoad(gep)); + } + + absl::Status HandleBitSlice(BitSlice* bit_slice) override { + llvm::Value* value = node_map_.at(bit_slice->operand(0)); + Value shift_amount( + UBits(bit_slice->start(), value->getType()->getIntegerBitWidth())); + XLS_ASSIGN_OR_RETURN( + llvm::Constant * start, + type_converter_->ToLlvmConstant(value->getType(), shift_amount)); + + // Then shift and "mask" (by casting) the input value. + llvm::Value* shifted_value = builder_->CreateLShr(value, start); + llvm::Value* truncated_value = builder_->CreateTrunc( + shifted_value, llvm::IntegerType::get(*context_, bit_slice->width())); + return StoreResult(bit_slice, truncated_value); + } + + absl::Status HandleConcat(Concat* concat) override { + llvm::Type* dest_type = + type_converter_->ConvertToLlvmType(*concat->GetType()); + llvm::Value* base = llvm::ConstantInt::get(dest_type, 0); + + int current_shift = dest_type->getIntegerBitWidth(); + for (const Node* xls_operand : concat->operands()) { + // Widen each operand to the full size, shift to the right location, and + // bitwise or into the result value. + int64 operand_width = xls_operand->BitCountOrDie(); + llvm::Value* operand = node_map_.at(xls_operand); + operand = builder_->CreateZExt(operand, dest_type); + llvm::Value* shifted_operand = + builder_->CreateShl(operand, current_shift - operand_width); + base = builder_->CreateOr(base, shifted_operand); + + current_shift -= operand_width; + } + + return StoreResult(concat, base); + } + + absl::Status HandleCountedFor(CountedFor* counted_for) override { + XLS_ASSIGN_OR_RETURN(llvm::Function * function, + GetModuleFunction(counted_for->body())); + // One for the loop carry and one for the index. + std::vector args(counted_for->invariant_args().size() + 2); + for (int i = 0; i < counted_for->invariant_args().size(); i++) { + args[i + 2] = node_map_.at(counted_for->invariant_args()[i]); + } + args[1] = node_map_.at(counted_for->initial_value()); + + llvm::Type* function_type = function->getType()->getPointerElementType(); + for (int i = 0; i < counted_for->trip_count(); ++i) { + args[0] = llvm::ConstantInt::get(function_type->getFunctionParamType(0), + i * counted_for->stride()); + args[1] = builder_->CreateCall(function, {args}); + } + + return StoreResult(counted_for, args[1]); + } + + absl::Status HandleDecode(Decode* decode) override { + llvm::Value* input = node_map_.at(decode->operand(0)); + llvm::Type* result_type = + llvm::IntegerType::get(*context_, decode->width()); + // If the input value is greater than this op's width, then return 0. + // In that case, the shl will produce a poison value, but it'll be unused. + llvm::Value* cast_input = builder_->CreateZExt(input, result_type); + llvm::Value* overflow = builder_->CreateICmpUGE( + cast_input, llvm::ConstantInt::get(result_type, decode->width())); + llvm::Value* result = builder_->CreateSelect( + overflow, llvm::ConstantInt::get(result_type, 0), + builder_->CreateShl(llvm::ConstantInt::get(result_type, 1), + cast_input)); + + return StoreResult(decode, result); + } + + absl::Status HandleEncode(Encode* encode) override { + llvm::Value* input = node_map_.at(encode->operand(0)); + llvm::Type* input_type = input->getType(); + llvm::Value* input_one = llvm::ConstantInt::get(input_type, 1); + + llvm::Type* result_type = + type_converter_->ConvertToLlvmType(*encode->GetType()); + llvm::Value* result = llvm::ConstantInt::get(result_type, 0); + + llvm::Value* result_zero = llvm::ConstantInt::get(result_type, 0); + + // For each bit in the input, if it's set, bitwise-OR its [numeric] value + // with the result. + for (int i = 0; i < input_type->getIntegerBitWidth(); ++i) { + llvm::Value* bit_set = builder_->CreateICmpEQ( + builder_->CreateAnd(input, input_one), input_one); + + // Chained select, i.e., a = (b ? c : (d ? e : (...))), etc. + llvm::Value* or_value = builder_->CreateSelect( + bit_set, llvm::ConstantInt::get(result_type, i), result_zero); + result = builder_->CreateOr(result, or_value); + + input = builder_->CreateLShr(input, input_one); + } + + return StoreResult(encode, result); + } + + absl::Status HandleEq(CompareOp* eq) override { + llvm::Value* lhs = node_map_.at(eq->operand(0)); + llvm::Value* rhs = node_map_.at(eq->operand(1)); + llvm::Value* result = builder_->CreateICmpEQ(lhs, rhs); + return StoreResult(eq, result); + } + + absl::Status HandleIdentity(UnOp* identity) override { + return StoreResult(identity, node_map_.at(identity->operand(0))); + } + + absl::Status HandleInvoke(Invoke* invoke) override { + XLS_ASSIGN_OR_RETURN(llvm::Function * function, + GetModuleFunction(invoke->to_apply())); + + std::vector args(invoke->operand_count()); + for (int i = 0; i < invoke->operand_count(); i++) { + args[i] = node_map_[invoke->operand(i)]; + } + + llvm::Value* invoke_inst = builder_->CreateCall(function, args); + return StoreResult(invoke, invoke_inst); + } + + absl::Status HandleLiteral(Literal* literal) override { + Type* xls_type = literal->GetType(); + XLS_ASSIGN_OR_RETURN( + llvm::Value * llvm_literal, + type_converter_->ToLlvmConstant(*xls_type, literal->value())); + + return StoreResult(literal, llvm_literal); + } + + absl::Status HandleMap(Map* map) override { + XLS_ASSIGN_OR_RETURN(llvm::Function * to_apply, + GetModuleFunction(map->to_apply())); + + llvm::Value* input = node_map_.at(map->operand(0)); + llvm::Type* input_type = input->getType(); + llvm::FunctionType* function_type = llvm::cast( + to_apply->getType()->getPointerElementType()); + + llvm::Value* result = CreateTypedZeroValue(llvm::ArrayType::get( + function_type->getReturnType(), input_type->getArrayNumElements())); + + for (uint32 i = 0; i < input_type->getArrayNumElements(); ++i) { + llvm::Value* iter_input = builder_->CreateExtractValue(input, {i}); + llvm::Value* iter_result = builder_->CreateCall(to_apply, iter_input); + result = builder_->CreateInsertValue(result, iter_result, {i}); + } + + return StoreResult(map, result); + } + + absl::Status HandleSMul(ArithOp* mul) override { return HandleArithOp(mul); } + + absl::Status HandleUMul(ArithOp* mul) override { return HandleArithOp(mul); } + + absl::Status HandleNaryAnd(NaryOp* and_op) override { + llvm::Value* result = node_map_.at((and_op->operand(0))); + for (int i = 1; i < and_op->operand_count(); ++i) { + result = builder_->CreateAnd(result, node_map_.at(and_op->operand(i))); + } + return StoreResult(and_op, result); + } + + absl::Status HandleNaryNand(NaryOp* nand_op) override { + llvm::Value* result = node_map_.at((nand_op->operand(0))); + for (int i = 1; i < nand_op->operand_count(); ++i) { + result = builder_->CreateAnd(result, node_map_.at(nand_op->operand(i))); + } + result = builder_->CreateNot(result); + return StoreResult(nand_op, result); + } + + absl::Status HandleNaryNor(NaryOp* nor_op) override { + llvm::Value* result = node_map_.at((nor_op->operand(0))); + for (int i = 1; i < nor_op->operand_count(); ++i) { + result = builder_->CreateOr(result, node_map_.at(nor_op->operand(i))); + } + result = builder_->CreateNot(result); + return StoreResult(nor_op, result); + } + + absl::Status HandleNaryOr(NaryOp* or_op) override { + llvm::Value* result = node_map_.at((or_op->operand(0))); + for (int i = 1; i < or_op->operand_count(); ++i) { + result = builder_->CreateOr(result, node_map_.at(or_op->operand(i))); + } + return StoreResult(or_op, result); + } + + absl::Status HandleNaryXor(NaryOp* xor_op) override { + llvm::Value* result = node_map_.at((xor_op->operand(0))); + for (int i = 1; i < xor_op->operand_count(); ++i) { + result = builder_->CreateXor(result, node_map_.at(xor_op->operand(i))); + } + return StoreResult(xor_op, result); + } + + absl::Status HandleNe(CompareOp* ne) override { + llvm::Value* lhs = node_map_.at(ne->operand(0)); + llvm::Value* rhs = node_map_.at(ne->operand(1)); + llvm::Value* result = builder_->CreateICmpNE(lhs, rhs); + return StoreResult(ne, result); + } + + absl::Status HandleNeg(UnOp* neg) override { + llvm::Value* llvm_neg = builder_->CreateNeg(node_map_.at(neg->operand(0))); + return StoreResult(neg, llvm_neg); + } + + absl::Status HandleNot(UnOp* not_op) override { + llvm::Value* llvm_not = + builder_->CreateNot(node_map_.at(not_op->operand(0))); + return StoreResult(not_op, llvm_not); + } + + absl::Status HandleOneHot(OneHot* one_hot) override { + llvm::Value* input = node_map_.at(one_hot->operand(0)); + llvm::Type* input_type = input->getType(); + int input_width = input_type->getIntegerBitWidth(); + llvm::Type* int1_type = llvm::Type::getInt1Ty(*context_); + std::vector arg_types = {input_type, int1_type}; + llvm::Value* llvm_false = llvm::ConstantInt::getFalse(int1_type); + + llvm::Value* zeroes; + if (one_hot->priority() == LsbOrMsb::kLsb) { + llvm::Function* cttz = llvm::Intrinsic::getDeclaration( + module_, llvm::Intrinsic::cttz, arg_types); + zeroes = builder_->CreateCall(cttz, {input, llvm_false}); + } else { + llvm::Function* ctlz = llvm::Intrinsic::getDeclaration( + module_, llvm::Intrinsic::ctlz, arg_types); + zeroes = builder_->CreateCall(ctlz, {input, llvm_false}); + zeroes = builder_->CreateSub( + llvm::ConstantInt::get(input_type, input_width - 1), zeroes); + } + + // If the input is zero, then return the special high-bit value. + llvm::Value* zero_value = llvm::ConstantInt::get(input_type, 0); + llvm::Value* width_value = llvm::ConstantInt::get(input_type, input_width); + llvm::Value* eq_zero = builder_->CreateICmpEQ(input, zero_value); + llvm::Value* shift_amount = + builder_->CreateSelect(eq_zero, width_value, zeroes); + + llvm::Type* result_type = input_type->getWithNewBitWidth(input_width + 1); + llvm::Value* result = + builder_->CreateShl(llvm::ConstantInt::get(result_type, 1), + builder_->CreateZExt(shift_amount, result_type)); + return StoreResult(one_hot, result); + } + + absl::Status HandleOneHotSel(OneHotSelect* sel) override { + absl::Span cases = sel->cases(); + llvm::Type* input_type = node_map_.at(cases[0])->getType(); + + llvm::Value* result; + result = CreateTypedZeroValue(input_type); + + llvm::Value* selector = node_map_.at(sel->selector()); + llvm::Value* typed_zero = CreateTypedZeroValue(input_type); + llvm::Value* llvm_one = llvm::ConstantInt::get(selector->getType(), 1); + + for (const auto* node : cases) { + // Extract the current selector bit & see if set (CreateSelect requires an + // i1 argument, or we could directly use the AND result. + llvm::Value* is_hot = builder_->CreateICmpEQ( + builder_->CreateAnd(selector, llvm_one), llvm_one); + + // OR with zero might be slower than doing an if/else construct - if + // it turns out to be performance-critical, we can update it. + llvm::Value* or_value = + builder_->CreateSelect(is_hot, node_map_.at(node), typed_zero); + result = CreateAggregateOr(result, or_value); + selector = builder_->CreateLShr(selector, llvm_one); + } + + return StoreResult(sel, result); + } + + absl::Status HandleOrReduce(BitwiseReductionOp* op) override { + // OR-reduce is equivalent to checking if any bit is set in the input. + llvm::Value* operand = node_map_.at(op->operand(0)); + llvm::Value* eq = builder_->CreateICmpNE( + operand, llvm::ConstantInt::get(operand->getType(), 0)); + return StoreResult(op, eq); + } + + absl::Status HandleParam(Param* param) override { + // If we're not processing the first function in LLVM space, this is easy - + // just return the n'th argument to the active function. + // + // If this IS that entry function, then we need to pull in data from the + // opaque arg buffer: + // 1. Find out the index of the param we're loading. + // 2. Get the offset of that param into our arg buffer. + // 3. Cast that offset/pointer into the target type and load from it. + XLS_ASSIGN_OR_RETURN(int index, param->function()->GetParamIndex(param)); + llvm::Function* llvm_function = builder_->GetInsertBlock()->getParent(); + + if (!llvm_entry_function_ || param->function() != *llvm_entry_function_) { + return StoreResult(param, llvm_function->getArg(index)); + } + + // Remember that all input args are packed into a buffer specified as a + // single formal parameter, hence the 0 constant here. + llvm::Argument* llvm_arg = llvm_function->getArg(0); + + // Get the offset of the param in the buffer. + llvm::ConstantInt* gep_index = llvm::ConstantInt::get( + llvm::Type::getInt64Ty(*context_), arg_indices_[index].first); + llvm::Value* gep = builder_->CreateGEP(llvm_arg, gep_index); + + // Convert that offset into a ParamT pointer, and load from it. + llvm::Type* arg_type = + type_converter_->ConvertToLlvmType(*param->GetType()); + llvm::Type* llvm_arg_ptr_type = + llvm::PointerType::get(arg_type, /*AddressSpace=*/0); + llvm::Value* cast = builder_->CreateBitCast(gep, llvm_arg_ptr_type); + + llvm::LoadInst* load = builder_->CreateLoad(arg_type, cast); + return StoreResult(param, load); + } + + absl::Status HandleReverse(UnOp* reverse) override { + llvm::Value* input = node_map_.at(reverse->operand(0)); + llvm::Function* reverse_fn = llvm::Intrinsic::getDeclaration( + module_, llvm::Intrinsic::bitreverse, {input->getType()}); + return StoreResult(reverse, builder_->CreateCall(reverse_fn, {input})); + } + + absl::Status HandleSDiv(BinOp* binop) override { return HandleBinOp(binop); } + + absl::Status HandleSel(Select* sel) override { + // Sel is implemented by a cascading series of select ops, e.g., + // selector == 0 ? cases[0] : selector == 1 ? cases[1] : selector == 2 ? ... + llvm::Value* selector = node_map_.at(sel->selector()); + llvm::Value* llvm_sel = + sel->default_value() ? node_map_.at(*sel->default_value()) : nullptr; + for (int i = sel->cases().size() - 1; i >= 0; i--) { + Node* node = sel->cases()[i]; + if (llvm_sel == nullptr) { + // The last element in the select tree isn't a sel, but an actual value. + llvm_sel = node_map_.at(node); + } else { + llvm::Value* index = llvm::ConstantInt::get(selector->getType(), i); + llvm::Value* cmp = builder_->CreateICmpEQ(selector, index); + llvm_sel = builder_->CreateSelect(cmp, node_map_.at(node), llvm_sel); + } + } + return StoreResult(sel, llvm_sel); + } + + absl::Status HandleSGe(CompareOp* ge) override { + llvm::Value* lhs = node_map_.at(ge->operand(0)); + llvm::Value* rhs = node_map_.at(ge->operand(1)); + llvm::Value* result = builder_->CreateICmpSGE(lhs, rhs); + return StoreResult(ge, result); + } + + absl::Status HandleSGt(CompareOp* gt) override { + llvm::Value* lhs = node_map_.at(gt->operand(0)); + llvm::Value* rhs = node_map_.at(gt->operand(1)); + llvm::Value* result = builder_->CreateICmpSGT(lhs, rhs); + return StoreResult(gt, result); + } + + absl::Status HandleSignExtend(ExtendOp* sign_ext) override { + llvm::Type* new_type = + llvm::IntegerType::get(*context_, sign_ext->new_bit_count()); + return StoreResult( + sign_ext, + builder_->CreateSExt(node_map_.at(sign_ext->operand(0)), new_type)); + } + + absl::Status HandleSLe(CompareOp* le) override { + llvm::Value* lhs = node_map_.at(le->operand(0)); + llvm::Value* rhs = node_map_.at(le->operand(1)); + llvm::Value* result = builder_->CreateICmpSLE(lhs, rhs); + return StoreResult(le, result); + } + + absl::Status HandleSLt(CompareOp* lt) override { + llvm::Value* lhs = node_map_.at(lt->operand(0)); + llvm::Value* rhs = node_map_.at(lt->operand(1)); + llvm::Value* result = builder_->CreateICmpSLT(lhs, rhs); + return StoreResult(lt, result); + } + + absl::Status HandleShll(BinOp* binop) override { return HandleBinOp(binop); } + + absl::Status HandleShra(BinOp* binop) override { return HandleBinOp(binop); } + + absl::Status HandleShrl(BinOp* binop) override { return HandleBinOp(binop); } + + absl::Status HandleSub(BinOp* binop) override { return HandleBinOp(binop); } + + absl::Status HandleTuple(Tuple* tuple) override { + llvm::Type* tuple_type = + type_converter_->ConvertToLlvmType(*tuple->GetType()); + + llvm::Value* result = CreateTypedZeroValue(tuple_type); + for (uint32 i = 0; i < tuple->operand_count(); ++i) { + result = builder_->CreateInsertValue( + result, node_map_.at(tuple->operand(i)), {i}); + } + + return StoreResult(tuple, result); + } + + absl::Status HandleTupleIndex(TupleIndex* index) override { + llvm::Value* value = builder_->CreateExtractValue( + node_map_.at(index->operand(0)), index->index()); + return StoreResult(index, value); + } + + absl::Status HandleUDiv(BinOp* binop) override { return HandleBinOp(binop); } + + absl::Status HandleUGe(CompareOp* ge) override { + llvm::Value* lhs = node_map_.at(ge->operand(0)); + llvm::Value* rhs = node_map_.at(ge->operand(1)); + llvm::Value* result = builder_->CreateICmpUGE(lhs, rhs); + return StoreResult(ge, result); + } + + absl::Status HandleUGt(CompareOp* gt) override { + llvm::Value* lhs = node_map_.at(gt->operand(0)); + llvm::Value* rhs = node_map_.at(gt->operand(1)); + llvm::Value* result = builder_->CreateICmpUGT(lhs, rhs); + return StoreResult(gt, result); + } + + absl::Status HandleULe(CompareOp* le) override { + llvm::Value* lhs = node_map_.at(le->operand(0)); + llvm::Value* rhs = node_map_.at(le->operand(1)); + llvm::Value* result = builder_->CreateICmpULE(lhs, rhs); + return StoreResult(le, result); + } + + absl::Status HandleULt(CompareOp* lt) override { + llvm::Value* lhs = node_map_.at(lt->operand(0)); + llvm::Value* rhs = node_map_.at(lt->operand(1)); + llvm::Value* result = builder_->CreateICmpULT(lhs, rhs); + return StoreResult(lt, result); + } + + absl::Status HandleXorReduce(BitwiseReductionOp* op) override { + // XOR-reduce is equivalent to checking if the number of set bits is odd. + llvm::Value* operand = node_map_.at(op->operand(0)); + llvm::Function* ctpop = llvm::Intrinsic::getDeclaration( + module_, llvm::Intrinsic::ctpop, {operand->getType()}); + llvm::Value* pop_count = builder_->CreateCall(ctpop, {operand}); + + // Once we have the pop count, truncate to the first (i.e., "is odd") bit. + llvm::Value* truncated_value = + builder_->CreateTrunc(pop_count, llvm::IntegerType::get(*context_, 1)); + return StoreResult(op, truncated_value); + } + + absl::Status HandleZeroExtend(ExtendOp* zero_ext) override { + llvm::Value* base = node_map_.at(zero_ext->operand(0)); + llvm::Type* dest_type = + base->getType()->getWithNewBitWidth(zero_ext->new_bit_count()); + llvm::Value* zext = + builder_->CreateZExt(node_map_.at(zero_ext->operand(0)), dest_type); + return StoreResult(zero_ext, zext); + } + + llvm::Value* return_value() { return return_value_; } + + private: + absl::Status HandleArithOp(ArithOp* arith_op) { + bool is_signed; + switch (arith_op->op()) { + case Op::kSMul: + is_signed = true; + break; + case Op::kUMul: + is_signed = false; + break; + default: + return absl::InvalidArgumentError(absl::StrCat( + "Unsupported arithmetic op:", OpToString(arith_op->op()))); + } + llvm::Type* result_type = + type_converter_->ConvertToLlvmType(*arith_op->GetType()); + llvm::Value* lhs = builder_->CreateIntCast( + node_map_.at(arith_op->operands()[0]), result_type, is_signed); + llvm::Value* rhs = builder_->CreateIntCast( + node_map_.at(arith_op->operands()[1]), result_type, is_signed); + + llvm::Value* result; + switch (arith_op->op()) { + case Op::kUMul: + case Op::kSMul: + result = builder_->CreateMul(lhs, rhs); + break; + default: + return absl::InvalidArgumentError(absl::StrCat( + "Unsupported arithmetic op:", OpToString(arith_op->op()))); + } + return StoreResult(arith_op, result); + } + + absl::Status HandleBinOp(BinOp* binop) { + if (binop->operand_count() != 2) { + return absl::InvalidArgumentError( + absl::StrFormat("Expected 2 args to a binary op; instead got %d", + binop->operand_count())); + } + + llvm::Value* lhs = node_map_.at(binop->operands()[0]); + llvm::Value* rhs = node_map_.at(binop->operands()[1]); + llvm::Value* result; + switch (binop->op()) { + case Op::kAdd: + result = builder_->CreateAdd(lhs, rhs); + break; + case Op::kShll: + case Op::kShra: + case Op::kShrl: + result = EmitShiftOp(binop->op(), lhs, rhs); + break; + case Op::kSub: + result = builder_->CreateSub(lhs, rhs); + break; + case Op::kUDiv: + result = EmitDiv(lhs, rhs, /*is_signed=*/false); + break; + case Op::kSDiv: + result = EmitDiv(lhs, rhs, /*is_signed=*/true); + break; + default: + return absl::UnimplementedError( + absl::StrFormat("Unsupported/unimplemented bin op: %d", + static_cast(binop->op()))); + } + + return StoreResult(binop, result); + } + + llvm::Value* EmitShiftOp(Op op, llvm::Value* lhs, llvm::Value* rhs) { + // Shift operands are allowed to be different sizes in the [XLS] IR, so + // we need to cast them to be the same size here (for LLVM). + int common_width = std::max(lhs->getType()->getIntegerBitWidth(), + rhs->getType()->getIntegerBitWidth()); + llvm::Type* dest_type = llvm::IntegerType::get(*context_, common_width); + lhs = builder_->CreateZExt(lhs, dest_type); + rhs = builder_->CreateZExt(rhs, dest_type); + // In LLVM, shift overflow creates poison. In XLS, it creates zero. + llvm::Value* overflows = builder_->CreateICmpUGE( + rhs, llvm::ConstantInt::get(dest_type, common_width)); + + llvm::Value* inst; + llvm::Value* zero = llvm::ConstantInt::get(dest_type, 0); + llvm::Value* overflow_value = zero; + if (op == Op::kShll) { + inst = builder_->CreateShl(lhs, rhs); + } else if (op == Op::kShra) { + llvm::Value* high_bit = builder_->CreateLShr( + lhs, llvm::ConstantInt::get( + dest_type, lhs->getType()->getIntegerBitWidth() - 1)); + llvm::Value* high_bit_set = builder_->CreateICmpEQ( + high_bit, llvm::ConstantInt::get(dest_type, 1)); + overflow_value = builder_->CreateSelect( + high_bit_set, llvm::ConstantInt::getSigned(dest_type, -1), zero); + inst = builder_->CreateAShr(lhs, rhs); + } else { + inst = builder_->CreateLShr(lhs, rhs); + } + return builder_->CreateSelect(overflows, overflow_value, inst); + } + + llvm::Value* EmitDiv(llvm::Value* lhs, llvm::Value* rhs, bool is_signed) { + // XLS div semantics differ from LLVM's (and most software's) here: in XLS, + // division by zero returns the greatest value of that type, so 255 for an + // unsigned byte, and either -128 or 127 for a signed one. + // Thus, a little more work is necessary to emit LLVM IR matching the XLS + // div op than just IRBuilder::Create[SU]Div(). + int type_width = rhs->getType()->getIntegerBitWidth(); + llvm::Value* zero = llvm::ConstantInt::get(rhs->getType(), 0); + llvm::Value* rhs_eq_zero = builder_->CreateICmpEQ(rhs, zero); + llvm::Value* lhs_gt_zero = builder_->CreateICmpSGT(lhs, zero); + + // If rhs is zero, make LHS = the max/min value and the RHS 1, + // rather than introducing a proper conditional. + rhs = builder_->CreateSelect( + rhs_eq_zero, llvm::ConstantInt::get(rhs->getType(), 1), rhs); + if (is_signed) { + llvm::Value* max_value = + type_converter_ + ->ToLlvmConstant(rhs->getType(), + Value(Bits::MaxSigned(type_width))) + .value(); + llvm::Value* min_value = + type_converter_ + ->ToLlvmConstant(rhs->getType(), + Value(Bits::MinSigned(type_width))) + .value(); + + lhs = builder_->CreateSelect( + rhs_eq_zero, + builder_->CreateSelect(lhs_gt_zero, max_value, min_value), lhs); + return builder_->CreateSDiv(lhs, rhs); + } + + lhs = builder_->CreateSelect( + rhs_eq_zero, + type_converter_ + ->ToLlvmConstant(rhs->getType(), Value(Bits::AllOnes(type_width))) + .value(), + lhs); + return builder_->CreateUDiv(lhs, rhs); + } + + llvm::Constant* CreateTypedZeroValue(llvm::Type* type) { + if (type->isIntegerTy()) { + return llvm::ConstantInt::get(type, 0); + } else if (type->isArrayTy()) { + std::vector elements( + type->getArrayNumElements(), + CreateTypedZeroValue(type->getArrayElementType())); + return llvm::ConstantArray::get(llvm::cast(type), + elements); + } + + // Must be a tuple/struct, then. + std::vector elements(type->getStructNumElements()); + for (int i = 0; i < type->getStructNumElements(); ++i) { + elements[i] = CreateTypedZeroValue(type->getStructElementType(i)); + } + + return llvm::ConstantStruct::get(llvm::cast(type), + elements); + } + + llvm::Value* CreateAggregateOr(llvm::Value* lhs, llvm::Value* rhs) { + llvm::Type* arg_type = lhs->getType(); + if (arg_type->isIntegerTy()) { + return builder_->CreateOr(lhs, rhs); + } + + llvm::Value* result = CreateTypedZeroValue(arg_type); + int num_elements = arg_type->isArrayTy() ? arg_type->getArrayNumElements() + : arg_type->getNumContainedTypes(); + for (uint32 i = 0; i < num_elements; ++i) { + llvm::Value* iter_result = + CreateAggregateOr(builder_->CreateExtractValue(lhs, {i}), + builder_->CreateExtractValue(rhs, {i})); + result = builder_->CreateInsertValue(result, iter_result, {i}); + } + + return result; + } + + xabsl::StatusOr ConvertToLlvmConstant(Type* type, + const Value& value) { + if (type->IsBits()) { + return type_converter_->ToLlvmConstant( + type_converter_->ConvertToLlvmType(*type), value); + } else if (type->IsTuple()) { + TupleType* tuple_type = type->AsTupleOrDie(); + std::vector llvm_elements; + for (int i = 0; i < tuple_type->size(); ++i) { + XLS_ASSIGN_OR_RETURN( + llvm::Constant * llvm_element, + type_converter_->ToLlvmConstant(*tuple_type->element_type(i), + value.element(i))); + llvm_elements.push_back(llvm_element); + } + + llvm::Type* llvm_type = type_converter_->ConvertToLlvmType(*type); + return llvm::ConstantStruct::get(llvm::cast(llvm_type), + llvm_elements); + } else if (type->IsArray()) { + const ArrayType* array_type = type->AsArrayOrDie(); + std::vector elements; + for (const Value& element : value.elements()) { + XLS_ASSIGN_OR_RETURN(llvm::Constant * llvm_element, + type_converter_->ToLlvmConstant( + *array_type->element_type(), element)); + elements.push_back(llvm_element); + } + + llvm::Type* element_type = + type_converter_->ConvertToLlvmType(*array_type->element_type()); + return llvm::ConstantArray::get( + llvm::ArrayType::get(element_type, array_type->size()), elements); + } + + XLS_LOG(FATAL) << "Unknown value kind: " << value.kind(); + } + + xabsl::StatusOr GetModuleFunction(Function* xls_function) { + // If we've not processed this function yet, then do so. + llvm::Function* found_function = module_->getFunction(xls_function->name()); + if (found_function != nullptr) { + return found_function; + } + + // There are a couple of differences between this and entry function + // visitor initialization such that I think it makes slightly more sense + // to not factor it into a common block, but it's not clear-cut. + std::vector param_types(xls_function->params().size()); + for (int i = 0; i < xls_function->params().size(); ++i) { + param_types[i] = type_converter_->ConvertToLlvmType( + *xls_function->param(i)->GetType()); + } + + Type* return_type = xls_function->return_value()->GetType(); + llvm::Type* llvm_return_type = + type_converter_->ConvertToLlvmType(*return_type); + + llvm::FunctionType* function_type = llvm::FunctionType::get( + llvm_return_type, + llvm::ArrayRef(param_types.data(), param_types.size()), + /*isVarArg=*/false); + llvm::Function* function = llvm::cast( + module_ + ->getOrInsertFunction(xls_function->qualified_name(), function_type) + .getCallee()); + + llvm::BasicBlock* block = + llvm::BasicBlock::Create(*context_, xls_function->qualified_name(), + function, /*InsertBefore=*/nullptr); + llvm::IRBuilder<> builder(block); + BuilderVisitor visitor(module_, &builder, {}, absl::nullopt, + type_converter_); + XLS_RETURN_IF_ERROR(xls_function->Accept(&visitor)); + if (function_type->getReturnType()->isVoidTy()) { + builder.CreateRetVoid(); + } else { + builder.CreateRet(visitor.return_value()); + } + + return function; + } + + absl::Status StoreResult(Node* node, llvm::Value* value) { + XLS_RET_CHECK(!node_map_.contains(node)); + value->setName(verilog::SanitizeIdentifier(node->GetName())); + if (node->function()->return_value() == node) { + return_value_ = value; + } + node_map_[node] = value; + + return absl::OkStatus(); + } + + llvm::Module* module_; + llvm::LLVMContext* context_; + llvm::IRBuilder<>* builder_; + + // List of start/end (inclusive) byte offset pairs for each argument to this + // visitor's function. + std::vector> arg_indices_; + + // The last value constructed during this traversal - represents the return + // from calculation. + llvm::Value* return_value_; + + // Maps an XLS Node to the resulting LLVM Value. + absl::flat_hash_map node_map_; + + // Holds storage for array indexing ops. Since we need to dump arrays to + // storage to extract elements (i.e., for GEPs), it makes sense to only create + // and store the array once. + absl::flat_hash_map array_storage_; + + LlvmTypeConverter* type_converter_; + + // The entry point into LLVM space - the function specified in the constructor + // to the top-level LlvmIrJit object. + absl::optional llvm_entry_function_; +}; + +absl::once_flag once; +void OnceInit() { + LLVMInitializeNativeTarget(); + LLVMInitializeNativeAsmPrinter(); + LLVMInitializeNativeAsmParser(); +} + +} // namespace + +xabsl::StatusOr> LlvmIrJit::Create( + Function* xls_function, int64 opt_level) { + absl::call_once(once, OnceInit); + + auto jit = absl::WrapUnique(new LlvmIrJit(xls_function, opt_level)); + XLS_RETURN_IF_ERROR(jit->Init()); + XLS_RETURN_IF_ERROR(jit->CompileFunction()); + return jit; +} + +LlvmIrJit::LlvmIrJit(Function* xls_function, int64 opt_level) + : context_(std::make_unique()), + object_layer_( + execution_session_, + []() { return std::make_unique(); }), + dylib_(execution_session_.createBareJITDylib("main")), + data_layout_(""), + xls_function_(xls_function), + xls_function_type_(xls_function_->GetType()), + opt_level_(opt_level), + invoker_(nullptr) {} + +llvm::Expected LlvmIrJit::Optimizer( + llvm::orc::ThreadSafeModule module, + const llvm::orc::MaterializationResponsibility& responsibility) { + llvm::Module* bare_module = module.getModuleUnlocked(); + + XLS_VLOG(2) << "Unoptimized module IR:"; + XLS_VLOG(2).NoPrefix() << ir_runtime_->DumpToString(*bare_module); + + llvm::TargetLibraryInfoImpl library_info(target_machine_->getTargetTriple()); + llvm::PassManagerBuilder builder; + builder.OptLevel = opt_level_; + builder.LibraryInfo = + new llvm::TargetLibraryInfoImpl(target_machine_->getTargetTriple()); + + llvm::legacy::PassManager module_pass_manager; + builder.populateModulePassManager(module_pass_manager); + module_pass_manager.add(llvm::createTargetTransformInfoWrapperPass( + target_machine_->getTargetIRAnalysis())); + + llvm::legacy::FunctionPassManager function_pass_manager(bare_module); + builder.populateFunctionPassManager(function_pass_manager); + function_pass_manager.doInitialization(); + for (auto& function : *bare_module) { + function_pass_manager.run(function); + } + function_pass_manager.doFinalization(); + + bool dump_asm = false; + llvm::SmallVector stream_buffer; + llvm::raw_svector_ostream ostream(stream_buffer); + if (XLS_VLOG_IS_ON(3)) { + dump_asm = true; + if (target_machine_->addPassesToEmitFile( + module_pass_manager, ostream, nullptr, llvm::CGFT_AssemblyFile)) { + XLS_VLOG(3) << "Could not create ASM generation pass!"; + dump_asm = false; + } + } + + module_pass_manager.run(*bare_module); + + XLS_VLOG(2) << "Optimized module IR:"; + XLS_VLOG(2).NoPrefix() << ir_runtime_->DumpToString(*bare_module); + + if (dump_asm) { + XLS_VLOG(3) << "Generated ASM:"; + XLS_VLOG_LINES(3, std::string(stream_buffer.begin(), stream_buffer.end())); + } + return module; +} + +absl::Status LlvmIrJit::Init() { + auto error_or_target_builder = + llvm::orc::JITTargetMachineBuilder::detectHost(); + if (!error_or_target_builder) { + return absl::InternalError( + absl::StrCat("Unable to detect host: ", + llvm::toString(error_or_target_builder.takeError()))); + } + + auto error_or_target_machine = error_or_target_builder->createTargetMachine(); + if (!error_or_target_machine) { + return absl::InternalError( + absl::StrCat("Unable to create target machine: ", + llvm::toString(error_or_target_machine.takeError()))); + } + target_machine_ = std::move(error_or_target_machine.get()); + data_layout_ = target_machine_->createDataLayout(); + + execution_session_.runSessionLocked([this]() { + dylib_.addGenerator( + cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + data_layout_.getGlobalPrefix()))); + }); + + auto compiler = std::make_unique(*target_machine_); + compile_layer_ = std::make_unique( + execution_session_, object_layer_, std::move(compiler)); + + transform_layer_ = std::make_unique( + execution_session_, *compile_layer_, + [this](llvm::orc::ThreadSafeModule module, + const llvm::orc::MaterializationResponsibility& responsibility) { + return Optimizer(std::move(module), responsibility); + }); + + return absl::OkStatus(); +} + +absl::Status LlvmIrJit::CompileFunction() { + llvm::LLVMContext* bare_context = context_.getContext(); + auto module = std::make_unique("the_module", *bare_context); + module->setDataLayout(data_layout_); + type_converter_ = + std::make_unique(bare_context, data_layout_); + + // To return values > 64b in size, we need to copy them into a result buffer, + // instead of returning a fixed-size result element. + // To do this, we need to construct the function type, adding a result buffer + // arg (and setting the result type to void) and then storing the computation + // result therein. + std::vector param_types; + llvm::FunctionType* function_type; + // Create a dummy param to hold a packed representation of the input args. + param_types.push_back(llvm::PointerType::get( + llvm::Type::getInt8Ty(*bare_context), /*AddressSpace=*/0)); + + // Since we only pass around concrete values (i.e., not functions), we can + // use the flat byte count of the XLS type to size our result array. + Type* return_type = xls_function_type_->return_type(); + llvm::Type* llvm_return_type = + type_converter_->ConvertToLlvmType(*return_type); + param_types.push_back( + llvm::PointerType::get(llvm_return_type, /*AddressSpace=*/0)); + function_type = llvm::FunctionType::get( + llvm::Type::getVoidTy(*bare_context), + llvm::ArrayRef(param_types.data(), param_types.size()), + /*isVarArg=*/false); + + Package* xls_package = xls_function_->package(); + std::string function_name = + absl::StrFormat("%s::%s", xls_package->name(), xls_function_->name()); + llvm::Function* llvm_function = llvm::cast( + module->getOrInsertFunction(function_name, function_type).getCallee()); + auto basic_block = llvm::BasicBlock::Create( + *bare_context, "so_basic", llvm_function, /*InsertBefore=*/nullptr); + + llvm::IRBuilder<> builder(basic_block); + BuilderVisitor visitor(module.get(), &builder, xls_function_->params(), + xls_function_, type_converter_.get()); + XLS_RETURN_IF_ERROR(xls_function_->Accept(&visitor)); + llvm::Value* return_value = visitor.return_value(); + if (return_value == nullptr) { + return absl::InvalidArgumentError( + "Function had no (or an unsupported) return value specification!"); + } + + // Store the result to the output pointer. + return_type_bytes_ = type_converter_->GetTypeByteSize(*return_type); + if (return_value->getType()->isPointerTy()) { + llvm::Type* pointee_type = return_value->getType()->getPointerElementType(); + if (pointee_type != llvm_return_type) { + std::string output; + llvm::raw_string_ostream stream(output); + stream << "Produced return type does not match intended: produced: "; + pointee_type->print(stream, /*IsForDebug=*/true); + stream << ", expected: "; + llvm_return_type->print(stream, /*IsForDebug=*/true); + return absl::InternalError(stream.str()); + } + + builder.CreateMemCpy(llvm_function->getArg(llvm_function->arg_size() - 1), + llvm::MaybeAlign(0), return_value, llvm::MaybeAlign(0), + return_type_bytes_); + } else { + builder.CreateStore(return_value, + llvm_function->getArg(llvm_function->arg_size() - 1)); + } + builder.CreateRetVoid(); + + llvm::Error error = transform_layer_->add( + dylib_, llvm::orc::ThreadSafeModule(std::move(module), context_)); + if (error) { + return absl::UnknownError(absl::StrFormat( + "Error compiling converted IR: %s", llvm::toString(std::move(error)))); + } + + llvm::Expected symbol = + execution_session_.lookup(&dylib_, function_name); + if (!symbol) { + return absl::InternalError( + absl::StrFormat("Could not find start symbol \"%s\": %s", function_name, + llvm::toString(symbol.takeError()))); + } + + llvm::JITTargetAddress invoker = symbol->getAddress(); + invoker_ = + reinterpret_cast(invoker); + + // Precompute the offsets at which the arguments get packed, and what size of + // buffer the arguments get. + arg_sizes_.reserve(xls_function_type_->parameter_count()); + for (int64 i = 0; i < xls_function_type_->parameter_count(); ++i) { + const Type* type = xls_function_type_->parameter_type(i); + int64 arg_size = type_converter_->GetTypeByteSize(*type); + arg_sizes_.push_back(arg_size); + args_size_ += arg_sizes_.back(); + } + + ir_runtime_ = + std::make_unique(data_layout_, type_converter_.get()); + + return absl::OkStatus(); +} + +xabsl::StatusOr LlvmIrJit::Run(absl::Span args) { + absl::Span params = xls_function_->params(); + if (args.size() != params.size()) { + return absl::InvalidArgumentError( + absl::StrFormat("Arg list has the wrong size: %d vs expected %d.", + args.size(), xls_function_->params().size())); + } + + for (int i = 0; i < params.size(); i++) { + if (!ValueConformsToType(args[i], params[i]->GetType())) { + return absl::InvalidArgumentError(absl::StrFormat( + "Got argument %s for parameter %d which is not of type %s", + args[i].ToString(), i, params[i]->GetType()->ToString())); + } + } + + absl::InlinedVector packed_args; + packed_args.resize(args_size_); + XLS_RETURN_IF_ERROR( + ir_runtime_->PackArgs(args, xls_function_type_->parameters(), arg_sizes_, + args_size_, absl::MakeSpan(packed_args))); + absl::InlinedVector outputs(return_type_bytes_); + invoker_(packed_args.data(), outputs.data()); + + return ir_runtime_->UnpackBuffer(outputs.data(), + xls_function_type_->return_type()); +} + +xabsl::StatusOr LlvmIrJit::Run( + const absl::flat_hash_map& kwargs) { + XLS_ASSIGN_OR_RETURN(std::vector positional_args, + KeywordArgsToPositional(*xls_function_, kwargs)); + return Run(positional_args); +} + +absl::Status LlvmIrJit::RunToBuffer(absl::Span args, + absl::Span result_buffer) { + absl::Span params = xls_function_->params(); + if (args.size() != params.size()) { + return absl::InvalidArgumentError( + absl::StrFormat("Arg list has the wrong size: %d vs expected %d.", + args.size(), xls_function_->params().size())); + } + + for (int i = 0; i < params.size(); i++) { + if (!ValueConformsToType(args[i], params[i]->GetType())) { + return absl::InvalidArgumentError(absl::StrFormat( + "Got argument %s for parameter %d which is not of type %s", + args[i].ToString(), i, params[i]->GetType()->ToString())); + } + } + + if (result_buffer.size() < return_type_bytes_) { + return absl::InvalidArgumentError( + absl::StrCat("Result buffer too small - must be at least %d bytes!", + return_type_bytes_)); + } + + absl::InlinedVector packed_args; + packed_args.resize(args_size_); + XLS_RETURN_IF_ERROR( + ir_runtime_->PackArgs(args, xls_function_type_->parameters(), arg_sizes_, + args_size_, absl::MakeSpan(packed_args))); + invoker_(packed_args.data(), result_buffer.data()); + return absl::OkStatus(); +} + +} // namespace xls diff --git a/xls/ir/llvm_ir_jit.h b/xls/ir/llvm_ir_jit.h new file mode 100644 index 0000000000..3ac0df9c90 --- /dev/null +++ b/xls/ir/llvm_ir_jit.h @@ -0,0 +1,119 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_LLVM_IR_JIT_H_ +#define THIRD_PARTY_XLS_IR_LLVM_IR_JIT_H_ + +#include "absl/status/status.h" +#include "absl/types/span.h" +#include "llvm/ExecutionEngine/Orc/CompileUtils.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" +#include "llvm/ExecutionEngine/Orc/IRTransformLayer.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/Target/TargetMachine.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/function.h" +#include "xls/ir/llvm_ir_runtime.h" +#include "xls/ir/llvm_type_converter.h" +#include "xls/ir/package.h" +#include "xls/ir/value.h" +#include "xls/ir/value_view.h" + +namespace xls { + +// This class provides a facility to execute XLS functions (on the host) by +// converting it to LLVM IR, compiling it, and finally executing it. +class LlvmIrJit { + public: + // Returns an object containing a host-compiled version of the specified XLS + // function. + static xabsl::StatusOr> Create( + Function* xls_function, int64 opt_level = 3); + + // Executes the compiled function with the specified arguments. + xabsl::StatusOr Run(absl::Span args); + + // As above, buth with arguments as key-value pairs. + xabsl::StatusOr Run( + const absl::flat_hash_map& kwargs); + + // Executes the compiled function with the specified arguments, but does not + // unpack the result into a Value, instead populating the argument buffer with + // the computation results. The caller can then overlay a View type + // (from value_view.h) to consume the results. + // + // Argument packing and unpacking into and out of LLVM-space can consume a + // surprisingly large amount of execution time. Deferring such transformations + // (and applying views) can eliminate this overhead and still give access tor + // result data. Users needing less performance can still use the + // Value-returning methods above for code simplicity. + absl::Status RunToBuffer(absl::Span args, + absl::Span result_buffer); + + // Returns the function that the JIT executes. + Function* function() { return xls_function_; } + + // Gets the size of the compiled function's return type in bytes. + int64 GetReturnTypeSize() { return return_type_bytes_; } + + private: + explicit LlvmIrJit(Function* xls_function, int64 opt_level); + + // Performs non-trivial initialization (i.e., that which can fail). + absl::Status Init(); + + // Compiles the input function to host code. + absl::Status CompileFunction(); + + llvm::Expected Optimizer( + llvm::orc::ThreadSafeModule module, + const llvm::orc::MaterializationResponsibility& responsibility); + + llvm::orc::ThreadSafeContext context_; + llvm::orc::ExecutionSession execution_session_; + llvm::orc::RTDyldObjectLinkingLayer object_layer_; + llvm::orc::JITDylib& dylib_; + llvm::DataLayout data_layout_; + + std::unique_ptr target_machine_; + std::unique_ptr compile_layer_; + std::unique_ptr transform_layer_; + + Function* xls_function_; + FunctionType* xls_function_type_; + int64 opt_level_; + + // Size of the function's return type as flat bytes. + int64 return_type_bytes_; + // Cache for XLS type => LLVM type conversions. + absl::flat_hash_map xls_to_llvm_type_; + // Offsets for arguments to the function. + absl::InlinedVector arg_sizes_; + // Size of packed arg buffer. + int64 args_size_ = 0; + + std::unique_ptr type_converter_; + std::unique_ptr ir_runtime_; + + // When initialized, this points to the compiled output. + void (*invoker_)(uint8* inputs, uint8* outputs); +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_LLVM_IR_JIT_H_ diff --git a/xls/ir/llvm_ir_jit_test.cc b/xls/ir/llvm_ir_jit_test.cc new file mode 100644 index 0000000000..eb26515d5d --- /dev/null +++ b/xls/ir/llvm_ir_jit_test.cc @@ -0,0 +1,62 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/llvm_ir_jit.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/ir_evaluator_test.h" +#include "re2/re2.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +INSTANTIATE_TEST_SUITE_P( + LlvmIrJitTest, IrEvaluatorTest, + testing::Values(IrEvaluatorTestParam( + [](Function* function, + const std::vector& args) -> xabsl::StatusOr { + XLS_ASSIGN_OR_RETURN(auto jit, LlvmIrJit::Create(function)); + return jit->Run(args); + }, + [](Function* function, + const absl::flat_hash_map& kwargs) + -> xabsl::StatusOr { + XLS_ASSIGN_OR_RETURN(auto jit, LlvmIrJit::Create(function)); + return jit->Run(kwargs); + }))); + +// This test verifies that a compiled JIT function can be re-used. +TEST(LlvmIrJitTest, ReuseTest) { + Package package("my_package"); + std::string ir_text = R"( + fn get_identity(x: bits[8]) -> bits[8] { + ret identity.1: bits[8] = identity(x) + } + )"; + XLS_ASSERT_OK_AND_ASSIGN(Function * function, + Parser::ParseFunction(ir_text, &package)); + + XLS_ASSERT_OK_AND_ASSIGN(auto jit, LlvmIrJit::Create(function)); + EXPECT_THAT(jit->Run({Value(UBits(2, 8))}), IsOkAndHolds(Value(UBits(2, 8)))); + EXPECT_THAT(jit->Run({Value(UBits(4, 8))}), IsOkAndHolds(Value(UBits(4, 8)))); + EXPECT_THAT(jit->Run({Value(UBits(7, 8))}), IsOkAndHolds(Value(UBits(7, 8)))); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/llvm_ir_runtime.cc b/xls/ir/llvm_ir_runtime.cc new file mode 100644 index 0000000000..a78a7d6cbf --- /dev/null +++ b/xls/ir/llvm_ir_runtime.cc @@ -0,0 +1,310 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/llvm_ir_runtime.h" + +#include "absl/strings/str_format.h" +#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "xls/ir/format_preference.h" +#include "xls/ir/ir_parser.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" + +namespace xls { +namespace { + +constexpr int64 kCharBit = 8; + +} // namespace + +LlvmIrRuntime::LlvmIrRuntime(const llvm::DataLayout& data_layout, + LlvmTypeConverter* type_converter) + : data_layout_(data_layout), type_converter_(type_converter) {} + +absl::Status LlvmIrRuntime::PackArgs(absl::Span args, + absl::Span arg_types, + absl::Span arg_sizes, + int64 total_args_size, + absl::Span buffer) { + if (buffer.size() < total_args_size) { + return absl::InvalidArgumentError(absl::StrFormat( + "Input buffer is not large enough to hold all arguments: %d vs. %d", + buffer.size(), total_args_size)); + } + if (total_args_size > 0) { + for (int i = 0; i < args.size(); ++i) { + BlitValueToBuffer(args[i], *arg_types[i], buffer); + buffer.remove_prefix(arg_sizes[i]); + } + } + + return absl::OkStatus(); +} + +Value LlvmIrRuntime::UnpackBuffer(const uint8* buffer, + const Type* result_type) { + switch (result_type->kind()) { + case TypeKind::kBits: { + const BitsType* bits_type = result_type->AsBitsOrDie(); + int64 bit_count = bits_type->bit_count(); + int64 byte_count = CeilOfRatio(bit_count, kCharBit); + absl::InlinedVector data; + data.reserve(byte_count); + for (int i = 0; i < byte_count; ++i) { + data.push_back(buffer[i]); + } + + // We could copy the data out of the buffer to avoid a swap, but it's + // probably not worth the effort. + if (data_layout_.isLittleEndian()) { + ByteSwap(absl::MakeSpan(data)); + } + + return Value(Bits::FromBytes(absl::MakeSpan(data), bit_count)); + } + case TypeKind::kTuple: { + // Just as with arg packing, we need the DataLayout to tell us where each + // arg is placed in the output buffer. + const TupleType* tuple_type = result_type->AsTupleOrDie(); + llvm::Type* llvm_type = type_converter_->ConvertToLlvmType(*tuple_type); + const llvm::StructLayout* layout = + data_layout_.getStructLayout(llvm::cast(llvm_type)); + + std::vector values; + values.reserve(tuple_type->size()); + for (int i = 0; i < tuple_type->size(); ++i) { + Value value = UnpackBuffer(buffer + layout->getElementOffset(i), + tuple_type->element_type(i)); + values.push_back(value); + } + return Value::TupleOwned(std::move(values)); + } + case TypeKind::kArray: { + const ArrayType* array_type = result_type->AsArrayOrDie(); + if (array_type->size() == 0) { + return Value::ArrayOrDie({}); + } + + const Type* element_type = array_type->element_type(); + llvm::Type* llvm_element_type = + type_converter_->ConvertToLlvmType(*array_type->element_type()); + std::vector values; + values.reserve(array_type->size()); + for (int i = 0; i < array_type->size(); ++i) { + llvm::Constant* index = + type_converter_->ToLlvmConstant(BitsType(64), Value(UBits(i, 64))) + .value(); + int64 offset = + data_layout_.getIndexedOffsetInType(llvm_element_type, index); + Value value = UnpackBuffer(buffer + offset, element_type); + values.push_back(value); + } + + return Value::ArrayOrDie(values); + } + default: + XLS_LOG(FATAL) << "Unsupported XLS Value kind: " << result_type->kind(); + } +} + +void LlvmIrRuntime::BlitValueToBuffer(const Value& value, const Type& type, + absl::Span buffer) { + if (value.IsBits()) { + const Bits& bits = value.bits(); + int64 byte_count = CeilOfRatio(bits.bit_count(), kCharBit); + bits.ToBytes(absl::MakeSpan(buffer.data(), byte_count), + data_layout_.isBigEndian()); + + // Clear out any bits in storage above that indicated by the data type - + // LLVM requires this for safe operation, e.g., bit 127 in the 128-bit + // actual allocated storage for a i127 must be 0. + // Max of 7 bits of remainder on the [little-endian] most-significant byte. + int remainder_bits = bits.bit_count() % kCharBit; + if (remainder_bits != 0) { + buffer[byte_count - 1] &= static_cast(Mask(remainder_bits)); + } + } else if (value.IsArray()) { + const ArrayType* array_type = type.AsArrayOrDie(); + int64 element_size = + type_converter_->GetTypeByteSize(*array_type->element_type()); + for (int i = 0; i < value.size(); ++i) { + BlitValueToBuffer(value.element(i), *array_type->element_type(), buffer); + buffer = buffer.subspan(element_size); + } + } else if (value.IsTuple()) { + // Due to per-target data packing (esp. as realized by the LLVM IR + // load/store instructions), we need to make sure we blit args into LLVM + // space as the underlying runtime expects, which means we need the + // DataLayout to tell us where each constituent element should be placed. + llvm::Type* llvm_type = type_converter_->ConvertToLlvmType(type); + const llvm::StructLayout* layout = + data_layout_.getStructLayout(llvm::cast(llvm_type)); + + const TupleType* tuple_type = type.AsTupleOrDie(); + for (int i = 0; i < value.size(); ++i) { + BlitValueToBuffer(value.element(i), *tuple_type->element_type(i), + buffer.subspan(layout->getElementOffset(i))); + } + } else { + XLS_LOG(FATAL) << "Unsupported XLS Value kind: " << value.kind(); + } +} + +template +std::string LlvmIrRuntime::DumpToString(const T& llvm_object) { + std::string buffer; + llvm::raw_string_ostream ostream(buffer); + llvm_object.print(ostream); + ostream.flush(); + return buffer; +} + +template <> +std::string LlvmIrRuntime::DumpToString( + const llvm::Module& llvm_object) { + std::string buffer; + llvm::raw_string_ostream ostream(buffer); + llvm_object.print(ostream, nullptr); + ostream.flush(); + return buffer; +} + +} // namespace xls + +extern "C" { + +// One-time initialization of LLVM targets. +absl::once_flag once; +void OnceInit() { + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); +} + +// Simple container struct to hold the resources needed by the below functions. +struct RuntimeState { + std::unique_ptr context; + std::unique_ptr target_machine; + std::unique_ptr type_converter; + std::unique_ptr runtime; +}; + +std::unique_ptr GetRuntimeState() { + absl::call_once(once, OnceInit); + auto state = std::make_unique(); + state->context = std::make_unique(); + auto error_or_target_builder = + llvm::orc::JITTargetMachineBuilder::detectHost(); + if (!error_or_target_builder) { + absl::PrintF("Unable to create TargetMachineBuilder!\n"); + return nullptr; + } + + auto error_or_target_machine = error_or_target_builder->createTargetMachine(); + if (!error_or_target_machine) { + absl::PrintF("Unable to create TargetMachine!\n"); + return nullptr; + } + + state->target_machine = std::move(error_or_target_machine.get()); + llvm::DataLayout data_layout = state->target_machine->createDataLayout(); + state->type_converter = std::make_unique( + state->context.get(), data_layout); + state->runtime = std::make_unique( + data_layout, state->type_converter.get()); + return state; +} + +int64 GetArgBufferSize(int arg_count, const char** input_args) { + std::unique_ptr state = GetRuntimeState(); + if (state == nullptr) { + return -1; + } + + xls::Package package("meow meow meow"); + int64 args_size = 0; + for (int i = 1; i < arg_count; i++) { + auto status_or_value = xls::Parser::ParseTypedValue(input_args[i]); + if (!status_or_value.ok()) { + return -2; + } + + xls::Value value = status_or_value.value(); + xls::Type* type = package.GetTypeForValue(value); + args_size += state->type_converter->GetTypeByteSize(*type); + } + + return args_size; +} + +// It's a little bit wasteful to re-do all the work above in this function, +// but it's a whole lot easier to write this way. If we tried to do this all in +// one function, we'd run into weirdness with LLVM IR return types or handling +// new/free in the context of LLVM, and how those would map to allocas. +// Since LLVM "main" execution isn't a throughput case, it's really not a +// problem to be a bit wasteful, especially when it makes things that much +// simpler. +int64 PackArgs(int arg_count, const char** input_args, uint8* buffer, + int buffer_size) { + std::unique_ptr state = GetRuntimeState(); + if (state == nullptr) { + return -1; + } + + xls::Package package("meow meow meow"); + std::vector values; + std::vector types; + std::vector arg_sizes; + values.reserve(arg_count); + types.reserve(arg_count); + arg_sizes.reserve(arg_count); + int64 total_args_size = 0; + // Skip argv[0]. + for (int i = 1; i < arg_count; i++) { + auto status_or_value = xls::Parser::ParseTypedValue(input_args[i]); + if (!status_or_value.ok()) { + return -3 - i; + } + + values.push_back(status_or_value.value()); + types.push_back(package.GetTypeForValue(values.back())); + arg_sizes.push_back(state->type_converter->GetTypeByteSize(*types.back())); + total_args_size += arg_sizes.back(); + } + + XLS_CHECK_OK(state->runtime->PackArgs(values, types, arg_sizes, + total_args_size, + absl::MakeSpan(buffer, buffer_size))); + return 0; +} + +int UnpackAndPrintBuffer(const char* output_type_string, int arg_count, + const char** input_args, const uint8* buffer) { + std::unique_ptr state = GetRuntimeState(); + if (state == nullptr) { + return -1; + } + + xls::Package package("oink oink oink"); + xls::Type* output_type = + xls::Parser::ParseType(output_type_string, &package).value(); + xls::Value output = state->runtime->UnpackBuffer(buffer, output_type); + absl::PrintF("%s\n", output.ToString()); + + return 0; +} + +} // extern "C" diff --git a/xls/ir/llvm_ir_runtime.h b/xls/ir/llvm_ir_runtime.h new file mode 100644 index 0000000000..2839da52b7 --- /dev/null +++ b/xls/ir/llvm_ir_runtime.h @@ -0,0 +1,96 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_LLVM_IR_RUNTIME_H_ +#define THIRD_PARTY_XLS_IR_LLVM_IR_RUNTIME_H_ + +#include "absl/status/status.h" +#include "absl/types/span.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/Support/raw_ostream.h" +#include "xls/common/integral_types.h" +#include "xls/ir/llvm_type_converter.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" + +namespace xls { + +// LlvmIrRuntime contains routines necessary for executing code generated by the +// IR JIT. For type resolution, the JIT packs input data into and pulls +// data out of a flat character buffer, thus these routines are necessary. +class LlvmIrRuntime { + public: + LlvmIrRuntime(const llvm::DataLayout& data_layout, + LlvmTypeConverter* type_converter); + + // Packs the specified values into a flat buffer with the data layout + // expected by LLVM. + absl::Status PackArgs(absl::Span args, + absl::Span arg_types, + absl::Span arg_sizes, + int64 total_args_size, absl::Span buffer); + + // Returns a Value constructed from the data inside "buffer" whose + // contents are laid out according to the LLVM interpretation of the passed-in + // type. + Value UnpackBuffer(const uint8* buffer, const Type* result_type); + + // Splats the value into the buffer according to the data layout expected by + // LLVM. + void BlitValueToBuffer(const Value& value, const Type& type, + absl::Span buffer); + + // Returns a textual description of the argument LLVM object. + template + static std::string DumpToString(const T& llvm_object); + + private: + llvm::DataLayout data_layout_; + LlvmTypeConverter* type_converter_; +}; + +template <> +std::string LlvmIrRuntime::DumpToString( + const llvm::Module& llvm_object); + +extern "C" { + +// Below are defined simple C interfaces to the LlvmIrRuntime functions above, +// currently only for use by LLVM IR samples wrapped in "main" functions +// generated by llvm_main_generator.cc. Such test cases are for debugging +// differences between the LLVM IR JIT and the XLS IR interpreter. + +// Parses the set of args (as "int argc, char** argv" that contain textual +// representations of XLS IR Values and determines how much storage is needed to +// contain them as LLVM Values format. +// On failure, a negative value will be returned. +int64 GetArgBufferSize(int arg_count, const char** input_args); + +// Packs the set of args (as above) into the specified buffer. This buffer must +// be large enough to contain the LLVM Value representation of these values. +// On failure, a negative value will be returned, otherwise this returns 0. +int64 PackArgs(int arg_count, const char** input_args, uint8* buffer, + int buffer_size); + +// Takes in a buffer containing LLVM-packed data and converts into an XLS Value, +// which is then printed to stdout. +int UnpackAndPrintBuffer(const char* output_type_string, int arg_count, + const char** input_args, const uint8* buffer); + +} // extern "C" + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_LLVM_IR_RUNTIME_H_ diff --git a/xls/ir/llvm_type_converter.cc b/xls/ir/llvm_type_converter.cc new file mode 100644 index 0000000000..223c8fbd53 --- /dev/null +++ b/xls/ir/llvm_type_converter.cc @@ -0,0 +1,121 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/llvm_type_converter.h" + +#include "xls/common/logging/logging.h" +#include "xls/ir/ir_parser.h" + +namespace xls { + +LlvmTypeConverter::LlvmTypeConverter(llvm::LLVMContext* context, + const llvm::DataLayout& data_layout) + : context_(*context), data_layout_(data_layout) {} + +llvm::Type* LlvmTypeConverter::ConvertToLlvmType(const Type& xls_type) { + auto it = type_cache_.find(&xls_type); + if (it != type_cache_.end()) { + return it->second; + } + llvm::Type* llvm_type; + if (xls_type.IsBits()) { + llvm_type = + llvm::IntegerType::get(context_, xls_type.AsBitsOrDie()->bit_count()); + } else if (xls_type.IsTuple()) { + std::vector tuple_types; + + const TupleType* tuple_type = xls_type.AsTupleOrDie(); + for (Type* tuple_elem_type : tuple_type->element_types()) { + llvm::Type* llvm_type = ConvertToLlvmType(*tuple_elem_type); + tuple_types.push_back(llvm_type); + } + + llvm_type = llvm::StructType::get(context_, tuple_types); + } else if (xls_type.IsArray()) { + const ArrayType* array_type = xls_type.AsArrayOrDie(); + llvm::Type* element_type = ConvertToLlvmType(*array_type->element_type()); + llvm_type = llvm::ArrayType::get(element_type, array_type->size()); + } else { + XLS_LOG(FATAL) << absl::StrCat("Type not supported for LLVM conversion: %s", + xls_type.ToString()); + } + type_cache_.insert({&xls_type, llvm_type}); + return llvm_type; +} + +xabsl::StatusOr LlvmTypeConverter::ToLlvmConstant( + const Type& type, const Value& value) { + return ToLlvmConstant(ConvertToLlvmType(type), value); +} + +xabsl::StatusOr LlvmTypeConverter::ToLlvmConstant( + llvm::Type* type, const Value& value) { + if (type->isIntegerTy()) { + return ToIntegralConstant(type, value); + } else if (type->isStructTy()) { + std::vector llvm_elements; + for (int i = 0; i < type->getStructNumElements(); ++i) { + XLS_ASSIGN_OR_RETURN( + llvm::Constant * llvm_element, + ToLlvmConstant(type->getStructElementType(i), value.element(i))); + llvm_elements.push_back(llvm_element); + } + + return llvm::ConstantStruct::get(llvm::cast(type), + llvm_elements); + } else if (type->isArrayTy()) { + std::vector elements; + llvm::Type* element_type = type->getArrayElementType(); + for (const Value& element : value.elements()) { + XLS_ASSIGN_OR_RETURN(llvm::Constant * llvm_element, + ToLlvmConstant(element_type, element)); + elements.push_back(llvm_element); + } + + return llvm::ConstantArray::get( + llvm::ArrayType::get(element_type, type->getArrayNumElements()), + elements); + } + XLS_LOG(FATAL) << "Unknown value kind: " << value.kind(); +} + +xabsl::StatusOr LlvmTypeConverter::ToIntegralConstant( + llvm::Type* type, const Value& value) { + Bits xls_bits = value.bits(); + + if (xls_bits.bit_count() > 64) { + std::vector bytes = xls_bits.ToBytes(); + if (data_layout_.isLittleEndian()) { + ByteSwap(absl::MakeSpan(bytes)); + } + + bytes.resize(xls::RoundUpToNearest(bytes.size(), 8UL), 0); + + auto array_ref = llvm::ArrayRef( + reinterpret_cast(bytes.data()), + CeilOfRatio(static_cast(bytes.size()), + static_cast(CHAR_BIT))); + return llvm::ConstantInt::get(type, + llvm::APInt(xls_bits.bit_count(), array_ref)); + } else { + XLS_ASSIGN_OR_RETURN(uint64 bits, value.bits().ToUint64()); + return llvm::ConstantInt::get(type, bits); + } +} + +int64 LlvmTypeConverter::GetTypeByteSize(const Type& type) { + return data_layout_.getTypeAllocSize(ConvertToLlvmType(type)).getFixedSize(); +} + +} // namespace xls diff --git a/xls/ir/llvm_type_converter.h b/xls/ir/llvm_type_converter.h new file mode 100644 index 0000000000..6eccc3d57c --- /dev/null +++ b/xls/ir/llvm_type_converter.h @@ -0,0 +1,72 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_LLVM_TYPE_CONVERTER_H_ +#define THIRD_PARTY_XLS_IR_LLVM_TYPE_CONVERTER_H_ + +#include "absl/container/inlined_vector.h" +#include "absl/status/status.h" +#include "absl/types/span.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Module.h" +#include "xls/common/integral_types.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" + +namespace xls { + +// LlvmTypeConverter handles the work of translating from XLS types and values +// into the corresponding LLVM elements. +// +// This class must live as long as its constructor argument module. +class LlvmTypeConverter { + public: + LlvmTypeConverter(llvm::LLVMContext* context, + const llvm::DataLayout& data_layout); + + llvm::Type* ConvertToLlvmType(const Type& type); + + // Converts the input XLS Value to an LLVM Constant of the specified type. + xabsl::StatusOr ToLlvmConstant(llvm::Type* type, + const Value& value); + xabsl::StatusOr ToLlvmConstant(const Type& type, + const Value& value); + + // Returns the number of bytes that LLVM will internally use to store the + // given element. This is not simply the flat bit count of the type (rounded + // up to 8 bits) - a type with four 6-bit members will be held in 4 i8s, + // instead of the three that the flat bit count would suggest. The type width + // rules aren't necessarily immediately obvious, but fortunately the + // DataLayout object can handle ~all of the work for us. + int64 GetTypeByteSize(const Type& type); + + private: + using TypeCache = absl::flat_hash_map; + + // Handles the special (and base) case of converting Bits types to LLVM. + xabsl::StatusOr ToIntegralConstant(llvm::Type* type, + const Value& value); + + llvm::LLVMContext& context_; + llvm::DataLayout data_layout_; + + // Cache of XLS -> LLVM type conversions. + TypeCache type_cache_; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_LLVM_TYPE_CONVERTER_H_ diff --git a/xls/ir/lsb_or_msb.h b/xls/ir/lsb_or_msb.h new file mode 100644 index 0000000000..39e6d5ba07 --- /dev/null +++ b/xls/ir/lsb_or_msb.h @@ -0,0 +1,24 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_LSB_OR_MSB_H_ +#define THIRD_PARTY_XLS_IR_LSB_OR_MSB_H_ + +namespace xls { + +enum class LsbOrMsb { kLsb, kMsb }; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_LSB_OR_MSB_H_ diff --git a/xls/ir/node.cc b/xls/ir/node.cc new file mode 100644 index 0000000000..4cb50e615e --- /dev/null +++ b/xls/ir/node.cc @@ -0,0 +1,510 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/node.h" + +#include "absl/algorithm/container.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/dfs_visitor.h" +#include "xls/ir/function.h" +#include "xls/ir/nodes.h" +#include "xls/ir/package.h" + +namespace xls { + +Node::Node(Op op, Type* type, absl::optional loc, + Function* function) + : function_(function), + id_(function_->package()->GetNextNodeId()), + op_(op), + type_(type), + loc_(loc) {} + +void Node::AddOperand(Node* operand) { + XLS_VLOG(3) << " Adding operand " << operand->GetName() << " as #" + << operands_.size() << " operand of " << GetName(); + operands_.push_back(operand); + operand->AddUser(this); + XLS_VLOG(3) << " " << operand->GetName() + << " user now: " << operand->GetUsersString(); +} + +void Node::AddOperands(absl::Span operands) { + for (Node* operand : operands) { + AddOperand(operand); + } +} + +void Node::AddOptionalOperand(absl::optional operand) { + if (operand.has_value()) { + AddOperand(*operand); + } +} + +absl::Status Node::AddNodeToFunctionAndReplace( + std::unique_ptr replacement) { + Node* replacement_ptr = function()->AddNode(std::move(replacement)); + XLS_RETURN_IF_ERROR(Verify(replacement_ptr)); + return ReplaceUsesWith(replacement_ptr).status(); +} + +void Node::AddUser(Node* user) { + auto insert_result = users_set_.insert(user); + if (insert_result.second) { + users_.push_back(user); + // Keep the users sequence sorted by ordinal for stability. + absl::c_sort(users_, [](Node* a, Node* b) { return a->id() < b->id(); }); + } +} + +void Node::RemoveUser(Node* user) { + users_set_.erase(user); + users_.erase(std::remove(users_.begin(), users_.end(), user), users_.end()); +} + +absl::Status Node::VisitSingleNode(DfsVisitor* visitor) { + switch (op()) { + case Op::kAdd: + XLS_RETURN_IF_ERROR(visitor->HandleAdd(down_cast(this))); + break; + case Op::kAnd: + XLS_RETURN_IF_ERROR(visitor->HandleNaryAnd(down_cast(this))); + break; + case Op::kAndReduce: + XLS_RETURN_IF_ERROR( + visitor->HandleAndReduce(down_cast(this))); + break; + case Op::kNand: + XLS_RETURN_IF_ERROR(visitor->HandleNaryNand(down_cast(this))); + break; + case Op::kNor: + XLS_RETURN_IF_ERROR(visitor->HandleNaryNor(down_cast(this))); + break; + case Op::kArray: + XLS_RETURN_IF_ERROR(visitor->HandleArray(down_cast(this))); + break; + case Op::kBitSlice: + XLS_RETURN_IF_ERROR(visitor->HandleBitSlice(down_cast(this))); + break; + case Op::kConcat: + XLS_RETURN_IF_ERROR(visitor->HandleConcat(down_cast(this))); + break; + case Op::kDecode: + XLS_RETURN_IF_ERROR(visitor->HandleDecode(down_cast(this))); + break; + case Op::kEncode: + XLS_RETURN_IF_ERROR(visitor->HandleEncode(down_cast(this))); + break; + case Op::kEq: + XLS_RETURN_IF_ERROR(visitor->HandleEq(down_cast(this))); + break; + case Op::kIdentity: + XLS_RETURN_IF_ERROR(visitor->HandleIdentity(down_cast(this))); + break; + case Op::kArrayIndex: + XLS_RETURN_IF_ERROR( + visitor->HandleArrayIndex(down_cast(this))); + break; + case Op::kInvoke: + XLS_RETURN_IF_ERROR(visitor->HandleInvoke(down_cast(this))); + break; + case Op::kCountedFor: + XLS_RETURN_IF_ERROR( + visitor->HandleCountedFor(down_cast(this))); + break; + case Op::kLiteral: + XLS_RETURN_IF_ERROR(visitor->HandleLiteral(down_cast(this))); + break; + case Op::kMap: + XLS_RETURN_IF_ERROR(visitor->HandleMap(down_cast(this))); + break; + case Op::kNe: + XLS_RETURN_IF_ERROR(visitor->HandleNe(down_cast(this))); + break; + case Op::kNeg: + XLS_RETURN_IF_ERROR(visitor->HandleNeg(down_cast(this))); + break; + case Op::kNot: + XLS_RETURN_IF_ERROR(visitor->HandleNot(down_cast(this))); + break; + case Op::kOneHot: + XLS_RETURN_IF_ERROR(visitor->HandleOneHot(down_cast(this))); + break; + case Op::kOneHotSel: + XLS_RETURN_IF_ERROR( + visitor->HandleOneHotSel(down_cast(this))); + break; + case Op::kOr: + XLS_RETURN_IF_ERROR(visitor->HandleNaryOr(down_cast(this))); + break; + case Op::kOrReduce: + XLS_RETURN_IF_ERROR( + visitor->HandleOrReduce(down_cast(this))); + break; + case Op::kParam: + XLS_RETURN_IF_ERROR(visitor->HandleParam(down_cast(this))); + break; + case Op::kReverse: + XLS_RETURN_IF_ERROR(visitor->HandleReverse(down_cast(this))); + break; + case Op::kSDiv: + XLS_RETURN_IF_ERROR(visitor->HandleSDiv(down_cast(this))); + break; + case Op::kSel: + XLS_RETURN_IF_ERROR(visitor->HandleSel(down_cast(this))); + break; + case Op::kSGt: + XLS_RETURN_IF_ERROR(visitor->HandleSGt(down_cast(this))); + break; + case Op::kSGe: + XLS_RETURN_IF_ERROR(visitor->HandleSGe(down_cast(this))); + break; + case Op::kShll: + XLS_RETURN_IF_ERROR(visitor->HandleShll(down_cast(this))); + break; + case Op::kShra: + XLS_RETURN_IF_ERROR(visitor->HandleShra(down_cast(this))); + break; + case Op::kShrl: + XLS_RETURN_IF_ERROR(visitor->HandleShrl(down_cast(this))); + break; + case Op::kSLe: + XLS_RETURN_IF_ERROR(visitor->HandleSLe(down_cast(this))); + break; + case Op::kSLt: + XLS_RETURN_IF_ERROR(visitor->HandleSLt(down_cast(this))); + break; + case Op::kSMul: + XLS_RETURN_IF_ERROR(visitor->HandleSMul(down_cast(this))); + break; + case Op::kSub: + XLS_RETURN_IF_ERROR(visitor->HandleSub(down_cast(this))); + break; + case Op::kTupleIndex: + XLS_RETURN_IF_ERROR( + visitor->HandleTupleIndex(down_cast(this))); + break; + case Op::kTuple: + XLS_RETURN_IF_ERROR(visitor->HandleTuple(down_cast(this))); + break; + case Op::kUDiv: + XLS_RETURN_IF_ERROR(visitor->HandleUDiv(down_cast(this))); + break; + case Op::kUGe: + XLS_RETURN_IF_ERROR(visitor->HandleUGe(down_cast(this))); + break; + case Op::kUGt: + XLS_RETURN_IF_ERROR(visitor->HandleUGt(down_cast(this))); + break; + case Op::kULe: + XLS_RETURN_IF_ERROR(visitor->HandleULe(down_cast(this))); + break; + case Op::kULt: + XLS_RETURN_IF_ERROR(visitor->HandleULt(down_cast(this))); + break; + case Op::kUMul: + XLS_RETURN_IF_ERROR(visitor->HandleUMul(down_cast(this))); + break; + case Op::kXor: + XLS_RETURN_IF_ERROR(visitor->HandleNaryXor(down_cast(this))); + break; + case Op::kXorReduce: + XLS_RETURN_IF_ERROR( + visitor->HandleXorReduce(down_cast(this))); + break; + case Op::kSignExt: + XLS_RETURN_IF_ERROR( + visitor->HandleSignExtend(down_cast(this))); + break; + case Op::kZeroExt: + XLS_RETURN_IF_ERROR( + visitor->HandleZeroExtend(down_cast(this))); + break; + } + return absl::OkStatus(); +} + +absl::Status Node::Accept(DfsVisitor* visitor) { + if (visitor->IsVisited(this)) { + return absl::OkStatus(); + } + if (visitor->IsTraversing(this)) { + return absl::InternalError( + absl::StrFormat("Cycle detected which includes node %s", GetName())); + } + visitor->SetTraversing(this); + for (Node* operand : operands()) { + XLS_RETURN_IF_ERROR(operand->Accept(visitor)); + } + visitor->UnsetTraversing(this); + visitor->MarkVisited(this); + return VisitSingleNode(visitor); +} + +bool Node::IsDefinitelyEqualTo(const Node* other) const { + if (op() != other->op()) { + return false; + } + auto same_type = [&](const Node* a, const Node* b) { + return a->GetType()->IsEqualTo(b->GetType()); + }; + + // Must have the same operand count, and each operand must be the same type. + if (operand_count() != other->operand_count()) { + return false; + } + for (int64 i = 0; i < operand_count(); ++i) { + if (!same_type(operand(i), other->operand(i))) { + return false; + } + } + return same_type(this, other); +} + +std::string Node::GetName() const { + if (Is()) { + return As()->name(); + } + return absl::StrFormat("%s.%d", OpToString(op_), id_); +} + +std::string Node::ToStringInternal(bool include_operand_types) const { + auto node_to_name = [](const Node* n, bool include_type) -> std::string { + std::string name = n->GetName(); + if (include_type) { + absl::StrAppend(&name, ": ", n->GetType()->ToString()); + } + return name; + }; + + std::string ret = node_to_name(this, false); + Type* t = GetType(); + absl::StrAppend(&ret, ": ", t->ToString()); + absl::StrAppend(&ret, " = ", OpToString(op_)); + std::vector args; + for (Node* operand : operands()) { + args.push_back(node_to_name(operand, include_operand_types)); + } + switch (op_) { + case Op::kParam: + args.push_back(GetName()); + break; + case Op::kLiteral: + args.push_back( + absl::StrFormat("value=%s", As()->value().ToHumanString())); + break; + case Op::kCountedFor: + for (int64 i = 0; i < As()->invariant_args().size(); ++i) { + args.pop_back(); + } + args.push_back( + absl::StrFormat("trip_count=%d", As()->trip_count())); + args.push_back(absl::StrFormat("stride=%d", As()->stride())); + args.push_back( + absl::StrFormat("body=%s", As()->body()->name())); + if (!As()->invariant_args().empty()) { + args.push_back(absl::StrFormat( + "invariant_args=[%s]", + absl::StrJoin(As()->invariant_args(), ", ", + [](std::string* out, const Node* node) { + absl::StrAppend(out, node->GetName()); + }))); + } + break; + case Op::kMap: + args.push_back( + absl::StrFormat("to_apply=%s", As()->to_apply()->name())); + break; + case Op::kInvoke: + args.push_back( + absl::StrFormat("to_apply=%s", As()->to_apply()->name())); + break; + case Op::kTupleIndex: + args.push_back(absl::StrFormat("index=%d", As()->index())); + break; + case Op::kOneHot: + args.push_back(absl::StrFormat( + "lsb_prio=%s", + As()->priority() == LsbOrMsb::kLsb ? "true" : "false")); + break; + case Op::kOneHotSel: { + const OneHotSelect* sel = As(); + args = {operand(0)->GetName()}; + args.push_back(absl::StrFormat( + "cases=[%s]", absl::StrJoin(sel->cases(), ", ", + [](std::string* out, const Node* node) { + absl::StrAppend(out, node->GetName()); + }))); + break; + } + case Op::kSel: { + const Select* sel = As(loc(), new_operands[0], + new_operands.subspan(1, cases_size_), + new_default_value); +} + +xabsl::StatusOr OneHotSelect::Clone(absl::Span new_operands, + Function* new_function) const { + return new_function->MakeNode(loc(), new_operands[0], + new_operands.subspan(1)); +} + +bool Select::AllCases(std::function p) const { + for (Node* case_ : cases()) { + if (!p(case_)) { + return false; + } + } + if (default_value().has_value()) { + if (!p(default_value().value())) { + return false; + } + } + return true; +} + +} // namespace xls diff --git a/xls/ir/number_parser.cc b/xls/ir/number_parser.cc new file mode 100644 index 0000000000..2daf08baa9 --- /dev/null +++ b/xls/ir/number_parser.cc @@ -0,0 +1,236 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/number_parser.h" + +#include "absl/strings/numbers.h" +#include "absl/strings/str_replace.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits_ops.h" + +namespace xls { + +using absl::StrFormat; + +// Parses the given input as an unsigned number (with no format prefix) of the +// given format. 'orig_string' is the string used in error message. +static xabsl::StatusOr ParseUnsignedNumberHelper( + absl::string_view input, FormatPreference format, + absl::string_view orig_string, int64 bit_count = kMinimumBitCount) { + if (format == FormatPreference::kDefault) { + return absl::InvalidArgumentError("Cannot specify default format."); + } + + std::string numeric_string = absl::StrReplaceAll(input, {{"_", ""}}); + if (numeric_string.empty()) { + return absl::InvalidArgumentError( + StrFormat("Could not convert %s to a number", orig_string)); + } + + if (format == FormatPreference::kDecimal) { + uint64 magnitude; + if (!absl::SimpleAtoi(numeric_string, &magnitude)) { + return absl::InvalidArgumentError(StrFormat( + "Could not convert %s to 64-bit decimal number", orig_string)); + } + + if (bit_count == kMinimumBitCount) { + return UBits(magnitude, Bits::MinBitCountUnsigned(magnitude)); + } + return UBits(magnitude, bit_count); + } + + int base; + int base_bits; + std::string base_name; + if (format == FormatPreference::kBinary) { + base = 2; + base_bits = 1; + base_name = "binary"; + } else if (format == FormatPreference::kHex) { + base = 16; + base_bits = 4; + base_name = "hexadecimal"; + } else { + return absl::InvalidArgumentError(StrFormat("Invalid format: %d", format)); + } + + // Walk through string 64 bits at a time (16 hexadecimal symbols or 64 + // binary symbols) and convert each to a separate Bits value. Then + // concatenate them all together. + const int64 step_size = 64 / base_bits; + std::vector chunks; + for (int64 i = 0; i < numeric_string.size(); i = i + step_size) { + int64 chunk_length = std::min(step_size, numeric_string.size() - i); + uint64 chunk_value; + if (!absl::numbers_internal::safe_strtoi_base( + numeric_string.substr(i, chunk_length), &chunk_value, base)) { + return absl::InvalidArgumentError(StrFormat( + "Could not convert %s to %s number", orig_string, base_name)); + } + chunks.push_back(UBits(chunk_value, chunk_length * base_bits)); + } + + Bits unnarrowed = bits_ops::Concat(chunks); + if (bit_count == kMinimumBitCount) { + // Narrow the Bits value to be just wide enough to hold the value. + int64 new_width = unnarrowed.bit_count() - unnarrowed.CountLeadingZeros(); + return unnarrowed.Slice(0, new_width); + } else if (bit_count > unnarrowed.bit_count()) { + BitsRope rope(bit_count); + rope.push_back(unnarrowed); + rope.push_back(Bits(bit_count - unnarrowed.bit_count())); + return rope.Build(); + } else { + return unnarrowed.Slice(0, bit_count); + } +} + +xabsl::StatusOr ParseUnsignedNumberWithoutPrefix(absl::string_view input, + FormatPreference format, + int64 bit_count) { + return ParseUnsignedNumberHelper(input, format, /*orig_string=*/input, + bit_count); +} + +xabsl::StatusOr> GetSignAndMagnitude( + absl::string_view input) { + // Literal numbers can be one of: + // 1) decimal numbers, eg '123' + // 2) binary numbers, eg '0b010101' + // 3) hexadecimal numbers, eg '0xdeadbeef' + // Binary and hexadecimal numbers can be arbitrarily large. Decimal numbers + // must fit in 64 bits. + if (input.empty()) { + return absl::InvalidArgumentError( + StrFormat("Cannot parse empty string as a number.")); + } + bool is_negative = false; + // The substring containing the actual numeric characters. + absl::string_view numeric_substring = input; + if (input[0] == '-') { + is_negative = true; + numeric_substring = numeric_substring.substr(1); + } + FormatPreference format = FormatPreference::kDecimal; + if (numeric_substring.size() >= 2 && numeric_substring[0] == '0') { + char base_char = numeric_substring[1]; + if (base_char == 'b') { + format = FormatPreference::kBinary; + } else if (base_char == 'x') { + format = FormatPreference::kHex; + } else { + return absl::InvalidArgumentError( + StrFormat("Invalid numeric base %#x = '%c'. Expected 'b' or 'x'.", + base_char, base_char)); + } + numeric_substring = numeric_substring.substr(2); + } + XLS_ASSIGN_OR_RETURN(Bits value, + ParseUnsignedNumberHelper(numeric_substring, format, + /*orig_string=*/input)); + return std::make_pair(is_negative, value); +} + +xabsl::StatusOr ParseNumber(absl::string_view input) { + std::pair pair; + XLS_ASSIGN_OR_RETURN(pair, GetSignAndMagnitude(input)); + bool is_negative = pair.first; + const Bits& magnitude = pair.second; + if (is_negative && !magnitude.IsAllZeros()) { + Bits result = bits_ops::Negate( + bits_ops::ZeroExtend(magnitude, magnitude.bit_count() + 1)); + // We want to return the narrowest Bits object which can hold the (negative) + // twos-complement number. Shave off all but one of the leading ones. + int64 leading_ones = 0; + for (int64 i = result.bit_count() - 1; i >= 0 && result.Get(i) == 1; --i) { + ++leading_ones; + } + XLS_RET_CHECK_GT(leading_ones, 0); + return result.Slice(0, result.bit_count() - leading_ones + 1); + } + return magnitude; +} + +xabsl::StatusOr ParseNumberAsUint64(absl::string_view input) { + std::pair pair; + XLS_ASSIGN_OR_RETURN(pair, GetSignAndMagnitude(input)); + bool is_negative = pair.first; + if ((is_negative && !pair.second.IsAllZeros()) || + pair.second.bit_count() > 64) { + return absl::InvalidArgumentError( + StrFormat("Value is not representable as an uint64: %s", input)); + } + return pair.second.ToUint64(); +} + +xabsl::StatusOr ParseNumberAsInt64(absl::string_view input) { + std::pair pair; + XLS_ASSIGN_OR_RETURN(pair, GetSignAndMagnitude(input)); + bool is_negative = pair.first; + Bits magnitude = pair.second; + auto not_representable = [&]() { + return absl::InvalidArgumentError( + StrFormat("Value is not representable as an int64: %s", input)); + }; + if (!is_negative) { + // A non-negative number must fit in 63 bits to be represented as a 64-bit + // twos-complement number. + if (magnitude.bit_count() > 63) { + return not_representable(); + } + return bits_ops::ZeroExtend(magnitude, 64).ToInt64(); + } + + // To be representable as a 64-bit twos -complement negative number the + // magnitude must be representable in 63 bits OR the magnitude must be equal + // to the magnitude of the most negative number (0x80000...). + if (magnitude.bit_count() >= 64) { + if ((magnitude.bit_count() == 64) && magnitude.msb() && + (magnitude.PopCount() == 1)) { + return std::numeric_limits::min(); + } + return not_representable(); + } + // At this point 'magnitude' is an unsigned number of 63 or fewer bits. Zero + // extend and negate for the result. + return bits_ops::Negate(bits_ops::ZeroExtend(magnitude, 64)).ToInt64(); +} + +xabsl::StatusOr ParseNumberAsBool(absl::string_view input) { + if (input == "true") { + return true; + } + if (input == "false") { + return false; + } + std::pair pair; + XLS_ASSIGN_OR_RETURN(pair, GetSignAndMagnitude(input)); + Bits magnitude = pair.second; + bool is_negative = pair.first; + auto not_representable = [&]() { + return absl::InvalidArgumentError( + StrFormat("Value is not representable as a bool: %s", input)); + }; + if (is_negative) { + return not_representable(); + } + if (magnitude.bit_count() > 1) { + return not_representable(); + } + return magnitude.Get(0); +} + +} // namespace xls diff --git a/xls/ir/number_parser.h b/xls/ir/number_parser.h new file mode 100644 index 0000000000..d58b4d399c --- /dev/null +++ b/xls/ir/number_parser.h @@ -0,0 +1,74 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Parse routines for turning numerical strings into Bits and integral values. + +#ifndef THIRD_PARTY_XLS_IR_NUMBER_PARSER_H_ +#define THIRD_PARTY_XLS_IR_NUMBER_PARSER_H_ + +#include "absl/strings/string_view.h" +#include "xls/common/integral_types.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/format_preference.h" + +namespace xls { + +// Flag value to indicate that parsing a number to Bits should use the minimum +// width necessary to represent the produced value. +constexpr int64 kMinimumBitCount = -1; + +// Returns a bool indicating whether the literal token is negative, and a Bits +// containing the magnitude. The Bits value is the minimum width necessary to +// hold the (necessarily) unsigned magnitude. +xabsl::StatusOr> GetSignAndMagnitude( + absl::string_view input); + +// Parses the given string as a number and returns the result as a Bits +// value. Number may be represented in decimal, hexadecimal (prefixed with +// '0x'), or binary (prefixed with '0b'). Hexadecimal and binary numbers can be +// arbitrarily wide. Decimal numbers are limited to 64 bits. Numbers can include +// underscores (e.g., "0xdead_beef"). +// +// For negative values (leading '-' in parsed string), the width of the Bits +// value is exactly wide enough to hold the value as a twos-complement number +// (with a minimum width of 1). For non-negative numbers, the width of Bits +// value is exactly wide enough to hold the value as an unsigned +// number. Examples: +// +// "0" => UBits(0, 1) +// "-1" => SBits(-1, 1) (equivalently UBits(1, 1)) +// "0b011" => UBits(3, 2) +// "10" => UBits(10, 4) +// "-10" => SBits(-10, 5) (equivalently UBits(22, 5)) +// "0x17" => UBits(0x17, 5) +xabsl::StatusOr ParseNumber(absl::string_view input); + +// Parses the string as a number and returns the value as a (u)int64. Returns an +// error if the number is not representable as a (u)int64. +xabsl::StatusOr ParseNumberAsInt64(absl::string_view input); +xabsl::StatusOr ParseNumberAsUint64(absl::string_view input); + +xabsl::StatusOr ParseNumberAsBool(absl::string_view input); + +// Parse an unsigned number but the given input does not include any +// format-specifying prefix (e.g., "0x" for hexadecimal). Rather, the format is +// specified by argument. +xabsl::StatusOr ParseUnsignedNumberWithoutPrefix( + absl::string_view input, FormatPreference format = FormatPreference::kHex, + int64 bit_count = kMinimumBitCount); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_NUMBER_PARSER_H_ diff --git a/xls/ir/number_parser_test.cc b/xls/ir/number_parser_test.cc new file mode 100644 index 0000000000..10158ab9f5 --- /dev/null +++ b/xls/ir/number_parser_test.cc @@ -0,0 +1,182 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/number_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/integral_types.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits_ops.h" + +namespace xls { +namespace { + +using status_testing::StatusIs; + +TEST(NumberParserTest, ParseNumbersAsUint64) { + auto expect_uint64_value = [](absl::string_view s, uint64 expected) { + XLS_ASSERT_OK_AND_ASSIGN(uint64 value, ParseNumberAsUint64(s)); + EXPECT_EQ(value, expected); + }; + expect_uint64_value("0", 0); + expect_uint64_value("-0", 0); + expect_uint64_value("3", 3); + expect_uint64_value("12_34_567", 1234567); + expect_uint64_value("0b0", 0); + expect_uint64_value("0b1", 1); + expect_uint64_value("0b1100110011010101010010101011010", 1718265178UL); + expect_uint64_value("0x0", 0); + expect_uint64_value("0x1", 1); + expect_uint64_value("0xdeadbeef", 3735928559UL); + expect_uint64_value("0xdeadbeefdeadbeef", 0xdeadbeefdeadbeefULL); + expect_uint64_value("0xffffffffffffffff", std::numeric_limits::max()); +} + +TEST(NumberParserTest, ParseNumbersAsInt64) { + auto expect_int64_value = [](absl::string_view s, int64 expected) { + XLS_ASSERT_OK_AND_ASSIGN(int64 value, ParseNumberAsInt64(s)); + EXPECT_EQ(value, expected); + }; + expect_int64_value("0", 0); + expect_int64_value("-0", 0); + expect_int64_value("3", 3); + expect_int64_value("1234567", 1234567); + expect_int64_value("0b0", 0); + expect_int64_value("0b1", 1); + expect_int64_value("0b1100110011010101010010101011010", 1718265178UL); + expect_int64_value("0x0", 0); + expect_int64_value("0x1", 1); + expect_int64_value("0xdead_beef", 3735928559UL); + expect_int64_value("-1", -1); + expect_int64_value("-2", -2); + expect_int64_value("-0xf00", -0xf00); + expect_int64_value("-0b1010", -10); + expect_int64_value("0x7fff_ffff_ffff_ffff", + std::numeric_limits::max()); + expect_int64_value("-0x8000_0000_0000_0000", + std::numeric_limits::min()); +} + +TEST(NumberParserTest, ParseNumbersAsBits) { + auto expect_bits_value = [](absl::string_view s, const Bits& expected) { + XLS_ASSERT_OK_AND_ASSIGN(Bits value, ParseNumber(s)); + EXPECT_EQ(value, expected); + }; + // Verifies that width of the Bits type is the minimum required to represent + // the number. + expect_bits_value("0", UBits(0, 0)); + expect_bits_value("-0", UBits(0, 0)); + // UBits(1, 1) is -1 in 1-bit twos complement. + expect_bits_value("-1", UBits(1, 1)); + expect_bits_value("-0x00000001", UBits(1, 1)); + expect_bits_value("-4", UBits(4, 3)); + expect_bits_value("5", UBits(5, 3)); + expect_bits_value("-5", SBits(-5, 4)); + expect_bits_value("127", UBits(127, 7)); + expect_bits_value("128", UBits(128, 8)); + expect_bits_value("-128", SBits(-128, 8)); + expect_bits_value("-129", SBits(-129, 9)); + expect_bits_value("-0x8000_0000_0000_0000", + SBits(std::numeric_limits::min(), 64)); + + expect_bits_value("0xbeef_abcd_fab2_3456_7890_1010_101a_cbde_fadd_fff", + bits_ops::Concat({UBits(0xbeefabcdfab23456ULL, 64), + UBits(0x78901010101acbdeULL, 64), + UBits(0xfaddfffULL, 28)})); + expect_bits_value( + "0b011" + "__1010_0010_1011_1010_1010_1011_0101_0111" + "__0101_0101_0100_1110_1101_0101_1000_1111" + "__1001_1101_0111_0111_0001_0010_1101_0101" + "__0101_1001_1101_1010_1111_0110_1011_0111", + bits_ops::Concat({UBits(3, 2), UBits(0xa2baab57554ed58fULL, 64), + UBits(0x9d7712d559daf6b7ULL, 64)})); +} + +TEST(NumberParserTest, ParseNumbersWithoutPrefix) { + auto expect_bits_value = [](absl::string_view s, FormatPreference format, + const Bits& expected) { + XLS_ASSERT_OK_AND_ASSIGN(Bits value, + ParseUnsignedNumberWithoutPrefix(s, format)); + EXPECT_EQ(value, expected); + }; + expect_bits_value("0", FormatPreference::kDecimal, UBits(0, 0)); + expect_bits_value("0", FormatPreference::kBinary, UBits(0, 0)); + expect_bits_value("0", FormatPreference::kHex, UBits(0, 0)); + expect_bits_value("1", FormatPreference::kDecimal, UBits(1, 1)); + expect_bits_value("1", FormatPreference::kBinary, UBits(1, 1)); + expect_bits_value("1", FormatPreference::kHex, UBits(1, 1)); + + expect_bits_value("42", FormatPreference::kDecimal, UBits(42, 6)); + expect_bits_value("1234567890", FormatPreference::kDecimal, + UBits(1234567890ULL, 31)); + expect_bits_value("12345678901234567890", FormatPreference::kDecimal, + UBits(12345678901234567890ULL, 64)); + + expect_bits_value("1010_1011_1100", FormatPreference::kBinary, + UBits(0xabc, 12)); + expect_bits_value("abcdef", FormatPreference::kHex, UBits(0xabcdef, 24)); +} + +TEST(NumberParserTest, ParseNumberErrors) { + EXPECT_THAT( + ParseNumberAsUint64("").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr("Cannot parse empty string as a number"))); + EXPECT_THAT(ParseNumberAsUint64("123b").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr( + "Could not convert 123b to 64-bit decimal number"))); + EXPECT_THAT( + ParseNumberAsUint64("0b").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr("Could not convert 0b to a number"))); + EXPECT_THAT( + ParseNumberAsUint64("0b__").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr("Could not convert 0b__ to a number"))); + EXPECT_THAT(ParseNumberAsUint64("0b02").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr( + "Could not convert 0b02 to binary number"))); + EXPECT_THAT(ParseNumberAsUint64("0x0fr").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr( + "Could not convert 0x0fr to hexadecimal number"))); + EXPECT_THAT(ParseNumberAsUint64("-1").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr( + "Value is not representable as an uint64"))); + EXPECT_THAT(ParseNumberAsUint64("-0xdeadbeef").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr( + "Value is not representable as an uint64"))); + EXPECT_THAT(ParseNumberAsUint64("0xdeadbeefdeadbeef000").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr( + "Value is not representable as an uint64"))); + + EXPECT_THAT( + ParseNumberAsInt64("0xdeadbeefdeadbeef").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr("Value is not representable as an int64"))); + EXPECT_THAT( + ParseNumberAsInt64("-0x8000_0000_0000_0001").status(), + StatusIs(absl::StatusCode::kInvalidArgument, + ::testing::HasSubstr("Value is not representable as an int64"))); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/op_header.tmpl b/xls/ir/op_header.tmpl new file mode 100644 index 0000000000..4772fad438 --- /dev/null +++ b/xls/ir/op_header.tmpl @@ -0,0 +1,76 @@ +#ifndef THIRD_PARTY_XLS_IR_OP_ +#define THIRD_PARTY_XLS_IR_OP_ + +#include +#include + +#include "xls/common/integral_types.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "xls/common/status/statusor.h" + +namespace xls { + +// Enumerates the operator for nodes in the IR. +enum class Op { + {% for op in spec.OPS -%} + {{ op.enum_name }}, + {% endfor -%} +}; + +inline std::vector AllOps() { + return { + {% for op in spec.OPS -%} + Op::{{ op.enum_name }}, + {% endfor -%} + }; +} + +const int64 kOpLimit = static_cast(Op::{{ spec.OPS[-1].enum_name }})+1; + +// Converts the "op" enumeration to a human readable string. +std::string OpToString(Op op); + +// Converts a human readable op string into the "op" enumeration. +xabsl::StatusOr StringToOp(absl::string_view op_str); + +// Returns whether the operation is a compare operation. +bool OpIsCompare(Op op); + +// Returns whether the operation is associative, eg., kAdd, or kOr. +bool OpIsAssociative(Op op); + +// Returns whether the operation is commutative, eg., kAdd, or kEq. +bool OpIsCommutative(Op op); + +// Returns whether the operation is a bitwise logical op, eg., kAnd or kOr. +bool OpIsBitWise(Op op); + +// Returns the delay of this operation in picoseconds. +// TODO(meheff): This value should be plugable and be derived from other aspects +// of Node, not just the op. +int64 OpDelayInPs(Op op); + +// Forward declare all Op classes (subclasses of Node). +{% for op_class in spec.OpClass.kinds.values() -%} +class {{ op_class.name }}; +{% endfor %} + +// Returns whether the given Op has the OpT node subclass. +template +bool IsOpClass(Op op) { + static_assert(!std::is_same::value, "OpT is not a Node subclass"); + return false; +} + +{% for op_class in spec.OpClass.kinds.values() -%} +template<> +bool IsOpClass<{{op_class.name}}>(Op op); +{% endfor %} + +// Streams the string for "op" to the given output stream. +std::ostream& operator<<(std::ostream& os, Op op); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_OP_ diff --git a/xls/ir/op_source.tmpl b/xls/ir/op_source.tmpl new file mode 100644 index 0000000000..05e990bae7 --- /dev/null +++ b/xls/ir/op_source.tmpl @@ -0,0 +1,93 @@ +#include "xls/ir/op.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/statusor.h" +#include "absl/container/flat_hash_map.h" + +namespace xls { + +std::string OpToString(Op op) { + static const absl::flat_hash_map* op_map = + new absl::flat_hash_map({ +{% for op in spec.OPS -%} +{Op::{{ op.enum_name }}, "{{ op.name }}"}, +{% endfor -%} + }); + auto found = op_map->find(op); + if (found == op_map->end()) { + XLS_LOG(FATAL) << "OpToString(" << static_cast(op) + << ") failed, unknown op"; + } + return found->second; +} + +xabsl::StatusOr StringToOp(absl::string_view op_str) { + static const absl::flat_hash_map* string_map = + new absl::flat_hash_map({ +{% for op in spec.OPS -%} +{"{{ op.name }}", Op::{{ op.enum_name }}}, +{% endfor -%} + }); + auto found = string_map->find(op_str); + if (found == string_map->end()) { + return absl::InvalidArgumentError( + absl::StrCat("Unknown operation for string-to-op conversion: ", op_str)); + } + return found->second; +} + +bool OpIsCompare(Op op) { +{% for op in spec.OPS -%} +{%- if spec.Property.COMPARISON in op.properties -%} +if (op == Op::{{ op.enum_name }}) return true; +{% endif -%} +{% endfor -%} + return false; +} + +bool OpIsAssociative(Op op) { +{% for op in spec.OPS -%} +{%- if spec.Property.ASSOCIATIVE in op.properties -%} +if (op == Op::{{ op.enum_name }}) return true; +{% endif -%} +{% endfor -%} + return false; +} + +bool OpIsCommutative(Op op) { +{% for op in spec.OPS -%} +{%- if spec.Property.COMMUTATIVE in op.properties -%} +if (op == Op::{{ op.enum_name }}) return true; +{% endif -%} +{% endfor -%} + return false; +} + +bool OpIsBitWise(Op op) { +{% for op in spec.OPS -%} +{%- if spec.Property.BITWISE in op.properties -%} +if (op == Op::{{ op.enum_name }}) return true; +{% endif -%} +{% endfor -%} + return false; +} + +{% for op_class in spec.OpClass.kinds.values() -%} +template<> +bool IsOpClass<{{op_class.name}}>(Op op) { +{% for op in spec.OPS -%} +{%- if op.op_class == op_class -%} +if (op == Op::{{ op.enum_name }}) return true; +{% endif -%} +{% endfor -%} + return false; +} + +{% endfor %} + +std::ostream& operator<<(std::ostream& os, Op op) { + os << OpToString(op); + return os; +} + +} // namespace xls diff --git a/xls/ir/op_specification.py b/xls/ir/op_specification.py new file mode 100644 index 0000000000..f9eea96593 --- /dev/null +++ b/xls/ir/op_specification.py @@ -0,0 +1,916 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +"""Specification for XLS ops. + +The contents of this file is used to generate op.h and op.cc. +""" + +import collections +import enum +from typing import List, Optional, Text + + +class ConstructorArgument(object): + """Describes an argument to the Node class constructor. + + Attributes: + name: Name of the argument. + cpp_type: The C++ type of the argument. + clone_expression: The expression to use when calling the constructor when + cloning. + """ + + def __init__(self, + name: Text, + cpp_type: Text, + clone_expression: Optional[Text] = None): + self.name = name + self.cpp_type = cpp_type + self.clone_expression = clone_expression + + +class DataMember(object): + """Describes a data member of the Node class. + + Attributes: + name: Name of the data member. Should have a trailing '_' to follow the C++ + style guide. + cpp_type: The C++ type of the data member. + init: The expression to initialize the data member with. This is evaluated + in the constructor member initializer list. + equals_tmpl: A Python format string defining the expression for testing this + member for equality. The format fields are named 'lhs' and 'rhs'. Example: + '{lhs}.EqualTo({rhs})'. + """ + + def __init__(self, + name: Text, + cpp_type: Text, + init: Text, + equals_tmpl: Text = '{lhs} == {rhs}'): + self.name = name + self.cpp_type = cpp_type + self.init = init + self.equals_tmpl = equals_tmpl + + +class Method(object): + """Describes a method of the Node class. + + Attributes: + name: Name of the method. + return_cpp_type: The C++ type of the value returned by the method. + expression: The expression to produce the value returned by the method. + params: Optional string of C++ parameters that gets inserted into the method + signature between "()"s. + """ + + def __init__(self, + name: Text, + return_cpp_type: Text, + expression: Text, + params: Text = ''): + self.name = name + self.return_cpp_type = return_cpp_type + self.expression = expression + self.params = params + + +class Attribute(object): + """Describes an attribute of a Node class. + + An Attribute desugars into a ConstructorArgument, DataMember, and an accessor + Method. + + Attributes: + name: The name of the attribute. The constructor argument and accessor + method share this name. The data member is the same name with a '_' + suffix. + constructor_argument: The ConstructorArgument of the attribute. + data_member: The DataMember of the attribute. + method: The accessor Method of the attribute. + """ + + def __init__(self, + name: Text, + cpp_type: Text, + arg_cpp_type: Optional[Text] = None, + return_cpp_type: Optional[Text] = None, + equals_tmpl: Text = '{lhs} == {rhs}'): + """Initialize an Attribute. + + Args: + name: The name of the attribute. The constructor argument and accessor + method share this name. The data member is the same name with a '_' + suffix. + cpp_type: The C++ type of the data member holding the attribute. + arg_cpp_type: The C++ type of the constructor argument for passing in the + attribute value. Defaults to cpp_type. + return_cpp_type: The return type of the accessor method for the attribute. + Defaults to cpp_type. + equals_tmpl: A Python format string defining the expression for testing + this member for equality. The format fields are named 'lhs' and 'rhs'. + For example, '{lhs}.EqualTo({rhs})'. + """ + + self.name = name + self.constructor_argument = ConstructorArgument( + name=self.name, + cpp_type=cpp_type if arg_cpp_type is None else arg_cpp_type, + clone_expression=self.name + '()') + self.data_member = DataMember( + name=name + '_', cpp_type=cpp_type, init=name, equals_tmpl=equals_tmpl) + self.method = Method( + name=name, + return_cpp_type=cpp_type + if return_cpp_type is None else return_cpp_type, + expression=self.data_member.name) + + +class Int64Attribute(Attribute): + + def __init__(self, name): + super(Int64Attribute, self).__init__(name, cpp_type='int64') + + +class TypeAttribute(Attribute): + + def __init__(self, name): + super(TypeAttribute, self).__init__(name, cpp_type='Type*') + + +class FunctionAttribute(Attribute): + + def __init__(self, name): + super(FunctionAttribute, self).__init__( + name, + cpp_type='Function*', + equals_tmpl='{lhs}->IsDefinitelyEqualTo({rhs})') + + +class ValueAttribute(Attribute): + + def __init__(self, name): + super(ValueAttribute, self).__init__( + name, cpp_type='Value', return_cpp_type='const Value&') + + +class StringAttribute(Attribute): + + def __init__(self, name): + super(StringAttribute, self).__init__( + name, + cpp_type='std::string', + return_cpp_type='const std::string&', + arg_cpp_type='absl::string_view') + + +class LsbOrMsbAttribute(Attribute): + + def __init__(self, name): + super(LsbOrMsbAttribute, self).__init__( + name, + cpp_type='LsbOrMsb', + return_cpp_type='LsbOrMsb', + arg_cpp_type='LsbOrMsb') + + +class Property(enum.Enum): + """Enumeration of properties of Ops. + + An Op can have zero or more properties. + """ + BITWISE = 1 # Ops such as kXor, kAnd, and kOr + ASSOCIATIVE = 2 + COMMUTATIVE = 3 + COMPARISON = 4 + + +class Operand(object): + + def __init__(self, name: Text): + self.name = name + self.add_method = 'AddOperand' + + +class OperandSpan(Operand): + + def __init__(self, name: Text): + super(OperandSpan, self).__init__(name) + self.name = name + self.add_method = 'AddOperands' + + +class OptionalOperand(Operand): + + def __init__(self, name: Text): + super(OptionalOperand, self).__init__(name) + self.name = name + self.add_method = 'AddOptionalOperand' + + +class OpClass(object): + """Describes a C++ subclass of xls::Node.""" + + # Collection of all OpClass instances. + kinds = collections.OrderedDict() + + def __init__(self, + name: Text, + op: Text, + operands: List[Operand], + xls_type_expression: Text, + attributes: List[Attribute] = (), + extra_constructor_args=(), + extra_data_members=(), + extra_methods: List[Method] = (), + custom_clone_method: bool = False): + """Initializes an OpClass. + + Args: + name: The name of the class. + op: The expression for the Op associated with this class (e.g., + 'Op::kParam'). + operands: The list of operands. + xls_type_expression: The expression evaluated in the constructor member + initialization list which produces the xls::Type* of this node. + attributes: List of Attributes of this class. + extra_constructor_args: List of additional constructor arguments. + extra_data_members: List of additional data members. + extra_methods: List of additional class methods. + custom_clone_method: Whether this class has a custom clone method. If true + the method should be defined directly in nodes_source.tmpl. + """ + self.name = name + self.op = op + self.operands = operands + self.xls_type_expression = xls_type_expression + self.attributes = attributes + self.extra_constructor_args = extra_constructor_args + self.extra_data_members = extra_data_members + self.extra_methods = extra_methods + self.custom_clone_method = custom_clone_method + + def constructor_args_str(self) -> Text: + """The constructor arguments list as a single string.""" + args = [ + ConstructorArgument('loc', 'absl::optional', 'loc()') + ] + for o in self.operands: + if isinstance(o, OperandSpan): + args.append(ConstructorArgument(o.name, 'absl::Span')) + elif isinstance(o, OptionalOperand): + args.append(ConstructorArgument(o.name, 'absl::optional')) + else: + args.append(ConstructorArgument(o.name, 'Node*')) + args.extend(a.constructor_argument for a in self.attributes) + args.extend(self.extra_constructor_args) + args.append(ConstructorArgument('function', 'Function*', 'function()')) + return ', '.join(a.cpp_type + ' ' + a.name for a in args) + + def base_constructor_invocation(self): + return 'Node({op}, {type_expr}, loc, function)'.format( + op=self.op, type_expr=self.xls_type_expression) + + def methods(self) -> List[Method]: + """Returns the methods defined for this class.""" + methods = [a.method for a in self.attributes] + methods.extend(self.extra_methods) + return methods + + def clone_args_str(self, new_operands: Text) -> Text: + """Returns the arguments to pass to the constructor during cloning. + + Args: + new_operands: The name of the span variable containing the new operands + during cloning. + """ + assert not self.custom_clone_method + args = ['loc()'] + if len(self.operands) == 1 and isinstance(self.operands[0], OperandSpan): + args.append(new_operands) + else: + for i, o in enumerate(self.operands): + assert isinstance(o, Operand) + args.append('{}[{}]'.format(new_operands, i)) + args.extend('{}()'.format(a.name) for a in self.attributes) + args.extend(a.clone_expression for a in self.extra_constructor_args) + return ', '.join(args) + + def data_members(self) -> List[DataMember]: + """Returns the data members of the class.""" + members = [a.data_member for a in self.attributes] + members.extend(self.extra_data_members) + return members + + def equal_to_expr(self) -> Text: + """Returns expression used in IsDefinitelyEqualTo to compare expression.""" + + def data_member_equal(m): + lhs = m.name + rhs = 'other->As<{cls}>()->{name}'.format(cls=self.name, name=m.name) + return m.equals_tmpl.format(lhs=lhs, rhs=rhs) + + assert self.data_members() + return '&& '.join(data_member_equal(m) for m in self.data_members()) + + +class Op(object): + """Describes an xls::Op. + + Attributes: + enum_name: The name of the C++ enum value (e.g., 'kParam'). + name: The name of the op as it appears in textual IR (e.g., 'param'). + op_class: The OpClass value indicating the C++ Node subclass of the op. + properties: A List of Properties describing the op. + """ + + def __init__(self, enum_name: Text, name: Text, op_class: OpClass, + properties: List[Property]): + self.enum_name = enum_name + self.name = name + self.op_class = op_class + self.properties = properties + +# pyformat: disable +OpClass.kinds['ARRAY'] = OpClass( + name='Array', + op='Op::kArray', + operands=[OperandSpan('elements')], + xls_type_expression='function->package()->GetArrayType(elements.size(), element_type)', + attributes=[TypeAttribute('element_type')], + extra_methods=[Method(name='size', + return_cpp_type='int64', + expression='operand_count()')] +) + +OpClass.kinds['ARRAY_INDEX'] = OpClass( + name='ArrayIndex', + op='Op::kArrayIndex', + operands=[Operand('arg'), Operand('index')], + xls_type_expression='arg->GetType()->AsArrayOrDie()->element_type()', +) + +OpClass.kinds['BIN_OP'] = OpClass( + name='BinOp', + op='op', + operands=[Operand('lhs'), Operand('rhs')], + xls_type_expression='lhs->GetType()', + extra_constructor_args=[ConstructorArgument(name='op', + cpp_type='Op', + clone_expression='op()')] +) + +OpClass.kinds['ARITH_OP'] = OpClass( + name='ArithOp', + op='op', + operands=[Operand('lhs'), Operand('rhs')], + xls_type_expression='function->package()->GetBitsType(width)', + extra_constructor_args=[ConstructorArgument(name='op', + cpp_type='Op', + clone_expression='op()')], + attributes=[Int64Attribute('width')] +) + +OpClass.kinds['BITWISE_REDUCTION_OP'] = OpClass( + name='BitwiseReductionOp', + op='op', + operands=[Operand('operand')], + xls_type_expression='function->package()->GetBitsType(1)', + extra_constructor_args=[ConstructorArgument(name='op', + cpp_type='Op', + clone_expression='op()')] +) + +OpClass.kinds['NARY_OP'] = OpClass( + name='NaryOp', + op='op', + operands=[OperandSpan('args')], + xls_type_expression='args[0]->GetType()', + extra_constructor_args=[ConstructorArgument(name='op', + cpp_type='Op', + clone_expression='op()')] +) + +OpClass.kinds['BIT_SLICE'] = OpClass( + name='BitSlice', + op='Op::kBitSlice', + operands=[Operand('arg')], + xls_type_expression='function->package()->GetBitsType(width)', + attributes=[Int64Attribute('start'), + Int64Attribute('width')], +) + +OpClass.kinds['COMPARE_OP'] = OpClass( + name='CompareOp', + op='op', + operands=[Operand('lhs'), Operand('rhs')], + xls_type_expression='function->package()->GetBitsType(1)', + extra_constructor_args=[ConstructorArgument(name='op', + cpp_type='Op', + clone_expression='op()')] +) + +OpClass.kinds['CONCAT'] = OpClass( + name='Concat', + op='Op::kConcat', + operands=[OperandSpan('args')], + xls_type_expression='GetConcatType(function->package(), args)', + extra_methods=[Method(name='GetOperandSliceData', return_cpp_type='SliceData', expression=None, params='int64 operandno')] +) + +OpClass.kinds['COUNTED_FOR'] = OpClass( + name='CountedFor', + op='Op::kCountedFor', + operands=[Operand('initial_value'), + OperandSpan('invariant_args')], + xls_type_expression='initial_value->GetType()', + attributes=[Int64Attribute('trip_count'), + Int64Attribute('stride'), + FunctionAttribute('body')], + extra_methods=[Method(name='initial_value', + return_cpp_type='Node*', + expression='operand(0)'), + Method(name='invariant_args', + return_cpp_type='absl::Span', + expression='operands().subspan(1)')], + custom_clone_method=True +) + +OpClass.kinds['EXTEND_OP'] = OpClass( + name='ExtendOp', + op='op', + operands=[Operand('arg')], + xls_type_expression='function->package()->GetBitsType(new_bit_count)', + attributes=[Int64Attribute('new_bit_count')], + extra_constructor_args=[ConstructorArgument(name='op', + cpp_type='Op', + clone_expression='op()')] +) + +OpClass.kinds['INVOKE'] = OpClass( + name='Invoke', + op='Op::kInvoke', + operands=[OperandSpan('args')], + xls_type_expression='to_apply->return_value()->GetType()', + attributes=[FunctionAttribute('to_apply')], +) + +OpClass.kinds['LITERAL'] = OpClass( + name='Literal', + op='Op::kLiteral', + operands=[], + xls_type_expression='function->package()->GetTypeForValue(value)', + attributes=[ValueAttribute('value')], + extra_methods=[Method('IsZero', 'bool', + 'value().IsBits() && value().bits().IsAllZeros()')], +) + +OpClass.kinds['MAP'] = OpClass( + name='Map', + op='Op::kMap', + operands=[Operand('arg')], + xls_type_expression='GetMapType(arg, to_apply)', + attributes=[FunctionAttribute('to_apply')], +) + +OpClass.kinds['ONE_HOT'] = OpClass( + name='OneHot', + op='Op::kOneHot', + attributes=[LsbOrMsbAttribute('priority')], + operands=[Operand('input')], + xls_type_expression='function->package()->GetBitsType(' + 'input->BitCountOrDie() + 1)' +) + +OpClass.kinds['ONE_HOT_SELECT'] = OpClass( + name='OneHotSelect', + op='Op::kOneHotSel', + operands=[Operand('selector'), + OperandSpan('cases')], + xls_type_expression='cases[0]->GetType()', + extra_methods=[Method(name='selector', + return_cpp_type='Node*', + expression='operand(0)'), + Method(name='cases', + return_cpp_type='absl::Span', + expression='operands().subspan(1)')], + custom_clone_method=True +) + +OpClass.kinds['PARAM'] = OpClass( + name='Param', + op='Op::kParam', + operands=[], + xls_type_expression='type', + attributes=[StringAttribute('name')], + extra_constructor_args=[ConstructorArgument(name='type', + cpp_type='Type*', + clone_expression='GetType()')], +) + +OpClass.kinds['SELECT'] = OpClass( + name='Select', + op='Op::kSel', + operands=[Operand('selector'), + OperandSpan('cases'), + OptionalOperand('default_value')], + xls_type_expression='cases[0]->GetType()', + extra_data_members=[ + DataMember(name='cases_size_', + cpp_type='int64', + init='cases.size()'), + DataMember(name='has_default_value_', + cpp_type='bool', + init='default_value.has_value()')], + extra_methods=[Method(name='selector', + return_cpp_type='Node*', + expression='operand(0)'), + Method(name='cases', + return_cpp_type='absl::Span', + expression='operands().subspan(1, cases_size_)'), + Method(name='default_value', + return_cpp_type='absl::optional', + expression='has_default_value_ ' + '? absl::optional(operands().back()) ' + ': absl::nullopt'), + Method(name='AllCases', + return_cpp_type='bool', + expression='', + params='std::function p'), + Method(name='any_case', + return_cpp_type='Node*', + expression='!cases().empty() ? cases().front() : default_value().has_value() ? default_value().value() : nullptr')], + custom_clone_method=True +) + +OpClass.kinds['TUPLE'] = OpClass( + name='Tuple', + op='Op::kTuple', + operands=[OperandSpan('elements')], + xls_type_expression='GetTupleType(function->package(), elements)', + extra_methods=[Method(name='size', + return_cpp_type='int64', + expression='operand_count()')], +) + +OpClass.kinds['TUPLE_INDEX'] = OpClass( + name='TupleIndex', + op='Op::kTupleIndex', + operands=[Operand('arg')], + xls_type_expression='arg->GetType()->AsTupleOrDie()->element_type(index)', + attributes=[Int64Attribute('index')], +) + +OpClass.kinds['UN_OP'] = OpClass( + name='UnOp', + op='op', + operands=[Operand('arg')], + xls_type_expression='arg->GetType()', + extra_constructor_args=[ConstructorArgument(name='op', + cpp_type='Op', + clone_expression='op()')] +) + +OpClass.kinds['DECODE'] = OpClass( + name='Decode', + op='Op::kDecode', + operands=[Operand('arg')], + xls_type_expression='function->package()->GetBitsType(width)', + attributes=[Int64Attribute('width')], +) + +OpClass.kinds['ENCODE'] = OpClass( + name='Encode', + op='Op::kEncode', + operands=[Operand('arg')], + # Subtract one from the width expression to account for zero-based + # numbering. + xls_type_expression='function->package()->GetBitsType(Bits::MinBitCountUnsigned(arg->BitCountOrDie() - 1))', +) + +OPS = [ + Op( + enum_name='kAdd', + name='add', + op_class=OpClass.kinds['BIN_OP'], + properties=[Property.ASSOCIATIVE, + Property.COMMUTATIVE], + ), + Op( + enum_name='kAnd', + name='and', + op_class=OpClass.kinds['NARY_OP'], + properties=[Property.BITWISE, + Property.ASSOCIATIVE, + Property.COMMUTATIVE], + ), + Op( + enum_name='kAndReduce', + name='and_reduce', + op_class=OpClass.kinds['BITWISE_REDUCTION_OP'], + properties=[], + ), + Op( + enum_name='kNand', + name='nand', + op_class=OpClass.kinds['NARY_OP'], + # Note: not associative, because of the inversion. + properties=[Property.BITWISE, + Property.COMMUTATIVE], + ), + Op( + enum_name='kNor', + name='nor', + op_class=OpClass.kinds['NARY_OP'], + # Note: not associative, because of the inversion. + properties=[Property.BITWISE, + Property.COMMUTATIVE], + ), + Op( + enum_name='kArray', + name='array', + op_class=OpClass.kinds['ARRAY'], + properties=[], + ), + Op( + enum_name='kArrayIndex', + name='array_index', + op_class=OpClass.kinds['ARRAY_INDEX'], + properties=[], + ), + Op( + enum_name='kBitSlice', + name='bit_slice', + op_class=OpClass.kinds['BIT_SLICE'], + properties=[], + ), + Op( + enum_name='kConcat', + name='concat', + op_class=OpClass.kinds['CONCAT'], + properties=[], + ), + Op( + enum_name='kCountedFor', + name='counted_for', + op_class=OpClass.kinds['COUNTED_FOR'], + properties=[], + ), + Op( + enum_name='kDecode', + name='decode', + op_class=OpClass.kinds['DECODE'], + properties=[], + ), + Op( + enum_name='kEncode', + name='encode', + op_class=OpClass.kinds['ENCODE'], + properties=[], + ), + Op( + enum_name='kEq', + name='eq', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON, + Property.COMMUTATIVE], + ), + Op( + enum_name='kIdentity', + name='identity', + op_class=OpClass.kinds['UN_OP'], + properties=[], + ), + Op( + enum_name='kInvoke', + name='invoke', + op_class=OpClass.kinds['INVOKE'], + properties=[], + ), + Op( + enum_name='kLiteral', + name='literal', + op_class=OpClass.kinds['LITERAL'], + properties=[], + ), + Op( + enum_name='kMap', + name='map', + op_class=OpClass.kinds['MAP'], + properties=[], + ), + Op( + enum_name='kNe', + name='ne', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON, + Property.COMMUTATIVE], + ), + Op( + enum_name='kNeg', + name='neg', + op_class=OpClass.kinds['UN_OP'], + properties=[], + ), + Op( + enum_name='kNot', + name='not', + op_class=OpClass.kinds['UN_OP'], + properties=[Property.BITWISE], + ), + Op( + enum_name='kOneHot', + name='one_hot', + op_class=OpClass.kinds['ONE_HOT'], + properties=[], + ), + Op( + enum_name='kOneHotSel', + name='one_hot_sel', + op_class=OpClass.kinds['ONE_HOT_SELECT'], + properties=[], + ), + Op( + enum_name='kOr', + name='or', + op_class=OpClass.kinds['NARY_OP'], + properties=[Property.BITWISE, + Property.ASSOCIATIVE, + Property.COMMUTATIVE], + ), + Op( + enum_name='kOrReduce', + name='or_reduce', + op_class=OpClass.kinds['BITWISE_REDUCTION_OP'], + properties=[], + ), + Op( + enum_name='kParam', + name='param', + op_class=OpClass.kinds['PARAM'], + properties=[], + ), + Op( + enum_name='kReverse', + name='reverse', + op_class=OpClass.kinds['UN_OP'], + properties=[], + ), + Op( + enum_name='kSDiv', + name='sdiv', + op_class=OpClass.kinds['BIN_OP'], + properties=[], + ), + Op( + enum_name='kSel', + name='sel', + op_class=OpClass.kinds['SELECT'], + properties=[], + ), + Op( + enum_name='kSGe', + name='sge', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON], + ), + Op( + enum_name='kSGt', + name='sgt', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON], + ), + Op( + enum_name='kShll', + name='shll', + op_class=OpClass.kinds['BIN_OP'], + properties=[], + ), + Op( + enum_name='kShrl', + name='shrl', + op_class=OpClass.kinds['BIN_OP'], + properties=[], + ), + Op( + enum_name='kShra', + name='shra', + op_class=OpClass.kinds['BIN_OP'], + properties=[], + ), + Op( + enum_name='kSignExt', + name='sign_ext', + op_class=OpClass.kinds['EXTEND_OP'], + properties=[], + ), + Op( + enum_name='kSLe', + name='sle', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON], + ), + Op( + enum_name='kSLt', + name='slt', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON], + ), + Op( + enum_name='kSMul', + name='smul', + op_class=OpClass.kinds['ARITH_OP'], + properties=[Property.ASSOCIATIVE, + Property.COMMUTATIVE], + ), + Op( + enum_name='kSub', + name='sub', + op_class=OpClass.kinds['BIN_OP'], + properties=[], + ), + Op( + enum_name='kTuple', + name='tuple', + op_class=OpClass.kinds['TUPLE'], + properties=[], + ), + Op( + enum_name='kTupleIndex', + name='tuple_index', + op_class=OpClass.kinds['TUPLE_INDEX'], + properties=[], + ), + Op( + enum_name='kUDiv', + name='udiv', + op_class=OpClass.kinds['BIN_OP'], + properties=[], + ), + Op( + enum_name='kUGe', + name='uge', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON], + ), + Op( + enum_name='kUGt', + name='ugt', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON], + ), + Op( + enum_name='kULe', + name='ule', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON], + ), + Op( + enum_name='kULt', + name='ult', + op_class=OpClass.kinds['COMPARE_OP'], + properties=[Property.COMPARISON], + ), + Op( + enum_name='kUMul', + name='umul', + op_class=OpClass.kinds['ARITH_OP'], + properties=[Property.ASSOCIATIVE, + Property.COMMUTATIVE], + ), + Op( + enum_name='kXor', + name='xor', + op_class=OpClass.kinds['NARY_OP'], + properties=[Property.BITWISE, + Property.ASSOCIATIVE, + Property.COMMUTATIVE], + ), + Op( + enum_name='kXorReduce', + name='xor_reduce', + op_class=OpClass.kinds['BITWISE_REDUCTION_OP'], + properties=[], + ), + Op( + enum_name='kZeroExt', + name='zero_ext', + op_class=OpClass.kinds['EXTEND_OP'], + properties=[], + ), +] +# pyformat: enable diff --git a/xls/ir/package.cc b/xls/ir/package.cc new file mode 100644 index 0000000000..deb1b4fe75 --- /dev/null +++ b/xls/ir/package.cc @@ -0,0 +1,349 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/package.h" + +#include + +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/strong_int.h" +#include "xls/ir/function.h" +#include "xls/ir/type.h" + +namespace xls { +namespace { +constexpr char kMain[] = "main"; +} + +Package::Package(absl::string_view name, + absl::optional entry) + : entry_(entry), name_(name) {} + +Package::~Package() {} + +Function* Package::AddFunction(std::unique_ptr f) { + functions_.push_back(std::move(f)); + return functions_.back().get(); +} + +xabsl::StatusOr Package::GetFunction( + absl::string_view func_name) const { + for (auto& f : functions_) { + if (f->name() == func_name) { + return f.get(); + } + } + return absl::NotFoundError(absl::StrFormat( + "Package does not have a function with name: \"%s\"; available: [%s]", + func_name, + absl::StrJoin(functions_, ", ", + [](std::string* out, const std::unique_ptr& f) { + absl::StrAppend(out, f->name()); + }))); +} + +void Package::DeleteDeadFunctions(absl::Span dead_funcs) { + std::vector> to_unlink; + for (std::unique_ptr& f : functions_) { + if (std::find(dead_funcs.begin(), dead_funcs.end(), f.get()) != + dead_funcs.end()) { + XLS_VLOG(1) << "Function is dead: " << f->name(); + to_unlink.push_back(std::move(f)); + f = nullptr; + } + } + + // Destruct all the functions here instead of when they go out of scope, + // just in case there's meaningful logging in the destructor. + to_unlink.clear(); + + // Get rid of nullptrs we made in the functions vector. + functions_.erase(std::remove(functions_.begin(), functions_.end(), nullptr), + functions_.end()); +} + +xabsl::StatusOr Package::EntryFunction() { + auto by_name = GetFunctionByName(); + + if (entry_.has_value()) { + auto it = by_name.find(entry_.value()); + if (it != by_name.end()) { + return it->second; + } + std::string available = + absl::StrJoin(by_name.begin(), by_name.end(), ", ", + [](std::string* out, + const std::pair& item) { + absl::StrAppend(out, "\"", item.first, "\""); + }); + + return absl::NotFoundError( + absl::StrFormat("Could not find entry function for this package; " + "tried: [\"%s\"]; available: %s", + entry_.value(), available)); + } + + // Try a few possibilities of names for the canonical entry function. + const std::vector to_try = { + kMain, + name(), + absl::StrCat("__", name(), "__", kMain), + absl::StrCat("__", name(), "__", name()), + }; + + for (const std::string& attempt : to_try) { + auto it = by_name.find(attempt); + if (it != by_name.end()) { + return it->second; + } + } + + // Finally we use the only function if only one exists. + if (functions_.size() == 1) { + return functions_.front().get(); + } + auto quote = [](std::string* out, const std::string& s) { + absl::StrAppend(out, "\"", s, "\""); + }; + return absl::NotFoundError(absl::StrFormat( + "Could not find an entry function for the \"%s\" package; " + "attempted: [%s]", + name(), absl::StrJoin(to_try, ", ", quote))); +} + +xabsl::StatusOr Package::EntryFunction() const { + XLS_ASSIGN_OR_RETURN(Function * f, + const_cast(this)->EntryFunction()); + return f; +} + +SourceLocation Package::AddSourceLocation(absl::string_view filename, + Lineno lineno, Colno colno) { + Fileno this_fileno = GetOrCreateFileno(filename); + return SourceLocation(this_fileno, lineno, colno); +} + +std::string Package::SourceLocationToString(const SourceLocation loc) { + const std::string unknown = "UNKNOWN"; + absl::string_view filename = + fileno_to_filename_.find(loc.fileno()) != fileno_to_filename_.end() + ? fileno_to_filename_.at(loc.fileno()) + : unknown; + return absl::StrFormat("%s:%d", filename, loc.lineno().value()); +} + +BitsType* Package::GetBitsType(int64 bit_count) { + if (bit_count_to_type_.find(bit_count) != bit_count_to_type_.end()) { + return &bit_count_to_type_.at(bit_count); + } + auto it = bit_count_to_type_.emplace(bit_count, BitsType(bit_count)); + BitsType* new_type = &(it.first->second); + owned_types_.insert(new_type); + return new_type; +} + +ArrayType* Package::GetArrayType(int64 size, Type* element_type) { + ArrayKey key{size, element_type}; + if (array_types_.find(key) != array_types_.end()) { + return &array_types_.at(key); + } + XLS_CHECK(IsOwnedType(element_type)) + << "Type is not owned by package: " << *element_type; + auto it = array_types_.emplace(key, ArrayType(size, element_type)); + ArrayType* new_type = &(it.first->second); + owned_types_.insert(new_type); + return new_type; +} + +TupleType* Package::GetTupleType(absl::Span element_types) { + TypeVec key(element_types.begin(), element_types.end()); + if (tuple_types_.find(key) != tuple_types_.end()) { + return &tuple_types_.at(key); + } + for (const Type* element_type : element_types) { + XLS_CHECK(IsOwnedType(element_type)) + << "Type is not owned by package: " << *element_type; + } + auto it = tuple_types_.emplace(key, TupleType(element_types)); + TupleType* new_type = &(it.first->second); + owned_types_.insert(new_type); + return new_type; +} + +FunctionType* Package::GetFunctionType(absl::Span args_types, + Type* return_type) { + std::string key = FunctionType(args_types, return_type).ToString(); + if (function_types_.find(key) != function_types_.end()) { + return &function_types_.at(key); + } + for (Type* t : args_types) { + XLS_CHECK(IsOwnedType(t)) + << "Parameter type is not owned by package: " << t->ToString(); + } + auto it = function_types_.emplace(key, FunctionType(args_types, return_type)); + FunctionType* new_type = &(it.first->second); + owned_function_types_.insert(new_type); + return new_type; +} + +xabsl::StatusOr Package::GetTypeFromProto(const TypeProto& proto) { + if (!proto.has_type_enum()) { + return absl::InvalidArgumentError("Missing type_enum field in TypeProto."); + } + if (proto.type_enum() == TypeProto::BITS) { + if (!proto.has_bit_count() || proto.bit_count() < 0) { + return absl::InvalidArgumentError( + "Missing or invalid bit_count field in TypeProto."); + } + return GetBitsType(proto.bit_count()); + } + if (proto.type_enum() == TypeProto::TUPLE) { + std::vector elements; + for (const TypeProto& element_proto : proto.tuple_elements()) { + XLS_ASSIGN_OR_RETURN(Type * element, GetTypeFromProto(element_proto)); + elements.push_back(element); + } + return GetTupleType(elements); + } + if (proto.type_enum() == TypeProto::ARRAY) { + if (!proto.has_array_size() || proto.array_size() < 0) { + return absl::InvalidArgumentError( + "Missing or invalid array_size field in TypeProto."); + } + if (!proto.has_array_element()) { + return absl::InvalidArgumentError( + "Missing array_element field in TypeProto."); + } + XLS_ASSIGN_OR_RETURN(Type * element_type, + GetTypeFromProto(proto.array_element())); + return GetArrayType(proto.array_size(), element_type); + } + return absl::InvalidArgumentError(absl::StrFormat( + "Invalid type_enum value in TypeProto: %d", proto.type_enum())); +} + +xabsl::StatusOr Package::GetFunctionTypeFromProto( + const FunctionTypeProto& proto) { + std::vector param_types; + for (const TypeProto& param_proto : proto.parameters()) { + XLS_ASSIGN_OR_RETURN(Type * param_type, GetTypeFromProto(param_proto)); + param_types.push_back(param_type); + } + if (!proto.has_return_type()) { + return absl::InvalidArgumentError( + "Missing return_type field in FunctionTypeProto."); + } + XLS_ASSIGN_OR_RETURN(Type * return_type, + GetTypeFromProto(proto.return_type())); + return GetFunctionType(param_types, return_type); +} + +Type* Package::GetTypeForValue(const Value& value) { + switch (value.kind()) { + case ValueKind::kBits: + return GetBitsType(value.bits().bit_count()); + case ValueKind::kTuple: { + std::vector element_types; + for (const Value& value : value.elements()) { + element_types.push_back(GetTypeForValue(value)); + } + return GetTupleType(element_types); + } + case ValueKind::kArray: { + // No element type can be inferred for 0-element arrays. + if (value.empty()) { + return GetArrayType(0, nullptr); + } + return GetArrayType(value.size(), GetTypeForValue(value.elements()[0])); + } + case ValueKind::kInvalid: + break; + } + XLS_LOG(FATAL) << "Invalid value for type extraction."; +} + +Fileno Package::GetOrCreateFileno(absl::string_view filename) { + // Attempt to add a new fileno/filename pair to the map. + auto this_fileno = Fileno(filename_to_fileno_.size()); + if (auto it = filename_to_fileno_.find(std::string(filename)); + it != filename_to_fileno_.end()) { + return it->second; + } + filename_to_fileno_.emplace(std::string(filename), this_fileno); + fileno_to_filename_.emplace(this_fileno, std::string(filename)); + + return this_fileno; +} + +int64 Package::GetNodeCount() const { + int64 count = 0; + for (const auto& f : functions()) { + count += f->node_count(); + } + return count; +} + +bool Package::IsDefinitelyEqualTo(const Package* other) const { + auto entry_function_status = EntryFunction(); + if (!entry_function_status.ok()) { + return false; + } + auto other_entry_function_status = other->EntryFunction(); + if (!other_entry_function_status.ok()) { + return false; + } + const Function* entry = entry_function_status.value(); + const Function* other_entry = other_entry_function_status.value(); + return entry->IsDefinitelyEqualTo(other_entry); +} + +std::string Package::DumpIr() const { + std::string out; + absl::StrAppend(&out, "package ", name(), "\n\n"); + std::vector function_dumps; + for (auto& function : functions()) { + function_dumps.push_back(function->DumpIr()); + } + absl::StrAppend(&out, absl::StrJoin(function_dumps, "\n")); + return out; +} + +std::ostream& operator<<(std::ostream& os, const Package& package) { + os << package.DumpIr(); + return os; +} + +#define UnorderedMap std::unordered_map + +UnorderedMap Package::GetFunctionByName() { + UnorderedMap name_to_function; + for (std::unique_ptr& function : functions_) { + name_to_function[function->name()] = function.get(); + } + return name_to_function; +} + +std::vector Package::GetFunctionNames() const { + std::vector names; + for (const std::unique_ptr& function : functions_) { + names.push_back(function->name()); + } + std::sort(names.begin(), names.end()); + return names; +} + +} // namespace xls diff --git a/xls/ir/package.h b/xls/ir/package.h new file mode 100644 index 0000000000..8c50de0e67 --- /dev/null +++ b/xls/ir/package.h @@ -0,0 +1,207 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_PACKAGE_H_ +#define THIRD_PARTY_XLS_IR_PACKAGE_H_ + +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/container/inlined_vector.h" +#include "absl/container/node_hash_map.h" +#include "absl/strings/string_view.h" +#include "xls/common/integral_types.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/fileno.h" +#include "xls/ir/source_location.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" + +namespace xls { + +class Function; + +class Package { + public: + explicit Package(absl::string_view name, + absl::optional entry = absl::nullopt); + + // Note: functions have parent pointers to their packages, so we don't want + // them to be moved or copied; this makes Package non-moveable non-copyable. + Package(const Package& other) = delete; + Package& operator=(const Package& other) = delete; + + virtual ~Package(); + + // Returns whether the given type is one of the types owned by this package. + bool IsOwnedType(const Type* type) { + return owned_types_.find(type) != owned_types_.end(); + } + bool IsOwnedFunctionType(const FunctionType* function_type) { + return owned_function_types_.find(function_type) != + owned_function_types_.end(); + } + + BitsType* GetBitsType(int64 bit_count); + ArrayType* GetArrayType(int64 size, Type* element_type); + TupleType* GetTupleType(absl::Span element_types); + FunctionType* GetFunctionType(absl::Span args_types, + Type* return_type); + + // Creates and returned an owned type constructed from the given proto. + xabsl::StatusOr GetTypeFromProto(const TypeProto& proto); + xabsl::StatusOr GetFunctionTypeFromProto( + const FunctionTypeProto& proto); + + Type* GetTypeForValue(const Value& value); + + Function* AddFunction(std::unique_ptr f); + + // Get a function by name. + xabsl::StatusOr GetFunction(absl::string_view func_name) const; + + // Remove (dead) functions. + void DeleteDeadFunctions(absl::Span dead_funcs); + + // Returns the entry function of the package. + xabsl::StatusOr EntryFunction(); + xabsl::StatusOr EntryFunction() const; + + // Returns a new SourceLocation object containing a Fileno and Lineno pair. + // SourceLocation objects are added to XLS IR nodes and used for debug + // tracing. + // + // An example of how this function might be used from C++ is shown below: + // __FILE__ and __LINE__ macros are passed as arguments to a SourceLocation + // builder and the result is passed to a Node builder method: + // + // SourceLocation loc = package.AddSourceLocation(__FILE__, __LINE__) + // function_builder.SomeNodeType(node_args, loc); + // + // An alternative front-end could instead call AddSourceLocation with the + // appropriate metada annotated on the front-end AST. + // + // If the file "filename" has been seen before, the Fileno is retrieved from + // an internal lookup table, otherwise a new Fileno id is generated and added + // to the table. + // TODO(dmlockhart): update to use ABSL_LOC and xabsl::SourceLocation. + SourceLocation AddSourceLocation(absl::string_view filename, Lineno lineno, + Colno colno); + + // Translates a SourceLocation object into a human readable debug identifier + // of the form: ":". + std::string SourceLocationToString(const SourceLocation loc); + + // Retrieves the next node ID to assign to a node in the package and + // increments the next node counter. For use in node construction. + int64 GetNextNodeId() { return next_node_id_++; } + + // Adds a file to the file-number table and returns its corresponding number. + // If it already exists, returns the existing file-number entry. + Fileno GetOrCreateFileno(absl::string_view filename); + + // Returns the total number of nodes in the graph. Traverses the functions and + // sums the node counts. + int64 GetNodeCount() const; + + // Returns the functions in this package. + absl::Span> functions() { + return absl::MakeSpan(functions_); + } + absl::Span> functions() const { + return functions_; + } + + const std::string& name() const { return name_; } + + // Returns true if analysis indicates that this package always produces the + // same value as 'other' when run with the same arguments. The analysis is + // conservative and false may be returned for some "equivalent" packages. + bool IsDefinitelyEqualTo(const Package* other) const; + + // Dumps the IR in a parsable text format. + std::string DumpIr() const; + + std::vector GetFunctionNames() const; + + int64 next_node_id() const { return next_node_id_; } + + // Intended for use by the parser when node ids are suggested by the IR text. + void set_next_node_id(int64 value) { next_node_id_ = value; } + + private: + friend class FunctionBuilder; + + absl::optional entry_; + + #define UnorderedSet std::unordered_set + #define UnorderedMap std::unordered_map + #define StableMap std::map + + // Helper that returns a map from the names of functions inside this package + // to the functions themselves. + UnorderedMap GetFunctionByName(); + + // Name of this package. + std::string name_; + + // Ordinal to assign to the next node created in this package. + int64 next_node_id_ = 1; + + std::vector> functions_; + + // Set of owned types in this package. + UnorderedSet owned_types_; + + // Set of owned function types in this package. + UnorderedSet owned_function_types_; + + // Mapping from bit count to the owned "bits" type with that many bits. Use + // node_hash_map for pointer stability. + StableMap bit_count_to_type_; + + // Mapping from the size and element type of an array type to the owned + // ArrayType. Use node_hash_map for pointer stability. + using ArrayKey = std::pair; + StableMap array_types_; + + // Mapping from elements to the owned tuple type. + // + // Uses node_hash_map for pointer stability. + using TypeVec = absl::InlinedVector; + StableMap tuple_types_; + + // Mapping from Type:ToString to the owned function type. Use + // node_hash_map for pointer stability. + StableMap function_types_; + + // Mapping of Fileno ids to string filenames, and vice-versa for reverse + // lookups. These two data structures must be updated together for consistency + // and should always contain the same number of entries. + UnorderedMap fileno_to_filename_; + UnorderedMap filename_to_fileno_; + +#undef StableMap +#undef UnorderedMap +#undef UnorderedSet +}; + +std::ostream& operator<<(std::ostream& os, const Package& package); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_PACKAGE_H_ diff --git a/xls/ir/package_test.cc b/xls/ir/package_test.cc new file mode 100644 index 0000000000..8b955ed298 --- /dev/null +++ b/xls/ir/package_test.cc @@ -0,0 +1,144 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/package.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/type.h" +#include "xls/ir/xls_type.pb.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class PackageTest : public IrTestBase {}; + +TEST_F(PackageTest, GetBitsTypes) { + Package p("my_package"); + EXPECT_FALSE(p.IsOwnedType(nullptr)); + + BitsType* bits42 = p.GetBitsType(42); + EXPECT_TRUE(bits42->IsBits()); + EXPECT_EQ(bits42->bit_count(), 42); + EXPECT_TRUE(p.IsOwnedType(bits42)); + EXPECT_EQ(bits42, p.GetBitsType(42)); + EXPECT_EQ("bits[42]", bits42->ToString()); + + BitsType imposter(42); + EXPECT_FALSE(p.IsOwnedType(&imposter)); + + BitsType* bits77 = p.GetBitsType(77); + EXPECT_TRUE(p.IsOwnedType(bits77)); + EXPECT_NE(bits77, bits42); + + TypeProto bits77_proto = bits77->ToProto(); + EXPECT_EQ(bits77_proto.type_enum(), TypeProto::BITS); + EXPECT_EQ(bits77_proto.bit_count(), 77); + EXPECT_THAT(p.GetTypeFromProto(bits77_proto), IsOkAndHolds(bits77)); +} + +TEST_F(PackageTest, GetArrayTypes) { + Package p("my_package"); + BitsType* bits42 = p.GetBitsType(42); + + ArrayType* array_bits42 = p.GetArrayType(123, bits42); + EXPECT_TRUE(array_bits42->IsArray()); + EXPECT_EQ(array_bits42->size(), 123); + EXPECT_EQ(array_bits42, p.GetArrayType(123, bits42)); + EXPECT_EQ(array_bits42->element_type(), p.GetBitsType(42)); + EXPECT_EQ("bits[42][123]", array_bits42->ToString()); + + ArrayType* array_array_bits42 = p.GetArrayType(444, array_bits42); + EXPECT_TRUE(array_array_bits42->IsArray()); + EXPECT_EQ(array_array_bits42->size(), 444); + EXPECT_EQ(array_array_bits42->element_type(), array_bits42); + EXPECT_EQ(array_array_bits42, p.GetArrayType(444, array_bits42)); + + EXPECT_EQ("bits[42][123][444]", array_array_bits42->ToString()); + + EXPECT_THAT(p.GetTypeFromProto(array_array_bits42->ToProto()), + IsOkAndHolds(array_array_bits42)); +} + +TEST_F(PackageTest, GetTupleTypes) { + Package p("my_package"); + BitsType* bits42 = p.GetBitsType(42); + BitsType* bits86 = p.GetBitsType(86); + + TupleType* tuple1 = p.GetTupleType({bits42}); + EXPECT_EQ("(bits[42])", tuple1->ToString()); + EXPECT_TRUE(p.IsOwnedType(tuple1)); + + TupleType* tuple2 = p.GetTupleType({bits42, bits86}); + EXPECT_EQ("(bits[42], bits[86])", tuple2->ToString()); + EXPECT_TRUE(p.IsOwnedType(tuple2)); + + TupleType* empty_tuple = p.GetTupleType({}); + EXPECT_EQ("()", empty_tuple->ToString()); + EXPECT_TRUE(p.IsOwnedType(empty_tuple)); + + TupleType* tuple_of_arrays = + p.GetTupleType({p.GetArrayType(2, bits42), p.GetArrayType(4, bits86)}); + EXPECT_EQ("(bits[42][2], bits[86][4])", tuple_of_arrays->ToString()); + EXPECT_TRUE(p.IsOwnedType(tuple_of_arrays)); + + TupleType* nested_tuple = p.GetTupleType({empty_tuple, tuple2, bits86}); + EXPECT_EQ("((), (bits[42], bits[86]), bits[86])", nested_tuple->ToString()); + EXPECT_TRUE(p.IsOwnedType(nested_tuple)); + + EXPECT_THAT(p.GetTypeFromProto(nested_tuple->ToProto()), + IsOkAndHolds(nested_tuple)); +} + +TEST_F(PackageTest, IsDefinitelyEqualTo) { + const char text1[] = R"( +package package1 + +fn f(x: bits[32], y: bits[32]) -> bits[32] { + ret add.1: bits[32] = add(x, y) +} + +fn main(a: bits[32]) -> bits[32] { + ret invoke.5: bits[32] = invoke(a, a, to_apply=f) +} +)"; + + // This package uses subtract instead of add. + const char text2[] = R"( +package package1 + +fn f(x: bits[32], y: bits[32]) -> bits[32] { + ret sub.1: bits[32] = sub(x, y) +} + +fn main(a: bits[32]) -> bits[32] { + ret invoke.5: bits[32] = invoke(a, a, to_apply=f) +} +)"; + + XLS_ASSERT_OK_AND_ASSIGN(auto p1, ParsePackage(text1)); + XLS_ASSERT_OK_AND_ASSIGN(auto p1_2, ParsePackage(text1)); + XLS_ASSERT_OK_AND_ASSIGN(auto p2, ParsePackage(text2)); + + EXPECT_TRUE(p1->IsDefinitelyEqualTo(p1.get())); + EXPECT_TRUE(p1->IsDefinitelyEqualTo(p1_2.get())); + EXPECT_FALSE(p1->IsDefinitelyEqualTo(p2.get())); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/python/BUILD b/xls/ir/python/BUILD new file mode 100644 index 0000000000..46f67b2d20 --- /dev/null +++ b/xls/ir/python/BUILD @@ -0,0 +1,355 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pytype tests are present in this file +load("//dependency_support/pybind11:pybind11.bzl", "xls_pybind_extension") + +package( + default_visibility = [ + "//xls:xls_internal", + "//xls/frontend/xlscc:xlscc_internal", + ], + licenses = ["notice"], # Apache 2.0 +) + +xls_pybind_extension( + name = "bits", + srcs = ["bits.cc"], + deps = [ + "//xls/common/status:statusor_pybind_caster", + "//xls/ir:bits", + ], +) + +py_test( + name = "bits_test", + srcs = ["bits_test.py"], + python_version = "PY3", + deps = [ + ":bits", + "@com_google_absl_py//absl/testing:absltest", + ], +) + +xls_pybind_extension( + name = "fileno", + srcs = ["fileno.cc"], + deps = [ + "//xls/ir:source_location", + ], +) + +py_test( + name = "fileno_test", + srcs = ["fileno_test.py"], + python_version = "PY3", + deps = [ + ":fileno", + "@com_google_absl_py//absl/testing:absltest", + ], +) + +xls_pybind_extension( + name = "format_preference", + srcs = ["format_preference.cc"], + deps = [ + "//xls/ir:format_preference", + ], +) + +xls_pybind_extension( + name = "function", + srcs = ["function.cc"], + py_deps = [ + ":type", + ], + deps = [ + ":wrapper_types", + "//xls/ir", + ], +) + +py_test( + name = "function_test", + srcs = ["function_test.py"], + python_version = "PY3", + deps = [ + ":bits", + ":function_builder", + ":package", + ":type", + ":value", + "@com_google_absl_py//absl/testing:absltest", + ], +) + +xls_pybind_extension( + name = "function_builder", + srcs = ["function_builder.cc"], + py_deps = [ + ":bits", # build_cleaner: keep + ":function", # build_cleaner: keep + ":lsb_or_msb", # build_cleaner: keep + ":package", # build_cleaner: keep + ":source_location", # build_cleaner: keep + ":type", # build_cleaner: keep + ":value", # build_cleaner: keep + ], + deps = [ + ":wrapper_types", + "//xls/common/status:statusor_pybind_caster", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/common/python:absl_casters", + ], +) + +xls_pybind_extension( + name = "ir_interpreter", + srcs = ["ir_interpreter.cc"], + py_deps = [ + ":function", # build_cleaner: keep + ":ir_interpreter_stats", # build_cleaner: keep + ":value", # build_cleaner: keep + ], + deps = [ + ":wrapper_types", + "//xls/common/status:statusor_pybind_caster", + "//xls/ir:ir_interpreter", + "//xls/common/python:absl_casters", + ], +) + +py_test( + name = "ir_interpreter_test", + srcs = ["ir_interpreter_test.py"], + python_version = "PY3", + deps = [ + ":ir_interpreter", + ":ir_parser", + ":value", # build_cleaner: keep + "@com_google_absl_py//absl/testing:absltest", + ], +) + +xls_pybind_extension( + name = "ir_parser", + srcs = ["ir_parser.cc"], + py_deps = [ + ":package", # build_cleaner: keep + ":type", # build_cleaner: keep + ":value", # build_cleaner: keep + ], + deps = [ + ":wrapper_types", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/common/status:statusor_pybind_caster", + "//xls/ir:ir_parser", + "//xls/common/python:absl_casters", + ], +) + +py_test( + name = "ir_parser_test", + srcs = ["ir_parser_test.py"], + python_version = "PY3", + deps = [ + ":format_preference", + ":ir_parser", + ":package", + "@com_google_absl_py//absl/testing:absltest", + ], +) + +py_test( + name = "function_builder_test", + srcs = ["function_builder_test.py"], + python_version = "PY3", + deps = [ + ":bits", + ":fileno", + ":function_builder", + ":lsb_or_msb", + ":package", + ":source_location", + ":value", + "@com_google_absl_py//absl/testing:absltest", + ], +) + +xls_pybind_extension( + name = "ir_interpreter_stats", + srcs = ["ir_interpreter_stats.cc"], + deps = [ + "//xls/ir:ir_interpreter_stats", + ], +) + +xls_pybind_extension( + name = "lsb_or_msb", + srcs = ["lsb_or_msb.cc"], + deps = [ + "//xls/ir", + ], +) + +xls_pybind_extension( + name = "number_parser", + srcs = ["number_parser.cc"], + py_deps = [ + ":bits", # build_cleaner: keep + ":format_preference", # build_cleaner: keep + ], + deps = [ + "//xls/common/status:statusor_pybind_caster", + "//xls/ir:number_parser", + ], +) + +py_test( + name = "number_parser_test", + srcs = ["number_parser_test.py"], + python_version = "PY3", + deps = [ + ":number_parser", + "@com_google_absl_py//absl/testing:absltest", + ], +) + +xls_pybind_extension( + name = "package", + srcs = ["package.cc"], + py_deps = [ + ":fileno", # build_cleaner: keep + ":function", # build_cleaner: keep + ":type", # build_cleaner: keep + ], + deps = [ + ":wrapper_types", + "//xls/common/status:statusor_pybind_caster", + "//xls/ir", + "//xls/common/python:absl_casters", + ], +) + +py_test( + name = "package_test", + srcs = ["package_test.py"], + python_version = "PY3", + deps = [ + ":bits", + ":fileno", + ":function", + ":function_builder", + ":package", + ":type", + ":value", + "@com_google_absl_py//absl/testing:absltest", + ], +) + +xls_pybind_extension( + name = "source_location", + srcs = ["source_location.cc"], + py_deps = [ + ":fileno", # build_cleaner: keep + ], + deps = [ + "//xls/ir:source_location", + ], +) + +xls_pybind_extension( + name = "type", + srcs = ["type.cc"], + deps = [ + ":wrapper_types", + "//xls/common/status:statusor_pybind_caster", + "//xls/ir", # build_cleaner: keep + "//xls/ir:type", + ], +) + +py_test( + name = "type_test", + srcs = ["type_test.py"], + python_version = "PY3", + deps = [ + ":bits", + ":function_builder", + ":package", + ":type", + ":value", + "//xls/common:test_base", + ], +) + +cc_library( + name = "wrapper_types", + hdrs = ["wrapper_types.h"], + copts = [ + "-fexceptions", + ], + features = ["-use_header_modules"], + deps = [ + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:type", + "@pybind11", + ], +) + +xls_pybind_extension( + name = "value", + srcs = ["value.cc"], + py_deps = [ + ":bits", # build_cleaner: keep + ":format_preference", # build_cleaner: keep + ], + deps = [ + "//xls/common/status:statusor_pybind_caster", + "//xls/ir:value", + "//xls/common/python:absl_casters", + ], +) + +xls_pybind_extension( + name = "verifier", + srcs = ["verifier.cc"], + py_deps = [ + ":function", # build_cleaner: keep + ":package", # build_cleaner: keep + ], + deps = [ + ":wrapper_types", + "//xls/common/status:statusor_pybind_caster", + "//xls/ir", + ], +) + +py_test( + name = "verifier_test", + srcs = ["verifier_test.py"], + python_version = "PY3", + deps = [ + ":bits", + ":function_builder", + ":package", + ":value", + ":verifier", + "@com_google_absl_py//absl/testing:absltest", + ], +) diff --git a/xls/ir/python/bits.cc b/xls/ir/python/bits.cc new file mode 100644 index 0000000000..647abfd695 --- /dev/null +++ b/xls/ir/python/bits.cc @@ -0,0 +1,35 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/bits.h" + +#include "pybind11/pybind11.h" +#include "xls/common/status/statusor_pybind_caster.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(bits, m) { + py::class_(m, "Bits") + .def(py::init()) + .def("bit_count", &Bits::bit_count) + .def("to_uint", &Bits::ToUint64) + .def("to_int", &Bits::ToInt64); + + m.def("UBits", &UBitsWithStatus, py::arg("value"), py::arg("bit_count")); + m.def("SBits", &SBitsWithStatus, py::arg("value"), py::arg("bit_count")); +} + +} // namespace xls diff --git a/xls/ir/python/bits_test.py b/xls/ir/python/bits_test.py new file mode 100644 index 0000000000..2e376ff233 --- /dev/null +++ b/xls/ir/python/bits_test.py @@ -0,0 +1,34 @@ +# Lint as: python3 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for xls.ir.python.bits.""" + +from xls.ir.python import bits +from absl.testing import absltest + + +class BitsTest(absltest.TestCase): + + def test_bits(self): + self.assertEqual(43, bits.UBits(43, 7).to_uint()) + self.assertEqual(53, bits.UBits(53, 7).to_int()) + self.assertEqual(33, bits.SBits(33, 8).to_uint()) + self.assertEqual(83, bits.SBits(83, 8).to_int()) + self.assertEqual(-83, bits.SBits(-83, 8).to_int()) + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/fileno.cc b/xls/ir/python/fileno.cc new file mode 100644 index 0000000000..61abd16003 --- /dev/null +++ b/xls/ir/python/fileno.cc @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/fileno.h" + +#include "pybind11/pybind11.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(fileno, m) { + py::class_(m, "Fileno") + .def(py::init()); + + py::class_(m, "Lineno") + .def(py::init()); + + py::class_(m, "Colno") + .def(py::init()); +} + +} // namespace xls diff --git a/xls/ir/python/fileno_test.py b/xls/ir/python/fileno_test.py new file mode 100644 index 0000000000..a5571bed70 --- /dev/null +++ b/xls/ir/python/fileno_test.py @@ -0,0 +1,37 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Lint as: python3 + +"""Tests for xls.ir.python.fileno.""" + +from xls.ir.python import fileno +from absl.testing import absltest + + +class FilenoTest(absltest.TestCase): + + def test_numbers(self): + fileno.Fileno(10) + fileno.Lineno(11) + fileno.Colno(12) + + def test_big_numbers(self): + fileno.Fileno(123456789) + fileno.Lineno(2**20) + fileno.Colno(2**30) + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/format_preference.cc b/xls/ir/python/format_preference.cc new file mode 100644 index 0000000000..17c0ddc6b1 --- /dev/null +++ b/xls/ir/python/format_preference.cc @@ -0,0 +1,31 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/format_preference.h" + +#include "pybind11/pybind11.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(format_preference, m) { + py::enum_(m, "FormatPreference") + .value("DEFAULT", FormatPreference::kDefault) + .value("BINARY", FormatPreference::kBinary) + .value("DECIMAL", FormatPreference::kDecimal) + .value("HEX", FormatPreference::kHex); +} + +} // namespace xls diff --git a/xls/ir/python/function.cc b/xls/ir/python/function.cc new file mode 100644 index 0000000000..11a296be5a --- /dev/null +++ b/xls/ir/python/function.cc @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/function.h" + +#include "pybind11/pybind11.h" +#include "xls/ir/python/wrapper_types.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(function, m) { + py::module::import("xls.ir.python.type"); + + py::class_(m, "Function") + .def("dump_ir", PyWrap(&Function::DumpIr), + py::arg("recursive") = false) + .def("get_type", PyWrap(&Function::GetType)) + .def_property_readonly("name", PyWrap(&Function::name)); +} + +} // namespace xls diff --git a/xls/ir/python/function_builder.cc b/xls/ir/python/function_builder.cc new file mode 100644 index 0000000000..a19d19ce22 --- /dev/null +++ b/xls/ir/python/function_builder.cc @@ -0,0 +1,198 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/function_builder.h" + +#include + +#include "pybind11/pybind11.h" +#include "xls/common/python/absl_casters.h" +#include "xls/common/status/statusor_pybind_caster.h" +#include "xls/ir/package.h" +#include "xls/ir/python/wrapper_types.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(function_builder, m) { + py::module::import("xls.ir.python.bits"); + py::module::import("xls.ir.python.function"); + py::module::import("xls.ir.python.lsb_or_msb"); + py::module::import("xls.ir.python.package"); + py::module::import("xls.ir.python.source_location"); + py::module::import("xls.ir.python.type"); + py::module::import("xls.ir.python.value"); + + py::class_(m, "BValue") + .def("__str__", PyWrap(&BValue::ToString)) + .def("get_builder", PyWrap(&BValue::builder)) + .def("get_type", PyWrap(&BValue::GetType)); + + // Explicitly select overload when pybind11 can't infer it. + BValue (FunctionBuilder::*add_or)( + BValue, BValue, absl::optional) = &FunctionBuilder::Or; + BValue (FunctionBuilder::*add_nary_or)(absl::Span, + absl::optional) = + &FunctionBuilder::Or; + BValue (FunctionBuilder::*add_literal_bits)( + Bits, absl::optional) = &FunctionBuilder::Literal; + BValue (FunctionBuilder::*add_literal_value)( + Value, absl::optional) = &FunctionBuilder::Literal; + BValue (FunctionBuilder::*add_sel)(BValue, BValue, BValue, + absl::optional) = + &FunctionBuilder::Select; + BValue (FunctionBuilder::*add_sel_multi)( + BValue, absl::Span, absl::optional, + absl::optional) = &FunctionBuilder::Select; + BValue (FunctionBuilder::*add_smul)( + BValue, BValue, absl::optional) = &FunctionBuilder::SMul; + BValue (FunctionBuilder::*add_umul)( + BValue, BValue, absl::optional) = &FunctionBuilder::UMul; + BValue (FunctionBuilder::*match_true)( + absl::Span, absl::Span, BValue, + absl::optional) = &FunctionBuilder::MatchTrue; + + py::class_(m, "FunctionBuilder") + .def(py::init(), py::arg("name"), + py::arg("package")) + + .def_property_readonly("name", PyWrap(&FunctionBuilder::name)) + + .def("add_param", PyWrap(&FunctionBuilder::Param), py::arg("name"), + py::arg("type"), py::arg("loc") = absl::nullopt) + + .def("add_shra", PyWrap(&FunctionBuilder::Shra), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_shrl", PyWrap(&FunctionBuilder::Shrl), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_shll", PyWrap(&FunctionBuilder::Shll), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_or", PyWrap(add_or), py::arg("lhs"), py::arg("rhs"), + py::arg("loc") = absl::nullopt) + .def("add_nary_or", PyWrap(add_nary_or), py::arg("operands"), + py::arg("loc") = absl::nullopt) + .def("add_xor", PyWrap(&FunctionBuilder::Xor), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_and", PyWrap(&FunctionBuilder::And), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_smul", PyWrap(add_smul), py::arg("lhs"), py::arg("rhs"), + py::arg("loc") = absl::nullopt) + .def("add_umul", PyWrap(add_umul), py::arg("lhs"), py::arg("rhs"), + py::arg("loc") = absl::nullopt) + .def("add_udiv", PyWrap(&FunctionBuilder::UDiv), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_sub", PyWrap(&FunctionBuilder::Subtract), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_add", PyWrap(&FunctionBuilder::Add), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + + .def("add_concat", PyWrap(&FunctionBuilder::Concat), py::arg("operands"), + py::arg("loc") = absl::nullopt) + + .def("add_ule", PyWrap(&FunctionBuilder::ULe), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_ult", PyWrap(&FunctionBuilder::ULt), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_uge", PyWrap(&FunctionBuilder::UGe), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_ugt", PyWrap(&FunctionBuilder::UGt), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + + .def("add_sle", PyWrap(&FunctionBuilder::SLe), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_slt", PyWrap(&FunctionBuilder::SLt), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_sge", PyWrap(&FunctionBuilder::SGe), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_sgt", PyWrap(&FunctionBuilder::SGt), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + + .def("add_eq", PyWrap(&FunctionBuilder::Eq), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + .def("add_ne", PyWrap(&FunctionBuilder::Ne), py::arg("lhs"), + py::arg("rhs"), py::arg("loc") = absl::nullopt) + + .def("add_neg", PyWrap(&FunctionBuilder::Negate), py::arg("x"), + py::arg("loc") = absl::nullopt) + .def("add_not", PyWrap(&FunctionBuilder::Not), py::arg("x"), + py::arg("loc") = absl::nullopt) + .def("add_clz", PyWrap(&FunctionBuilder::Clz), py::arg("x"), + py::arg("loc") = absl::nullopt) + .def("add_ctz", PyWrap(&FunctionBuilder::Ctz), py::arg("x"), + py::arg("loc") = absl::nullopt) + + .def("add_one_hot", PyWrap(&FunctionBuilder::OneHot), py::arg("arg"), + py::arg("lsb_is_prio"), py::arg("loc") = absl::nullopt) + .def("add_one_hot_sel", PyWrap(&FunctionBuilder::OneHotSelect), + py::arg("selector"), py::arg("cases"), + py::arg("loc") = absl::nullopt) + + .def("add_literal_bits", PyWrap(add_literal_bits), py::arg("bits"), + py::arg("loc") = absl::nullopt) + .def("add_literal_value", PyWrap(add_literal_value), py::arg("value"), + py::arg("loc") = absl::nullopt) + + .def("add_sel", PyWrap(add_sel), py::arg("selector"), py::arg("on_true"), + py::arg("on_false"), py::arg("loc") = absl::nullopt) + .def("add_sel_multi", PyWrap(add_sel_multi), py::arg("selector"), + py::arg("cases"), py::arg("default_value"), + py::arg("loc") = absl::nullopt) + .def("add_match_true", PyWrap(match_true), py::arg("case_clauses"), + py::arg("case_values"), py::arg("default_value"), + py::arg("loc") = absl::nullopt) + + .def("add_tuple", PyWrap(&FunctionBuilder::Tuple), py::arg("elements"), + py::arg("loc") = absl::nullopt) + .def("add_array", PyWrap(&FunctionBuilder::Array), py::arg("elements"), + py::arg("element_type"), py::arg("loc") = absl::nullopt) + + .def("add_tuple_index", PyWrap(&FunctionBuilder::TupleIndex), + py::arg("arg"), py::arg("idx"), py::arg("loc") = absl::nullopt) + + .def("add_counted_for", PyWrap(&FunctionBuilder::CountedFor), + py::arg("init_value"), py::arg("trip_count"), py::arg("stride"), + py::arg("body"), py::arg("invariant_args"), + py::arg("loc") = absl::nullopt) + + .def("add_map", PyWrap(&FunctionBuilder::Map), py::arg("operand"), + py::arg("to_apply"), py::arg("loc") = absl::nullopt) + + .def("add_invoke", PyWrap(&FunctionBuilder::Invoke), py::arg("args"), + py::arg("to_apply"), py::arg("loc") = absl::nullopt) + + .def("add_array_index", PyWrap(&FunctionBuilder::ArrayIndex), + py::arg("arg"), py::arg("idx"), py::arg("loc") = absl::nullopt) + .def("add_reverse", PyWrap(&FunctionBuilder::Reverse), py::arg("arg"), + py::arg("loc") = absl::nullopt) + .def("add_identity", PyWrap(&FunctionBuilder::Identity), py::arg("arg"), + py::arg("loc") = absl::nullopt) + .def("add_signext", PyWrap(&FunctionBuilder::SignExtend), py::arg("arg"), + py::arg("new_bit_count"), py::arg("loc") = absl::nullopt) + .def("add_zeroext", PyWrap(&FunctionBuilder::ZeroExtend), py::arg("arg"), + py::arg("new_bit_count"), py::arg("loc") = absl::nullopt) + .def("add_bit_slice", PyWrap(&FunctionBuilder::BitSlice), py::arg("arg"), + py::arg("start"), py::arg("width"), py::arg("loc") = absl::nullopt) + + .def("add_and_reduce", PyWrap(&FunctionBuilder::AndReduce), + py::arg("operand"), py::arg("loc") = absl::nullopt) + .def("add_or_reduce", PyWrap(&FunctionBuilder::OrReduce), + py::arg("operand"), py::arg("loc") = absl::nullopt) + .def("add_xor_reduce", PyWrap(&FunctionBuilder::XorReduce), + py::arg("operand"), py::arg("loc") = absl::nullopt) + + .def("build", PyWrap(&FunctionBuilder::Build)); +} + +} // namespace xls diff --git a/xls/ir/python/function_builder_test.py b/xls/ir/python/function_builder_test.py new file mode 100644 index 0000000000..9316032c0e --- /dev/null +++ b/xls/ir/python/function_builder_test.py @@ -0,0 +1,227 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Lint as: python3 + +"""Tests for xls.ir.python.function_builder.""" + +from xls.ir.python import bits as bits_mod +from xls.ir.python import fileno as fileno_mod +from xls.ir.python import function_builder +from xls.ir.python import lsb_or_msb +from xls.ir.python import package as ir_package +from xls.ir.python import source_location +from xls.ir.python import value as ir_value +from absl.testing import absltest + + +class FunctionBuilderTest(absltest.TestCase): + + def test_simple_build_and_dump_package(self): + p = ir_package.Package('test_package') + fileno = p.get_or_create_fileno('my_file.x') + fb = function_builder.FunctionBuilder('test_function', p) + t = p.get_bits_type(32) + x = fb.add_param('x', t) + + lineno = fileno_mod.Lineno(42) + colno = fileno_mod.Colno(64) + loc = source_location.SourceLocation(fileno, lineno, colno) + fb.add_or(x, x, loc=loc) + + fb.build() + self.assertMultiLineEqual( + p.dump_ir(), """\ +package test_package + +fn test_function(x: bits[32]) -> bits[32] { + ret or.2: bits[32] = or(x, x, pos=0,42,64) +} +""") + + def test_invoke_adder_2_plus_3_eq_5(self): + p = ir_package.Package('test_package') + fb = function_builder.FunctionBuilder('add_wrapper', p) + t = p.get_bits_type(32) + x = fb.add_param('x', t) + y = fb.add_param('y', t) + fb.add_add(x, y) + add_wrapper = fb.build() + + main_fb = function_builder.FunctionBuilder('main', p) + two = main_fb.add_literal_bits(bits_mod.UBits(value=2, bit_count=32)) + three = main_fb.add_literal_bits(bits_mod.UBits(value=3, bit_count=32)) + observed = main_fb.add_invoke([two, three], add_wrapper) + main_fb.add_eq( + observed, + main_fb.add_literal_bits(bits_mod.UBits(value=5, bit_count=32))) + main_fb.build() + self.assertMultiLineEqual( + p.dump_ir(), """\ +package test_package + +fn add_wrapper(x: bits[32], y: bits[32]) -> bits[32] { + ret add.3: bits[32] = add(x, y) +} + +fn main() -> bits[1] { + literal.4: bits[32] = literal(value=2) + literal.5: bits[32] = literal(value=3) + invoke.6: bits[32] = invoke(literal.4, literal.5, to_apply=add_wrapper) + literal.7: bits[32] = literal(value=5) + ret eq.8: bits[1] = eq(invoke.6, literal.7) +} +""") + + def test_literal_array(self): + p = ir_package.Package('test_package') + fb = function_builder.FunctionBuilder('f', p) + fb.add_literal_value( + ir_value.Value.make_array([ + ir_value.Value(bits_mod.UBits(value=5, bit_count=32)), + ir_value.Value(bits_mod.UBits(value=6, bit_count=32)), + ])) + fb.build() + self.assertMultiLineEqual( + p.dump_ir(), """\ +package test_package + +fn f() -> bits[32][2] { + ret literal.1: bits[32][2] = literal(value=[5, 6]) +} +""") + + def test_match_true(self): + p = ir_package.Package('test_package') + fb = function_builder.FunctionBuilder('f', p) + pred_t = p.get_bits_type(1) + expr_t = p.get_bits_type(32) + pred_x = fb.add_param('pred_x', pred_t) + x = fb.add_param('x', expr_t) + pred_y = fb.add_param('pred_y', pred_t) + y = fb.add_param('y', expr_t) + default = fb.add_param('default', expr_t) + fb.add_match_true([pred_x, pred_y], [x, y], default) + fb.build() + self.assertMultiLineEqual( + p.dump_ir(), """\ +package test_package + +fn f(pred_x: bits[1], x: bits[32], pred_y: bits[1], y: bits[32], default: bits[32]) -> bits[32] { + concat.6: bits[2] = concat(pred_y, pred_x) + one_hot.7: bits[3] = one_hot(concat.6, lsb_prio=true) + ret one_hot_sel.8: bits[32] = one_hot_sel(one_hot.7, cases=[x, y, default]) +} +""") + + def test_bvalue_methods(self): + # This test is mainly about checking that pybind11 is able to map parameter + # and return types properly. Because of this it's not necessary to check + # the result at the end; that methods don't throw when called is enough. + p = ir_package.Package('test_package') + fb = function_builder.FunctionBuilder('test_function', p) + x = fb.add_param('param_name', p.get_bits_type(32)) + + self.assertIn('param_name', str(x)) + self.assertEqual('test_function', x.get_builder().name) + self.assertEqual(32, x.get_type().get_bit_count()) + + def test_all_add_methods(self): + # This test is mainly about checking that pybind11 is able to map parameter + # and return types properly. Because of this it's not necessary to check + # the result at the end; that methods don't throw when called is enough. + p = ir_package.Package('test_package') + fileno = p.get_or_create_fileno('my_file.x') + lineno = fileno_mod.Lineno(42) + colno = fileno_mod.Colno(64) + loc = source_location.SourceLocation(fileno, lineno, colno) + fb = function_builder.FunctionBuilder('test_function', p) + + input_function_builder = function_builder.FunctionBuilder('fn', p) + input_function_builder.add_literal_value( + ir_value.Value(bits_mod.UBits(7, 8))) + input_function = input_function_builder.build() + + single_zero_bit = fb.add_literal_value( + ir_value.Value(bits_mod.UBits(value=0, bit_count=1))) + t = p.get_bits_type(32) + x = fb.add_param('x', t) + + fb.add_shra(x, x, loc=loc) + fb.add_shra(x, x, loc=loc) + fb.add_shrl(x, x, loc=loc) + fb.add_shll(x, x, loc=loc) + fb.add_or(x, x, loc=loc) + fb.add_nary_or([x], loc=loc) + fb.add_xor(x, x, loc=loc) + fb.add_and(x, x, loc=loc) + fb.add_smul(x, x, loc=loc) + fb.add_umul(x, x, loc=loc) + fb.add_udiv(x, x, loc=loc) + fb.add_sub(x, x, loc=loc) + fb.add_add(x, x, loc=loc) + + fb.add_concat([x], loc=loc) + + fb.add_ule(x, x, loc=loc) + fb.add_ult(x, x, loc=loc) + fb.add_uge(x, x, loc=loc) + fb.add_ugt(x, x, loc=loc) + + fb.add_sle(x, x, loc=loc) + fb.add_slt(x, x, loc=loc) + fb.add_sge(x, x, loc=loc) + fb.add_sgt(x, x, loc=loc) + + fb.add_eq(x, x, loc=loc) + fb.add_ne(x, x, loc=loc) + + fb.add_neg(x, loc=loc) + fb.add_not(x, loc=loc) + fb.add_clz(x, loc=loc) + + fb.add_one_hot(x, lsb_or_msb.LsbOrMsb.LSB, loc=loc) + fb.add_one_hot_sel(x, [x], loc=loc) + + fb.add_literal_bits(bits_mod.UBits(value=2, bit_count=32), loc=loc) + fb.add_literal_value( + ir_value.Value(bits_mod.UBits(value=5, bit_count=32)), loc=loc) + + fb.add_sel(x, x, x, loc=loc) + fb.add_sel_multi(x, [x], x, loc=loc) + fb.add_match_true([single_zero_bit], [x], x, loc=loc) + + tuple_node = fb.add_tuple([x], loc=loc) + fb.add_array([x], t, loc=loc) + + fb.add_tuple_index(tuple_node, 0, loc=loc) + + fb.add_counted_for(x, 1, 1, input_function, [x], loc=loc) + + fb.add_map(fb.add_array([x], t, loc=loc), input_function, loc=loc) + + fb.add_invoke([x], input_function, loc=loc) + + fb.add_array_index(fb.add_array([x], t, loc=loc), x, loc=loc) + fb.add_reverse(fb.add_array([x], t, loc=loc), loc=loc) + fb.add_identity(x, loc=loc) + fb.add_signext(x, 10, loc=loc) + fb.add_zeroext(x, 10, loc=loc) + fb.add_bit_slice(x, 4, 2, loc=loc) + + fb.build() + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/function_test.py b/xls/ir/python/function_test.py new file mode 100644 index 0000000000..ac3b717af9 --- /dev/null +++ b/xls/ir/python/function_test.py @@ -0,0 +1,45 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Lint as: python3 + +"""Tests for xls.ir.python.function.""" + +from xls.ir.python import bits +from xls.ir.python import function_builder +from xls.ir.python import package +from xls.ir.python import type as type_mod +from xls.ir.python import value +from absl.testing import absltest + + +def build_function(name='function_name'): + pkg = package.Package('pname') + builder = function_builder.FunctionBuilder(name, pkg) + builder.add_literal_value(value.Value(bits.UBits(7, 8))) + return builder.build() + + +class FunctionTest(absltest.TestCase): + + def test_methods(self): + fn = build_function('function_name') + + self.assertIn('function_name', fn.dump_ir()) + self.assertIsInstance(fn.get_type(), type_mod.FunctionType) + self.assertEqual('function_name', fn.name) + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/ir_interpreter.cc b/xls/ir/python/ir_interpreter.cc new file mode 100644 index 0000000000..15498599e8 --- /dev/null +++ b/xls/ir/python/ir_interpreter.cc @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_interpreter.h" + +#include "pybind11/pybind11.h" +#include "xls/common/python/absl_casters.h" +#include "xls/common/status/statusor_pybind_caster.h" +#include "xls/ir/python/wrapper_types.h" + +namespace py = pybind11; + +namespace xls { +namespace ir_interpreter { + +PYBIND11_MODULE(ir_interpreter, m) { + py::module::import("xls.ir.python.function"); + py::module::import("xls.ir.python.ir_interpreter_stats"); + py::module::import("xls.ir.python.value"); + + m.def("run_function_kwargs", PyWrap(&RunKwargs), py::arg("f"), + py::arg("args"), py::arg("stats") = nullptr); + m.def("run_function", PyWrap(&Run), py::arg("f"), py::arg("args"), + py::arg("stats") = nullptr); +} + +} // namespace ir_interpreter +} // namespace xls diff --git a/xls/ir/python/ir_interpreter_stats.cc b/xls/ir/python/ir_interpreter_stats.cc new file mode 100644 index 0000000000..8f75c874cd --- /dev/null +++ b/xls/ir/python/ir_interpreter_stats.cc @@ -0,0 +1,27 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_interpreter_stats.h" + +#include "pybind11/pybind11.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(ir_interpreter_stats, m) { + py::class_(m, "InterpreterStats"); // NOLINT +} + +} // namespace xls diff --git a/xls/ir/python/ir_interpreter_test.py b/xls/ir/python/ir_interpreter_test.py new file mode 100644 index 0000000000..6097d014d1 --- /dev/null +++ b/xls/ir/python/ir_interpreter_test.py @@ -0,0 +1,50 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Lint as: python3 + +"""Tests for xls.ir.python.ir_interpreter.""" + +from xls.ir.python import ir_interpreter +from xls.ir.python import ir_parser +from absl.testing import absltest + + +class IrInterpreterTest(absltest.TestCase): + + def test_add_one(self): + ir = """ + package test_package + + fn add_one(x: bits[32]) -> bits[32] { + literal.2: bits[32] = literal(value=1) + ret add.3: bits[32] = add(x, literal.2) + } + """ + p = ir_parser.Parser.parse_package(ir) + f = p.get_function('add_one') + args = dict(x=ir_parser.Parser.parse_typed_value('bits[32]:0xf00')) + result = ir_interpreter.run_function_kwargs(f, args) + self.assertEqual(result, + ir_parser.Parser.parse_typed_value('bits[32]:0xf01')) + + result = ir_interpreter.run_function( + f, + [ir_parser.Parser.parse_typed_value('bits[32]:0xf05')]) + self.assertEqual(result, + ir_parser.Parser.parse_typed_value('bits[32]:0xf06')) + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/ir_parser.cc b/xls/ir/python/ir_parser.cc new file mode 100644 index 0000000000..4b508a4215 --- /dev/null +++ b/xls/ir/python/ir_parser.cc @@ -0,0 +1,53 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ir_parser.h" + +#include "pybind11/pybind11.h" +#include "xls/common/python/absl_casters.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/common/status/statusor_pybind_caster.h" +#include "xls/ir/python/wrapper_types.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(ir_parser, m) { + py::module::import("xls.ir.python.package"); + py::module::import("xls.ir.python.type"); + py::module::import("xls.ir.python.value"); + + py::class_(m, "Parser") + .def_static( + "parse_package", + [](absl::string_view input_string, + absl::optional filename) + -> xabsl::StatusOr { + XLS_ASSIGN_OR_RETURN(std::unique_ptr package, + Parser::ParsePackage(input_string, filename)); + std::shared_ptr shared_package = std::move(package); + return PackageHolder(shared_package.get(), shared_package); + }, + py::arg("input_string"), py::arg("filename") = absl::nullopt) + + .def_static("parse_value", PyWrap(&Parser::ParseValue), + py::arg("input_string"), py::arg("expected_type")) + + .def_static("parse_typed_value", &Parser::ParseTypedValue, + py::arg("input_string")); +} + +} // namespace xls diff --git a/xls/ir/python/ir_parser_test.py b/xls/ir/python/ir_parser_test.py new file mode 100644 index 0000000000..2545593dd3 --- /dev/null +++ b/xls/ir/python/ir_parser_test.py @@ -0,0 +1,50 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Lint as: python3 + +"""Tests for xls.ir.python.ir_parser.""" + +from xls.ir.python import ir_parser +from xls.ir.python import package as package_mod +from xls.ir.python.format_preference import FormatPreference +from absl.testing import absltest + + +class IrParserTest(absltest.TestCase): + + def test_parse_value(self): + p = package_mod.Package('test_package') + u32 = p.get_bits_type(32) + u8 = p.get_bits_type(8) + t = p.get_tuple_type([u32, u8]) + s = '({}, {})'.format(0xdeadbeef, 0xcd) + v = ir_parser.Parser.parse_value(s, t) + self.assertEqual(s, str(v)) + + v = ir_parser.Parser.parse_value('(0xdeadbeef, 0xcd)', t) + self.assertEqual('(bits[32]:0xdead_beef, bits[8]:0xcd)', + v.to_str(FormatPreference.HEX)) + + def test_parse_typed_value(self): + s = 'bits[32]:0x42' + v = ir_parser.Parser.parse_typed_value(s) + self.assertEqual('66', str(v)) + + def test_parse_package(self): + p = ir_parser.Parser.parse_package('package test_package') + self.assertIsInstance(p, package_mod.Package) + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/lsb_or_msb.cc b/xls/ir/python/lsb_or_msb.cc new file mode 100644 index 0000000000..9817b34a0c --- /dev/null +++ b/xls/ir/python/lsb_or_msb.cc @@ -0,0 +1,29 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/lsb_or_msb.h" + +#include "pybind11/pybind11.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(lsb_or_msb, m) { + py::enum_(m, "LsbOrMsb") + .value("LSB", LsbOrMsb::kLsb) + .value("MSB", LsbOrMsb::kMsb); +} + +} // namespace xls diff --git a/xls/ir/python/number_parser.cc b/xls/ir/python/number_parser.cc new file mode 100644 index 0000000000..8637b78ab8 --- /dev/null +++ b/xls/ir/python/number_parser.cc @@ -0,0 +1,33 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/number_parser.h" + +#include "pybind11/pybind11.h" +#include "xls/common/status/statusor_pybind_caster.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(number_parser, m) { + py::module::import("xls.ir.python.bits"); + py::module::import("xls.ir.python.format_preference"); + + m.def("bits_from_string", &ParseUnsignedNumberWithoutPrefix, py::arg("data"), + py::arg("format") = FormatPreference::kHex, + py::arg("bit_count") = kMinimumBitCount); +} + +} // namespace xls diff --git a/xls/ir/python/number_parser_test.py b/xls/ir/python/number_parser_test.py new file mode 100644 index 0000000000..2fa82d14fd --- /dev/null +++ b/xls/ir/python/number_parser_test.py @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Lint as: python3 + +"""Tests for xls.ir.python.number_parser.""" + +from xls.ir.python import number_parser +from absl.testing import absltest + + +class NumberParserTest(absltest.TestCase): + + def test_number_parser(self): + self.assertEqual(15, number_parser.bits_from_string('0xf').to_uint()) + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/package.cc b/xls/ir/python/package.cc new file mode 100644 index 0000000000..5ffb437282 --- /dev/null +++ b/xls/ir/python/package.cc @@ -0,0 +1,64 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/package.h" + +#include "pybind11/pybind11.h" +#include "xls/common/python/absl_casters.h" +#include "xls/common/status/statusor_pybind_caster.h" +#include "xls/ir/function.h" +#include "xls/ir/python/wrapper_types.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(package, m) { + py::module::import("xls.ir.python.type"); + py::module::import("xls.ir.python.fileno"); + py::module::import("xls.ir.python.function"); + + py::class_(m, "Package") + .def(py::init([](absl::string_view name) { + auto package = std::make_shared(name); + return PackageHolder(package.get(), package); + }), + py::arg("name")) + .def("dump_ir", PyWrap(&Package::DumpIr)) + .def("get_bits_type", PyWrap(&Package::GetBitsType), py::arg("bit_count")) + .def("get_array_type", PyWrap(&Package::GetArrayType), py::arg("size"), + py::arg("element")) + .def( + "get_tuple_type", + [](PackageHolder* package, + absl::Span element_types) { + // PyWrap doesn't support absl::Spans of pointers to wrapped + // objects, need to wrap manually. + std::vector unwrapped_element_types; + unwrapped_element_types.reserve(element_types.size()); + for (const auto& type_holder : element_types) { + unwrapped_element_types.push_back(&type_holder.deref()); + } + return TupleTypeHolder( + package->deref().GetTupleType(unwrapped_element_types), + package->package()); + }, + py::arg("element_types")) + .def("get_or_create_fileno", PyWrap(&Package::GetOrCreateFileno), + py::arg("filename")) + .def("get_function", PyWrap(&Package::GetFunction), py::arg("func_name")) + .def("get_function_names", PyWrap(&Package::GetFunctionNames)); +} + +} // namespace xls diff --git a/xls/ir/python/package_test.py b/xls/ir/python/package_test.py new file mode 100644 index 0000000000..43af8c930f --- /dev/null +++ b/xls/ir/python/package_test.py @@ -0,0 +1,47 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for xls.ir.python.package.""" + +from xls.ir.python import bits +from xls.ir.python import fileno +from xls.ir.python import function +from xls.ir.python import function_builder +from xls.ir.python import package +from xls.ir.python import type as ir_type +from xls.ir.python import value as ir_value +from absl.testing import absltest + + +class PackageTest(absltest.TestCase): + + def test_package_methods(self): + pkg = package.Package('pkg') + fb = function_builder.FunctionBuilder('f', pkg) + fb.add_literal_value(ir_value.Value(bits.UBits(7, 8))) + fb.build() + + self.assertIn('pkg', pkg.dump_ir()) + self.assertIsInstance(pkg.get_bits_type(4), ir_type.BitsType) + self.assertIsInstance( + pkg.get_array_type(4, pkg.get_bits_type(4)), ir_type.ArrayType) + self.assertIsInstance( + pkg.get_tuple_type([pkg.get_bits_type(4)]), ir_type.TupleType) + self.assertIsInstance(pkg.get_or_create_fileno('file'), fileno.Fileno) + self.assertIsInstance(pkg.get_function('f'), function.Function) + self.assertEqual(['f'], pkg.get_function_names()) + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/source_location.cc b/xls/ir/python/source_location.cc new file mode 100644 index 0000000000..3a4b5a7106 --- /dev/null +++ b/xls/ir/python/source_location.cc @@ -0,0 +1,31 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/source_location.h" + +#include "pybind11/pybind11.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(source_location, m) { + py::module::import("xls.ir.python.fileno"); + + py::class_(m, "SourceLocation") + .def(py::init(), py::arg("fileno"), + py::arg("lineno"), py::arg("colno")); +} + +} // namespace xls diff --git a/xls/ir/python/type.cc b/xls/ir/python/type.cc new file mode 100644 index 0000000000..4030ef73e5 --- /dev/null +++ b/xls/ir/python/type.cc @@ -0,0 +1,46 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/type.h" + +#include "pybind11/pybind11.h" +#include "xls/common/status/statusor_pybind_caster.h" +#include "xls/ir/python/wrapper_types.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(type, m) { + auto type = py::class_(m, "Type"); + type.def("__str__", PyWrap(&Type::ToString)); + + py::class_(m, "BitsType", type) + .def("get_bit_count", PyWrap(&BitsType::bit_count)); + + py::class_(m, "ArrayType", type) + .def("get_size", PyWrap(&ArrayType::size)) + .def("get_element_type", PyWrap(&ArrayType::element_type)); + + py::class_(m, "TupleType", type); // NOLINT + + py::class_(m, "FunctionType") + .def("__str__", PyWrap(&FunctionType::ToString)) + .def("return_type", PyWrap(&FunctionType::return_type)) + .def("get_parameter_count", PyWrap(&FunctionType::parameter_count)) + .def("get_parameter_type", PyWrap(&FunctionType::parameter_type), + py::arg("i")); +} + +} // namespace xls diff --git a/xls/ir/python/type_test.py b/xls/ir/python/type_test.py new file mode 100644 index 0000000000..4d7af8dda9 --- /dev/null +++ b/xls/ir/python/type_test.py @@ -0,0 +1,80 @@ +# Lint as: python3 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for xls.ir.python.type.""" + +from xls.common import test_base +from xls.ir.python import bits +from xls.ir.python import function_builder +from xls.ir.python import package +from xls.ir.python import type as ir_type +from xls.ir.python import value as ir_value + + +class TypeTest(test_base.XlsTestCase): + + def test_type(self): + pkg = package.Package('pkg') + t = pkg.get_bits_type(4) + + self.assertIn('4', str(t)) + + def test_bits_type(self): + pkg = package.Package('pkg') + t = pkg.get_bits_type(4) + + self.assertEqual(4, t.get_bit_count()) + + def test_array_type(self): + pkg = package.Package('pkg') + bit_type = pkg.get_bits_type(4) + t = pkg.get_array_type(3, bit_type) + + self.assertEqual(3, t.get_size()) + self.assertEqual(4, t.get_element_type().get_bit_count()) + + def test_type_polymorphism(self): + # Verify that pybind11 knows to down-cast Type objects. + + pkg = package.Package('pkg') + bits_type = pkg.get_bits_type(4) + array_type = pkg.get_array_type(3, bits_type) + tuple_type = pkg.get_tuple_type([bits_type]) + + self.assertIsInstance( + pkg.get_array_type(1, bits_type).get_element_type(), ir_type.BitsType) + self.assertIsInstance( + pkg.get_array_type(1, array_type).get_element_type(), ir_type.ArrayType) + self.assertIsInstance( + pkg.get_array_type(1, tuple_type).get_element_type(), ir_type.TupleType) + + def test_function_type(self): + pkg = package.Package('pname') + builder = function_builder.FunctionBuilder('f_name', pkg) + builder.add_param('x', pkg.get_bits_type(32)) + builder.add_literal_value(ir_value.Value(bits.UBits(7, 8))) + fn = builder.build() + fn_type = fn.get_type() + + self.assertIsInstance(fn_type, ir_type.FunctionType) + self.assertIn('bits[32]', str(fn_type)) + self.assertEqual(8, fn_type.return_type().get_bit_count()) + self.assertEqual(1, fn_type.get_parameter_count()) + self.assertEqual(32, fn_type.get_parameter_type(0).get_bit_count()) + + +if __name__ == '__main__': + test_base.main() diff --git a/xls/ir/python/value.cc b/xls/ir/python/value.cc new file mode 100644 index 0000000000..5bcfa3d3ea --- /dev/null +++ b/xls/ir/python/value.cc @@ -0,0 +1,49 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/value.h" + +#include "pybind11/pybind11.h" +#include "xls/common/python/absl_casters.h" +#include "xls/common/status/statusor_pybind_caster.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(value, m) { + py::module::import("xls.ir.python.bits"); + py::module::import("xls.ir.python.format_preference"); + + py::class_(m, "Value") + .def(py::init(), py::arg("bits")) + .def("__eq__", &Value::operator==) + .def("__ne__", &Value::operator!=) + + .def("is_bits", &Value::IsBits) + .def("is_array", &Value::IsArray) + .def("is_tuple", &Value::IsTuple) + .def("get_bits", &Value::GetBitsWithStatus) + + .def("get_elements", &Value::GetElements) + + .def("__str__", &Value::ToHumanString, + py::arg("preference") = FormatPreference::kDefault) + .def("to_str", &Value::ToString, py::arg("preference")) + + .def_static("make_array", &Value::Array, py::arg("elements")) + .def_static("make_tuple", &Value::Tuple, py::arg("elements")); +} + +} // namespace xls diff --git a/xls/ir/python/verifier.cc b/xls/ir/python/verifier.cc new file mode 100644 index 0000000000..d15350cfe1 --- /dev/null +++ b/xls/ir/python/verifier.cc @@ -0,0 +1,37 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/verifier.h" + +#include "pybind11/pybind11.h" +#include "xls/common/status/statusor_pybind_caster.h" +#include "xls/ir/python/wrapper_types.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(verifier, m) { + py::module::import("xls.ir.python.function"); + py::module::import("xls.ir.python.package"); + + // Explicitly select overload, they can't be inferred. + absl::Status (*verify_function)(Function* package) = &Verify; + absl::Status (*verify_package)(Package* package) = &Verify; + + m.def("verify_function", PyWrap(verify_function), py::arg("function")); + m.def("verify_package", PyWrap(verify_package), py::arg("package")); +} + +} // namespace xls diff --git a/xls/ir/python/verifier_test.py b/xls/ir/python/verifier_test.py new file mode 100644 index 0000000000..e25b3a60e7 --- /dev/null +++ b/xls/ir/python/verifier_test.py @@ -0,0 +1,44 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Lint as: python3 + +"""Tests for xls.ir.python.verifier.""" + +from xls.ir.python import bits +from xls.ir.python import function_builder +from xls.ir.python import package +from xls.ir.python import value as ir_value +from xls.ir.python import verifier +from absl.testing import absltest + + +class VerifierTest(absltest.TestCase): + + def test_verify_package(self): + pkg = package.Package('pkg') + verifier.verify_package(pkg) + + def test_verify_function(self): + pkg = package.Package('pname') + builder = function_builder.FunctionBuilder('f_name', pkg) + builder.add_param('x', pkg.get_bits_type(32)) + builder.add_literal_value(ir_value.Value(bits.UBits(7, 8))) + fn = builder.build() + + verifier.verify_function(fn) + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/ir/python/wrapper_types.h b/xls/ir/python/wrapper_types.h new file mode 100644 index 0000000000..5792ac6cbe --- /dev/null +++ b/xls/ir/python/wrapper_types.h @@ -0,0 +1,481 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_PYTHON_WRAPPER_TYPES_H_ +#define THIRD_PARTY_XLS_IR_PYTHON_WRAPPER_TYPES_H_ + +#include +#include + +#include "pybind11/cast.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/package.h" +#include "xls/ir/type.h" + +namespace xls { + +// A number of C++ IR types contain unowning Package* pointers. In order to +// ensure memory safety, all Python references to such types values are wrapped +// in objects that contain not just the unowning pointer but but also a +// shared_ptr to its Package. +// +// Unfortunately, this wrapping causes quite a lot of friction in defining the +// pybind11 bindings. This file contains type definitions and helpers for this. +// The main helper function is PyBind, which in most cases makes it possible to +// write pybind11 wrapper declarations mostly as usual even for objects that are +// wrapped, and when parameters and return values are of types that need to be +// wrapped. + +// Classes that are a wrapper that contains both an unowning pointer and an +// owning pointer should inherit this class to signal to helper functions that +// it is such a wrapper. +// +// Subclasses of this class should have a T& deref() const method that returns +// the wrapped unowned object. +// +// Subclasses of this class are expected to be copyable. +struct PyHolder {}; + +// Base class for wrapper objects that have objects that are owned by Package. +// (BValue and FunctionHolder need more than just a shared_ptr to Package so +// they don't use this.) +template +class PointerOwnedByPackage : public PyHolder { + public: + PointerOwnedByPackage(T* pointer, const std::shared_ptr& owner) + : pointer_(pointer), owner_(owner) {} + + T& deref() const { return *pointer_; } + const std::shared_ptr& package() { return owner_; } + + private: + T* pointer_; + std::shared_ptr owner_; +}; + +// Abstract base class for wrappers of xls::Type objects. pybind11 expects to be +// able to do static_cast up casts for types that are declared to inherit each +// other so it is necessary that the wrapper class hierarchy mirrors the +// hierarchy of the wrapped objects. +class TypeHolder : public PointerOwnedByPackage { + protected: + using PointerOwnedByPackage::PointerOwnedByPackage; +}; + +// Wrapper for BitsType* pointers. +struct BitsTypeHolder : public TypeHolder { + BitsTypeHolder(BitsType* pointer, const std::shared_ptr& owner) + : TypeHolder(pointer, owner) {} + + BitsType& deref() const { + return static_cast(TypeHolder::deref()); + } +}; + +// Wrapper for ArrayType* pointers. +struct ArrayTypeHolder : public TypeHolder { + ArrayTypeHolder(ArrayType* pointer, const std::shared_ptr& owner) + : TypeHolder(pointer, owner) {} + + ArrayType& deref() const { + return static_cast(TypeHolder::deref()); + } +}; + +// Wrapper for TupleType* pointers. +struct TupleTypeHolder : public TypeHolder { + TupleTypeHolder(TupleType* pointer, const std::shared_ptr& owner) + : TypeHolder(pointer, owner) {} + + TupleType& deref() const { + return static_cast(TypeHolder::deref()); + } +}; + +// Wrapper for FunctionType* pointers. +struct FunctionTypeHolder : public PointerOwnedByPackage { + using PointerOwnedByPackage::PointerOwnedByPackage; +}; + +// Wrapper for Function* pointers. +struct FunctionHolder : public PointerOwnedByPackage { + using PointerOwnedByPackage::PointerOwnedByPackage; +}; + +// Wrapper for Package objects. +// +// A Package can be referred to through unowned pointers by other objects like +// FunctionBuilder and Type. For this reason, it is held in a shared_ptr, so +// that it can be kept alive for as long as any Python object refers to it, even +// if there is no longer a direct reference to the Package. +// +// Using PointerOwnedByPackage makes this object contain a Package* and a +// std::shared_ptr which is a little bit weird, but it allows reusing +// helper functions that work on PointerOwnedByPackage. +struct PackageHolder : public PointerOwnedByPackage { + using PointerOwnedByPackage::PointerOwnedByPackage; +}; + +// Wrapper for BValue objects. +// +// A BValue C++ object has unowned pointers both to its Package and to its +// FunctionBuilder. To keep them alive for as long as the Python reference +// exists, this holder keeps shared_ptrs to the Package and FunctionBuilder. +class BValueHolder : public PyHolder { + public: + BValueHolder(const BValue& value, const std::shared_ptr& package, + const std::shared_ptr& builder) + : value_(std::make_shared(value)), + package_(package), + builder_(builder) {} + + const std::shared_ptr& value() const { return value_; } + const std::shared_ptr& package() const { return package_; } + const std::shared_ptr& builder() const { return builder_; } + + BValue& deref() const { return *value_; } + + private: + std::shared_ptr value_; + std::shared_ptr package_; + std::shared_ptr builder_; +}; + +// Wrapper for FunctionBuilder objects. +// +// A FunctionBuilder has unowned pointers to the beloning package. To keep the +// Package alive for as long as the Python reference exists, it is held by +// shared_ptr here. Furthermore, since FunctionBuilder can be referred to by +// other BValue objects, it itself is kept in a shared_ptr too. +class FunctionBuilderHolder : public PyHolder { + public: + FunctionBuilderHolder(absl::string_view name, PackageHolder package) + : package_(package.package()), + builder_(std::make_shared(name, &package.deref())) {} + + FunctionBuilderHolder(const std::shared_ptr& package, + const std::shared_ptr& builder) + : package_(package), builder_(builder) {} + + FunctionBuilder& deref() const { return *builder_; } + + const std::shared_ptr& package() const { return package_; } + const std::shared_ptr& builder() const { return builder_; } + + private: + std::shared_ptr package_; + std::shared_ptr builder_; +}; + +// PyHolderTypeTraits is a template that defines a mapping from a wrapped type +// (for example BitsType) to its holder type (in this case BitsTypeHolder). +template +struct PyHolderTypeTraits; + +template <> +struct PyHolderTypeTraits { + using Holder = TypeHolder; +}; + +template <> +struct PyHolderTypeTraits { + using Holder = BitsTypeHolder; +}; + +template <> +struct PyHolderTypeTraits { + using Holder = ArrayTypeHolder; +}; + +template <> +struct PyHolderTypeTraits { + using Holder = TupleTypeHolder; +}; + +template <> +struct PyHolderTypeTraits { + using Holder = FunctionTypeHolder; +}; + +template <> +struct PyHolderTypeTraits { + using Holder = FunctionHolder; +}; + +template <> +struct PyHolderTypeTraits { + using Holder = PackageHolder; +}; + +template <> +struct PyHolderTypeTraits { + using Holder = BValueHolder; +}; + +template <> +struct PyHolderTypeTraits { + using Holder = FunctionBuilderHolder; +}; + +// PyHolderType finds the wrapper type of a given type. For example, +// PyHolderType is BitsTypeHolder. +template +using PyHolderType = typename PyHolderTypeTraits::Holder; + +// Takes something that potentially should be wrapped by a holder object (for +// example BValue) and wraps it in a holder object (for example a BValueHolder). +// The second parameter is a PyHolder object that is used to get the owning +// pointers required to construct the holder object. +// +// If T is not a type that needs to be wrapped, this function returns `value`. +// +// This function is used by PyWrap on return values of wrapped methods. +// +// Making PyWrap able to wrap a new return type is done by adding a new overload +// of the WrapInPyHolder function. +template +auto WrapInPyHolderIfHolderExists(T&& value, Holder* holder) + -> decltype(WrapInPyHolder(std::forward(value), holder)) { + return WrapInPyHolder(std::forward(value), holder); +} +template +auto WrapInPyHolderIfHolderExists(T&& value, ...) { + return value; +} + +// Tells PyWrap how to wrap BValue return values, see +// WrapInPyHolderIfHolderExists. +static BValueHolder WrapInPyHolder(const BValue& value, + FunctionBuilderHolder* holder) { + return BValueHolder(value, holder->package(), holder->builder()); +} + +// Tells PyWrap how to wrap FunctionBuilder return values from BValue methods, +// see WrapInPyHolderIfHolderExists. +static FunctionBuilderHolder WrapInPyHolder(FunctionBuilder* builder, + BValueHolder* holder) { + return FunctionBuilderHolder(holder->package(), holder->builder()); +} + +// Tells PyWrap how to wrap return values from BValue methods of pointers owned +// by Package, see WrapInPyHolderIfHolderExists. +template +PyHolderType WrapInPyHolder(T* value, BValueHolder* holder) { + return PyHolderType(value, holder->package()); +} + +// Tells PyWrap how to wrap return values of pointers owned by Package, see +// WrapInPyHolderIfHolderExists. +template +PyHolderType WrapInPyHolder(T* value, PointerOwnedByPackage* holder) { + return PyHolderType(value, holder->package()); +} + +// Tells PyWrap how to wrap xabsl::StatusOr return values of types that need to +// be wrapped, see WrapInPyHolderIfHolderExists. +template +xabsl::StatusOr> WrapInPyHolder( + const xabsl::StatusOr& value, Holder* holder) { + if (value.ok()) { + return PyHolderType(value.value(), holder->package()); + } else { + return value.status(); + } +} + +// HasPyHolderType is true when T is a type that has a PyHolder type. +template > +struct HasPyHolderTypeHelper : std::false_type {}; +template +struct HasPyHolderTypeHelper< + T, std::void_t::Holder>> + : public std::true_type {}; +template +constexpr bool HasPyHolderType = HasPyHolderTypeHelper::value; +static_assert(!HasPyHolderType); +static_assert(HasPyHolderType); + +// WrappedFunctionParameterTraits defines how PyWrap handles parameters of +// functions it wraps: For a function void Foo(T t), the wrapped function will +// be of type void (*)(WrappedFunctionParameterTraits::WrappedType t). +// +// PyWrap then uses WrappedFunctionParameterTraits::Unwrap(t) to extract the +// potentially wrapped underlying type to pass it to the wrapped function. +// +// This primary template specifies how PyWrap behaves for types that are not +// wrapped. Partial specializations define the behavior for wrapped types. +template > +struct WrappedFunctionParameterTraits { + using WrappedType = T; + + static T Unwrap(T&& t) { return t; } +}; + +// WrappedFunctionParameterTraits for absl::Spans of types that have wrapper +// types. +template +struct WrappedFunctionParameterTraits, + std::enable_if_t>> { + using WrappedType = absl::Span>; + + static std::vector Unwrap(const WrappedType& t) { + std::vector values; + values.reserve(t.size()); + for (auto& value : t) { + values.push_back(value.deref()); + } + return values; + } +}; + +// WrappedFunctionParameterTraits for absl::optionals of types that have wrapper +// types. +template +struct WrappedFunctionParameterTraits, + std::enable_if_t>> { + using WrappedType = absl::optional>; + + static absl::optional Unwrap(const WrappedType& t) { + if (t) { + return t->deref(); + } else { + return {}; + } + } +}; + +// WrappedFunctionParameterTraits for types that have wrapper types. +template +struct WrappedFunctionParameterTraits< + T, std::enable_if_t>>> { + private: + using NonPointerType = PyHolderType>; + + public: + using WrappedType = + std::conditional_t, NonPointerType*, NonPointerType>; + + static T Unwrap(const WrappedType& t) { + if constexpr (std::is_pointer_v) { + return &t->deref(); + } else { + return t.deref(); + } + } +}; + +// Helper function with shared logic for the two PyWrap overloads that take +// pointers to member methods (the const and the non-const variant). +template +auto PyWrapHelper(MethodPointer method_pointer) { + return + [method_pointer]( + PyHolderType* t, + typename WrappedFunctionParameterTraits::WrappedType... args) { + auto unwrapped = ((t->deref()).*method_pointer)( + WrappedFunctionParameterTraits::Unwrap( + std::forward< + typename WrappedFunctionParameterTraits::WrappedType>( + args))...); + return WrapInPyHolderIfHolderExists(std::move(unwrapped), t); + }; +} + +// PyWrap is the main interface for the code in this file. It is designed to be +// used in pybind11 interface definition code when declaring methods of wrapped +// types. It takes a pointer to a method of a wrapped type that potentially has +// parameters of types that are wrapped, and potentially returns something that +// needs to be wrapped. +// +// Its return value is a functor that unwraps the object, calls the given method +// after unwrapping the parameters and then wraps the return value before +// returning. +// +// Example usage (here, FunctionHolder is a PyHolder type that holds a +// Function*): +// +// py::class_(m, "Function") +// .def("dump_ir", PyWrap(&Function::DumpIr)); +// +// Note that even though Function* is wrapped in FunctionHolder, the type can be +// declared almost as if the object is not wrapped. For comparison, an unwrapped +// type would have been defined like this: +// +// py::class_(m, "Function") +// .def("dump_ir", &Function::DumpIr); +template +auto PyWrap(ReturnT (T::*method_pointer)(Args...) const) { + return PyWrapHelper( + method_pointer); +} + +// PyWrap for member methods. For more info see the docs on the other overload. +template +auto PyWrap(ReturnT (T::*method_pointer)(Args...)) { + return PyWrapHelper( + method_pointer); +} + +// PyWrap for static methods and free functions. This behaves like PyWrap for +// methods, except that it is not able to wrap return values, because it doesn't +// have a pointer to a this PyHolder object that it can extract owned pointers +// from. +template +auto PyWrap(ReturnT (*function_pointer)(Args...)) { + return + [function_pointer]( + typename WrappedFunctionParameterTraits::WrappedType... args) { + // Because there is no wrapped this pointer, it's not possible to wrap + // the return value. This only unwraps parameters when applicable. + return (*function_pointer)(WrappedFunctionParameterTraits::Unwrap( + std::forward< + typename WrappedFunctionParameterTraits::WrappedType>( + args))...); + }; +} + +} // namespace xls + +// Tell pybind11 how to cast a TypeHolder to a specific type. This is necessary +// to make wrapped C++ methods that return a Type* work, otherwise it's not +// possible to call methods of subtypes in Python. +namespace pybind11 { +template <> +struct polymorphic_type_hook { + static const void* get(const xls::TypeHolder* src, + const std::type_info*& type) { // NOLINT + if (src == nullptr) { + return nullptr; + } + + if (src->deref().IsBits()) { + type = &typeid(xls::BitsTypeHolder); + return static_cast(src); + } else if (src->deref().IsTuple()) { + type = &typeid(xls::TupleTypeHolder); + return static_cast(src); + } else if (src->deref().IsArray()) { + type = &typeid(xls::ArrayTypeHolder); + return static_cast(src); + } + + return src; + } +}; +} // namespace pybind11 + +#endif // THIRD_PARTY_XLS_IR_PYTHON_WRAPPER_TYPES_H_ diff --git a/xls/ir/render_specification_against_template.py b/xls/ir/render_specification_against_template.py new file mode 100644 index 0000000000..70ddb658e5 --- /dev/null +++ b/xls/ir/render_specification_against_template.py @@ -0,0 +1,39 @@ +# Lint as: python3 +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Renders the specification metadata against the commandline-provided template. + +Output C++ source code is printed to stdout. +""" + +from absl import app + +import jinja2 + +from xls.common import runfiles +from xls.ir import op_specification as specification + + +def main(argv): + env = jinja2.Environment(undefined=jinja2.StrictUndefined) + template = env.from_string(runfiles.get_contents_as_text(argv[1])) + print('// DO NOT EDIT: this file is AUTOMATICALLY GENERATED and should not ' + 'be changed.') + print(template.render(spec=specification)) + + +if __name__ == '__main__': + app.run(main) diff --git a/xls/ir/source_location.h b/xls/ir/source_location.h new file mode 100644 index 0000000000..8bf341fa19 --- /dev/null +++ b/xls/ir/source_location.h @@ -0,0 +1,51 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_SOURCE_LOCATION_H_ +#define THIRD_PARTY_XLS_IR_SOURCE_LOCATION_H_ + +#include "absl/strings/str_format.h" +#include "absl/types/optional.h" +#include "xls/ir/fileno.h" + +namespace xls { + +// Stores a tuple of unique identifiers specifying a file id and line number. +// SourceLocation objects are added to XLS IR nodes and used for debug tracing. +class SourceLocation { + public: + SourceLocation() : SourceLocation(Fileno(0), Lineno(0), Colno(0)) {} + SourceLocation(Fileno fileno, Lineno lineno, Colno colno) + : fileno_(fileno), lineno_(lineno), colno_(colno) {} + SourceLocation(const SourceLocation& other) = default; + SourceLocation& operator=(const SourceLocation& other) = default; + + Fileno fileno() const { return fileno_; } + Lineno lineno() const { return lineno_; } + Colno colno() const { return colno_; } + + std::string ToString() const { + return absl::StrFormat("%d,%d,%d", fileno_.value(), lineno_.value(), + colno_.value()); + } + + private: + Fileno fileno_; + Lineno lineno_; + Colno colno_; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_SOURCE_LOCATION_H_ diff --git a/xls/ir/ternary.cc b/xls/ir/ternary.cc new file mode 100644 index 0000000000..7d7d12d03f --- /dev/null +++ b/xls/ir/ternary.cc @@ -0,0 +1,87 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/ternary.h" + +#include "absl/strings/str_format.h" + +namespace xls { + +std::string ToString(const TernaryVector& value) { + std::string result = "0b"; + for (int64 i = value.size() - 1; i >= 0; --i) { + std::string symbol; + switch (value[i]) { + case TernaryValue::kKnownZero: + symbol = "0"; + break; + case TernaryValue::kKnownOne: + symbol = "1"; + break; + case TernaryValue::kUnknown: + symbol = "X"; + break; + } + absl::StrAppend(&result, symbol); + if (i != 0 && i % 4 == 0) { + absl::StrAppend(&result, "_"); + } + } + return result; +} + +std::string ToString(const TernaryValue& value) { + switch (value) { + case TernaryValue::kKnownZero: + return "TernaryValue::kKnownZero"; + case TernaryValue::kKnownOne: + return "TernaryValue::kKnownOne"; + case TernaryValue::kUnknown: + return "TernaryValue::kUnknown"; + } + XLS_LOG(FATAL) << "Invalid ternary value: " << static_cast(value); +} + +xabsl::StatusOr StringToTernaryVector(absl::string_view s) { + auto invalid_input = [&]() { + return absl::InvalidArgumentError( + absl::StrFormat("Invalid ternary string: %s", s)); + }; + if (s.substr(0, 2) != "0b") { + return invalid_input(); + } + TernaryVector result; + for (char c : s.substr(2)) { + switch (c) { + case '0': + result.push_back(TernaryValue::kKnownZero); + break; + case '1': + result.push_back(TernaryValue::kKnownOne); + break; + case 'X': + case 'x': + result.push_back(TernaryValue::kUnknown); + break; + case '_': + break; + default: + return invalid_input(); + } + } + std::reverse(result.begin(), result.end()); + return result; +} + +} // namespace xls diff --git a/xls/ir/ternary.h b/xls/ir/ternary.h new file mode 100644 index 0000000000..3172b3d83e --- /dev/null +++ b/xls/ir/ternary.h @@ -0,0 +1,136 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_TERNARY_H_ +#define THIRD_PARTY_XLS_IR_TERNARY_H_ + +#include + +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" + +namespace xls { + +// Ternary logic is used to propagate known and unknown bit values through the +// graph. Ternary logic allows three values for each "bit". +enum class TernaryValue : int8 { kKnownZero = 0, kKnownOne = 1, kUnknown = 2 }; + +// A vector of ternary values. Analogous to the Bits object for concrete values. +using TernaryVector = std::vector; + +// Format of the ternary vector is, for example: 0b10XX1 +std::string ToString(const TernaryVector& value); +std::string ToString(const TernaryValue& value); + +// Converts the given string to a TernaryVector. Expects string to be of form +// emitted by ToString (for example, 0b01X0). Underscores are ignored. +xabsl::StatusOr StringToTernaryVector(absl::string_view s); + +inline std::ostream& operator<<(std::ostream& os, TernaryValue value) { + os << ToString(value); + return os; +} + +inline std::ostream& operator<<(std::ostream& os, TernaryVector vector) { + os << ToString(vector); + return os; +} + +namespace ternary_ops { + +inline bool IsKnown(TernaryValue t) { return t != TernaryValue::kUnknown; } +inline bool IsUnknown(TernaryValue t) { return t == TernaryValue::kUnknown; } + +inline bool AllUnknown(const TernaryVector& v) { + return std::all_of(v.begin(), v.end(), IsUnknown); +} + +inline TernaryValue And(const TernaryValue& a, const TernaryValue& b) { + // Truth table: + // rhs + // & | 0 1 X + // --+------------ + // lhs 0 | 0 0 0 + // 1 | 0 1 X + // X | 0 X X + if (a == TernaryValue::kKnownZero || b == TernaryValue::kKnownZero) { + return TernaryValue::kKnownZero; + } + if (a == TernaryValue::kKnownOne && b == TernaryValue::kKnownOne) { + return TernaryValue::kKnownOne; + } + return TernaryValue::kUnknown; +} + +inline TernaryValue Or(const TernaryValue& a, const TernaryValue& b) { + // Truth table: + // rhs + // | | 0 1 X + // --+------------ + // lhs 0 | 0 1 X + // 1 | 1 1 1 + // X | X 1 X + if (a == TernaryValue::kKnownOne || b == TernaryValue::kKnownOne) { + return TernaryValue::kKnownOne; + } + if (a == TernaryValue::kKnownZero && b == TernaryValue::kKnownZero) { + return TernaryValue::kKnownZero; + } + return TernaryValue::kUnknown; +} + +// Returns whether a is (definitely) equal to b. +// +// Note that this also operates as the "meet" operator as in a lattice, where +// "bottom" is represented by "X". +inline TernaryValue Equals(const TernaryValue& a, const TernaryValue& b) { + // Truth table: + // rhs + // | | 0 1 X + // --+------------ + // lhs 0 | 0 X X + // 1 | X 1 X + // X | X X X + if (IsKnown(a) && IsKnown(b) && a == b) { + return a; + } + return TernaryValue::kUnknown; +} + +// Returns the "equals" operator above for all bits in same-sized ternary +// vectors. +inline TernaryVector Equals(const TernaryVector& lhs, + const TernaryVector& rhs) { + XLS_CHECK_EQ(lhs.size(), rhs.size()); + TernaryVector result; + result.resize(lhs.size()); + for (int64 i = 0; i < lhs.size(); ++i) { + result[i] = Equals(lhs[i], rhs[i]); + } + return result; +} + +inline TernaryVector BitsToTernary(const Bits& bits) { + TernaryVector result; + result.resize(bits.bit_count()); + for (int64 i = 0; i < bits.bit_count(); ++i) { + result[i] = static_cast(bits.Get(i)); + } + return result; +} + +} // namespace ternary_ops +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_TERNARY_H_ diff --git a/xls/ir/type.cc b/xls/ir/type.cc new file mode 100644 index 0000000000..4d00aca8fd --- /dev/null +++ b/xls/ir/type.cc @@ -0,0 +1,183 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/type.h" + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" + +namespace xls { + +std::string TypeKindToString(TypeKind type_kind) { + switch (type_kind) { + case TypeKind::kTuple: + return "tuple"; + case TypeKind::kBits: + return "bits"; + case TypeKind::kArray: + return "array"; + } + return absl::StrFormat("", static_cast(type_kind)); +} + +std::ostream& operator<<(std::ostream& os, TypeKind type_kind) { + os << TypeKindToString(type_kind); + return os; +} + +xabsl::StatusOr Type::AsBits() { + if (IsBits()) { + return AsBitsOrDie(); + } + return absl::InvalidArgumentError("Type is not 'bits': " + ToString()); +} + +xabsl::StatusOr Type::AsArray() { + if (IsArray()) { + return AsArrayOrDie(); + } + return absl::InvalidArgumentError("Type is not an array: " + ToString()); +} + +TypeProto BitsType::ToProto() const { + TypeProto proto; + proto.set_type_enum(TypeProto::BITS); + proto.set_bit_count(bit_count()); + return proto; +} + +bool BitsType::IsEqualTo(const Type* other) const { + if (this == other) { + return true; + } + return other->IsBits() && bit_count() == other->AsBitsOrDie()->bit_count(); +} + +TypeProto TupleType::ToProto() const { + TypeProto proto; + proto.set_type_enum(TypeProto::TUPLE); + for (Type* element : element_types()) { + *proto.add_tuple_elements() = element->ToProto(); + } + return proto; +} + +bool TupleType::IsEqualTo(const Type* other) const { + if (this == other) { + return true; + } + if (!other->IsTuple()) { + return false; + } + const TupleType* other_tuple = other->AsTupleOrDie(); + if (size() != other_tuple->size()) { + return false; + } + for (int64 i = 0; i < size(); ++i) { + if (!element_type(i)->IsEqualTo(other_tuple->element_type(i))) { + return false; + } + } + return true; +} + +TypeProto ArrayType::ToProto() const { + TypeProto proto; + proto.set_type_enum(TypeProto::ARRAY); + proto.set_array_size(size()); + *proto.mutable_array_element() = element_type()->ToProto(); + return proto; +} + +bool ArrayType::IsEqualTo(const Type* other) const { + if (this == other) { + return true; + } + if (!other->IsArray()) { + return false; + } + const ArrayType* other_array = other->AsArrayOrDie(); + return size() == other_array->size() && + element_type()->IsEqualTo(other_array->element_type()); +} + +std::ostream& operator<<(std::ostream& os, const Type& type) { + os << type.ToString(); + return os; +} + +std::string TupleType::ToString() const { + std::vector pieces; + for (Type* member : members_) { + pieces.push_back(member->ToString()); + } + return absl::StrCat("(", absl::StrJoin(pieces, ", "), ")"); +} + +BitsType::BitsType(int64 bit_count) + : Type(TypeKind::kBits), bit_count_(bit_count) { + XLS_CHECK_GE(bit_count_, 0); +} + +std::string BitsType::ToString() const { + return absl::StrFormat("bits[%d]", bit_count()); +} + +std::string ArrayType::ToString() const { + return absl::StrFormat("%s[%d]", element_type()->ToString(), size()); +} + +FunctionTypeProto FunctionType::ToProto() const { + FunctionTypeProto proto; + for (Type* parameter : parameters()) { + *proto.add_parameters() = parameter->ToProto(); + } + *proto.mutable_return_type() = return_type()->ToProto(); + return proto; +} + +bool FunctionType::IsEqualTo(const FunctionType* other) const { + if (this == other) { + return true; + } + if (!return_type()->IsEqualTo(other->return_type())) { + return false; + } + if (parameter_count() != other->parameter_count()) { + return false; + } + for (int64 i = 0; i < parameter_count(); ++i) { + if (!parameter_type(i)->IsEqualTo(other->parameter_type(i))) { + return false; + } + } + return true; +} + +std::string FunctionType::ToString() const { + std::vector pieces; + for (Type* parameter : parameters()) { + pieces.push_back(parameter->ToString()); + } + return absl::StrCat("(", absl::StrJoin(pieces, ", "), ") -> ", + return_type()->ToString()); +} + +std::ostream& operator<<(std::ostream& os, const Type* type) { + os << (type == nullptr ? std::string("") : type->ToString()); + return os; +} + +} // namespace xls diff --git a/xls/ir/type.h b/xls/ir/type.h new file mode 100644 index 0000000000..f36d7bcae5 --- /dev/null +++ b/xls/ir/type.h @@ -0,0 +1,245 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_TYPE_H_ +#define THIRD_PARTY_XLS_IR_TYPE_H_ + +#include +#include +#include +#include +#include + +#include "absl/types/span.h" +#include "xls/common/casts.h" +#include "xls/common/integral_types.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/xls_type.pb.h" + +namespace xls { + +class BitsType; +class TupleType; +class ArrayType; +class FunctionType; + +enum class TypeKind { kTuple, kBits, kArray }; + +std::string TypeKindToString(TypeKind kind); +std::ostream& operator<<(std::ostream& os, TypeKind type_kind); + +// Abstract base class for types represented in the IR. +// +// The abstract type base can be checked-downconverted via the As*() methods +// below. +class Type { + public: + virtual ~Type() = default; + + // Creates a Type object from the given proto. + static xabsl::StatusOr> FromProto( + const TypeProto& proto); + + // Returns a proto representation of the type. + virtual TypeProto ToProto() const = 0; + + TypeKind kind() const { return kind_; } + + // Returns true if this type and 'other' represent the same type. + virtual bool IsEqualTo(const Type* other) const = 0; + + bool IsBits() const { return kind_ == TypeKind::kBits; } + BitsType* AsBitsOrDie(); + const BitsType* AsBitsOrDie() const; + xabsl::StatusOr AsBits(); + + bool IsTuple() const { return kind_ == TypeKind::kTuple; } + TupleType* AsTupleOrDie(); + const TupleType* AsTupleOrDie() const; + + bool IsArray() const { return kind_ == TypeKind::kArray; } + ArrayType* AsArrayOrDie(); + const ArrayType* AsArrayOrDie() const; + xabsl::StatusOr AsArray(); + + // Returns the count of bits required to represent the underlying type; e.g. + // for tuples this will be the sum of the bit count from all its members, for + // a "bits" type it will be the count of bits. + virtual int64 GetFlatBitCount() const = 0; + + // Returns the number of leaf Bits types in this object. + virtual int64 leaf_count() const = 0; + + virtual std::string ToString() const = 0; + + protected: + explicit Type(TypeKind kind) : kind_(kind) {} + + private: + TypeKind kind_; +}; + +std::ostream& operator<<(std::ostream& os, const Type& type); +std::ostream& operator<<(std::ostream& os, const Type* type); + +// Represents a type that is vector of bits with a fixed bit_count(). +class BitsType : public Type { + public: + explicit BitsType(int64 bit_count); + ~BitsType() override {} + int64 bit_count() const { return bit_count_; } + + TypeProto ToProto() const override; + bool IsEqualTo(const Type* other) const override; + int64 GetFlatBitCount() const override { return bit_count(); } + + int64 leaf_count() const override { return 1; } + + // Returns a string like "bits[32]". + std::string ToString() const override; + + private: + int64 bit_count_; +}; + +// Represents a type that is a tuple (of values of other potentially different +// types). +// +// Note that tuples can be empty. +class TupleType : public Type { + public: + explicit TupleType(absl::Span members) + : Type(TypeKind::kTuple), members_(members.begin(), members.end()) { + leaf_count_ = 0; + for (Type* t : members) { + leaf_count_ += t->leaf_count(); + } + } + ~TupleType() override {} + std::string ToString() const override; + + TypeProto ToProto() const override; + bool IsEqualTo(const Type* other) const override; + + // Returns the number of elements of the tuple. + int64 size() const { return members_.size(); } + + // Returns the type of the given element. + Type* element_type(int64 index) const { return members_.at(index); } + + // Returns the element types of the tuple. + absl::Span element_types() const { return members_; } + + int64 leaf_count() const override { return leaf_count_; } + + int64 GetFlatBitCount() const override { + int64 total = 0; + for (const Type* type : members_) { + total += type->GetFlatBitCount(); + } + return total; + } + + private: + int64 leaf_count_; + std::vector members_; +}; + +// Represents a type that is a one-dimensional array of identical types. +// +// Note that arrays can be empty. +class ArrayType : public Type { + public: + explicit ArrayType(int64 size, Type* element_type) + : Type(TypeKind::kArray), size_(size), element_type_(element_type) {} + ~ArrayType() override {} + std::string ToString() const override; + + TypeProto ToProto() const override; + bool IsEqualTo(const Type* other) const override; + + Type* element_type() const { return element_type_; } + int64 size() const { return size_; } + + int64 GetFlatBitCount() const override { + return element_type_->GetFlatBitCount() * size_; + } + + int64 leaf_count() const override { + return size_ * element_type()->leaf_count(); + } + + private: + int64 size_; + Type* element_type_; +}; + +// Represents a type that is a function with parameters and return type. +class FunctionType { + public: + explicit FunctionType(absl::Span parameters, Type* return_type) + : parameters_(parameters.begin(), parameters.end()), + return_type_(return_type) {} + ~FunctionType() {} + std::string ToString() const; + + FunctionTypeProto ToProto() const; + bool IsEqualTo(const FunctionType* other) const; + + int64 parameter_count() const { return parameters_.size(); } + absl::Span parameters() const { return parameters_; } + Type* parameter_type(int64 i) const { return parameters_.at(i); } + Type* return_type() const { return return_type_; } + + private: + std::vector parameters_; + Type* return_type_; +}; + +// -- Inlines + +inline const BitsType* Type::AsBitsOrDie() const { + XLS_CHECK_EQ(kind(), TypeKind::kBits) << ToString(); + return down_cast(this); +} + +inline BitsType* Type::AsBitsOrDie() { + XLS_CHECK_EQ(kind(), TypeKind::kBits) << ToString(); + return down_cast(this); +} + +inline const TupleType* Type::AsTupleOrDie() const { + XLS_CHECK_EQ(kind(), TypeKind::kTuple); + return down_cast(this); +} + +inline TupleType* Type::AsTupleOrDie() { + XLS_CHECK_EQ(kind(), TypeKind::kTuple); + return down_cast(this); +} + +inline const ArrayType* Type::AsArrayOrDie() const { + XLS_CHECK_EQ(kind(), TypeKind::kArray); + return down_cast(this); +} + +inline ArrayType* Type::AsArrayOrDie() { + XLS_CHECK_EQ(kind(), TypeKind::kArray); + return down_cast(this); +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_TYPE_H_ diff --git a/xls/ir/type_test.cc b/xls/ir/type_test.cc new file mode 100644 index 0000000000..4e68879348 --- /dev/null +++ b/xls/ir/type_test.cc @@ -0,0 +1,120 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/type.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" + +namespace xls { +namespace { + +TEST(TypeTest, TestVariousTypes) { + BitsType b42(42); + BitsType b42_2(42); + BitsType b123(123); + + EXPECT_TRUE(b42.IsEqualTo(&b42)); + EXPECT_TRUE(b42.IsEqualTo(&b42_2)); + EXPECT_FALSE(b42.IsEqualTo(&b123)); + + EXPECT_EQ(b42.leaf_count(), 1); + EXPECT_EQ(b42_2.leaf_count(), 1); + EXPECT_EQ(b123.leaf_count(), 1); + + TupleType t_empty({}); + TupleType t1({&b42, &b42}); + TupleType t2({&b42, &b42}); + TupleType t3({&b42, &b42_2}); + TupleType t4({&b42, &b42, &b42}); + + EXPECT_TRUE(t_empty.IsEqualTo(&t_empty)); + EXPECT_FALSE(t_empty.IsEqualTo(&t1)); + EXPECT_FALSE(t_empty.IsEqualTo(&b42)); + EXPECT_TRUE(t1.IsEqualTo(&t1)); + EXPECT_TRUE(t1.IsEqualTo(&t2)); + EXPECT_TRUE(t1.IsEqualTo(&t3)); + EXPECT_FALSE(t1.IsEqualTo(&t4)); + + EXPECT_EQ(t_empty.leaf_count(), 0); + EXPECT_EQ(t4.leaf_count(), 3); + + TupleType t_nested_empty({&t_empty}); + TupleType t_nested1({&t1, &t2}); + TupleType t_nested2({&t2, &t1}); + TupleType t_nested3({&t1, &t3}); + TupleType t_nested4({&t1, &t4}); + + EXPECT_TRUE(t_nested_empty.IsEqualTo(&t_nested_empty)); + EXPECT_FALSE(t_nested_empty.IsEqualTo(&t_empty)); + EXPECT_TRUE(t_nested1.IsEqualTo(&t_nested2)); + EXPECT_TRUE(t_nested1.IsEqualTo(&t_nested3)); + EXPECT_FALSE(t_nested1.IsEqualTo(&t_nested4)); + + EXPECT_EQ(t_nested_empty.leaf_count(), 0); + EXPECT_EQ(t_nested3.leaf_count(), 4); + + ArrayType a1(7, &b42); + ArrayType a2(7, &b42_2); + ArrayType a3(3, &b42); + ArrayType a4(7, &b123); + + EXPECT_TRUE(a1.IsEqualTo(&a1)); + EXPECT_TRUE(a1.IsEqualTo(&a2)); + EXPECT_FALSE(a1.IsEqualTo(&a3)); + EXPECT_FALSE(a1.IsEqualTo(&a4)); + + EXPECT_EQ(a1.leaf_count(), 7); + EXPECT_EQ(a3.leaf_count(), 3); + + // Arrays-of-tuples. + ArrayType a_of_t1(42, &t1); + ArrayType a_of_t2(42, &t2); + ArrayType a_of_t3(42, &t4); + + EXPECT_TRUE(a_of_t1.IsEqualTo(&a_of_t2)); + EXPECT_FALSE(a_of_t1.IsEqualTo(&a_of_t3)); + + EXPECT_EQ(a_of_t1.leaf_count(), 84); + EXPECT_EQ(a_of_t3.leaf_count(), 126); + + // Tuple-of-Arrays. + TupleType t_of_a1({&a1, &a1, &a2}); + TupleType t_of_a2({&a1, &a1, &a1}); + TupleType t_of_a3({&a1, &a2, &a3}); + + EXPECT_TRUE(t_of_a1.IsEqualTo(&t_of_a2)); + EXPECT_FALSE(t_of_a1.IsEqualTo(&t_of_a3)); + EXPECT_FALSE(t_of_a1.IsEqualTo(&b42)); + + EXPECT_EQ(t_of_a1.leaf_count(), 21); + EXPECT_EQ(t_of_a3.leaf_count(), 17); + + // Function types. + FunctionType f_type1({&b42, &a1}, &b42); + FunctionType f_type2({&b42, &a2}, &b42); + FunctionType f_type3({&b42}, &b42); + FunctionType f_type4({}, &b42); + FunctionType f_type5({&b42, &a1}, &b123); + + EXPECT_TRUE(f_type1.IsEqualTo(&f_type2)); + EXPECT_FALSE(f_type1.IsEqualTo(&f_type3)); + EXPECT_FALSE(f_type1.IsEqualTo(&f_type4)); + EXPECT_FALSE(f_type1.IsEqualTo(&f_type5)); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/unwrapping_iterator.h b/xls/ir/unwrapping_iterator.h new file mode 100644 index 0000000000..7dedee6135 --- /dev/null +++ b/xls/ir/unwrapping_iterator.h @@ -0,0 +1,100 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_UNWRAPPING_ITERATOR_H_ +#define THIRD_PARTY_XLS_IR_UNWRAPPING_ITERATOR_H_ + +#include +#include + +namespace xls { + +// UnwrappingIterator is a transforming iterator that calls get() on the +// elements it returns. +// +// Shamelessly copied from jlebar's +// third_party/tensorflow/compiler/xla/iterator_util.h. +// +// Together with absl::iterator_range, this lets classes which contain a +// collection of std::unique_ptrs expose a view of raw pointers to consumers. +// For example: +// +// class MyContainer { +// public: +// absl::iterator_range< +// UnwrappingIterator>::iterator>> +// things() { +// return {MakeUnwrappingIterator(things_.begin()), +// MakeUnwrappingIterator(things_.end())}; +// } +// +// absl::iterator_range>::const_iterator>> +// things() const { +// return {MakeUnwrappingIterator(things_.begin()), +// MakeUnwrappingIterator(things_.end())}; +// } +// +// private: +// std::vector> things_; +// }; +// +// MyContainer container = ...; +// for (Thing* t : container.things()) { +// ... +// } +// +// For simplicity, UnwrappingIterator is currently unconditionally an +// input_iterator -- it doesn't inherit any superpowers NestedIterator may have. +template +class UnwrappingIterator + : public std::iterator()->get())> { + private: + NestedIter iter_; + + public: + explicit UnwrappingIterator(NestedIter iter) : iter_(std::move(iter)) {} + + auto operator*() -> decltype(iter_->get()) { return iter_->get(); } + auto operator-> () -> decltype(iter_->get()) { return iter_->get(); } + UnwrappingIterator& operator++() { + ++iter_; + return *this; + } + UnwrappingIterator operator++(int) { + UnwrappingIterator temp(iter_); + operator++(); + return temp; + } + + friend bool operator==(const UnwrappingIterator& a, + const UnwrappingIterator& b) { + return a.iter_ == b.iter_; + } + + friend bool operator!=(const UnwrappingIterator& a, + const UnwrappingIterator& b) { + return !(a == b); + } +}; + +template +UnwrappingIterator MakeUnwrappingIterator(NestedIter iter) { + return UnwrappingIterator(std::move(iter)); +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_UNWRAPPING_ITERATOR_H_ diff --git a/xls/ir/value.cc b/xls/ir/value.cc new file mode 100644 index 0000000000..ae7a4b6be8 --- /dev/null +++ b/xls/ir/value.cc @@ -0,0 +1,263 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/value.h" + +#include "absl/algorithm/container.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "re2/re2.h" + +namespace xls { + +/* static */ xabsl::StatusOr Value::Array( + absl::Span elements) { + if (elements.empty()) { + return absl::UnimplementedError("Empty array Values are not supported."); + } + + // Blow up if they're not all of the same type. + for (int64 i = 1; i < elements.size(); ++i) { + XLS_RET_CHECK(elements[0].SameTypeAs(elements[i])); + } + return Value(ValueKind::kArray, elements); +} + +std::string ValueKindToString(ValueKind kind) { + switch (kind) { + case ValueKind::kInvalid: + return "invalid"; + case ValueKind::kBits: + return "bits"; + case ValueKind::kTuple: + return "tuple"; + case ValueKind::kArray: + return "array"; + default: + return absl::StrCat("(kind), ")>"); + } +} + +int64 Value::GetFlatBitCount() const { + if (kind() == ValueKind::kBits) { + return bits().bit_count(); + } else if (kind() == ValueKind::kTuple) { + int64 total_size = 0; + for (const Value& e : elements()) { + total_size += e.GetFlatBitCount(); + } + return total_size; + } else if (kind() == ValueKind::kArray) { + if (empty()) { + return 0; + } + return size() * element(0).GetFlatBitCount(); + } else { + XLS_LOG(FATAL) << "Invalid value kind: " << kind(); + } +} + +bool Value::IsAllZeros() const { + if (kind() == ValueKind::kBits) { + return bits().IsAllZeros(); + } else if (kind() == ValueKind::kTuple || kind() == ValueKind::kArray) { + for (const Value& e : elements()) { + if (!e.IsAllZeros()) { + return false; + } + } + } else { + XLS_LOG(FATAL) << "Invalid value kind: " << kind(); + } + return true; +} + +bool Value::IsAllOnes() const { + if (kind() == ValueKind::kBits) { + return bits().IsAllOnes(); + } else if (kind() == ValueKind::kTuple || kind() == ValueKind::kArray) { + for (const Value& e : elements()) { + if (!e.IsAllOnes()) { + return false; + } + } + } else { + XLS_LOG(FATAL) << "Invalid value kind: " << kind(); + } + return true; +} + +std::string Value::ToString(FormatPreference preference) const { + switch (kind_) { + case ValueKind::kInvalid: + return ""; + case ValueKind::kBits: + return absl::StrFormat("bits[%d]:%s", bits().bit_count(), + bits().ToString(preference)); + case ValueKind::kTuple: + return absl::StrCat( + "(", + absl::StrJoin(elements(), ", ", + [&](std::string* out, const Value& element) { + absl::StrAppend(out, element.ToString(preference)); + }), + ")"); + case ValueKind::kArray: + return absl::StrCat( + "[", + absl::StrJoin(elements(), ", ", + [&](std::string* out, const Value& element) { + absl::StrAppend(out, element.ToString(preference)); + }), + "]"); + } + XLS_LOG(FATAL) << "Value has invalid kind: " << static_cast(kind_); +} + +void Value::FlattenTo(BitPushBuffer* buffer) const { + switch (kind_) { + case ValueKind::kBits: + bits().FlattenTo(buffer); + return; + case ValueKind::kTuple: + case ValueKind::kArray: + for (const Value& element : elements()) { + element.FlattenTo(buffer); + } + return; + case ValueKind::kInvalid: + break; + } + XLS_LOG(FATAL) << "Invalid value kind: " << ValueKindToString(kind_); +} + +xabsl::StatusOr> Value::GetElements() const { + if (!absl::holds_alternative>(payload_)) { + return absl::InvalidArgumentError("Value does not hold elements."); + } + return std::vector(elements().begin(), elements().end()); +} + +xabsl::StatusOr Value::GetBitsWithStatus() const { + if (!IsBits()) { + return absl::InvalidArgumentError( + "Attempted to convert a non-Bits Value to Bits."); + } + return bits(); +} + +std::string Value::ToHumanString(FormatPreference preference) const { + switch (kind_) { + case ValueKind::kInvalid: + return ""; + case ValueKind::kBits: + return bits().ToString(preference); + case ValueKind::kArray: + return absl::StrCat("[", + absl::StrJoin(elements(), ", ", + [&](std::string* out, const Value& v) { + absl::StrAppend( + out, v.ToHumanString(preference)); + }), + "]"); + case ValueKind::kTuple: + return absl::StrCat("(", + absl::StrJoin(elements(), ", ", + [&](std::string* out, const Value& v) { + return absl::StrAppend( + out, v.ToHumanString(preference)); + }), + ")"); + } + XLS_LOG(FATAL) << "Invalid value kind: " << ValueKindToString(kind_); +} + +bool Value::SameTypeAs(const Value& other) const { + if (kind() != other.kind()) { + return false; + } + switch (kind()) { + case ValueKind::kBits: + return bits().bit_count() == other.bits().bit_count(); + case ValueKind::kTuple: + if (size() != other.size()) { + return false; + } + for (int64 i = 0; i < size(); ++i) { + if (!element(i).SameTypeAs(other.element(i))) { + return false; + } + } + return true; + case ValueKind::kArray: { + return size() == other.size() && element(0).SameTypeAs(other.element(0)); + } + case ValueKind::kInvalid: + break; + } + XLS_LOG(FATAL) << "Invalid value encountered: " << ValueKindToString(kind()); +} + +xabsl::StatusOr Value::TypeAsProto() const { + TypeProto proto; + switch (kind()) { + case ValueKind::kBits: + proto.set_type_enum(TypeProto::BITS); + proto.set_bit_count(bits().bit_count()); + break; + case ValueKind::kTuple: + proto.set_type_enum(TypeProto::TUPLE); + for (const Value& elem : elements()) { + XLS_ASSIGN_OR_RETURN(*proto.add_tuple_elements(), elem.TypeAsProto()); + } + break; + case ValueKind::kArray: { + if (elements().empty()) { + return absl::InternalError( + "Cannot determine type of empty array value"); + } + proto.set_type_enum(TypeProto::ARRAY); + proto.set_array_size(size()); + XLS_ASSIGN_OR_RETURN(*proto.mutable_array_element(), + elements().front().TypeAsProto()); + break; + } + case ValueKind::kInvalid: + return absl::InternalError(absl::StrCat("Invalid value kind: ", kind())); + } + return proto; +} + +bool Value::operator==(const Value& other) const { + if (kind() != other.kind()) { + return false; + } + + if (IsBits()) { + return bits() == other.bits(); + } + + // All non-Bits types are container types -- should have a size attribute. + if (size() != other.size()) { + return false; + } + + return absl::c_equal(elements(), other.elements()); +} + +} // namespace xls diff --git a/xls/ir/value.h b/xls/ir/value.h new file mode 100644 index 0000000000..71f7838a6b --- /dev/null +++ b/xls/ir/value.h @@ -0,0 +1,147 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_VALUE_H_ +#define THIRD_PARTY_XLS_IR_VALUE_H_ + +#include "absl/types/span.h" +#include "absl/types/variant.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/xls_type.pb.h" + +namespace xls { + +class Type; + +enum class ValueKind { + kInvalid, + + kBits, + + kTuple, + + // Arrays must be homogeneous in their elements, and may choose to use a + // more efficient storage mechanism as a result. For now we always used the + // boxed Value type, though. + kArray, +}; + +std::string ValueKindToString(ValueKind kind); + +inline std::ostream& operator<<(std::ostream& os, ValueKind kind) { + os << ValueKindToString(kind); + return os; +} + +// Represents a value in the XLS system; e.g. values can be "bits", tuples of +// values, or arrays or values. Arrays are represented similarly to tuples, but +// are monomorphic and potentially multi-dimensional. +// +// TODO(leary): 2019-04-04 Arrays are not currently multi-dimensional, we had +// some discussion around this, maybe they should be? +class Value { + public: + static Value Tuple(absl::Span elements) { + return Value(ValueKind::kTuple, elements); + } + static Value TupleOwned(std::vector&& elements) { + return Value(ValueKind::kTuple, elements); + } + + // All members of "elements" must be of the same type, or an error status will + // be returned. + static xabsl::StatusOr Array(absl::Span elements); + + // As above, but as a precondition all elements must be known to be of the + // same type. + static Value ArrayOrDie(absl::Span elements) { + return Array(elements).value(); + } + + Value() : kind_(ValueKind::kInvalid), payload_(nullptr) {} + + explicit Value(Bits bits) + : kind_(ValueKind::kBits), payload_(std::move(bits)) {} + + // Serializes the contents of this value as bits in the buffer. + void FlattenTo(BitPushBuffer* buffer) const; + + ValueKind kind() const { return kind_; } + bool IsTuple() const { return kind_ == ValueKind::kTuple; } + bool IsArray() const { return kind_ == ValueKind::kArray; } + bool IsBits() const { return absl::holds_alternative(payload_); } + const Bits& bits() const { return absl::get(payload_); } + xabsl::StatusOr GetBitsWithStatus() const; + + xabsl::StatusOr> GetElements() const; + + absl::Span elements() const { + return absl::get>(payload_); + } + const Value& element(int64 i) const { return elements().at(i); } + int64 size() const { return elements().size(); } + bool empty() const { return elements().empty(); } + + // Returns the total number of bits in this value. + int64 GetFlatBitCount() const; + + // Returns whether all bits within this value are zeros/ones. + bool IsAllZeros() const; + bool IsAllOnes() const; + + // Converts this value to a descriptive string format for printing to + // screen. String includes the type. Examples: + // bits[8]:0b01 + // (bits[1]:0, bits[32]:42) + // [bits[7]:22, bits[7]:1, bits[7]:123] + std::string ToString( + FormatPreference preference = FormatPreference::kDefault) const; + + // Emits the Value as a human-readable string where numbers represented as + // undecorated numbers without bit width (eg, "42" or "0xabcd"). This is used + // when emitting the literals in the IR where the bit count is already known + // and a more compact representation is desirable. + std::string ToHumanString( + FormatPreference preference = FormatPreference::kDefault) const; + + // Returns the type of the Value as a type proto. + xabsl::StatusOr TypeAsProto() const; + + // Returns true if 'other' has the same type as this Value. + bool SameTypeAs(const Value& other) const; + + bool operator==(const Value& other) const; + bool operator!=(const Value& other) const { return !(*this == other); } + + private: + Value(ValueKind kind, absl::Span elements) + : kind_(kind), + payload_(std::vector(elements.begin(), elements.end())) {} + + Value(ValueKind kind, std::vector&& elements) + : kind_(kind), payload_(std::move(elements)) {} + + ValueKind kind_; + absl::variant, Bits> payload_; +}; + +inline std::ostream& operator<<(std::ostream& os, const Value& value) { + os << value.ToString(FormatPreference::kDefault); + return os; +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_VALUE_H_ diff --git a/xls/ir/value_helpers.cc b/xls/ir/value_helpers.cc new file mode 100644 index 0000000000..3249819952 --- /dev/null +++ b/xls/ir/value_helpers.cc @@ -0,0 +1,117 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/value_helpers.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" + +namespace xls { +namespace { + +Value ValueOfType(Type* type, + const std::function& fbits) { + switch (type->kind()) { + case TypeKind::kBits: + return Value(fbits(type->AsBitsOrDie()->bit_count())); + case TypeKind::kTuple: { + std::vector elements; + for (Type* element_type : type->AsTupleOrDie()->element_types()) { + elements.push_back(ValueOfType(element_type, fbits)); + } + return Value::Tuple(elements); + } + case TypeKind::kArray: { + std::vector elements; + for (int64 i = 0; i < type->AsArrayOrDie()->size(); ++i) { + elements.push_back( + ValueOfType(type->AsArrayOrDie()->element_type(), fbits)); + } + return Value::Array(elements).value(); + } + } + XLS_LOG(FATAL) << "Invalid kind: " << type->kind(); +} + +} // namespace + +Value ZeroOfType(Type* type) { + return ValueOfType( + type, [](int64 bit_count) { return UBits(0, /*bit_count=*/bit_count); }); +} + +Value AllOnesOfType(Type* type) { + return ValueOfType( + type, [](int64 bit_count) { return SBits(-1, /*bit_count=*/bit_count); }); +} + +Value RandomValue(Type* type, std::minstd_rand* engine) { + if (type->IsTuple()) { + TupleType* tuple_type = type->AsTupleOrDie(); + std::vector elements; + for (int64 i = 0; i < tuple_type->size(); ++i) { + elements.push_back(RandomValue(tuple_type->element_type(i), engine)); + } + return Value::Tuple(elements); + } + if (type->IsArray()) { + ArrayType* array_type = type->AsArrayOrDie(); + std::vector elements; + for (int64 i = 0; i < array_type->size(); ++i) { + elements.push_back(RandomValue(array_type->element_type(), engine)); + } + return Value::Array(elements).value(); + } + int64 bit_count = type->AsBitsOrDie()->bit_count(); + std::vector bytes; + std::uniform_int_distribution generator(0, 255); + for (int64 i = 0; i < bit_count; i += 8) { + bytes.push_back(generator(*engine)); + } + return Value(Bits::FromBytes(bytes, bit_count)); +} + +std::vector RandomFunctionArguments(Function* f, + std::minstd_rand* engine) { + std::vector inputs; + for (Param* param : f->params()) { + inputs.push_back(RandomValue(param->GetType(), engine)); + } + return inputs; +} + +Value F32ToTuple(float value) { + uint32 x = absl::bit_cast(value); + bool sign = x >> 31; + uint8 exp = x >> 23; + uint32 fraction = x & Mask(23); + return Value::Tuple({Value(Bits::FromBytes({sign}, /*bit_count=*/1)), + Value(Bits::FromBytes({exp}, /*bit_count=*/8)), + Value(UBits(fraction, /*bit_count=*/23))}); +} + +xabsl::StatusOr TupleToF32(const Value& v) { + XLS_ASSIGN_OR_RETURN(uint32 sign, v.element(0).bits().ToUint64()); + XLS_ASSIGN_OR_RETURN(uint32 exp, v.element(1).bits().ToUint64()); + XLS_ASSIGN_OR_RETURN(uint32 fraction, v.element(2).bits().ToUint64()); + // Validate the values were all appropriate. + XLS_DCHECK_EQ(sign, sign & Mask(1)); + XLS_DCHECK_EQ(exp, exp & Mask(8)); + XLS_DCHECK_EQ(fraction, fraction & Mask(23)); + // Reconstruct the float. + uint32 x = (sign << 31) | (exp << 23) | fraction; + return absl::bit_cast(x); +} + +} // namespace xls diff --git a/xls/ir/value_helpers.h b/xls/ir/value_helpers.h new file mode 100644 index 0000000000..e617035acf --- /dev/null +++ b/xls/ir/value_helpers.h @@ -0,0 +1,95 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_VALUE_HELPERS_H_ +#define THIRD_PARTY_XLS_IR_VALUE_HELPERS_H_ + +#include + +#include "absl/strings/str_cat.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" + +namespace xls { + +// Returns a zero value for the given type. +// +// Note that for composite values (tuples / arrays) all member values are zero. +Value ZeroOfType(Type* type); + +// As above, but fills bits with all-ones pattern. +Value AllOnesOfType(Type* type); + +// For use in e.g. StrJoin as a lambda. +inline void ValueFormatter(std::string* out, const Value& value) { + absl::StrAppend(out, value.ToString()); +} +inline void ValueFormatterBinary(std::string* out, const Value& value) { + absl::StrAppend(out, value.ToString(FormatPreference::kBinary)); +} + +// Returns a Value with random uniformly distributed bits using the given +// engine. +Value RandomValue(Type* type, std::minstd_rand* engine); + +// Returns a set of argument values for the given function with random uniformly +// distributed bits using the given engine. +std::vector RandomFunctionArguments(Function* f, + std::minstd_rand* engine); + +// Returns whether "value" conforms to type "type" -- this lets us avoid +// constructing a Type and doing an equivalence check on hot paths. +inline bool ValueConformsToType(const Value& value, const Type* type) { + switch (value.kind()) { + case ValueKind::kBits: + return type->IsBits() && + value.bits().bit_count() == type->AsBitsOrDie()->bit_count(); + case ValueKind::kArray: + return type->IsArray() && + ValueConformsToType(value.element(0), + type->AsArrayOrDie()->element_type()); + case ValueKind::kTuple: { + if (!type->IsTuple()) { + return false; + } + const TupleType* tuple_type = type->AsTupleOrDie(); + if (tuple_type->size() != value.size()) { + return false; + } + for (int64 i = 0; i < tuple_type->size(); ++i) { + if (!ValueConformsToType(value.element(i), + tuple_type->element_type(i))) { + return false; + } + } + return true; + } + default: + XLS_LOG(FATAL) << "Invalid value kind: " << value.kind(); + } +} + +// Converts a float value in C++ to a 3-tuple suitable for feeding the XLS rsqrt +// routine. +Value F32ToTuple(float value); + +// Converts a 3-tuple (XLS rsqrt routine float representation) into a C++ float. +xabsl::StatusOr TupleToF32(const Value& v); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_VALUE_HELPERS_H_ diff --git a/xls/ir/value_helpers_test.cc b/xls/ir/value_helpers_test.cc new file mode 100644 index 0000000000..95ec1fd05e --- /dev/null +++ b/xls/ir/value_helpers_test.cc @@ -0,0 +1,115 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/value_helpers.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/package.h" +#include "xls/ir/value.h" + +namespace xls { +namespace { + +TEST(ValueHelpersTest, RandomBits) { + Package p("test_package"); + std::minstd_rand rng_engine; + + Value b0 = RandomValue(p.GetBitsType(0), &rng_engine); + EXPECT_TRUE(b0.IsBits()); + EXPECT_EQ(b0.bits().bit_count(), 0); + + Value b1 = RandomValue(p.GetBitsType(1), &rng_engine); + EXPECT_TRUE(b1.IsBits()); + EXPECT_EQ(b1.bits().bit_count(), 1); + + Value b1234 = RandomValue(p.GetBitsType(1234), &rng_engine); + EXPECT_TRUE(b1234.IsBits()); + EXPECT_EQ(b1234.bits().bit_count(), 1234); + + // Do simple tests of a moderately sized sample of 64-bit random Bits values. + // With overwhelming probability: + // (1) generated values should all be distinct. + // (2) deltas between consecutive generated values should all be distinct. + // (3) every bit should be set to 0 and 1 at least once. + const int64 kSampleCount = 1024; + const int64 kBitWidth = 64; + absl::flat_hash_set samples; + std::vector bit_set_count(kBitWidth); + uint64 previous_sample = 1; + for (int64 i = 0; i < kSampleCount; ++i) { + Value b = RandomValue(p.GetBitsType(kBitWidth), &rng_engine); + XLS_ASSERT_OK_AND_ASSIGN(uint64 as_uint64, b.bits().ToUint64()); + uint64 delta = as_uint64 - previous_sample; + EXPECT_FALSE(samples.contains(as_uint64)); + EXPECT_FALSE(samples.contains(delta)); + for (int64 j = 0; j < kBitWidth; ++j) { + if (b.bits().Get(j)) { + ++bit_set_count[j]; + } + } + samples.insert(as_uint64); + samples.insert(delta); + previous_sample = as_uint64; + } + + for (int64 i = 0; i < kBitWidth; ++i) { + EXPECT_GT(bit_set_count[i], 0); + EXPECT_LT(bit_set_count[i], kSampleCount); + } +} + +TEST(ValueHelpersTest, Determinism) { + Package p("test_package"); + std::minstd_rand rng_engine0; + std::minstd_rand rng_engine1; + EXPECT_EQ(RandomValue(p.GetBitsType(42), &rng_engine0), + RandomValue(p.GetBitsType(42), &rng_engine1)); + EXPECT_EQ(RandomValue(p.GetBitsType(42), &rng_engine0), + RandomValue(p.GetBitsType(42), &rng_engine1)); +} + +TEST(ValueHelpersTest, RandomOtherTypes) { + Package p("test_package"); + std::minstd_rand rng_engine; + + Value empty_tuple = RandomValue(p.GetTupleType({}), &rng_engine); + EXPECT_TRUE(empty_tuple.IsTuple()); + EXPECT_EQ(empty_tuple.size(), 0); + + Value tuple = RandomValue( + p.GetTupleType({p.GetBitsType(12345), p.GetBitsType(32)}), &rng_engine); + EXPECT_TRUE(tuple.IsTuple()); + EXPECT_EQ(tuple.size(), 2); + // Overwhelmingly likely that the 12345-bit number is larger than the 32-bit + // number. + EXPECT_TRUE( + bits_ops::UGreaterThan(tuple.element(0).bits(), tuple.element(1).bits())); + + Value array = + RandomValue(p.GetArrayType(123, p.GetBitsType(57)), &rng_engine); + EXPECT_TRUE(array.IsArray()); + EXPECT_EQ(array.size(), 123); + for (int64 i = 0; i < 123; ++i) { + // Overwhelmingly likely that the elements are non-zero. + EXPECT_NE(array.element(i).bits().ToInt64().value(), 0); + } +} + +} // namespace +} // namespace xls diff --git a/xls/ir/value_test.cc b/xls/ir/value_test.cc new file mode 100644 index 0000000000..9ecdc914c3 --- /dev/null +++ b/xls/ir/value_test.cc @@ -0,0 +1,141 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/value.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits.h" +#include "xls/ir/package.h" + +namespace xls { + +TEST(ValueTest, ToHumanString) { + Value bits_value(UBits(42, 33)); + EXPECT_EQ(bits_value.ToHumanString(), "42"); + + XLS_ASSERT_OK_AND_ASSIGN(Value array_value, + Value::Array({Value(UBits(3, 8)), Value(UBits(4, 8)), + Value(UBits(5, 8))})); + EXPECT_EQ(array_value.ToHumanString(), "[3, 4, 5]"); + + XLS_ASSERT_OK_AND_ASSIGN(Value nested_array_value, + Value::Array({array_value, array_value})); + EXPECT_EQ(nested_array_value.ToHumanString(), "[[3, 4, 5], [3, 4, 5]]"); + + Value tuple_value = Value::Tuple( + {array_value, Value(UBits(42, 8)), Value(UBits(123, 8))}); + EXPECT_EQ(tuple_value.ToHumanString(), "([3, 4, 5], 42, 123)"); +} + +TEST(ValueTest, ToString) { + Value bits_value(UBits(42, 33)); + EXPECT_EQ(bits_value.ToString(), "bits[33]:42"); + + XLS_ASSERT_OK_AND_ASSIGN(Value array_value, + Value::Array({Value(UBits(3, 8)), Value(UBits(4, 8)), + Value(UBits(5, 8))})); + EXPECT_EQ(array_value.ToString(), "[bits[8]:3, bits[8]:4, bits[8]:5]"); + + XLS_ASSERT_OK_AND_ASSIGN(Value nested_array_value, + Value::Array({array_value, array_value})); + EXPECT_EQ( + nested_array_value.ToString(), + "[[bits[8]:3, bits[8]:4, bits[8]:5], [bits[8]:3, bits[8]:4, bits[8]:5]]"); + + Value tuple_value = + Value::Tuple({array_value, Value(UBits(42, 17)), Value(UBits(123, 33))}); + EXPECT_EQ(tuple_value.ToString(), + "([bits[8]:3, bits[8]:4, bits[8]:5], bits[17]:42, bits[33]:123)"); +} + +TEST(ValueTest, SameTypeAs) { + Value b1(UBits(42, 33)); + Value b2(UBits(42, 10)); + Value b3(UBits(0, 33)); + EXPECT_TRUE(b1.SameTypeAs(b1)); + EXPECT_FALSE(b1.SameTypeAs(b2)); + EXPECT_TRUE(b1.SameTypeAs(b3)); + + Value tuple1 = Value::Tuple({b1, b2}); + Value tuple2 = Value::Tuple({b1, b2}); + Value tuple3 = Value::Tuple({b1, b2, b3}); + EXPECT_TRUE(tuple1.SameTypeAs(tuple1)); + EXPECT_TRUE(tuple1.SameTypeAs(tuple2)); + EXPECT_FALSE(tuple1.SameTypeAs(tuple3)); + + XLS_ASSERT_OK_AND_ASSIGN(Value array1, Value::Array({b1, b3})); + XLS_ASSERT_OK_AND_ASSIGN(Value array2, Value::Array({b3, b1})); + XLS_ASSERT_OK_AND_ASSIGN(Value array3, Value::Array({b1, b3, b3})); + EXPECT_TRUE(array1.SameTypeAs(array1)); + EXPECT_TRUE(array1.SameTypeAs(array2)); + EXPECT_FALSE(array1.SameTypeAs(array3)); + + EXPECT_FALSE(b1.SameTypeAs(tuple1)); + EXPECT_FALSE(b1.SameTypeAs(array1)); +} + +TEST(ValueTest, IsAllZeroOnes) { + EXPECT_TRUE(Value(UBits(0, 0)).IsAllZeros()); + EXPECT_TRUE(Value(UBits(0, 0)).IsAllOnes()); + + EXPECT_TRUE(Value(UBits(0, 1)).IsAllZeros()); + EXPECT_FALSE(Value(UBits(0, 1)).IsAllOnes()); + + EXPECT_FALSE(Value(UBits(1, 1)).IsAllZeros()); + EXPECT_TRUE(Value(UBits(1, 1)).IsAllOnes()); + + EXPECT_TRUE(Value(UBits(0, 8)).IsAllZeros()); + EXPECT_FALSE(Value(UBits(0, 8)).IsAllOnes()); + + EXPECT_FALSE(Value(UBits(255, 8)).IsAllZeros()); + EXPECT_TRUE(Value(UBits(255, 8)).IsAllOnes()); + + EXPECT_FALSE(Value(UBits(123, 32)).IsAllZeros()); + EXPECT_FALSE(Value(UBits(123, 32)).IsAllOnes()); + + EXPECT_TRUE(Value::ArrayOrDie({Value(UBits(0, 32)), Value(UBits(0, 32)), + Value(UBits(0, 32))}) + .IsAllZeros()); + EXPECT_FALSE(Value::ArrayOrDie({Value(UBits(0, 32)), Value(UBits(1234, 32)), + Value(UBits(0, 32))}) + .IsAllZeros()); + + EXPECT_TRUE(Value::ArrayOrDie({Value(UBits(127, 7)), Value(UBits(127, 7)), + Value(UBits(127, 7))}) + .IsAllOnes()); + EXPECT_FALSE(Value::ArrayOrDie({Value(UBits(127, 7)), Value(UBits(126, 7)), + Value(UBits(127, 7))}) + .IsAllOnes()); + + EXPECT_TRUE(Value::Tuple({}).IsAllZeros()); + EXPECT_TRUE(Value::Tuple({}).IsAllOnes()); + + EXPECT_TRUE( + Value::Tuple({Value(UBits(0, 3)), Value(UBits(0, 7)), Value(UBits(0, 1))}) + .IsAllZeros()); + EXPECT_FALSE( + Value::Tuple({Value(UBits(0, 3)), Value(UBits(1, 7)), Value(UBits(0, 1))}) + .IsAllZeros()); + + EXPECT_TRUE(Value::Tuple({Value(UBits(7, 3)), Value(UBits(127, 7)), + Value(UBits(1, 1))}) + .IsAllOnes()); + EXPECT_FALSE(Value::Tuple({Value(UBits(7, 3)), Value(UBits(121, 7)), + Value(UBits(1, 1))}) + .IsAllOnes()); +} + +} // namespace xls diff --git a/xls/ir/value_test_util.cc b/xls/ir/value_test_util.cc new file mode 100644 index 0000000000..36a7455023 --- /dev/null +++ b/xls/ir/value_test_util.cc @@ -0,0 +1,27 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/value_test_util.h" + +namespace xls { + +::testing::AssertionResult ValuesEqual(const Value& a, const Value& b) { + if (a != b) { + return testing::AssertionFailure() + << a.ToString() << " != " << b.ToString(); + } + return testing::AssertionSuccess(); +} + +} // namespace xls diff --git a/xls/ir/value_test_util.h b/xls/ir/value_test_util.h new file mode 100644 index 0000000000..b3287c61df --- /dev/null +++ b/xls/ir/value_test_util.h @@ -0,0 +1,33 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_VALUE_TEST_UTIL_H_ +#define THIRD_PARTY_XLS_IR_VALUE_TEST_UTIL_H_ + +#include "gtest/gtest.h" +#include "xls/ir/value.h" + +namespace xls { + +// Returns an assertion result indicating whether the given two values were +// equal. If equal the return value is AssertionSuccess, otherwise +// AssertionFailure. For large Values (arrays, tuples, and very wide bit widths) +// this method can give a more readable message than EXPECT_EQ(a, b) by +// highlighting the differences between the two Values rather than just dumping +// the string representation of the Values. +::testing::AssertionResult ValuesEqual(const Value& a, const Value& b); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_VALUE_TEST_UTIL_H_ diff --git a/xls/ir/value_test_util_test.cc b/xls/ir/value_test_util_test.cc new file mode 100644 index 0000000000..509e7dd96d --- /dev/null +++ b/xls/ir/value_test_util_test.cc @@ -0,0 +1,32 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/value_test_util.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/ir/value.h" + +namespace xls { +namespace { + +TEST(ValueTestUtilTest, ValuesEqual) { + EXPECT_TRUE(ValuesEqual(Value(UBits(1, 1)), Value(UBits(1, 1)))); + EXPECT_FALSE(ValuesEqual(Value(UBits(1, 1)), Value(UBits(0, 1)))); + + EXPECT_FALSE(ValuesEqual(Value(UBits(1, 1234)), Value(UBits(1, 10)))); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/value_view.h b/xls/ir/value_view.h new file mode 100644 index 0000000000..a234ef80ce --- /dev/null +++ b/xls/ir/value_view.h @@ -0,0 +1,181 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file defines "View" types for XLS IR Values - these overlay some, but +// not nearly all, Value-type semantics on top of flat byte buffers. +// +// These types make heavy use of template metaprogramming to reduce dispatch +// overheads as much as possible, e.g., for JIT-evaluating a test sample. +// +// TODO(rspringer): It probably makes sense to have functions to copy a view to +// a real corresponding type. +#ifndef THIRD_PARTY_XLS_IR_VALUE_VIEW_H_ +#define THIRD_PARTY_XLS_IR_VALUE_VIEW_H_ + +#include "xls/common/integral_types.h" +#include "xls/common/logging/logging.h" +#include "xls/common/math_util.h" + +namespace xls { + +// ArrayView provides some array-type functionality on top of a flat character +// buffer. +template +class ArrayView { + public: + explicit ArrayView(absl::Span buffer) : buffer_(buffer) { + XLS_DCHECK(buffer_.size() == GetTypeSize()) + << "Span isn't sized to this array's type!"; + } + + // Gets the storage size of this array. + static constexpr uint64 GetTypeSize() { + return ElementT::GetTypeSize() * kNumElements; + } + + // Gets the N'th element in the array. + static ElementT Get(const uint8* buffer, int index) { + return ElementT(buffer + (ElementT::GetTypeSize() * index)); + } + + private: + absl::Span buffer_; +}; + +// Statically generates a value for masking off high bits in a value. +template +inline uint64 MakeMask() { + return (1 << kBitCount) | (MakeMask()); +} + +template <> +inline uint64 MakeMask<0>() { + return 1; +} + +// BitsView provides some Bits-type functionality on top of a flat character +// buffer. +template +class BitsView { + public: + explicit BitsView(const uint8* buffer) : buffer_(buffer) {} + + // Gets the storage size of this type. + static constexpr uint64 GetTypeSize() { + constexpr uint64 kCharBit = 8; + + // Constexpr ceiling division. + return CeilOfRatio(kNumBits, kCharBit); + } + + // Determines the appropriate return type for this BitsView type. + static_assert(kNumBits != 0 && kNumBits <= 64); + typedef typename std::conditional< + (kNumBits > 32), uint64, + typename std::conditional< + (kNumBits > 16), uint32, + typename std::conditional< + (kNumBits > 8), uint16, + typename std::conditional<(kNumBits > 1), uint8, bool>::type>:: + type>::type>::type ReturnT; + + // Note that this will only return the first 8 bytes of a > 64b type. + // Values larger than 64 bits should be converted to proper Bits type before + // usage. + ReturnT GetValue() { + return *reinterpret_cast(buffer_) & + MakeMask(); + } + + private: + const uint8* buffer_; +}; + +// TupleView provides some tuple-type functionality on top of a flat character +// buffer. +// +// This is the big one, as tuples can be comprised of arbitrarily-typed +// elements, so some template horribleness is necessary to determine (at compile +// time) the type of the N'th elements. +template +class TupleView { + public: + explicit TupleView(const uint8* buffer) : buffer_(buffer) {} + const uint8* buffer() { return buffer_; } + + // Forward declaration of the element-type-accessing template. The definition + // is way below for readability. + template + struct element_accessor; + + // Gets the N'th element in the tuple. + template + typename element_accessor::type Get() { + return typename element_accessor::type( + buffer_ + GetOffset(0)); + } + + // Gets the size of this tuple type (as represented in the buffer). + template + static constexpr uint64 GetTypeSize() { + return FrontT::GetTypeSize() + GetTypeSize(); + } + + template + static constexpr uint64 GetTypeSize() { + return LastT::GetTypeSize(); + } + + // ---- Element access. + // Recursive case for element access. Simply walks down the type list. + template + static constexpr uint64 GetOffset( + uint8 offset, + typename std::enable_if<(kElementIndex > 0)>::type* dummy = nullptr) { + return GetOffset(offset + + FrontT::GetTypeSize()); + } + + // Base case - we've arrived at the element of interest. + template + static constexpr uint64 GetOffset( + uint8 offset, + typename std::enable_if<(kElementIndex == 0)>::type* dummy = nullptr) { + // If offset isn't aligned to our type size, then add padding. + // TODO(rspringer): We need to require the buffer to be aligned, too. + // TODO(rspringer): Aligned to min(our_type_size, 8)? + uint64 padding = offset % FrontT::GetTypeSize(); + return offset + padding; + } + + // ---- Element type access. + // Metaprogramming horrors to get the type of the N'th element. + // Recursive case - keep drilling down until the element of interst. + template + struct element_accessor + : element_accessor {}; + + // Base case - we've arrived at the type of interest. + template + struct element_accessor<0, FrontT, Rest...> { + typedef FrontT type; + }; + + private: + const uint8* buffer_; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_VALUE_VIEW_H_ diff --git a/xls/ir/value_view_helpers.h b/xls/ir/value_view_helpers.h new file mode 100644 index 0000000000..b5f3b8e967 --- /dev/null +++ b/xls/ir/value_view_helpers.h @@ -0,0 +1,36 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file holds a collection of helpful utilities when dealing with value +// views. +#ifndef THIRD_PARTY_XLS_IR_VALUE_VIEW_HELPERS_H_ +#define THIRD_PARTY_XLS_IR_VALUE_VIEW_HELPERS_H_ + +#include "absl/base/casts.h" +#include "xls/ir/value_view.h" + +namespace xls { + +// View representation of a 32-bit float value. +using F32TupleView = TupleView, BitsView<8>, BitsView<23>>; + +inline float F32TupleViewToFloat(F32TupleView tuple) { + return absl::bit_cast((tuple.Get<0>().GetValue() << 31) | + (tuple.Get<1>().GetValue() << 23) | + (tuple.Get<2>().GetValue() & 0x7FFFFF)); +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_VALUE_VIEW_HELPERS_H_ diff --git a/xls/ir/verifier.cc b/xls/ir/verifier.cc new file mode 100644 index 0000000000..edfe8f2795 --- /dev/null +++ b/xls/ir/verifier.cc @@ -0,0 +1,776 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/verifier.h" + +#include "absl/strings/str_format.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/math_util.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/dfs_visitor.h" + +namespace xls { +namespace { + +using ::absl::StrCat; +using ::absl::StrFormat; + +// Visitor which verifies various properties of Nodes including the types of the +// operands and the type of the result. +class NodeChecker : public DfsVisitor { + public: + absl::Status HandleAdd(BinOp* add) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(add, 2)); + return ExpectAllSameBitsType(add); + } + + absl::Status HandleAndReduce(BitwiseReductionOp* and_reduce) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(and_reduce, 1)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(and_reduce, 0)); + return ExpectHasBitsType(and_reduce, 1); + } + + absl::Status HandleOrReduce(BitwiseReductionOp* or_reduce) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(or_reduce, 1)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(or_reduce, 0)); + return ExpectHasBitsType(or_reduce, 1); + } + + absl::Status HandleXorReduce(BitwiseReductionOp* xor_reduce) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(xor_reduce, 1)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(xor_reduce, 0)); + return ExpectHasBitsType(xor_reduce, 1); + } + + absl::Status HandleNaryAnd(NaryOp* and_op) override { + XLS_RETURN_IF_ERROR(ExpectOperandCountGt(and_op, 0)); + return ExpectAllSameBitsType(and_op); + } + + absl::Status HandleNaryNand(NaryOp* nand_op) override { + XLS_RETURN_IF_ERROR(ExpectOperandCountGt(nand_op, 0)); + return ExpectAllSameBitsType(nand_op); + } + + absl::Status HandleNaryNor(NaryOp* nor_op) override { + XLS_RETURN_IF_ERROR(ExpectOperandCountGt(nor_op, 0)); + return ExpectAllSameBitsType(nor_op); + } + + absl::Status HandleArray(Array* array) override { + XLS_RETURN_IF_ERROR(ExpectHasArrayType(array)); + ArrayType* array_type = array->GetType()->AsArrayOrDie(); + XLS_RETURN_IF_ERROR(ExpectOperandCount(array, array_type->size())); + Type* element_type = array_type->element_type(); + for (int64 i = 0; i < array->operand_count(); ++i) { + XLS_RETURN_IF_ERROR(ExpectSameType( + array->operand(i), array->operand(i)->GetType(), array, element_type, + StrCat("operand ", i), "array element type")); + } + return absl::OkStatus(); + } + + absl::Status HandleBitSlice(BitSlice* bit_slice) override { + XLS_RETURN_IF_ERROR(ExpectHasBitsType(bit_slice, bit_slice->width())); + XLS_RETURN_IF_ERROR(ExpectOperandCount(bit_slice, 1)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(bit_slice, 0)); + BitsType* operand_type = bit_slice->operand(0)->GetType()->AsBitsOrDie(); + if (operand_type->bit_count() < 0) { + return absl::InternalError( + StrFormat("Start index of bit slice must be non-negative: %s", + bit_slice->ToString())); + } + if (bit_slice->width() < 0) { + return absl::InternalError( + StrFormat("Width of bit slice must be non-negative: %s", + bit_slice->ToString())); + } + const int64 bits_required = bit_slice->start() + bit_slice->width(); + if (operand_type->bit_count() < bits_required) { + return absl::InternalError( + StrFormat("Expected operand 0 of %s to have at least %d bits (start " + "%d + width %d), has only %d: %s", + bit_slice->GetName(), bits_required, bit_slice->start(), + bit_slice->width(), operand_type->bit_count(), + bit_slice->ToString())); + } + return absl::OkStatus(); + } + + absl::Status HandleConcat(Concat* concat) override { + // All operands should be bits types. + int64 total_bits = 0; + for (int64 i = 0; i < concat->operand_count(); ++i) { + Type* operand_type = concat->operand(i)->GetType(); + XLS_RETURN_IF_ERROR(ExpectHasBitsType(concat)); + total_bits += operand_type->AsBitsOrDie()->bit_count(); + } + return ExpectHasBitsType(concat, /*expected_bit_count=*/total_bits); + } + + absl::Status HandleCountedFor(CountedFor* counted_for) override { + // TODO(meheff): Verify signature of called function. + + XLS_RET_CHECK_GE(counted_for->trip_count(), 0); + if (counted_for->operand_count() == 0) { + return absl::InternalError(StrFormat( + "Expected %s to have at least 1 operand", counted_for->GetName())); + } + return ExpectSameType( + counted_for->operand(0), counted_for->operand(0)->GetType(), + counted_for, counted_for->GetType(), "operand", counted_for->GetName()); + } + + absl::Status HandleDecode(Decode* decode) override { + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(decode, 0)); + XLS_RETURN_IF_ERROR(ExpectHasBitsType(decode, decode->width())); + // The width of the decode output must be less than or equal to + // 2**input_width. + const int64 operand_width = decode->operand(0)->BitCountOrDie(); + if (operand_width < 63 && (decode->width() > (1LL << operand_width))) { + return absl::InternalError( + StrFormat("Decode output width (%d) greater than 2**${operand width} " + "where operand width is %d", + decode->width(), operand_width)); + } + return absl::OkStatus(); + } + + absl::Status HandleEncode(Encode* encode) override { + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(encode, 0)); + // Width of the encode output must be ceil(log_2(operand_width)). Subtract + // one from the width to account for zero-based numbering. + return ExpectHasBitsType( + encode, + Bits::MinBitCountUnsigned(encode->operand(0)->BitCountOrDie() - 1)); + } + + absl::Status HandleUDiv(BinOp* div) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(div, 2)); + return ExpectAllSameBitsType(div); + } + + absl::Status HandleSDiv(BinOp* div) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(div, 2)); + return ExpectAllSameBitsType(div); + } + + absl::Status HandleEq(CompareOp* eq) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(eq, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(eq)); + return ExpectHasBitsType(eq, /*expected_bit_count=*/1); + } + + absl::Status HandleUGe(CompareOp* ge) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(ge, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(ge)); + return ExpectHasBitsType(ge, /*expected_bit_count=*/1); + } + + absl::Status HandleUGt(CompareOp* gt) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(gt, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(gt)); + return ExpectHasBitsType(gt, /*expected_bit_count=*/1); + } + + absl::Status HandleSGe(CompareOp* ge) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(ge, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(ge)); + return ExpectHasBitsType(ge, /*expected_bit_count=*/1); + } + + absl::Status HandleSGt(CompareOp* gt) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(gt, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(gt)); + return ExpectHasBitsType(gt, /*expected_bit_count=*/1); + } + + absl::Status HandleIdentity(UnOp* identity) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(identity, 1)); + return ExpectAllSameType(identity); + } + + absl::Status HandleArrayIndex(ArrayIndex* index) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(index, 2)); + XLS_RETURN_IF_ERROR(ExpectHasArrayType(index->operand(0))); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(index, 1)); + Type* element_type = + index->operand(0)->GetType()->AsArrayOrDie()->element_type(); + return ExpectSameType(index, index->GetType(), index->operand(0), + element_type, "array index operation", + "array operand element type"); + } + + absl::Status HandleInvoke(Invoke* invoke) override { + // Verify the signature (inputs and output) of the invoked function matches + // the Invoke node. + Function* func = invoke->to_apply(); + for (int64 i = 0; i < invoke->operand_count(); ++i) { + XLS_RETURN_IF_ERROR(ExpectSameType( + invoke->operand(i), invoke->operand(i)->GetType(), func->params()[i], + func->params()[i]->GetType(), StrFormat("invoke operand %d", i), + StrFormat("invoked function argument %d", i))); + } + + XLS_RETURN_IF_ERROR( + ExpectSameType(invoke, invoke->GetType(), func->return_value(), + func->return_value()->GetType(), "invoke operation", + "invoked function return value")); + + return absl::OkStatus(); + } + + absl::Status HandleLiteral(Literal* literal) override { + // Verify type matches underlying Value object. + XLS_RETURN_IF_ERROR(ExpectOperandCount(literal, 0)); + return ExpectValueIsType(literal->value(), literal->GetType()); + } + + absl::Status HandleULe(CompareOp* le) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(le, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(le)); + return ExpectHasBitsType(le, /*expected_bit_count=*/1); + } + + absl::Status HandleULt(CompareOp* lt) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(lt, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(lt)); + return ExpectHasBitsType(lt, /*expected_bit_count=*/1); + } + absl::Status HandleSLe(CompareOp* le) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(le, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(le)); + return ExpectHasBitsType(le, /*expected_bit_count=*/1); + } + + absl::Status HandleSLt(CompareOp* lt) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(lt, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(lt)); + return ExpectHasBitsType(lt, /*expected_bit_count=*/1); + } + + absl::Status HandleMap(Map* map) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(map, 1)); + XLS_RETURN_IF_ERROR(ExpectHasArrayType(map)); + XLS_RETURN_IF_ERROR(ExpectHasArrayType(map->operand(0))); + + // Verify the signature of the applied function against the operand and + // output element types. + Type* output_element_type = map->GetType()->AsArrayOrDie()->element_type(); + XLS_RETURN_IF_ERROR(ExpectSameType( + map, output_element_type, map->to_apply()->return_value(), + map->to_apply()->return_value()->GetType(), "map output element", + "applied function return type")); + + Type* operand_element_type = + map->operand(0)->GetType()->AsArrayOrDie()->element_type(); + XLS_RET_CHECK_EQ(1, map->to_apply()->params().size()); + XLS_RETURN_IF_ERROR(ExpectSameType( + map->operand(0), operand_element_type, map->to_apply()->params()[0], + map->to_apply()->params()[0]->GetType(), "map operand element", + "applied function input type")); + + return absl::OkStatus(); + } + + absl::Status HandleSMul(ArithOp* mul) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(mul, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(mul, 0)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(mul, 1)); + return ExpectHasBitsType(mul); + } + + absl::Status HandleUMul(ArithOp* mul) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(mul, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(mul, 0)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(mul, 1)); + return ExpectHasBitsType(mul); + } + + absl::Status HandleNe(CompareOp* ne) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(ne, 2)); + XLS_RETURN_IF_ERROR(ExpectOperandsSameBitsType(ne)); + return ExpectHasBitsType(ne, /*expected_bit_count=*/1); + } + + absl::Status HandleNeg(UnOp* neg) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(neg, 1)); + return ExpectAllSameBitsType(neg); + } + + absl::Status HandleNot(UnOp* not_op) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(not_op, 1)); + return ExpectAllSameBitsType(not_op); + } + + absl::Status HandleOneHot(OneHot* one_hot) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(one_hot, 1)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(one_hot, 0)); + int64 operand_bit_count = one_hot->operand(0)->BitCountOrDie(); + // The output of one_hot should be one wider than the input to account for + // the default value. + return ExpectHasBitsType(one_hot, operand_bit_count + 1); + } + + absl::Status HandleOneHotSel(OneHotSelect* sel) override { + if (sel->operand_count() < 2) { + return absl::InternalError( + StrFormat("Expected %s to have at least 2 operands", sel->GetName())); + } + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(sel, /*operand_no=*/0)); + int64 selector_width = sel->selector()->BitCountOrDie(); + if (selector_width != sel->cases().size()) { + return absl::InternalError(StrFormat("Selector has %d bits for %d cases", + selector_width, + sel->cases().size())); + } + return absl::OkStatus(); + } + + absl::Status HandleNaryOr(NaryOp* or_op) override { + XLS_RETURN_IF_ERROR(ExpectOperandCountGt(or_op, 0)); + return ExpectAllSameBitsType(or_op); + } + + absl::Status HandleNaryXor(NaryOp* xor_op) override { + XLS_RETURN_IF_ERROR(ExpectOperandCountGt(xor_op, 0)); + return ExpectAllSameBitsType(xor_op); + } + + absl::Status HandleParam(Param* param) override { + return ExpectOperandCount(param, 0); + } + + absl::Status HandleReverse(UnOp* reverse) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(reverse, 1)); + return ExpectAllSameBitsType(reverse); + } + + absl::Status HandleSel(Select* sel) override { + if (sel->operand_count() < 2) { + return absl::InternalError( + StrFormat("Expected %s to have at least 2 operands", sel->GetName())); + } + + XLS_RETURN_IF_ERROR(ExpectHasBitsType(sel->selector())); + const int64 selector_width = sel->selector()->BitCountOrDie(); + const int64 minimum_selector_width = + Bits::MinBitCountUnsigned(sel->cases().size() - 1); + const bool power_of_2_cases = IsPowerOfTwo(sel->cases().size()); + if (selector_width < minimum_selector_width) { + return absl::InternalError(StrFormat( + "Selector must have at least %d bits to select amongst %d cases (has " + "only %d bits)", + minimum_selector_width, sel->cases().size(), selector_width)); + } else if (selector_width == minimum_selector_width && power_of_2_cases && + sel->default_value().has_value()) { + return absl::InternalError( + StrFormat("Select has useless default value: selector has %d bits " + "with %d cases", + selector_width, sel->cases().size())); + } else if ((selector_width > minimum_selector_width || + (selector_width == minimum_selector_width && + !power_of_2_cases)) && + !sel->default_value().has_value()) { + return absl::InternalError(StrFormat( + "Select has no default value: selector has %d bits with %d cases", + selector_width, sel->cases().size())); + } + + for (int64 i = 0; i < sel->cases().size(); ++i) { + Type* operand_type = sel->cases()[i]->GetType(); + if (operand_type != sel->GetType()) { + return absl::InternalError(StrFormat( + "Case %d (operand %d) type %s does not match node type: %s", i, + i + 1, operand_type->ToString(), sel->ToString())); + } + } + return absl::OkStatus(); + } + + absl::Status HandleShll(BinOp* shll) override { return HandleShiftOp(shll); } + + absl::Status HandleShra(BinOp* shra) override { return HandleShiftOp(shra); } + + absl::Status HandleShrl(BinOp* shrl) override { return HandleShiftOp(shrl); } + + absl::Status HandleSub(BinOp* sub) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(sub, 2)); + return ExpectAllSameBitsType(sub); + } + + absl::Status HandleTuple(Tuple* tuple) override { + XLS_RETURN_IF_ERROR(ExpectHasTupleType(tuple)); + if (!tuple->GetType()->IsTuple()) { + return absl::InternalError( + StrFormat("Expected node to have tuple type: %s", tuple->ToString())); + } + TupleType* type = tuple->GetType()->AsTupleOrDie(); + if (type->size() != tuple->operand_count()) { + return absl::InternalError( + StrFormat("Type element count %d does not match operand count %d: %s", + type->size(), tuple->operand_count(), tuple->ToString())); + } + for (int64 i = 0; i < tuple->operand_count(); ++i) { + XLS_RETURN_IF_ERROR( + ExpectSameType(tuple->operand(i), tuple->operand(i)->GetType(), tuple, + type->element_type(i), StrFormat("operand %d", i), + StrFormat("tuple node %s", tuple->ToString()))); + } + return absl::OkStatus(); + } + + absl::Status HandleTupleIndex(TupleIndex* index) override { + XLS_RETURN_IF_ERROR(ExpectOperandCount(index, 1)); + XLS_RETURN_IF_ERROR(ExpectHasTupleType(index->operand(0))); + TupleType* operand_type = index->operand(0)->GetType()->AsTupleOrDie(); + if ((index->index() < 0) || (index->index() >= operand_type->size())) { + return absl::InternalError( + StrFormat("Tuple index value %d out of bounds: %s", index->index(), + index->ToString())); + } + Type* element_type = operand_type->element_type(index->index()); + return ExpectSameType(index, index->GetType(), index->operand(0), + element_type, "tuple index operation", + "tuple operand element type"); + } + + absl::Status HandleSignExtend(ExtendOp* sign_ext) override { + return HandleExtendOp(sign_ext); + } + absl::Status HandleZeroExtend(ExtendOp* zero_ext) override { + return HandleExtendOp(zero_ext); + } + + private: + absl::Status HandleShiftOp(Node* shift) { + // A shift-amount operand can have arbitrary width, but the shifted operand + // and the shift operation must be identical. + XLS_RETURN_IF_ERROR(ExpectHasBitsType(shift)); + XLS_RETURN_IF_ERROR( + ExpectSameType(shift->operand(0), shift->operand(0)->GetType(), shift, + shift->GetType(), "operand 0", "shift operation")); + return ExpectOperandHasBitsType(shift, 1); + } + + absl::Status HandleExtendOp(ExtendOp* ext) { + XLS_RETURN_IF_ERROR(ExpectOperandCount(ext, 1)); + XLS_RETURN_IF_ERROR(ExpectOperandHasBitsType(ext, /*operand_no=*/0)); + int64 operand_bit_count = ext->operand(0)->BitCountOrDie(); + int64 new_bit_count = ext->new_bit_count(); + if (new_bit_count < operand_bit_count) { + return absl::InternalError(StrFormat( + "Extending operation %s is actually truncating from %d bits to %d " + "bits.", + ext->ToStringWithOperandTypes(), operand_bit_count, new_bit_count)); + } + return absl::OkStatus(); + } + + // Verifies that the given node has the expected number of operands. + absl::Status ExpectOperandCount(Node* node, int64 expected) { + if (node->operand_count() != expected) { + return absl::InternalError( + StrFormat("Expected %s to have %d operands, has %d", node->GetName(), + expected, node->operand_count())); + } + return absl::OkStatus(); + } + + absl::Status ExpectOperandCountGt(Node* node, int64 expected) { + if (node->operand_count() <= expected) { + return absl::InternalError( + StrFormat("Expected %s to have > %d operands, has %d", + node->GetName(), expected, node->operand_count())); + } + return absl::OkStatus(); + } + + // Verifies that the given two types match. The argument desc_a (desc_b) is a + // description of type_a (type_b) used in the error message. + absl::Status ExpectSameType(Node* a_source, Type* type_a, Node* b_source, + Type* type_b, absl::string_view desc_a, + absl::string_view desc_b) const { + if (type_a != type_b) { + return absl::InternalError(StrFormat( + "Type of %s (%s via %s) does not match type of %s (%s via %s)", + desc_a, type_a->ToString(), a_source->GetName(), desc_b, + type_b->ToString(), b_source->GetName())); + } + return absl::OkStatus(); + } + + absl::Status ExpectHasArrayType(Node* node) const { + if (!node->GetType()->IsArray()) { + return absl::InternalError( + StrFormat("Expected %s to have Array type, has type %s", + node->GetName(), node->GetType()->ToString())); + } + return absl::OkStatus(); + } + + absl::Status ExpectHasTupleType(Node* node) const { + if (!node->GetType()->IsTuple()) { + return absl::InternalError( + StrFormat("Expected %s to have Tuple type, has type %s", + node->GetName(), node->GetType()->ToString())); + } + return absl::OkStatus(); + } + + absl::Status ExpectHasBitsType(Node* node, + int64 expected_bit_count = -1) const { + if (!node->GetType()->IsBits()) { + return absl::InternalError( + StrFormat("Expected %s to have Bits type, has type %s", + node->GetName(), node->GetType()->ToString())); + } + if (expected_bit_count != -1 && + node->BitCountOrDie() != expected_bit_count) { + return absl::InternalError( + StrFormat("Expected node to have bit count %d: %s", + expected_bit_count, node->ToString())); + } + return absl::OkStatus(); + } + + absl::Status ExpectOperandHasBitsType(Node* node, int64 operand_no, + int64 expected_bit_count = -1) const { + Node* operand = node->operand(operand_no); + if (!operand->GetType()->IsBits()) { + return absl::InternalError( + StrFormat("Expected operand %d of %s have Bits type, has type %s: %s", + operand_no, node->GetName(), node->GetType()->ToString(), + node->ToString())); + } + if (expected_bit_count != -1 && + operand->BitCountOrDie() != expected_bit_count) { + return absl::InternalError(StrFormat( + "Expected operand %d of %s to have bit count %d: %s", operand_no, + node->GetName(), expected_bit_count, node->ToString())); + } + return absl::OkStatus(); + } + + // Verifies all operands and the node itself are BitsType with the same bit + // count. + absl::Status ExpectAllSameBitsType(Node* node) const { + XLS_RETURN_IF_ERROR(ExpectHasBitsType(node)); + return ExpectAllSameType(node); + } + + // Verifies all operands and the node itself are the same type. + absl::Status ExpectAllSameType(Node* node) const { + for (int64 i = 0; i < node->operand_count(); ++i) { + XLS_RETURN_IF_ERROR(ExpectSameType( + node->operand(i), node->operand(i)->GetType(), node, node->GetType(), + StrFormat("operand %d", i), node->GetName())); + } + return absl::OkStatus(); + } + + // Verifies all operands are BitsType with the same bit count. + absl::Status ExpectOperandsSameBitsType(Node* node) const { + if (node->operand_count() == 0) { + return absl::OkStatus(); + } + Type* type = node->operand(0)->GetType(); + for (int64 i = 1; i < node->operand_count(); ++i) { + XLS_RETURN_IF_ERROR(ExpectSameType( + node->operand(i), node->operand(i)->GetType(), node->operand(0), type, + StrFormat("operand %d", i), "operand 0")); + } + return absl::OkStatus(); + } + + // Verifies that the given Value has the given type. Walks the structures + // recursively. + absl::Status ExpectValueIsType(const Value& value, Type* type) { + switch (value.kind()) { + case ValueKind::kBits: + XLS_RET_CHECK(type->IsBits()); + XLS_RET_CHECK_EQ(value.bits().bit_count(), + type->AsBitsOrDie()->bit_count()); + break; + case ValueKind::kTuple: { + XLS_RET_CHECK(type->IsTuple()); + TupleType* tuple_type = type->AsTupleOrDie(); + XLS_RET_CHECK_EQ(value.elements().size(), tuple_type->size()); + for (int64 i = 0; i < tuple_type->size(); ++i) { + XLS_RETURN_IF_ERROR(ExpectValueIsType(value.elements()[i], + tuple_type->element_type(i))); + } + break; + } + case ValueKind::kArray: { + XLS_RET_CHECK(type->IsArray()); + ArrayType* array_type = type->AsArrayOrDie(); + XLS_RET_CHECK_EQ(value.elements().size(), array_type->size()); + for (int64 i = 0; i < array_type->size(); ++i) { + XLS_RETURN_IF_ERROR(ExpectValueIsType(value.elements()[i], + array_type->element_type())); + } + break; + } + default: + return absl::InternalError("Invalid Value type."); + } + return absl::OkStatus(); + } +}; + +absl::Status VerifyNodeIdUnique( + Node* node, + absl::flat_hash_map>* ids) { + // TODO(meheff): param IDs currently collide with non-param IDs. All IDs + // should be globally unique. + if (!node->Is()) { + if (!ids->insert({node->id(), node->loc()}).second) { + const absl::optional& loc = ids->at(node->id()); + return absl::InternalError(absl::StrFormat( + "ID %d is not unique; previously seen source location: %s", + node->id(), loc.has_value() ? loc->ToString().c_str() : "")); + } + } + return absl::OkStatus(); +} + +} // namespace + +absl::Status Verify(Package* package) { + XLS_VLOG(2) << "Verifying package:\n"; + XLS_VLOG_LINES(2, package->DumpIr()); + + for (auto& function : package->functions()) { + XLS_RETURN_IF_ERROR(Verify(function.get())); + } + + // Verify node IDs are unique within the package and uplinks point to this + // package. + absl::flat_hash_map> ids; + ids.reserve(package->GetNodeCount()); + for (auto& function : package->functions()) { + XLS_RET_CHECK(function->package() == package); + for (Node* node : function->nodes()) { + XLS_RETURN_IF_ERROR(VerifyNodeIdUnique(node, &ids)); + XLS_RET_CHECK(node->package() == package); + } + } + + // Ensure that the package's "next ID" is not in the space of IDs currently + // occupied by the package's nodes. + int64 max_id_seen = -1; + for (const auto& item : ids) { + max_id_seen = std::max(item.first, max_id_seen); + } + XLS_RET_CHECK_GT(package->next_node_id(), max_id_seen); + + // Verify function names are unique within the package. + absl::flat_hash_set functions; + absl::flat_hash_set function_names; + for (auto& function : package->functions()) { + XLS_RET_CHECK(!function_names.contains(function->name())) + << "Function with name " << function->name() + << " is not unique within package " << package->name(); + function_names.insert(function->name()); + + XLS_RET_CHECK(!functions.contains(function.get())) + << "Function with name " << function->name() + << " appears more than once in function list within package " + << package->name(); + functions.insert(function.get()); + } + + // TODO(meheff): Verify main entry point is one of the functions. + // TODO(meheff): Verify functions called by any node are in the set of + // functions owned by the package. + // TODO(meheff): Verify that there is no recursion. + + return absl::OkStatus(); +} + +absl::Status Verify(Function* function) { + XLS_VLOG(2) << "Verifying function:\n"; + XLS_VLOG_LINES(2, function->DumpIr()); + + // Verify all types are owned by package. + for (Node* node : function->nodes()) { + XLS_RET_CHECK(node->package()->IsOwnedType(node->GetType())); + XLS_RET_CHECK(node->package() == function->package()); + } + + // Verify ids are unique within the function. + absl::flat_hash_map> ids; + ids.reserve(function->node_count()); + for (Node* node : function->nodes()) { + XLS_RETURN_IF_ERROR(VerifyNodeIdUnique(node, &ids)); + } + + // Verify consistency of node::users() and node::operands(). + for (Node* node : function->nodes()) { + XLS_RETURN_IF_ERROR(Verify(node)); + } + + // Verify the set of parameter nodes is exactly Function::params(), and that + // the parameter names are unique. + absl::flat_hash_set param_names; + absl::flat_hash_set param_set; + for (Node* param : function->params()) { + XLS_RET_CHECK(param_set.insert(param).second) + << "Param appears more than once in Function::params()"; + XLS_RET_CHECK(param_names.insert(param->GetName()).second) + << "Param name " << param->GetName() + << " is duplicated in Function::params()"; + } + int64 param_node_count = 0; + for (Node* node : function->nodes()) { + if (node->Is()) { + XLS_RET_CHECK(param_set.contains(node)) + << "Param " << node->GetName() << " is not in Function::params()"; + param_node_count++; + } + } + XLS_RET_CHECK_EQ(param_set.size(), param_node_count) + << "Number of param nodes not equal to Function::params() size for " + "function " + << function->name(); + + return absl::OkStatus(); +} + +absl::Status Verify(Node* node) { + XLS_VLOG(2) << "Verifying node: " << node->ToString(); + + for (Node* operand : node->operands()) { + XLS_RET_CHECK(operand->HasUser(node)) + << "Expected " << node->GetName() << " to be a user of " + << operand->GetName(); + XLS_RET_CHECK(operand->function() == node->function()) + << StrFormat("Operand %s of node %s not in same function (%s vs %s).", + operand->GetName(), node->GetName(), + operand->function()->name(), node->function()->name()); + } + for (Node* user : node->users()) { + XLS_RET_CHECK(absl::c_linear_search(user->operands(), node)) + << "Expected " << node->GetName() << " to be a operand of " + << user->GetName(); + } + + NodeChecker node_checker; + return node->VisitSingleNode(&node_checker); +} + +} // namespace xls diff --git a/xls/ir/verifier.h b/xls/ir/verifier.h new file mode 100644 index 0000000000..2ee0e32f14 --- /dev/null +++ b/xls/ir/verifier.h @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_IR_VERIFIER_H_ +#define THIRD_PARTY_XLS_IR_VERIFIER_H_ + +#include "absl/status/status.h" +#include "xls/ir/function.h" +#include "xls/ir/node.h" +#include "xls/ir/package.h" + +namespace xls { + +class Node; +class Function; +class Package; + +// Verifies numerous invariants of the IR for the given package. Returns a +// error status if a violation is found. +absl::Status Verify(Package* package); + +// Overload for functions. +absl::Status Verify(Function* function); + +// Overload for nodes. +absl::Status Verify(Node* Node); + +} // namespace xls + +#endif // THIRD_PARTY_XLS_IR_VERIFIER_H_ diff --git a/xls/ir/verifier_test.cc b/xls/ir/verifier_test.cc new file mode 100644 index 0000000000..b561e71bb7 --- /dev/null +++ b/xls/ir/verifier_test.cc @@ -0,0 +1,159 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/ir/verifier.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/ir_test_base.h" + +namespace xls { +namespace { + +using status_testing::StatusIs; +using ::testing::HasSubstr; + +class VerifierTest : public IrTestBase { + protected: + VerifierTest() {} +}; + +TEST_F(VerifierTest, WellFormedPackage) { + std::string input = R"( +package WellFormedPackage + +fn graph(p: bits[42], q: bits[42]) -> bits[42] { + and.1: bits[42] = and(p, q) + add.2: bits[42] = add(and.1, q) + ret sub.3: bits[42] = sub(add.2, add.2) +} + +fn graph2(a: bits[16]) -> bits[16] { + neg.4: bits[16] = neg(a) + ret not.5: bits[16] = not(neg.4) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackageNoVerify(input)); + XLS_ASSERT_OK(Verify(p.get())); + XLS_ASSERT_OK(Verify(FindFunction("graph", p.get()))); + XLS_ASSERT_OK(Verify(FindFunction("graph2", p.get()))); +} + +TEST_F(VerifierTest, NonUniqueNodeId) { + std::string input = R"( +package NonUniqueNodeId + +fn graph(p: bits[42], q: bits[42]) -> bits[42] { + and.1: bits[42] = and(p, q) + add.2: bits[42] = add(and.1, q) + ret sub.2: bits[42] = sub(add.2, add.2) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackageNoVerify(input)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, p->GetFunction("graph")); + EXPECT_THAT(Verify(f), StatusIs(absl::StatusCode::kInternal, + HasSubstr("ID 2 is not unique"))); +} + +TEST_F(VerifierTest, NonUniqueFunctionName) { + std::string input = R"( +package NonUniqueFunctionName + +fn graph(p: bits[42], q: bits[42]) -> bits[42] { + and.1: bits[42] = and(p, q) + add.2: bits[42] = add(and.1, q) + ret sub.3: bits[42] = sub(add.2, add.2) +} + +fn graph(a: bits[16]) -> bits[16] { + neg.4: bits[16] = neg(a) + ret not.5: bits[16] = not(neg.4) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackageNoVerify(input)); + EXPECT_THAT(Verify(p.get()), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Function with name graph is not unique"))); +} + +TEST_F(VerifierTest, BinOpOperandTypeMismatch) { + std::string input = R"( +package BinOpOperandTypeMismatch + +fn graph(p: bits[2], q: bits[42], r: bits[42]) -> bits[42] { + ret and.1: bits[42] = and(q, r) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackageNoVerify(input)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, p->GetFunction("graph")); + // Replace lhs of the 'and' with a different bit-width value. + FindNode("and.1", f)->ReplaceOperand(FindNode("q", f), FindNode("p", f)); + EXPECT_THAT(Verify(f), StatusIs(absl::StatusCode::kInternal, + HasSubstr("Type of operand 0 (bits[2] via p) " + "does not match type of and.1"))); + EXPECT_THAT(Verify(p.get()), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Type of operand 0 (bits[2] via p) does not " + "match type of and.1"))); +} + +TEST_F(VerifierTest, SelectWithUselessDefault) { + std::string input = R"( +package p + +fn f(p: bits[1], q: bits[42], r: bits[42]) -> bits[42] { + literal.1: bits[42] = literal(value=42) + ret sel.2: bits[42] = sel(p, cases=[q, r], default=literal.1) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackageNoVerify(input)); + EXPECT_THAT(Verify(p.get()), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Select has useless default value"))); +} + +TEST_F(VerifierTest, SelectWithMissingDefault) { + std::string input = R"( +package p + +fn f(p: bits[2], q: bits[42], r: bits[42], s:bits[42]) -> bits[42] { + ret sel.1: bits[42] = sel(p, cases=[q, r, s]) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackageNoVerify(input)); + EXPECT_THAT(Verify(p.get()), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Select has no default value"))); +} + +TEST_F(VerifierTest, SelectWithTooNarrowSelector) { + std::string input = R"( +package p + +fn f(p: bits[1], q: bits[42], r: bits[42], s:bits[42], t:bits[42]) -> bits[42] { + ret sel.1: bits[42] = sel(p, cases=[q, r, s, t]) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackageNoVerify(input)); + EXPECT_THAT( + Verify(p.get()), + StatusIs( + absl::StatusCode::kInternal, + HasSubstr( + "Selector must have at least 2 bits to select amongst 4 cases"))); +} + +} // namespace +} // namespace xls diff --git a/xls/ir/xls_type.proto b/xls/ir/xls_type.proto new file mode 100644 index 0000000000..d7a1bd1245 --- /dev/null +++ b/xls/ir/xls_type.proto @@ -0,0 +1,42 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package xls; + +message TypeProto { + enum TypeEnum { + INVALID = 0; + BITS = 1; + TUPLE = 2; + ARRAY = 3; + } + optional TypeEnum type_enum = 1; + + // For BITS types, this is the bit width. + optional int64 bit_count = 2; + + // For TUPLE types, the type of the elements. + repeated TypeProto tuple_elements = 3; + + // For ARRAY types, the number and type of the elements. + optional int64 array_size = 4; + optional TypeProto array_element = 5; +} + +message FunctionTypeProto { + repeated TypeProto parameters = 1; + optional TypeProto return_type = 2; +} diff --git a/xls/modules/BUILD b/xls/modules/BUILD new file mode 100644 index 0000000000..1bd06cb0ee --- /dev/null +++ b/xls/modules/BUILD @@ -0,0 +1,105 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build rules for DSLX modules. +load("//xls/build:build_defs.bzl", "dslx_test") + +package( + default_visibility = [ + "//xls:__subpackages__", + ], + licenses = ["notice"], # Apache 2.0 +) + +filegroup( + name = "ir_examples", + srcs = [ + ":fpadd_2x32_all_ir", + ":fpmul_2x32_all_ir", + ], +) + +dslx_test( + name = "fpadd_2x32", + srcs = ["fpadd_2x32.x"], + # TODO(b/152546795): Takes too long. + prove_unopt_eq_opt = False, +) + +# TODO(rspringer): Takes too long to run in normal testing. +cc_binary( + name = "fpadd_2x32_bounds", + srcs = ["fpadd_2x32_bounds.cc"], + data = [":fpadd_2x32_all_ir"], + deps = [ + "@com_google_absl//absl/base", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/status", + "@com_google_absl//absl/time", + "//xls/common:init_xls", + "//xls/common/file:filesystem", + "//xls/common/file:get_runfile_path", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir:ir_parser", + "//xls/tools:z3_translator", + "@z3//:api", + ], +) + +cc_test( + name = "fpadd_2x32_test", + srcs = ["fpadd_2x32_test.cc"], + data = [":fpadd_2x32_all_ir"], + tags = ["optonly"], + deps = [ + "@com_google_absl//absl/random", + "@com_google_absl//absl/status", + "//xls/common:init_xls", + "//xls/common/file:get_runfile_path", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/ir:llvm_ir_jit", + "//xls/ir:value_helpers", + "//xls/ir:value_view_helpers", + "//xls/tools:testbench", + ], +) + +dslx_test( + name = "fpmul_2x32", + srcs = ["fpmul_2x32.x"], + # TODO(rspringer): Currently takes too long. + prove_unopt_eq_opt = False, +) + +cc_test( + name = "fpmul_2x32_test", + srcs = ["fpmul_2x32_test.cc"], + data = [":fpmul_2x32_all_ir"], + deps = [ + "@com_google_absl//absl/random", + "@com_google_absl//absl/status", + "//xls/common:init_xls", + "//xls/common/file:get_runfile_path", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/ir:llvm_ir_jit", + "//xls/ir:value_helpers", + "//xls/ir:value_view_helpers", + "//xls/tools:testbench", + ], +) diff --git a/xls/modules/fpadd_2x32.x b/xls/modules/fpadd_2x32.x new file mode 100644 index 0000000000..c4c8bb7e32 --- /dev/null +++ b/xls/modules/fpadd_2x32.x @@ -0,0 +1,143 @@ +// This file implements most of IEEE-754 single-precision +// floating-point addition, with the following exceptions: +// - Both input and output denormals are treated as/flushed to 0. +// - Only round-to-nearest mode is supported. +// - No exception flags are raised/reported. +// In all other cases, results should be identical to other +// conforming implementations (modulo exact significand +// values in the NaN case. +import float32 + +type F32 = float32::F32; + +fn fpadd_2x32(x: F32, y: F32) -> F32 { + // Step 1: align the significands. + // - Bit widths: Base significant: u23. + // - Add the implied/leading 1 bit: u23 -> u24 + // - Add a sign bit: u24 -> u25 + let wide_x = ((x.sfd as u28) | u28:0x800000) << u28:3 in + let wide_y = ((y.sfd as u28) | u28:0x800000) << u28:3 in + + // Flush denormals to 0. + let wide_x = u28:0 if x.bexp == u8:0 else wide_x in + let wide_y = u28:0 if y.bexp == u8:0 else wide_y in + + // Shift the significands to align with the largest exponent. + let greater_exp = x if x.bexp > y.bexp else y in + let shift_x = greater_exp.bexp - x.bexp in + let shift_y = greater_exp.bexp - y.bexp in + let shifted_x = (wide_x >> (shift_x as u28)) as s28 in + let shifted_y = (wide_y >> (shift_y as u28)) as s28 in + + // Calculate the sticky bits - set to 1 if any set bits were + // shifted out of the significands. + let dropped_x = wide_x << ((u8:28 - shift_x) as u28) in + let dropped_y = wide_y << ((u8:28 - shift_y) as u28) in + let sticky_x = dropped_x > u28:0 in + let sticky_y = dropped_y > u28:0 in + let addend_x = shifted_x | (sticky_x as s28) in + let addend_y = shifted_y | (sticky_y as s28) in + + // Invert the mantissa if its source has a different sign than + // the larger value. + let addend_x = -addend_x if x.sign != greater_exp.sign else addend_x in + let addend_y = -addend_y if y.sign != greater_exp.sign else addend_y in + + // Step 2: Do some addition! + // Add one bit to capture potential carry: s28 -> s29. + let sfd = (addend_x as s29) + (addend_y as s29) in + let sfd_is_zero = sfd == s29:0 in + let result_sign = match (sfd_is_zero, sfd < s29:0) { + (true, _) => u1:0; + (false, true) => ~greater_exp.sign; + _ => greater_exp.sign; + } in + + // Get the absolute value of the result then chop off the sign bit: s29 -> u28. + let abs_sfd = (-sfd if sfd < s29:0 else sfd) as u28 in + + // Step 3: Normalize the significand (shift until the leading bit is a 1). + // If the carry bit is set, shift right one bit (to capture the new bit of + // precision) - but don't drop the sticky bit! + let carry_bit = abs_sfd[-1:] in + let carry_sfd = (abs_sfd >> u28:1) as u27 in + let carry_sfd = carry_sfd | (abs_sfd[0:1] as u27) in + + // If we cancelled higher bits, then we'll need to shift left. + // Leading zeroes will be 1 if there's no carry or cancellation. + let leading_zeroes = clz(abs_sfd) in + let cancel = leading_zeroes > u28:1 in + let cancel_sfd = (abs_sfd << (leading_zeroes - u28:1)) as u27 in + let shifted_sfd = match(carry_bit, cancel) { + (true, false) => carry_sfd; + (false, true) => cancel_sfd; + (false, false) => abs_sfd as u27; + _ => fail!(u27:666) + } in + + // Step 4: Rounding. + // Rounding down is a no-op, since we eventually have to shift off + // the extra precision bits, so we only need to be concerned with + // rounding up. We only support round to nearest, half to even + // mode. This means we round up if: + // - The last three bits are greater than 1/2 way between + // values, i.e., the last three bits are > 0b100. + // - We're exactly 1/2 way between values (0b100) and bit 3 is 1 + // (i.e., 0x...1100). In other words, if we're "halfway", we round + // in whichever direction makes the last bit in the significand 0. + let normal_chunk = shifted_sfd[0:3] in + let half_way_chunk = shifted_sfd[2:4] in + let do_round_up = + u1:1 if (normal_chunk > u3:0x4) | (half_way_chunk == u2:0x3) + else u1:0 in + + // We again need an extra bit for carry. + let rounded_sfd = (shifted_sfd as u28) + u28:0x8 if do_round_up + else (shifted_sfd as u28) in + let rounding_carry = rounded_sfd[-1:] in + + // After rounding, we can chop off the extra precision bits. + // As with normalization, if we carried, we need to shift right + // an extra place. + let sfd_shift = u28:3 + (u28:1 if rounded_sfd[-1:] else u28:0) in + let result_sfd = (rounded_sfd >> sfd_shift) as u23 in + + // Finally, adjust the exponent based on addition and rounding - + // each bit of carry or cancellation moves it by one place. + let wide_exponent = (greater_exp.bexp as s10) + (rounding_carry as s10) + + s10:1 - (leading_zeroes as s10) in + let wide_exponent = s10:0 if sfd_is_zero else wide_exponent in + + // Chop off the sign bit. + let wide_exponent = u9:0 if wide_exponent < s10:0 else (wide_exponent as u9) in + + // Extra bonus step 5: special case handling! + + // If the exponent underflowed, don't bother with denormals. Just flush to 0. + let result_sfd = u23:0 if wide_exponent < u9:1 else result_sfd in + + // Handle exponent overflow infinities. + let result_sfd = result_sfd if wide_exponent < u9:255 else u23:0 in + let result_exponent = wide_exponent as u8 if wide_exponent < u9:255 else u8:255 in + + // Handle arg infinities. + let is_operand_inf = float32::is_inf(x) | float32::is_inf(y) in + let result_exponent = u8:255 if is_operand_inf else result_exponent in + let result_sfd = u23:0 if is_operand_inf else result_sfd in + // Result infinity is negative iff all infinite operands are neg. + let has_pos_inf = (float32::is_inf(x) & (x.sign == u1:0)) | + (float32::is_inf(y) & (y.sign == u1:0)) in + let result_sign = ~has_pos_inf if is_operand_inf else result_sign in + + // Handle NaN; NaN trumps infinities, so we handle it last. + // -inf + inf = NaN, i.e., if we have both positive and negative inf. + let has_neg_inf = + (float32::is_inf(x) & (x.sign == u1:1)) | (float32::is_inf(y) & (y.sign == u1:1)) in + let is_result_nan = float32::is_nan(x) | float32::is_nan(y) | (has_pos_inf & has_neg_inf) in + let result_exponent = u8:255 if is_result_nan else result_exponent in + let result_sfd = u23:0x400000 if is_result_nan else result_sfd in + let result_sign = u1:0 if is_result_nan else result_sign in + + // Finally (finally!), construct the output float. + F32 { sign: result_sign, bexp: result_exponent, sfd: result_sfd as u23 } +} diff --git a/xls/modules/fpadd_2x32_bounds.cc b/xls/modules/fpadd_2x32_bounds.cc new file mode 100644 index 0000000000..bd58dc64fc --- /dev/null +++ b/xls/modules/fpadd_2x32_bounds.cc @@ -0,0 +1,192 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This program proves or disproves that the XLS 2x32-bit floating-point adder +// produces results less than a given (absolute) error bound when compared to +// a reference (in this case the Z3 floating-point type), using the Z3 SMT +// solver. +// +// With default flags, it proves that results are _exactly_ identical when +// subnormals are flushed to zero. +#include + +#include "absl/base/internal/sysinfo.h" +#include "absl/flags/flag.h" +#include "absl/status/status.h" +#include "absl/time/time.h" +#include "xls/common/file/filesystem.h" +#include "xls/common/file/get_runfile_path.h" +#include "xls/common/init_xls.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/ir_parser.h" +#include "xls/tools/z3_translator.h" +#include "../z3/src/api/z3_api.h" +#include "../z3/src/api/z3_fpa.h" + +ABSL_FLAG(absl::Duration, timeout, absl::InfiniteDuration(), + "How long to wait for the proof to complete."); +ABSL_FLAG(bool, flush_subnormals, true, + "Flush input and output subnormals to 0. If this flag is false, " + "the proof (and this test) will fail, as it expects ZERO (0.0f) " + "error between the calculations.\n" + "This option exists to demonstrate validity of result."); +ABSL_FLAG(bool, reference_use_opt_ir, true, + "Whether or not to use optimized IR or not."); +ABSL_FLAG(uint32, error_bound, 0, + "The error bound to prove. Z3 will aim to prove that the maximum " + "error between its FP impl and ours - for all inputs - will be " + "less than this value. This is an absolute, not relative, value. " + "This is specified as a uint32 to enable, e.g., subnormal values to " + "be specified."); + +namespace xls { + +constexpr const char kIrPath[] = "xls/modules/fpadd_2x32.ir"; +constexpr const char kOptIrPath[] = "xls/modules/fpadd_2x32.opt.ir"; +constexpr const char kFunctionName[] = "__fpadd_2x32__fpadd_2x32"; + +using z3_translator::Z3Translator; + +// Adds an error comparsion to the translated XLS function. To do so: +// - We convert the input arguments into Z3 floating-point types. +// - We flush any subnormals to 0 (as is done in the XLS function). +// - Perform Z3-internal FP addition. +// - Again flush subnormals to 0. +// - Take the absolute value of the difference between the two results. +// "Proper" FP error calculation would take the size of the arguments into +// effect, but it suffices for now to be draconian - we've been allowing 0 or +// + iota error. +// +// Returns "actual" vs. "expected" nodes (via reference) to query (via +// QueryNode) on failure. +xabsl::StatusOr CreateReferenceComparisonFunction( + Function* function, Z3Translator* translator, bool flush_subnormals, + Z3_ast* expected, Z3_ast* actual) { + // Get the translated XLS function, and create its return value. + Z3_context ctx = translator->ctx(); + Z3_ast result = translator->GetTranslation(function->return_value()); + + // The params to and result from fpadd_2x32 are FP32s, which are tuples of: + // - u1: sign + // - u8: exponent + // - u23: significand + // Which are trivially converted to Z3 floating-point types. + XLS_ASSIGN_OR_RETURN(Z3_ast xls_result, translator->ToFloat32(result)); + *actual = xls_result; + + // Create Z3 floating-point elements: + std::vector z3_params; + z3_params.reserve(function->params().size()); + for (const auto& param : function->params()) { + Z3_ast translation = translator->GetTranslation(param); + XLS_ASSIGN_OR_RETURN(Z3_ast fp, translator->ToFloat32(translation)); + if (flush_subnormals) { + XLS_ASSIGN_OR_RETURN(fp, translator->FloatFlushSubnormal(fp)); + } + z3_params.push_back(fp); + } + + // Construct the Z3 FP add: + Z3_ast rounding_mode = Z3_mk_fpa_round_nearest_ties_to_even(ctx); + Z3_ast z3_result = + Z3_mk_fpa_add(ctx, rounding_mode, z3_params[0], z3_params[1]); + if (flush_subnormals) { + XLS_ASSIGN_OR_RETURN(z3_result, translator->FloatFlushSubnormal(z3_result)); + } + *expected = z3_result; + + // Format NaNs like we expect (with 0x400000 in the significand). + Z3_ast is_nan = Z3_mk_fpa_is_nan(ctx, z3_result); + Z3_ast positive_nan = Z3_mk_fpa_numeral_int_uint(ctx, false, 0xFF, 0x400000, + Z3_mk_fpa_sort_32(ctx)); + Z3_ast negative_nan = Z3_mk_fpa_numeral_int_uint(ctx, true, 0xFF, 0x400000, + Z3_mk_fpa_sort_32(ctx)); + Z3_ast signed_nan = Z3_mk_ite(ctx, Z3_mk_fpa_is_negative(ctx, z3_result), + negative_nan, positive_nan); + z3_result = Z3_mk_ite(ctx, is_nan, signed_nan, z3_result); + + // Compare the two results. + Z3_ast error = Z3_mk_fpa_abs( + ctx, Z3_mk_fpa_sub(ctx, rounding_mode, z3_result, xls_result)); + + return error; +} + +xabsl::StatusOr> GetIr(bool opt_ir) { + std::filesystem::path ir_path = + GetXlsRunfilePath(opt_ir ? kOptIrPath : kIrPath); + XLS_ASSIGN_OR_RETURN(std::string ir_text, GetFileContents(ir_path)); + return Parser::ParsePackage(ir_text); +} + +absl::Status CompareToReference(bool use_opt_ir, uint32 error_bound, + bool flush_subnormals, absl::Duration timeout) { + XLS_ASSIGN_OR_RETURN(auto package, GetIr(use_opt_ir)); + XLS_ASSIGN_OR_RETURN(auto function, package->GetFunction(kFunctionName)); + + // Translate our IR into a matching Z3 AST. + XLS_ASSIGN_OR_RETURN( + std::unique_ptr translator, + z3_translator::Z3Translator::CreateAndTranslate(function)); + // "Wrap" that computation with another (also in Z3) to independently compute + // the sum and calculate the difference between the two results (the error). + Z3_ast expected; + Z3_ast actual; + XLS_ASSIGN_OR_RETURN(Z3_ast error, CreateReferenceComparisonFunction( + function, translator.get(), + flush_subnormals, &expected, &actual)); + + // Define the maximum allowable error for the proof to succeed. + Z3_context ctx = translator->ctx(); + Z3_ast bounds = Z3_mk_fpa_numeral_float( + ctx, absl::bit_cast(error_bound), Z3_mk_fpa_sort_32(ctx)); + + // Push all that work into z3, and have the solver do its work. + translator->SetTimeout(timeout); + + Z3_params params = Z3_mk_params(ctx); + Z3_params_inc_ref(ctx, params); + Z3_params_set_uint(ctx, params, Z3_mk_string_symbol(ctx, "sat.threads"), + absl::base_internal::NumCPUs()); + Z3_params_set_uint(ctx, params, Z3_mk_string_symbol(ctx, "threads"), + absl::base_internal::NumCPUs()); + + Z3_solver solver = Z3_mk_solver(ctx); + Z3_solver_inc_ref(ctx, solver); + Z3_solver_set_params(ctx, solver, params); + Z3_ast objective = Z3_mk_fpa_gt(ctx, error, bounds); + Z3_solver_assert(ctx, solver, objective); + + // Finally, print the output to the terminal in gorgeous two-color ASCII. + std::cout << z3_translator::SolverResultToString(ctx, solver) << std::endl; + + Z3_solver_dec_ref(ctx, solver); + Z3_params_dec_ref(ctx, params); + return absl::OkStatus(); +} + +} // namespace xls + +int main(int argc, char** argv) { + xls::InitXls(argv[0], argc, argv); + + XLS_QCHECK_OK(xls::CompareToReference( + absl::GetFlag(FLAGS_reference_use_opt_ir), + absl::GetFlag(FLAGS_error_bound), absl::GetFlag(FLAGS_flush_subnormals), + absl::GetFlag(FLAGS_timeout))); + return 0; +} diff --git a/xls/modules/fpadd_2x32_test.cc b/xls/modules/fpadd_2x32_test.cc new file mode 100644 index 0000000000..0b91a0c4e4 --- /dev/null +++ b/xls/modules/fpadd_2x32_test.cc @@ -0,0 +1,113 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Random-sampling test for the DSLX 2x32 floating-point adder. +#include +#include + +#include "absl/random/random.h" +#include "absl/status/status.h" +#include "xls/common/file/get_runfile_path.h" +#include "xls/common/init_xls.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/llvm_ir_jit.h" +#include "xls/ir/value_helpers.h" +#include "xls/ir/value_view_helpers.h" +#include "xls/tools/testbench.h" + +ABSL_FLAG(bool, use_opt_ir, true, "Use optimized IR."); +ABSL_FLAG(int, num_threads, 0, + "Number of threads to use. Set to 0 to use all."); +ABSL_FLAG(int64, num_samples, 1024 * 1024, "Number of random samples to test."); + +namespace xls { + +constexpr const char kOptIrPath[] = "xls/modules/fpadd_2x32.opt.ir"; +constexpr const char kIrPath[] = "xls/modules/fpadd_2x32.ir"; + +using Float2x32 = std::tuple; + +float FlushDenormals(float value) { + if (std::fpclassify(value) == FP_SUBNORMAL) { + return 0; + } + + return value; +} + +bool ZeroOrSubnormal(float value) { + return value == 0 || std::fpclassify(value) == FP_SUBNORMAL; +} + +// Generates two floats with reasonably unformly random bit patterns. +Float2x32 IndexToInput(uint64 index) { + thread_local absl::BitGen bitgen; + uint32 a = absl::Uniform(bitgen, 0u, std::numeric_limits::max()); + uint32 b = absl::Uniform(bitgen, 0u, std::numeric_limits::max()); + return Float2x32(absl::bit_cast(a), absl::bit_cast(b)); +} + +// The DSLX implementation uses the "round to nearest (half to even)" +// rounding mode, which is the default on most systems, hence we don't need +// to call fesetround(). +// The DSLX implementation also flushes input subnormals to 0, so we do that +// here as well. +float ComputeExpected(Float2x32 input) { + float x = FlushDenormals(std::get<0>(input)); + float y = FlushDenormals(std::get<1>(input)); + return x + y; +} + +// Computes FP addition via DSLX & the JIT. +float ComputeActual(LlvmIrJit* jit, absl::Span result_buffer, + Float2x32 input) { + auto x = F32ToTuple(std::get<0>(input)); + auto y = F32ToTuple(std::get<1>(input)); + memset(result_buffer.data(), 0, result_buffer.size()); + XLS_CHECK_OK(jit->RunToBuffer({x, y}, result_buffer)); + + F32TupleView result(result_buffer.data()); + return F32TupleViewToFloat(result); +} + +// Compares expected vs. actual results, taking into account two special cases. +bool CompareResults(float a, float b) { + // DSLX flushes subnormal outputs, while regular FP addition does not, so + // just check for that here. + return a == b || (std::isnan(a) && std::isnan(b)) || + (ZeroOrSubnormal(a) && ZeroOrSubnormal(b)); +} + +absl::Status RealMain(bool use_opt_ir, uint64 num_samples, int num_threads) { + Testbench testbench( + GetXlsRunfilePath(use_opt_ir ? kOptIrPath : kIrPath), + /*entry_function=*/"__fpadd_2x32__fpadd_2x32", 0, num_samples, + /*max_failures=*/1, IndexToInput, ComputeExpected, ComputeActual, + CompareResults); + if (num_threads != 0) { + XLS_RETURN_IF_ERROR(testbench.SetNumThreads(num_threads)); + } + return testbench.Run(); +} + +} // namespace xls + +int main(int argc, char** argv) { + xls::InitXls(argv[0], argc, argv); + XLS_QCHECK_OK(xls::RealMain(absl::GetFlag(FLAGS_use_opt_ir), + absl::GetFlag(FLAGS_num_samples), + absl::GetFlag(FLAGS_num_threads))); + return 0; +} diff --git a/xls/modules/fpmul_2x32.x b/xls/modules/fpmul_2x32.x new file mode 100644 index 0000000000..20a2743de2 --- /dev/null +++ b/xls/modules/fpmul_2x32.x @@ -0,0 +1,131 @@ +// This file implements [most of] IEEE 754 single-precision +// floating point multiplication, with the following exceptions: +// - Both input and output denormals are treated as/flushed to 0. +// - Only round-to-nearest mode is supported. +// - No exception flags are raised/reported. +// In all other cases, results should be identical to other +// conforming implementations (modulo exact significand +// values in the NaN case. +import float32 +import std + +type F32 = float32::F32; + +// Determines if the given value is 0, taking into account +// flushing subnormals. +fn is_zero(x: F32) -> u1 { + x.bexp == u8:0 +} + +fn fpmul_2x32(x: F32, y: F32) -> F32 { + // 1. Get and expand mantissas. + let x_sfd = (x.sfd as u48) | u48:0x80_0000 in + let y_sfd = (y.sfd as u48) | u48:0x80_0000 in + + // 1a. Flush subnorms to 0. + let x_sfd = u48:0 if x.bexp == u8:0 else x_sfd in + let y_sfd = u48:0 if y.bexp == u8:0 else y_sfd in + + // 2. Multiply integer mantissas. + let sfd: u48 = x_sfd * y_sfd in + + // 3. Add non-biased exponents. + // - Remove the bias from the exponents, add them, then restore the bias. + // - Simplifies from + // (A - 127) + (B - 127) + 127 = exp + // to + // A + B - 127 = exp + let exp = (x.bexp as s10) + (y.bexp as s10) - s10:0x7f in + + // Here is where we'd handle subnormals if we cared to. + // If the exponent remains < 0, even after reapplying the bias, + // then we'd calculate the extra exponent needed to get back to 0. + // We'd set the result exponent to 0 and shift the sfd to the right + // to capture that "extra" exponent. + // Since we just flush subnormals, we don't have to do any of that. + // Instead, if we're multiplying by 0, the result is 0. + let exp = s10:0 if is_zero(x) or is_zero(y) else exp in + + // 4. Normalize. Adjust the significand until our leading 1 is in + // bit 47 (the first past the 46 bits of actual significand). + // That'll be a shift of 1 or 0 places (since we're multiplying + // two values with leading 1s in bit 24). + let sfd_shift = sfd[-1:] as u48 in + + // If there is a leading 1, then we need to shift to the right one place - + // that means we gained a new significant digit at the top. + // Dont forget to maintain the sticky bit! + let sticky = sfd[0:1] as u48 in + let sfd = sfd >> sfd_shift in + let sfd = sfd | sticky in + + // Update the exponent if we shifted. + let exp = exp + (sfd_shift as s10) in + // If the value is currently subnormal, then we need to shift right by one + // space: a subnormal value doesn't have the leading 1, and thus has one + // fewer significant digits than normal numbers - in a sense, the -1th bit + // is the least significant (0) bit. + // Rounding (below) expects the least significant digit to start at position + // 0, so we shift subnormals to the left by one position to match normals. + // Again, track the sticky bit. This could be combined with the shift + // above, but it's easier to understand (and comment) if separated, and the + // optimizer will clean it up anyway. + let sticky = sfd[0:1] as u48 in + let sfd = sfd >> u48:1 if exp <= s10:0 else sfd in + let sfd = sfd | sticky in + + // 5. Round - we use nearest, half to even rounding. + // - We round down if less than 1/2 way between values, i.e. + // if bit 23 is 0. Rounding down is equivalent to doing nothing. + // - We round up if we're more than 1/2 way, i.e., if bit 23 + // is set along with any bit lower than 23. + // - If halfway (bit 23 set and no bit lower), then we round in + // whichever direction makes the result even. In other words, + // we round up if bit 25 is set. + let is_half_way = sfd[22:23] & (sfd[0:22] == u22:0) in + let greater_than_half_way = sfd[22:23] & (sfd[0:22] != u22:0) in + let do_round_up = greater_than_half_way or (is_half_way & sfd[23:24]) in + + // We're done with the extra precision bits now, so shift the + // significand into its almost-final width, adding one extra + // bit for potential rounding overflow. + let sfd = (sfd >> u48:23) as u23 in + let sfd = sfd as u24 in + let sfd = sfd + u24:1 if do_round_up else sfd in + + // Adjust the exponent if we overflowed during rounding. + // After checking for subnormals, we don't need the sign bit anymore. + let exp = exp + s10:1 if sfd[-1:] else exp in + let is_subnormal = exp <= s10:0 in + + // We're done - except for special cases... + let result_sign = x.sign != y.sign in + let result_exp = exp as u9 in + let result_sfd = sfd as u23 in + + // 6. Special cases! + // - Subnormals: flush to 0. + let result_exp = u9:0 if is_subnormal else result_exp in + let result_sfd = u23:0 if is_subnormal else result_sfd in + + // - Overflow infinites. Exp to 255, clear sfd. + let result_sfd = result_sfd if result_exp < u9:0xff else u23:0 in + let result_exp = result_exp as u8 if result_exp < u9:0xff else u8:0xff in + + // - Arg infinites. Any arg is infinite == result is infinite. + let is_operand_inf = float32::is_inf(x) or float32::is_inf(y) in + let result_exp = u8:0xff if is_operand_inf else result_exp in + let result_sfd = u23:0 if is_operand_inf else result_sfd in + + // - NaNs. NaN trumps infinities, so we handle it last. + // inf * 0 = NaN, i.e., + let has_0_arg = is_zero(x) or is_zero(y) in + let has_nan_arg = float32::is_nan(x) or float32::is_nan(y) in + let has_inf_arg = float32::is_inf(x) or float32::is_inf(y) in + let is_result_nan = has_nan_arg or (has_0_arg and has_inf_arg) in + let result_exp = u8:0xff if is_result_nan else result_exp in + let result_sfd = u23:0x40_0000 if is_result_nan else result_sfd in + let result_sign = u1:0 if is_result_nan else result_sign in + + F32 { sign: result_sign, bexp: result_exp, sfd: result_sfd } +} diff --git a/xls/modules/fpmul_2x32_test.cc b/xls/modules/fpmul_2x32_test.cc new file mode 100644 index 0000000000..adcb73bfb2 --- /dev/null +++ b/xls/modules/fpmul_2x32_test.cc @@ -0,0 +1,113 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Random-sampling test for the DSLX 2x32 floating-point multiplier. +#include +#include + +#include "absl/random/random.h" +#include "absl/status/status.h" +#include "xls/common/file/get_runfile_path.h" +#include "xls/common/init_xls.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/llvm_ir_jit.h" +#include "xls/ir/value_helpers.h" +#include "xls/ir/value_view_helpers.h" +#include "xls/tools/testbench.h" + +ABSL_FLAG(bool, use_opt_ir, true, "Use optimized IR."); +ABSL_FLAG(int, num_threads, 0, + "Number of threads to use. Set to 0 to use all."); +ABSL_FLAG(int64, num_samples, 1024 * 1024, "Number of random samples to test."); + +namespace xls { + +constexpr const char kOptIrPath[] = "xls/modules/fpmul_2x32.opt.ir"; +constexpr const char kIrPath[] = "xls/modules/fpmul_2x32.ir"; + +using Float2x32 = std::tuple; + +float FlushSubnormals(float value) { + if (std::fpclassify(value) == FP_SUBNORMAL) { + return 0; + } + + return value; +} + +bool ZeroOrSubnormal(float value) { + return value == 0 || std::fpclassify(value) == FP_SUBNORMAL; +} + +// Generates two floats with reasonably unformly random bit patterns. +Float2x32 IndexToInput(uint64 index) { + thread_local absl::BitGen bitgen; + uint32 a = absl::Uniform(bitgen, 0u, std::numeric_limits::max()); + uint32 b = absl::Uniform(bitgen, 0u, std::numeric_limits::max()); + return Float2x32(absl::bit_cast(a), absl::bit_cast(b)); +} + +// The DSLX implementation uses the "round to nearest (half to even)" +// rounding mode, which is the default on most systems, hence we don't need +// to call fesetround(). +// The DSLX implementation also flushes input subnormals to 0, so we do that +// here as well. +float ComputeExpected(Float2x32 input) { + float x = FlushSubnormals(std::get<0>(input)); + float y = FlushSubnormals(std::get<1>(input)); + return x * y; +} + +// Computes FP addition via DSLX & the JIT. +float ComputeActual(LlvmIrJit* jit, absl::Span result_buffer, + Float2x32 input) { + auto x = F32ToTuple(std::get<0>(input)); + auto y = F32ToTuple(std::get<1>(input)); + memset(result_buffer.data(), 0, result_buffer.size()); + XLS_CHECK_OK(jit->RunToBuffer({x, y}, result_buffer)); + + F32TupleView result(result_buffer.data()); + return F32TupleViewToFloat(result); +} + +// Compares expected vs. actual results, taking into account two special cases. +bool CompareResults(float a, float b) { + // DSLX flushes subnormal outputs, while regular FP addition does not, so + // just check for that here. + return a == b || (std::isnan(a) && std::isnan(b)) || + (ZeroOrSubnormal(a) && ZeroOrSubnormal(b)); +} + +absl::Status RealMain(bool use_opt_ir, uint64 num_samples, int num_threads) { + Testbench testbench( + GetXlsRunfilePath(use_opt_ir ? kOptIrPath : kIrPath), + /*entry_function=*/"__fpmul_2x32__fpmul_2x32", 0, num_samples, + /*max_failures=*/1, IndexToInput, ComputeExpected, ComputeActual, + CompareResults); + if (num_threads != 0) { + XLS_RETURN_IF_ERROR(testbench.SetNumThreads(num_threads)); + } + return testbench.Run(); +} + +} // namespace xls + +int main(int argc, char** argv) { + xls::InitXls(argv[0], argc, argv); + XLS_QCHECK_OK(xls::RealMain(absl::GetFlag(FLAGS_use_opt_ir), + absl::GetFlag(FLAGS_num_samples), + absl::GetFlag(FLAGS_num_threads))); + return 0; +} diff --git a/xls/netlist/BUILD b/xls/netlist/BUILD new file mode 100644 index 0000000000..4d4815a372 --- /dev/null +++ b/xls/netlist/BUILD @@ -0,0 +1,356 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# cc_proto_library is used in this file + +package( + default_visibility = ["//xls:xls_internal"], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "netlist", + srcs = ["netlist.cc"], + hdrs = ["netlist.h"], + deps = [ + ":cell_library", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "//xls/common:integral_types", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + ], +) + +cc_library( + name = "fake_cell_library", + testonly = True, + srcs = ["fake_cell_library.cc"], + hdrs = ["fake_cell_library.h"], + deps = [ + ":netlist", + "//xls/common/logging", + ], +) + +cc_library( + name = "interpreter", + srcs = ["interpreter.cc"], + hdrs = ["interpreter.h"], + deps = [ + ":function_parser", + ":netlist", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:str_format", + "//xls/codegen:flattening", + "//xls/common:integral_types", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir:bits", + "//xls/ir:bits_ops", + "//xls/ir:type", + "//xls/ir:value", + ], +) + +cc_test( + name = "interpreter_test", + srcs = ["interpreter_test.cc"], + deps = [ + ":fake_cell_library", + ":interpreter", + ":netlist", + ":netlist_parser", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + "//xls/common/status:matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "netlist_parser", + srcs = ["netlist_parser.cc"], + hdrs = ["netlist_parser.h"], + deps = [ + ":netlist", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:variant", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir:bits", + "@com_google_re2//:re2", + ], +) + +cc_test( + name = "netlist_parser_test", + srcs = ["netlist_parser_test.cc"], + deps = [ + ":fake_cell_library", + ":netlist_parser", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "//xls/common/status:matchers", + "@com_google_googletest//:gtest_main", + ], +) + +proto_library( + name = "netlist_proto", + srcs = ["netlist.proto"], +) + +cc_proto_library( + name = "netlist_cc_proto", + deps = [":netlist_proto"], +) + +cc_library( + name = "cell_library", + srcs = ["cell_library.cc"], + hdrs = ["cell_library.h"], + deps = [ + ":netlist_cc_proto", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:str_format", + "//xls/common:integral_types", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + ], +) + +cc_test( + name = "cell_library_test", + srcs = ["cell_library_test.cc"], + deps = [ + ":cell_library", + "@com_google_absl//absl/memory", + "//xls/common/status:matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_binary( + name = "parse_netlist_main", + srcs = ["parse_netlist_main.cc"], + deps = [ + ":find_logic_clouds", + ":netlist_parser", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:str_format", + "//xls/common:init_xls", + "//xls/common/file:filesystem", + "//xls/common/logging", + "//xls/common/status:status_macros", + ], +) + +sh_test( + name = "parse_netlist_main_test", + srcs = ["parse_netlist_main_test.sh"], + data = [ + ":parse_netlist_main", + ], +) + +cc_library( + name = "find_logic_clouds", + srcs = ["find_logic_clouds.cc"], + hdrs = ["find_logic_clouds.h"], + deps = [ + ":netlist", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "//xls/common/logging", + "//xls/data_structures:union_find", + ], +) + +cc_test( + name = "find_logic_clouds_test", + srcs = ["find_logic_clouds_test.cc"], + deps = [ + ":fake_cell_library", + ":find_logic_clouds", + ":netlist_parser", + "@com_google_absl//absl/memory", + "//xls/common/status:matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "logical_effort", + srcs = ["logical_effort.cc"], + hdrs = ["logical_effort.h"], + deps = [ + ":netlist", + "@com_google_absl//absl/strings:str_format", + "//xls/common:integral_types", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + ], +) + +cc_test( + name = "logical_effort_test", + srcs = ["logical_effort_test.cc"], + deps = [ + ":fake_cell_library", + ":logical_effort", + ":netlist_parser", + "@com_google_absl//absl/memory", + "//xls/common/status:matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "lib_parser", + srcs = ["lib_parser.cc"], + hdrs = ["lib_parser.h"], + deps = [ + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:optional", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + ], +) + +cc_test( + name = "lib_parser_test", + srcs = ["lib_parser_test.cc"], + deps = [ + ":lib_parser", + "@com_google_absl//absl/memory", + "//xls/common/status:matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "function_extractor", + srcs = ["function_extractor.cc"], + hdrs = ["function_extractor.h"], + deps = [ + ":lib_parser", + ":netlist_cc_proto", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/status", + "@com_google_absl//absl/types:variant", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + ], +) + +cc_test( + name = "function_extractor_test", + srcs = ["function_extractor_test.cc"], + deps = [ + ":function_extractor", + ":lib_parser", + ":netlist_cc_proto", + "@com_google_absl//absl/container:flat_hash_set", + "//xls/common/status:matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "function_parser", + srcs = ["function_parser.cc"], + hdrs = ["function_parser.h"], + deps = [ + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:optional", + "//xls/common:integral_types", + "//xls/common/status:ret_check", + "//xls/common/status:statusor", + ], +) + +cc_test( + name = "function_parser_test", + srcs = ["function_parser_test.cc"], + deps = [ + ":function_parser", + "//xls/common/status:matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "z3_translator", + srcs = ["z3_translator.cc"], + hdrs = ["z3_translator.h"], + deps = [ + ":function_parser", + ":netlist", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "@z3//:api", + ], +) + +cc_test( + name = "z3_translator_test", + srcs = ["z3_translator_test.cc"], + deps = [ + ":fake_cell_library", + ":netlist", + ":z3_translator", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/random", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "//xls/common:cleanup", + "//xls/common/logging", + "//xls/common/status:matchers", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "@z3//:api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/xls/netlist/cell_library.cc b/xls/netlist/cell_library.cc new file mode 100644 index 0000000000..49e632d14c --- /dev/null +++ b/xls/netlist/cell_library.cc @@ -0,0 +1,160 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/cell_library.h" + +#include "absl/strings/str_format.h" +#include "xls/common/integral_types.h" +#include "xls/common/status/status_macros.h" + +namespace xls { +namespace netlist { +namespace { + +xabsl::StatusOr CellKindFromProto(CellKindProto proto) { + switch (proto) { + case INVALID: + break; + case FLOP: + return CellKind::kFlop; + case INVERTER: + return CellKind::kInverter; + case BUFFER: + return CellKind::kBuffer; + case NAND: + return CellKind::kNand; + case NOR: + return CellKind::kNor; + case MULTIPLEXER: + return CellKind::kMultiplexer; + case XOR: + return CellKind::kXor; + case OTHER: + return CellKind::kOther; + } + return absl::InvalidArgumentError(absl::StrFormat( + "Invalid proto value for conversion to CellKind: %d", proto)); +} + +} // namespace + +std::string CellKindToString(CellKind kind) { + switch (kind) { + case CellKind::kFlop: + return "flop"; + case CellKind::kInverter: + return "inverter"; + case CellKind::kBuffer: + return "buffer"; + case CellKind::kNand: + return "nand"; + case CellKind::kNor: + return "nor"; + case CellKind::kXor: + return "xor"; + case CellKind::kMultiplexer: + return "multiplexer"; + case CellKind::kOther: + return "other"; + } + return absl::StrFormat("", static_cast(kind)); +} + +/* static */ xabsl::StatusOr CellLibraryEntry::FromProto( + const CellLibraryEntryProto& proto) { + XLS_ASSIGN_OR_RETURN(CellKind cell_kind, CellKindFromProto(proto.kind())); + return CellLibraryEntry(cell_kind, proto.name(), proto.input_names(), + proto.output_pins()); +} + +CellLibraryEntryProto CellLibraryEntry::ToProto() const { + CellLibraryEntryProto proto; + switch (kind_) { + case CellKind::kFlop: + proto.set_kind(CellKindProto::FLOP); + break; + case CellKind::kInverter: + proto.set_kind(CellKindProto::INVERTER); + break; + case CellKind::kBuffer: + proto.set_kind(CellKindProto::BUFFER); + break; + case CellKind::kNand: + proto.set_kind(CellKindProto::NAND); + break; + case CellKind::kNor: + proto.set_kind(CellKindProto::NOR); + break; + case CellKind::kMultiplexer: + proto.set_kind(CellKindProto::MULTIPLEXER); + break; + case CellKind::kXor: + proto.set_kind(CellKindProto::XOR); + break; + case CellKind::kOther: + proto.set_kind(CellKindProto::OTHER); + break; + } + proto.set_name(name_); + for (const std::string& input_name : input_names_) { + proto.add_input_names(input_name); + } + for (const OutputPin& output_pin : output_pins_) { + OutputPinProto* pin_proto = proto.add_output_pins(); + pin_proto->set_name(output_pin.name); + pin_proto->set_function(output_pin.function); + } + return proto; +} + +/* static */ xabsl::StatusOr CellLibrary::FromProto( + const CellLibraryProto& proto) { + CellLibrary cell_library; + for (const CellLibraryEntryProto& entry_proto : proto.entries()) { + XLS_ASSIGN_OR_RETURN(auto entry, CellLibraryEntry::FromProto(entry_proto)); + XLS_RETURN_IF_ERROR(cell_library.AddEntry(std::move(entry))); + } + return cell_library; +} + +CellLibraryProto CellLibrary::ToProto() const { + CellLibraryProto proto; + for (const auto& entry : entries_) { + *proto.add_entries() = entry.second->ToProto(); + } + return proto; +} + +absl::Status CellLibrary::AddEntry(CellLibraryEntry entry) { + if (entries_.find(entry.name()) != entries_.end()) { + return absl::FailedPreconditionError( + "Attempting to register a cell library entry with a duplicate name: " + + entry.name()); + } + entries_.insert({entry.name(), absl::make_unique(entry)}); + return absl::OkStatus(); +} + +xabsl::StatusOr CellLibrary::GetEntry( + absl::string_view name) const { + auto it = entries_.find(name); + if (it == entries_.end()) { + return absl::NotFoundError( + absl::StrCat("Cell not found in library: ", name)); + } + return it->second.get(); +} + +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/cell_library.h b/xls/netlist/cell_library.h new file mode 100644 index 0000000000..f7c0b0a085 --- /dev/null +++ b/xls/netlist/cell_library.h @@ -0,0 +1,127 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_NETLIST_CELL_LIBRARY_H_ +#define THIRD_PARTY_XLS_NETLIST_CELL_LIBRARY_H_ + +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "xls/common/status/statusor.h" +#include "xls/netlist/netlist.pb.h" + +namespace xls { +namespace netlist { + +enum class CellKind { + kFlop, + kInverter, + kBuffer, + kNand, + kNor, + kMultiplexer, + kXor, + kOther, +}; + +std::string CellKindToString(CellKind kind); + +// Captures useful information about (one of) a cell's output pin(s). Currently, +// that's just its name and string description of its calculating function. +struct OutputPin { + std::string name; + std::string function; +}; +inline bool operator==(const OutputPin& lhs, const OutputPin& rhs) { + return lhs.name == rhs.name && lhs.function == rhs.function; +} +template +H AbslHashValue(H state, const OutputPin& p) { + return H::combine(std::move(state), p.name, p.function); +} + +// Represents an entry in the cell library, listing inputs/outputs an the name +// of the cell module. +class CellLibraryEntry { + public: + static xabsl::StatusOr FromProto( + const CellLibraryEntryProto& proto); + + // InputNamesContainer and OutputNamesContainer are expected to be containers + // of std::strings. + template + CellLibraryEntry(CellKind kind, absl::string_view name, + const InputNamesContainer& input_names, + const OutputPinsContainer& output_pins, + absl::optional clock_name = absl::nullopt) + : kind_(kind), + name_(name), + input_names_(input_names.begin(), input_names.end()), + output_pins_(output_pins.begin(), output_pins.end()), + clock_name_(clock_name) {} + + CellLibraryEntry(CellKind kind, absl::string_view name, + const google::protobuf::RepeatedPtrField& input_names, + const google::protobuf::RepeatedPtrField& output_pins, + absl::optional clock_name = absl::nullopt) + : kind_(kind), + name_(name), + input_names_(input_names.begin(), input_names.end()), + clock_name_(clock_name) { + output_pins_.reserve(output_pins.size()); + for (const auto& proto : output_pins) { + output_pins_.push_back({proto.name(), proto.function()}); + } + } + + CellKind kind() const { return kind_; } + const std::string& name() const { return name_; } + absl::Span input_names() const { return input_names_; } + absl::Span output_pins() const { return output_pins_; } + absl::optional clock_name() const { return clock_name_; } + + CellLibraryEntryProto ToProto() const; + + private: + CellKind kind_; + std::string name_; + std::vector input_names_; + std::vector output_pins_; + absl::optional clock_name_; +}; + +// Represents a library of cells. The definitions (represented in +// "CellLibraryEntry"s) are referred to from Cell instances in the netlist +// Module. +class CellLibrary { + public: + static xabsl::StatusOr FromProto(const CellLibraryProto& proto); + + // Returns a NOT_FOUND status if there is not entry with the given name. + xabsl::StatusOr GetEntry( + absl::string_view name) const; + + absl::Status AddEntry(CellLibraryEntry entry); + + CellLibraryProto ToProto() const; + + private: + absl::flat_hash_map> entries_; +}; + +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_CELL_LIBRARY_H_ diff --git a/xls/netlist/cell_library_test.cc b/xls/netlist/cell_library_test.cc new file mode 100644 index 0000000000..d3d8dad5b5 --- /dev/null +++ b/xls/netlist/cell_library_test.cc @@ -0,0 +1,52 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/cell_library.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "xls/common/status/matchers.h" + +namespace xls { +namespace netlist { +namespace { + +TEST(CellLibraryTest, SerializeToProto) { + CellLibrary cell_library; + OutputPin pin; + pin.name = "Z"; + pin.function = "W"; + XLS_ASSERT_OK(cell_library.AddEntry(CellLibraryEntry( + CellKind::kInverter, "INV", std::vector{"A"}, + std::vector{pin}))); + CellLibraryProto proto = cell_library.ToProto(); + EXPECT_EQ(R"(entries { + kind: INVERTER + name: "INV" + input_names: "A" + output_pins { + name: "Z" + function: "W" + } +} +)", + proto.DebugString()); +} + +} // namespace +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/fake_cell_library.cc b/xls/netlist/fake_cell_library.cc new file mode 100644 index 0000000000..7e9c6a39d1 --- /dev/null +++ b/xls/netlist/fake_cell_library.cc @@ -0,0 +1,70 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/fake_cell_library.h" + +#include "xls/common/logging/logging.h" + +namespace xls { +namespace netlist { + +CellLibrary MakeFakeCellLibrary() { + CellLibrary cell_library; + OutputPin pin; + pin.name = "ZN"; + pin.function = "!A"; + XLS_CHECK_OK(cell_library.AddEntry(CellLibraryEntry( + CellKind::kInverter, "INV", std::vector{"A"}, + std::vector{pin}))); + pin.name = "Q"; + pin.function = "?"; + XLS_CHECK_OK(cell_library.AddEntry( + CellLibraryEntry(CellKind::kFlop, "DFF", std::vector{"D"}, + std::vector{pin}, "CLK"))); + pin.name = "Z"; + pin.function = "A&B"; + XLS_CHECK_OK(cell_library.AddEntry(CellLibraryEntry( + CellKind::kOther, "AND", std::vector{"A", "B"}, + std::vector{pin}))); + pin.name = "Z"; + pin.function = "A|B"; + XLS_CHECK_OK(cell_library.AddEntry(CellLibraryEntry( + CellKind::kOther, "OR", std::vector{"A", "B"}, + std::vector{pin}))); + pin.name = "Z"; + pin.function = "A^B"; + XLS_CHECK_OK(cell_library.AddEntry(CellLibraryEntry( + CellKind::kOther, "XOR", std::vector{"A", "B"}, + std::vector{pin}))); + pin.name = "ZN"; + pin.function = "!(A&B)"; + XLS_CHECK_OK(cell_library.AddEntry(CellLibraryEntry( + CellKind::kNand, "NAND", std::vector{"A", "B"}, + std::vector{pin}))); + pin.name = "ZN"; + pin.function = "!(A|B|C|D)"; + XLS_CHECK_OK(cell_library.AddEntry(CellLibraryEntry( + CellKind::kNor, "NOR4", std::vector{"A", "B", "C", "D"}, + std::vector{pin}))); + // A la https://en.wikipedia.org/wiki/AND-OR-Invert + pin.name = "ZN"; + pin.function = "!((A*B)|C)"; + XLS_CHECK_OK(cell_library.AddEntry(CellLibraryEntry( + CellKind::kOther, "AOI21", std::vector{"A", "B", "C"}, + std::vector{pin}))); + return cell_library; +} + +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/fake_cell_library.h b/xls/netlist/fake_cell_library.h new file mode 100644 index 0000000000..db1228e1de --- /dev/null +++ b/xls/netlist/fake_cell_library.h @@ -0,0 +1,29 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_NETLIST_FAKE_CELL_LIBRARY_H_ +#define THIRD_PARTY_XLS_NETLIST_FAKE_CELL_LIBRARY_H_ + +#include "xls/netlist/netlist.h" + +namespace xls { +namespace netlist { + +// Creates a fake cell library suitable for testing. +CellLibrary MakeFakeCellLibrary(); + +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_FAKE_CELL_LIBRARY_H_ diff --git a/xls/netlist/find_logic_clouds.cc b/xls/netlist/find_logic_clouds.cc new file mode 100644 index 0000000000..200931120a --- /dev/null +++ b/xls/netlist/find_logic_clouds.cc @@ -0,0 +1,184 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/find_logic_clouds.h" + +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" +#include "xls/data_structures/union_find.h" + +namespace xls { +namespace netlist { +namespace rtl { + +void Cluster::Add(Cell* cell) { + if (cell->kind() == CellKind::kFlop) { + terminating_flops_.push_back(cell); + } else { + other_cells_.push_back(cell); + } +} + +void Cluster::SortCells() { + auto cell_name_lt = [](const Cell* a, const Cell* b) { + return a->name() < b->name(); + }; + std::sort(terminating_flops_.begin(), terminating_flops_.end(), cell_name_lt); + std::sort(other_cells_.begin(), other_cells_.end(), cell_name_lt); +} + +std::vector FindLogicClouds(const Module& module, + bool include_vacuous) { + // We need stable pointers for the UnionFind nodes because they hold raw + // pointers to each other. + absl::flat_hash_map>> + cell_to_uf; + + // Gets-or-creates a UnionFind node for cell c. + auto get_uf = [&cell_to_uf](Cell* c) -> UnionFind* { + auto it = cell_to_uf.find(c); + if (it == cell_to_uf.end()) { + auto value = absl::make_unique>(); + auto* ptr = value.get(); + cell_to_uf[c] = std::move(value); + return ptr; + } + return it->second.get(); + }; + + // Helper for debugging that counts the number of equivalence classes in + // cell_to_uf. + auto count_equivalence_classes = [&cell_to_uf]() -> int64 { + absl::flat_hash_set*> classes; + for (auto& item : cell_to_uf) { + classes.insert(item.second->FindRoot()); + } + return classes.size(); + }; + + for (auto& item : module.cells()) { + Cell* cell = item.get(); + XLS_VLOG(4) << "Considering cell: " << cell->name(); + + // Flop output connectivity is excluded from the equivalence class, so we + // get partitions along flop (output) boundaries. + if (cell->kind() == CellKind::kFlop) { + // For flops we just make sure an equivalence class is present in the + // map, in case it doesn't have any cells on the input side. + (void)get_uf(cell); + XLS_VLOG(4) << "--- Now " << count_equivalence_classes() + << " equivalence classes for " << cell_to_uf.size() + << " cells."; + continue; + } + + for (auto& input : cell->inputs()) { + XLS_VLOG(4) << "- Considering input net: " << input.netref->name(); + for (Cell* connected : input.netref->connected_cells()) { + if (cell == connected) { + continue; + } + if (connected->kind() == CellKind::kFlop) { + XLS_VLOG(4) << absl::StreamFormat( + "-- Cell is connected to flop cell %s on an input pin; not " + "merging equivalence classes.", + connected->name()); + continue; + } + XLS_VLOG(4) << absl::StreamFormat("-- Cell %s is connected to cell %s", + cell->name(), connected->name()); + get_uf(cell)->Merge(get_uf(connected)); + XLS_VLOG(4) << "--- Now " << count_equivalence_classes() + << " equivalence classes for " << cell_to_uf.size() + << " cells."; + } + } + + for (const auto& iter : cell->outputs()) { + NetRef output = iter.netref; + XLS_VLOG(4) << "- Considering output net: " << output->name(); + for (Cell* connected : output->connected_cells()) { + if (cell == connected) { + continue; + } + XLS_VLOG(4) << absl::StreamFormat("-- Cell %s is connected to cell %s", + cell->name(), connected->name()); + get_uf(cell)->Merge(get_uf(connected)); + XLS_VLOG(4) << "--- Now " << count_equivalence_classes() + << " equivalence classes for " << cell_to_uf.size() + << " cells."; + } + } + } + + // Run through the cells and put them into clusters according to their + // equivalence classes. + absl::flat_hash_map*, Cluster> + equivalence_set_to_cluster; + for (auto& item : module.cells()) { + Cell* cell = item.get(); + UnionFind* equivalence_class = get_uf(cell)->FindRoot(); + equivalence_set_to_cluster[equivalence_class].Add(cell); + } + + // Put them into a vector and sort each cluster's internal cells for + // determinism. + std::vector clusters; + for (auto& item : equivalence_set_to_cluster) { + Cluster& cluster = item.second; + if (!include_vacuous && (cluster.terminating_flops().size() == 1 && + cluster.other_cells().empty())) { + // Vacuous 'just a flop' cluster. + continue; + } + cluster.SortCells(); + clusters.push_back(std::move(cluster)); + } + + // For convenience (for now) we convert the cell names to a string and rely on + // string comparison for deterministic order. + auto cells_to_str = [](absl::Span cells) { + return absl::StrJoin(cells, ", ", [](std::string* out, const Cell* cell) { + absl::StrAppend(out, cell->name()); + }); + }; + auto cluster_sort_lt = [cells_to_str](const Cluster& a, const Cluster& b) { + return std::make_pair(cells_to_str(a.terminating_flops()), + cells_to_str(a.other_cells())) < + std::make_pair(cells_to_str(b.terminating_flops()), + cells_to_str(b.other_cells())); + }; + std::sort(clusters.begin(), clusters.end(), cluster_sort_lt); + return clusters; +} + +std::string ClustersToString(absl::Span clusters) { + std::string s; + for (const Cluster& cluster : clusters) { + absl::StrAppend(&s, "cluster {\n"); + for (const Cell* f : cluster.terminating_flops()) { + absl::StrAppend(&s, " terminating_flop: ", f->name(), "\n"); + } + for (const Cell* f : cluster.other_cells()) { + absl::StrAppend(&s, " other_cell: ", f->name(), "\n"); + } + absl::StrAppend(&s, "}\n"); + } + return s; +} + +} // namespace rtl +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/find_logic_clouds.h b/xls/netlist/find_logic_clouds.h new file mode 100644 index 0000000000..914bb41509 --- /dev/null +++ b/xls/netlist/find_logic_clouds.h @@ -0,0 +1,64 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_NETLIST_FIND_LOGIC_CLOUDS_H_ +#define THIRD_PARTY_XLS_NETLIST_FIND_LOGIC_CLOUDS_H_ + +#include + +#include "xls/netlist/netlist.h" + +namespace xls { +namespace netlist { +namespace rtl { + +// Represents a cluster of cells. +class Cluster { + public: + // Adds a cell to this cluster. + void Add(Cell* cell); + + // Helper for sorting cells after a cluster is built (by name) so a stable + // order can be observed in callers. + void SortCells(); + + absl::Span terminating_flops() const { + return terminating_flops_; + } + absl::Span other_cells() const { return other_cells_; } + + private: + std::vector terminating_flops_; + std::vector other_cells_; +}; + +// Finds the connected clusters of logic cells between flops in the given module +// and returns them. As noted in the definition of "Cluster", flops are +// associated with their "input side" subgraphs; any logic following a final +// flop stage has no associated "terminating flop". +// +// include_vacuous indicates whether a terminating flop with no connected logic +// (e.g. a layer of flops that flop input to the module) should be considered to +// be a cluster, or just discarded. +std::vector FindLogicClouds(const Module& module, + bool include_vacuous = false); + +// Converts the clusters to a string suitable for debugging/testing. +std::string ClustersToString(absl::Span clusters); + +} // namespace rtl +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_FIND_LOGIC_CLOUDS_H_ diff --git a/xls/netlist/find_logic_clouds_test.cc b/xls/netlist/find_logic_clouds_test.cc new file mode 100644 index 0000000000..d29a3c25e7 --- /dev/null +++ b/xls/netlist/find_logic_clouds_test.cc @@ -0,0 +1,134 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/find_logic_clouds.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "xls/common/status/matchers.h" +#include "xls/netlist/fake_cell_library.h" +#include "xls/netlist/netlist_parser.h" + +namespace xls { +namespace netlist { +namespace rtl { +namespace { + +TEST(ClusterTest, TwoSimpleClusters) { + std::string netlist = R"(module main(clk, ai, ao); + input clk; + input ai; + output ao; + wire a1; + + DFF dff_0(.D(ai), .Q(a1), .CLK(clk)); + DFF dff_1(.D(a1), .Q(ao), .CLK(clk)); +endmodule)"; + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + std::vector clusters = FindLogicClouds(*m, /*include_vacuous=*/true); + EXPECT_EQ(2, clusters.size()); + EXPECT_EQ(R"(cluster { + terminating_flop: dff_0 +} +cluster { + terminating_flop: dff_1 +} +)", + ClustersToString(clusters)); +} + +TEST(ClusterTest, InputAndOutputGatesOnTwoFlops) { + std::string netlist = R"(module main(clk, ai, ao); + input clk; + input ai; + output ao; + wire ain, a1, a1n, a2; + + INV inv_a(.A(ai), .ZN(ain)); + DFF dff_0(.D(ain), .Q(a1), .CLK(clk)); + INV inv_b(.A(a1), .ZN(a1n)); + DFF dff_1(.D(a1n), .Q(a2), .CLK(clk)); + INV inv_c(.A(a2), .ZN(ao)); +endmodule)"; + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + std::vector clusters = FindLogicClouds(*m, /*include_vacuous=*/true); + EXPECT_EQ(3, clusters.size()); + EXPECT_EQ(R"(cluster { + other_cell: inv_c +} +cluster { + terminating_flop: dff_0 + other_cell: inv_a +} +cluster { + terminating_flop: dff_1 + other_cell: inv_b +} +)", + ClustersToString(clusters)); +} + +TEST(ClusterTest, TwoStagesWithMergeCloudInMiddle) { + // -(a0)->|dff_a0_a1|-(a1)->|inv_a1_a1n|-(a1n)->|dff_a1n_ao|->ao + // \--. + // -(b0)->|dff_b0_b1|-(b1)+>|and_a1_b1|-(ab1)->|dff_ab1_bo|->bo + std::string netlist = R"(module main(clk, a0, b0, ao, bo); + input clk; + input a0, b0; + output ao, bo; + wire a1, a1n, ab1, b1; + + DFF dff_a0_a1(.D(a0), .Q(a1), .CLK(clk)); + INV inv_a1_a1n(.A(a1), .ZN(a1n)); + DFF dff_a1n_ao(.D(a1n), .Q(ao), .CLK(clk)); + + DFF dff_b0_b1(.D(b0), .Q(b1), .CLK(clk)); + AND and_a1_b1(.A(a1), .B(b1), .Z(ab1)); + DFF dff_ab1_bo(.D(ab1), .Q(bo), .CLK(clk)); +endmodule)"; + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + std::vector clusters = FindLogicClouds(*m, /*include_vacuous=*/true); + EXPECT_EQ(3, clusters.size()); + EXPECT_EQ(R"(cluster { + terminating_flop: dff_a0_a1 +} +cluster { + terminating_flop: dff_a1n_ao + terminating_flop: dff_ab1_bo + other_cell: and_a1_b1 + other_cell: inv_a1_a1n +} +cluster { + terminating_flop: dff_b0_b1 +} +)", + ClustersToString(clusters)); +} + +} // namespace +} // namespace rtl +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/function_extractor.cc b/xls/netlist/function_extractor.cc new file mode 100644 index 0000000000..526fc29ddc --- /dev/null +++ b/xls/netlist/function_extractor.cc @@ -0,0 +1,160 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/function_extractor.h" + +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "absl/types/variant.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/netlist/lib_parser.h" +#include "xls/netlist/netlist.pb.h" + +namespace xls { +namespace netlist { +namespace function { +namespace { + +constexpr const char kDirectionKey[] = "direction"; +constexpr const char kFunctionKey[] = "function"; +constexpr const char kNextStateKey[] = "next_state"; +constexpr const char kInputValue[] = "input"; +constexpr const char kOutputValue[] = "output"; +constexpr const char kPinKind[] = "pin"; +constexpr const char kFfKind[] = "ff"; + +// Gets input and output pin names and output pin functions and adds them to the +// entry proto. +absl::Status ExtractFromPin(const cell_lib::Block& pin, + CellLibraryEntryProto* entry_proto) { + // I've yet to see an example where this isn't the case. + std::string name = pin.args[0]; + + absl::optional is_output; + std::string function; + for (const cell_lib::BlockEntry& entry : pin.entries) { + const auto* kv_entry = absl::get_if(&entry); + if (kv_entry) { + if (kv_entry->key == kDirectionKey) { + if (kv_entry->value == kOutputValue) { + is_output = true; + } else if (kv_entry->value == kInputValue) { + is_output = false; + } else { + // "internal" is at least one add'l direction. + // We don't care about such pins. + return absl::OkStatus(); + } + } else if (kv_entry->key == kFunctionKey) { + function = kv_entry->value; + } + } + } + + if (is_output == absl::nullopt) { + return absl::InvalidArgumentError( + absl::StrFormat("Pin %s has no direction entry!", name)); + } + + if (is_output.value()) { + OutputPinProto* output_pin = entry_proto->add_output_pins(); + output_pin->set_name(name); + if (!function.empty()) { + // Some output pins lack associated functions. + // Ignore them for now (during baseline dev). If they turn out to be + // important, I'll circle back. + output_pin->set_function(function); + } + } else { + entry_proto->add_input_names(name); + } + + return absl::OkStatus(); +} + +// If we see a ff (flop-flop) block, it contains a "next_state" field, with the +// function calculating the output value of the cell after the next clock. +// Since all our current (logic-checking) purposes are clockless, this is +// equivalent to being the "function" of an output pin. All known FF cells have +// a single output pin, so we sanity check for that. +// If so, then we replace that function with the one from the next_state field. +absl::Status ExtractFromFf(const cell_lib::Block& ff, + CellLibraryEntryProto* entry_proto) { + entry_proto->set_kind(netlist::CellKindProto::FLOP); + for (const cell_lib::BlockEntry& entry : ff.entries) { + const auto* kv_entry = absl::get_if(&entry); + if (kv_entry && kv_entry->key == kNextStateKey) { + std::string next_state_function = kv_entry->value; + XLS_RET_CHECK(entry_proto->output_pins_size() == 1); + entry_proto->mutable_output_pins(0)->set_function(next_state_function); + } + } + + return absl::OkStatus(); +} + +absl::Status ExtractFromCell(const cell_lib::Block& cell, + CellLibraryEntryProto* entry_proto) { + // I've yet to see an example where this isn't the case. + entry_proto->set_name(cell.args[0]); + + // Default kind; overridden only if necessary. + entry_proto->set_kind(netlist::CellKindProto::OTHER); + + for (const cell_lib::BlockEntry& entry : cell.entries) { + if (absl::holds_alternative>(entry)) { + auto& block_entry = absl::get>(entry); + if (block_entry->kind == kPinKind) { + XLS_RETURN_IF_ERROR(ExtractFromPin(*block_entry.get(), entry_proto)); + } else if (block_entry->kind == kFfKind) { + // If it's a flip-flop, we need to replace the pin's output function + // with it's next_state function. + XLS_RETURN_IF_ERROR(ExtractFromFf(*block_entry.get(), entry_proto)); + } + } + } + + return absl::OkStatus(); +} + +} // namespace + +xabsl::StatusOr ExtractFunctions( + cell_lib::CharStream* stream) { + cell_lib::Scanner scanner(stream); + absl::flat_hash_set kind_whitelist( + {"library", "cell", "pin", "direction", "function", "ff", "next_state"}); + cell_lib::Parser parser(&scanner); + + XLS_ASSIGN_OR_RETURN(std::unique_ptr block, + parser.ParseLibrary()); + CellLibraryProto proto; + for (const cell_lib::BlockEntry& entry : block->entries) { + if (absl::holds_alternative>(entry)) { + auto& block_entry = absl::get>(entry); + if (block_entry->kind == "cell") { + CellLibraryEntryProto* entry_proto = proto.add_entries(); + XLS_RETURN_IF_ERROR(ExtractFromCell(*block_entry.get(), entry_proto)); + } + } + } + + return proto; +} + +} // namespace function +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/function_extractor.h b/xls/netlist/function_extractor.h new file mode 100644 index 0000000000..8472b141c5 --- /dev/null +++ b/xls/netlist/function_extractor.h @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_NETLIST_FUNCTION_EXTRACTOR_H_ +#define THIRD_PARTY_XLS_NETLIST_FUNCTION_EXTRACTOR_H_ + +#include + +#include "xls/common/status/statusor.h" +#include "xls/netlist/lib_parser.h" +#include "xls/netlist/netlist.pb.h" + +namespace xls { +namespace netlist { +namespace function { + +// This program iterates through the blocks contained in a specified Liberty- +// formatted file and collects the input and output pins of a "cell". +// For output pins, the "function" is collected as well - that specifies the +// logical operation of the cell or pin (in the case of multiple output pins). +xabsl::StatusOr ExtractFunctions( + cell_lib::CharStream* stream); + +} // namespace function +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_FUNCTION_EXTRACTOR_H_ diff --git a/xls/netlist/function_extractor_test.cc b/xls/netlist/function_extractor_test.cc new file mode 100644 index 0000000000..6f9b1ea1db --- /dev/null +++ b/xls/netlist/function_extractor_test.cc @@ -0,0 +1,109 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/function_extractor.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_set.h" +#include "xls/common/status/matchers.h" +#include "xls/netlist/lib_parser.h" +#include "xls/netlist/netlist.pb.h" + +namespace xls { +namespace netlist { +namespace function { +namespace { + +TEST(FunctionExtractorTest, BasicFunctionality) { + std::string lib = R"( +library (blah) { + blah (blah) { + } + blah: "blahblahblah"; + cell (cell_1) { + pin (i0) { + direction: input; + } + pin (i1) { + direction: input; + } + pin (o) { + direction: output; + function: "meow"; + } + } +} + )"; + + XLS_ASSERT_OK_AND_ASSIGN(auto stream, cell_lib::CharStream::FromText(lib)); + XLS_ASSERT_OK_AND_ASSIGN(CellLibraryProto proto, ExtractFunctions(&stream)); + + const CellLibraryEntryProto entry = proto.entries(0); + EXPECT_EQ(entry.name(), "cell_1"); + absl::flat_hash_set input_names({"i0", "i1"}); + for (const auto& input_name : entry.input_names()) { + ASSERT_TRUE(input_names.contains(input_name)); + input_names.erase(input_name); + } + ASSERT_EQ(input_names.size(), 0); + + OutputPinProto output_pin = entry.output_pins(0); + ASSERT_EQ(output_pin.name(), "o"); + ASSERT_EQ(output_pin.function(), "meow"); +} + +TEST(FunctionExtractorTest, HippetyHoppetyTestTheFlippetyFloppety) { + std::string lib = R"( +library (blah) { + blah (blah) { + } + cell (cell_1) { + pin (i0) { + direction: input; + } + pin (i1) { + direction: input; + } + pin (q) { + direction: output; + function: "bleh"; + } + ff (whatever) { + next_state: "i0|i1"; + } + } +})"; + + XLS_ASSERT_OK_AND_ASSIGN(auto stream, cell_lib::CharStream::FromText(lib)); + XLS_ASSERT_OK_AND_ASSIGN(CellLibraryProto proto, ExtractFunctions(&stream)); + + const CellLibraryEntryProto entry = proto.entries(0); + EXPECT_EQ(entry.name(), "cell_1"); + absl::flat_hash_set input_names({"i0", "i1"}); + for (const auto& input_name : entry.input_names()) { + ASSERT_TRUE(input_names.contains(input_name)); + input_names.erase(input_name); + } + ASSERT_EQ(input_names.size(), 0); + + OutputPinProto output_pin = entry.output_pins(0); + ASSERT_EQ(output_pin.name(), "q"); + ASSERT_EQ(output_pin.function(), "i0|i1"); +} + +} // namespace +} // namespace function +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/function_parser.cc b/xls/netlist/function_parser.cc new file mode 100644 index 0000000000..b0404b41a6 --- /dev/null +++ b/xls/netlist/function_parser.cc @@ -0,0 +1,331 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/function_parser.h" + +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "xls/common/status/ret_check.h" + +namespace xls { +namespace netlist { +namespace function { +namespace { + +// Returns true if the specified character represents a binary op. +bool IsBinOp(char c) { + switch (c) { + case '&': + case '*': + case '+': + case '|': + case '^': + return true; + default: + return false; + } +} + +} // namespace + +Token Token::Simple(Kind kind, int64 pos) { return Token(kind, pos); } + +Token Token::Identifier(const std::string& s, int64 pos) { + return Token(Kind::kIdentifier, pos, s); +} + +Scanner::Scanner(std::string function) + : function_(std::move(function)), current_pos_(0) {} + +absl::Status Scanner::DropSpaces() { + XLS_ASSIGN_OR_RETURN(char next_char, PeekChar()); + while (next_char == ' ') { + XLS_RETURN_IF_ERROR(DropChar()); + XLS_ASSIGN_OR_RETURN(next_char, PeekChar()); + } + return absl::OkStatus(); +} + +xabsl::StatusOr Scanner::Pop() { + if (peeked_) { + Token ret = peeked_.value(); + peeked_.reset(); + return ret; + } + + if (Eof()) { + return absl::OutOfRangeError("At end of input."); + } + + XLS_ASSIGN_OR_RETURN(char next_char, PeekChar()); + if (IsIdentifierStart(next_char)) { + return ScanIdentifier(); + } + + XLS_RETURN_IF_ERROR(DropChar()); + int64 last_pos = current_pos_ - 1; + switch (next_char) { + case '\'': + return Token::Simple(Token::Kind::kInvertPrevious, last_pos); + case '!': + return Token::Simple(Token::Kind::kInvertFollowing, last_pos); + case '(': + return Token::Simple(Token::Kind::kOpenParen, last_pos); + case ')': + return Token::Simple(Token::Kind::kCloseParen, last_pos); + case ' ': { + // Unfortunately, the liberty spec isn't consistently followed - + // spaces are sometimes inserted into functions when not meant as ANDs. + // So - we need to condense all whitespace into a single token, then check + // to see if the next token is an expression or an operator. + // If the former, then interpret it as an AND, otherwise, drop it. + XLS_RETURN_IF_ERROR(DropSpaces()); + XLS_ASSIGN_OR_RETURN(next_char, PeekChar()); + if (IsBinOp(next_char)) { + return Pop(); + } else { + return Token::Simple(Token::Kind::kAnd, last_pos); + } + } + case '&': + case '*': { + XLS_RETURN_IF_ERROR(DropSpaces()); + return Token::Simple(Token::Kind::kAnd, last_pos); + } + case '+': + case '|': { + XLS_RETURN_IF_ERROR(DropSpaces()); + return Token::Simple(Token::Kind::kOr, last_pos); + } + case '^': { + XLS_RETURN_IF_ERROR(DropSpaces()); + return Token::Simple(Token::Kind::kXor, last_pos); + } + case '0': + return Token::Simple(Token::Kind::kLogicZero, last_pos); + case '1': + return Token::Simple(Token::Kind::kLogicOne, last_pos); + default: + return absl::InvalidArgumentError(absl::StrFormat( + "Unhandled character for scanning @ %d: '%c'", last_pos, next_char)); + } +} + +xabsl::StatusOr Scanner::Peek() { + if (peeked_) { + return peeked_.value(); + } + + XLS_ASSIGN_OR_RETURN(Token token, Pop()); + peeked_ = token; + return token; +} + +bool Scanner::IsIdentifierStart(char next_char) { + // Pin names are [a-z][A-Z][0-9]+ + return std::isalpha(next_char) || next_char == '"' || next_char == '_'; +} + +xabsl::StatusOr Scanner::ScanIdentifier() { + int64 start_pos = current_pos_; + XLS_ASSIGN_OR_RETURN(char next_char, PeekChar()); + bool need_closing_quote = next_char == '"'; + if (need_closing_quote) { + XLS_RETURN_IF_ERROR(DropChar()); + XLS_ASSIGN_OR_RETURN(next_char, PeekChar()); + } + + std::string identifier; + // Identifiers as defined in section 5.6 of the SystemVerilog standard. + while (!Eof() && + (std::isalnum(next_char) || next_char == '_' || next_char == '$')) { + identifier.push_back(next_char); + XLS_RETURN_IF_ERROR(DropChar()); + if (!Eof()) { + XLS_ASSIGN_OR_RETURN(next_char, PeekChar()); + } + } + + if (need_closing_quote) { + if (Eof() || next_char != '"') { + return absl::InvalidArgumentError( + "Quoted identifier must end with closing quote!"); + } + XLS_RETURN_IF_ERROR(DropChar()); + } + + return Token::Identifier(identifier, start_pos); +} + +xabsl::StatusOr Scanner::PeekChar() { + XLS_RET_CHECK_LT(current_pos_, function_.size()); + return function_.at(current_pos_); +} + +absl::Status Scanner::DropChar() { + XLS_RET_CHECK_LT(current_pos_, function_.size()); + current_pos_++; + return absl::OkStatus(); +} + +xabsl::StatusOr Parser::ParseFunction(std::string function) { + Parser parser(std::move(function)); + return parser.ParseOr(); +} + +Parser::Parser(std::string function) : scanner_(std::move(function)) {} + +xabsl::StatusOr Parser::ParseOr() { + XLS_ASSIGN_OR_RETURN(auto lhs, ParseAnd()); + if (scanner_.Eof()) { + return lhs; + } + + // Assume all ops of equal precedence are left-associative, so keep stacking + // them on. + XLS_ASSIGN_OR_RETURN(Token token, scanner_.Peek()); + while (token.kind() == Token::Kind::kOr) { + XLS_RETURN_IF_ERROR(scanner_.Pop().status()); + XLS_ASSIGN_OR_RETURN(auto rhs, ParseAnd()); + lhs = + Ast::BinOp(Ast::Kind::kOr, std::move(lhs), std::move(rhs), token.pos()); + + if (scanner_.Eof()) { + return lhs; + } + XLS_ASSIGN_OR_RETURN(token, scanner_.Peek()); + } + + return lhs; +} + +xabsl::StatusOr Parser::ParseAnd() { + XLS_ASSIGN_OR_RETURN(auto lhs, ParseXor()); + if (scanner_.Eof()) { + return lhs; + } + + // Assume all ops of equal precedence are left-associative, so keep stacking + // them on. + XLS_ASSIGN_OR_RETURN(Token token, scanner_.Peek()); + while (token.kind() == Token::Kind::kAnd) { + XLS_RETURN_IF_ERROR(scanner_.Pop().status()); + XLS_ASSIGN_OR_RETURN(auto rhs, ParseXor()); + lhs = Ast::BinOp(Ast::Kind::kAnd, std::move(lhs), std::move(rhs), + token.pos()); + + if (scanner_.Eof()) { + return lhs; + } + XLS_ASSIGN_OR_RETURN(token, scanner_.Peek()); + } + + return lhs; +} + +xabsl::StatusOr Parser::ParseXor() { + XLS_ASSIGN_OR_RETURN(auto lhs, ParseInvertNext()); + if (scanner_.Eof()) { + return lhs; + } + + // Assume all ops of equal precedence are left-associative, so keep stacking + // them on. + XLS_ASSIGN_OR_RETURN(Token token, scanner_.Peek()); + while (token.kind() == Token::Kind::kXor) { + XLS_RETURN_IF_ERROR(scanner_.Pop().status()); + XLS_ASSIGN_OR_RETURN(auto rhs, ParseInvertNext()); + lhs = Ast::BinOp(Ast::Kind::kXor, std::move(lhs), std::move(rhs), + token.pos()); + + if (scanner_.Eof()) { + return lhs; + } + XLS_ASSIGN_OR_RETURN(token, scanner_.Peek()); + } + + return lhs; +} + +xabsl::StatusOr Parser::ParseInvertNext() { + // Rather than have to track every potentially stacked negation, we'll just + // see if we have an odd or even count and apply after processing the + // following term. + XLS_ASSIGN_OR_RETURN(Token token, scanner_.Peek()); + bool do_negate = 0; + while (token.kind() == Token::Kind::kInvertFollowing) { + XLS_RETURN_IF_ERROR(scanner_.Pop().status()); + do_negate = !do_negate; + + // Don't need to check for EOF here, b/c we must have a following term. + XLS_ASSIGN_OR_RETURN(token, scanner_.Peek()); + } + + XLS_ASSIGN_OR_RETURN(auto term, ParseInvertPrev()); + if (do_negate) { + term = Ast::Not(std::move(term), token.pos()); + } + + return term; +} + +xabsl::StatusOr Parser::ParseInvertPrev() { + XLS_ASSIGN_OR_RETURN(auto term, ParseTerm()); + if (scanner_.Eof()) { + return term; + } + + XLS_ASSIGN_OR_RETURN(Token token, scanner_.Peek()); + while (token.kind() == Token::Kind::kInvertPrevious) { + XLS_RETURN_IF_ERROR(scanner_.Pop().status()); + term = Ast::Not(std::move(term), token.pos()); + + if (scanner_.Eof()) { + return term; + } + XLS_ASSIGN_OR_RETURN(token, scanner_.Peek()); + } + + return term; +} + +xabsl::StatusOr Parser::ParseTerm() { + XLS_ASSIGN_OR_RETURN(Token token, scanner_.Pop()); + switch (token.kind()) { + case Token::Kind::kOpenParen: { + XLS_ASSIGN_OR_RETURN(auto expr, ParseOr()); + XLS_ASSIGN_OR_RETURN(Token closeparen, scanner_.Pop()); + if (closeparen.kind() != Token::Kind::kCloseParen) { + return absl::InvalidArgumentError( + absl::StrFormat("Expected close parenthesis, got: %d @ %d", + static_cast(token.kind()), token.pos())); + } + return expr; + } + case Token::Kind::kIdentifier: + return Ast::Identifier(token.payload(), token.pos()); + case Token::Kind::kLogicZero: + return Ast::LiteralZero(token.pos()); + case Token::Kind::kLogicOne: + return Ast::LiteralOne(token.pos()); + default: + return absl::InvalidArgumentError( + absl::StrFormat("Invalid token kind: %d @ %d", + static_cast(token.kind()), token.pos())); + } +} + +} // namespace function +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/function_parser.h b/xls/netlist/function_parser.h new file mode 100644 index 0000000000..0f8860ea0f --- /dev/null +++ b/xls/netlist/function_parser.h @@ -0,0 +1,176 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Defines routines for parsing the "function" component of cell library +// "cell" groups. +// Specifications taken from the liberty reference: +// https://people.eecs.berkeley.edu/~alanmi/publications/other/liberty07_03.pdf +#ifndef THIRD_PARTY_XLS_NETLIST_FUNCTION_PARSER_H_ +#define THIRD_PARTY_XLS_NETLIST_FUNCTION_PARSER_H_ + +#include + +#include "absl/types/optional.h" +#include "xls/common/integral_types.h" +#include "xls/common/status/statusor.h" + +namespace xls { +namespace netlist { +namespace function { + +// Represents a single token in a cell group "function" attribute. +class Token { + public: + // The "kind" of the token; the lexographic element it represents. + enum class Kind { + kIdentifier, + kOpenParen, + kCloseParen, + kAnd, + kOr, + kXor, + kInvertFollowing, + kInvertPrevious, + kLogicOne, + kLogicZero, + }; + + // Builder for identifier tokens. + static Token Identifier(const std::string& s, int64 pos); + + // Builder for all other tokens. + static Token Simple(Kind kind, int64 pos); + + Token(Kind kind, int64 pos, + absl::optional payload = absl::nullopt) + : kind_(kind), pos_(pos), payload_(payload) {} + + Kind kind() { return kind_; } + int64 pos() { return pos_; } + std::string payload() { return payload_.value(); } + + private: + Kind kind_; + int64 pos_; + absl::optional payload_; +}; + +// Scans a function attribute and returns the component tokens. +// Only needs a single element of lookahead, so repeated calls to Peek() without +// an intervening Pop() will always return the same token. +class Scanner { + public: + Scanner(std::string function); + xabsl::StatusOr Pop(); + xabsl::StatusOr Peek(); + + bool Eof() { return current_pos_ == function_.size(); } + + private: + // Returns true if this character starts an identifier. + bool IsIdentifierStart(char next_char); + + // Processes a single identifier. + xabsl::StatusOr ScanIdentifier(); + + // Looks at and optionally consumes the next character of input. + xabsl::StatusOr PeekChar(); + absl::Status DropChar(); + + // Drops all spaces until the next token. + absl::Status DropSpaces(); + + std::string function_; + // Set if we've peeked @ a token. Calling Pop() will clear it. + absl::optional peeked_; + int64 current_pos_; +}; + +// Represents the logical computation represented by a function attribute in a +// pin's "function" attribute (or subtree thereof). +class Ast { + public: + // Since our grammer is so simple, we'll squish all AST node types into one + // enum. + enum class Kind { + kIdentifier, + kLiteralZero, + kLiteralOne, + kAnd, + kOr, + kXor, + kNot + }; + + static Ast Identifier(const std::string& name, int64 pos) { + return Ast(Kind::kIdentifier, pos, name); + } + + static Ast LiteralOne(int64 pos) { return Ast(Kind::kLiteralOne, pos); } + + static Ast LiteralZero(int64 pos) { return Ast(Kind::kLiteralZero, pos); } + + static Ast Not(Ast expr, int64 pos) { + Ast ast(Kind::kNot, pos); + ast.children_.push_back(expr); + return ast; + } + + static Ast BinOp(Kind kind, Ast lhs, Ast rhs, int64 pos) { + Ast ast(kind, pos); + ast.children_.push_back(lhs); + ast.children_.push_back(rhs); + return ast; + } + + Ast(Kind kind, int64 pos, std::string name = "") + : kind_(kind), pos_(pos), name_(name) {} + + Kind kind() const { return kind_; } + int64 pos() const { return pos_; } + std::string name() const { return name_; } + const std::vector& children() const { return children_; } + + private: + Kind kind_; + int64 pos_; + std::string name_; + std::vector children_; +}; + +// Creates a function AST from an input cell/pin function description. +class Parser { + public: + static xabsl::StatusOr ParseFunction(std::string function); + + private: + Parser(std::string function); + + // Parsers for each syntactic element in a function, here ordered from highest + // to lowest precedence. + xabsl::StatusOr ParseTerm(); + xabsl::StatusOr ParseInvertPrev(); + xabsl::StatusOr ParseInvertNext(); + xabsl::StatusOr ParseXor(); + xabsl::StatusOr ParseAnd(); + xabsl::StatusOr ParseOr(); + + Scanner scanner_; +}; + +} // namespace function +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_FUNCTION_PARSER_H_ diff --git a/xls/netlist/function_parser_test.cc b/xls/netlist/function_parser_test.cc new file mode 100644 index 0000000000..5ed2fcc44d --- /dev/null +++ b/xls/netlist/function_parser_test.cc @@ -0,0 +1,218 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/function_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" + +namespace xls { +namespace netlist { +namespace function { +namespace { + +// Extremely basic test that we're able to scan extremely simple Functions. +TEST(ScannerTest, SimpleScan) { + std::string function = "A+B"; + Scanner scanner(function); + XLS_ASSERT_OK_AND_ASSIGN(Token token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kIdentifier); + EXPECT_EQ(token.pos(), 0); + EXPECT_EQ(token.payload(), "A"); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kOr); + EXPECT_EQ(token.pos(), 1); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kIdentifier); + EXPECT_EQ(token.pos(), 2); + EXPECT_EQ(token.payload(), "B"); +} + +// Verifies that we can correctly parse an identifer with a leading digit. +TEST(ScannerTest, ScanLeadingDigitIdentifier) { + std::string function = R"("1A1"+"2B2")"; + Scanner scanner(function); + + XLS_ASSERT_OK_AND_ASSIGN(Token token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kIdentifier); + EXPECT_EQ(token.pos(), 0); + EXPECT_EQ(token.payload(), "1A1"); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kOr); + EXPECT_EQ(token.pos(), 5); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kIdentifier); + EXPECT_EQ(token.pos(), 6); + EXPECT_EQ(token.payload(), "2B2"); +} + +// Just tests recognition of more tokens. +TEST(ScannerTest, ScanMoreChecks) { + std::string function = "1A()!* &+|^01"; + Scanner scanner(function); + XLS_ASSERT_OK_AND_ASSIGN(Token token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kLogicOne); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kIdentifier); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kOpenParen); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kCloseParen); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kInvertFollowing); + + // One AND is "missing", because spaces following an operator are dropped + // (this is not per the Liberty spec, but matches actual cell library + // definitions). + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kAnd); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kAnd); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kOr); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kOr); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kXor); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kLogicZero); + + XLS_ASSERT_OK_AND_ASSIGN(token, scanner.Pop()); + EXPECT_EQ(token.kind(), Token::Kind::kLogicOne); + + EXPECT_FALSE(scanner.Pop().ok()); +} + +TEST(FunctionParserTest, SimpleSmoke) { + std::string function = R"(A+B)"; + XLS_ASSERT_OK_AND_ASSIGN(auto ast, Parser::ParseFunction(function)); + + ASSERT_EQ(ast.kind(), Ast::Kind::kOr); + ASSERT_EQ(ast.children()[0].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(ast.children()[0].name(), "A"); + ASSERT_EQ(ast.children()[1].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(ast.children()[1].name(), "B"); +} + +TEST(FunctionParserTest, SimpleCompound) { + std::string function = R"(A+B*C)"; + XLS_ASSERT_OK_AND_ASSIGN(auto ast, Parser::ParseFunction(function)); + + ASSERT_EQ(ast.kind(), Ast::Kind::kOr); + ASSERT_EQ(ast.children()[0].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(ast.children()[0].name(), "A"); + ASSERT_EQ(ast.children()[1].kind(), Ast::Kind::kAnd); + ASSERT_EQ(ast.children()[1].children()[0].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(ast.children()[1].children()[0].name(), "B"); + ASSERT_EQ(ast.children()[1].children()[1].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(ast.children()[1].children()[1].name(), "C"); +} + +TEST(FunctionParserTest, Inversions) { + // The AST should look something like (where all negations are '!'): + // + + // ! ! + // ! * + // A ! C + // B + std::string function = R"(!A'+(!B*C)')"; + XLS_ASSERT_OK_AND_ASSIGN(auto ast, Parser::ParseFunction(function)); + ASSERT_EQ(ast.kind(), Ast::Kind::kOr); + + // Left-hand side: + const Ast* lhs = &ast.children()[0]; + ASSERT_EQ(lhs->kind(), Ast::Kind::kNot); + lhs = &lhs->children()[0]; + ASSERT_EQ(lhs->kind(), Ast::Kind::kNot); + lhs = &lhs->children()[0]; + ASSERT_EQ(lhs->kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(lhs->name(), "A"); + + // Right-hand side: + const Ast* rhs = &ast.children()[1]; + ASSERT_EQ(rhs->kind(), Ast::Kind::kNot); + rhs = &rhs->children()[0]; + ASSERT_EQ(rhs->kind(), Ast::Kind::kAnd); + ASSERT_EQ(rhs->children()[1].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(rhs->children()[1].name(), "C"); + + // Continue to the left children. + rhs = &rhs->children()[0]; + ASSERT_EQ(rhs->kind(), Ast::Kind::kNot); + rhs = &rhs->children()[0]; + ASSERT_EQ(rhs->kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(rhs->name(), "B"); +} + +TEST(FunctionParserTest, SpaceAsAnd) { + // I really don't care for space-as-AND, but thus sayeth the standard. + // Expected AST: + // + // || + // || & + // & & ! F + // A B C ! E + // D + // + std::string function = R"(A B+C !D+(E' F))"; + XLS_ASSERT_OK_AND_ASSIGN(auto node, Parser::ParseFunction(function)); + ASSERT_EQ(node.kind(), Ast::Kind::kOr); + ASSERT_EQ(node.children()[0].kind(), Ast::Kind::kOr); + ASSERT_EQ(node.children()[1].kind(), Ast::Kind::kAnd); + + // Consider the RHS first (just for simpler bookkeeping): + const Ast* lhs = &node.children()[1].children()[0]; + const Ast* rhs = &node.children()[1].children()[1]; + ASSERT_EQ(lhs->kind(), Ast::Kind::kNot); + ASSERT_EQ(lhs->children()[0].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(lhs->children()[0].name(), "E"); + + ASSERT_EQ(rhs->kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(rhs->name(), "F"); + + // Now back to the left half of the tree: + lhs = &node.children()[0].children()[0]; + ASSERT_EQ(lhs->kind(), Ast::Kind::kAnd); + ASSERT_EQ(lhs->children()[0].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(lhs->children()[0].name(), "A"); + ASSERT_EQ(lhs->children()[1].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(lhs->children()[1].name(), "B"); + + rhs = &node.children()[0].children()[1]; + ASSERT_EQ(rhs->kind(), Ast::Kind::kAnd); + ASSERT_EQ(rhs->children()[0].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(rhs->children()[0].name(), "C"); + ASSERT_EQ(rhs->children()[1].kind(), Ast::Kind::kNot); + ASSERT_EQ(rhs->children()[1].children()[0].kind(), Ast::Kind::kIdentifier); + ASSERT_EQ(rhs->children()[1].children()[0].name(), "D"); +} + +} // namespace +} // namespace function +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/interpreter.cc b/xls/netlist/interpreter.cc new file mode 100644 index 0000000000..886272072d --- /dev/null +++ b/xls/netlist/interpreter.cc @@ -0,0 +1,276 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "xls/netlist/interpreter.h" + +#include "absl/strings/str_format.h" +#include "xls/codegen/flattening.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits.h" +#include "xls/ir/bits_ops.h" + +namespace xls { +namespace netlist { + +Interpreter::Interpreter(rtl::Netlist* netlist) : netlist_(netlist) {} + +xabsl::StatusOr Interpreter::InterpretModule( + const std::string& module_name, const Bits& inputs) { + // Do a topological sort through all cells, evaluating each as its inputs are + // fully satisfied, and store those results with each output wire. + const rtl::Module* module = netlist_->GetModule(module_name); + XLS_RET_CHECK(module != nullptr); + + XLS_RET_CHECK(module->inputs().size() == inputs.bit_count()) + << absl::StrFormat("Module inputs size: %d, input bit count: %d", + module->inputs().size(), inputs.bit_count()); + + // Consider each input as a processed wire. + absl::flat_hash_map processed_wires; + for (int i = 0; i < module->inputs().size(); i++) { + processed_wires[module->inputs()[i]] = inputs.Get(i); + } + + using OutputsT = absl::flat_hash_map; + XLS_ASSIGN_OR_RETURN(OutputsT outputs, + InterpretModule(module, processed_wires)); + + // Finally, collect up all module outputs. + BitsRope rope(module->outputs().size()); + for (const rtl::NetRef output : module->outputs()) { + rope.push_back(outputs[output]); + } + + return rope.Build(); +} + +xabsl::StatusOr> +Interpreter::InterpretModule( + const rtl::Module* module, + const absl::flat_hash_map& inputs) { + // Do a topological sort through all cells, evaluating each as its inputs are + // fully satisfied, and store those results with each output wire. + + // First, build up the list of "unsatisfied" cells. + absl::flat_hash_map> cell_inputs; + for (const auto& cell : module->cells()) { + absl::flat_hash_set inputs; + for (const auto& input : cell->inputs()) { + inputs.insert(input.netref); + } + cell_inputs[cell.get()] = std::move(inputs); + } + + // Set all inputs as "active". + std::deque active_wires; + for (const rtl::NetRef ref : module->inputs()) { + active_wires.push_back(ref); + } + + absl::flat_hash_map processed_wires; + for (const auto& input : inputs) { + processed_wires[input.first] = input.second; + } + + // Process all active wires : see if this wire satisfies all of a cell's + // inputs. If so, interpret the cell, and place its outputs on the active wire + // list. + while (!active_wires.empty()) { + rtl::NetRef wire = active_wires.front(); + active_wires.pop_front(); + XLS_VLOG(2) << "Processing wire: " << wire->name(); + + for (const auto& cell : wire->connected_cells()) { + if (IsCellOutput(*cell, wire)) { + continue; + } + + cell_inputs[cell].erase(wire); + if (cell_inputs[cell].empty()) { + XLS_VLOG(2) << "Processing cell: " << cell->name(); + XLS_RETURN_IF_ERROR(InterpretCell(*cell, &processed_wires)); + for (const auto& output : cell->outputs()) { + active_wires.push_back(output.netref); + } + } + } + } + + // Sanity check that we've processed all cells (i.e., that there aren't + // unsatisfiable cells). + for (const auto& cell : module->cells()) { + for (const auto& output : cell->outputs()) { + if (!processed_wires.contains(output.netref)) { + return absl::InvalidArgumentError(absl::StrFormat( + "Netlist contains unconnected subgraphs and cannot be translated. " + "Example: cell %s, output %s.", + cell->name(), output.netref->name())); + } + } + } + + absl::flat_hash_map outputs; + outputs.reserve(module->outputs().size()); + for (const rtl::NetRef output : module->outputs()) { + outputs[output] = processed_wires.at(output); + } + + return outputs; +} + +absl::Status Interpreter::InterpretCell( + const rtl::Cell& cell, + absl::flat_hash_map* processed_wires) { + const rtl::Module* module = + netlist_->GetModule(cell.cell_library_entry()->name()); + if (module != nullptr) { + // If this "cell" is actually a module defined in the netlist, + // then recursively evaluate it. + absl::flat_hash_map inputs; + // who's input/output name - needs to be internal + // need to map cell inputs to module inputs? + const std::vector& module_input_refs = module->inputs(); + const absl::Span module_input_names = + module->AsCellLibraryEntry()->input_names(); + + for (const auto& input : cell.inputs()) { + // We need to match the inputs - from the NetRefs in this module to the + // NetRefs in the child module. In Module, the order of inputs + // (as NetRefs) is the same as the input names in its CellLibraryEntry. + // That means, for each input (in this module): + // - Find the child module input pin/NetRef with the same name. + // - Assign the corresponding child module input NetRef to have the value + // of the wire in this module. + // If ever an input isn't found, that's bad. Abort. + bool input_found = false; + for (int i = 0; i < module_input_names.size(); i++) { + if (module_input_names[i] == input.pin_name) { + inputs[module_input_refs[i]] = processed_wires->at(input.netref); + input_found = true; + break; + } + } + + XLS_RET_CHECK(input_found) << absl::StrFormat( + "Could not find input pin \"%s\" in module \"%s\", referenced in " + "cell \"%s\"!", + input.pin_name, module->name(), cell.name()); + } + + using ChildOutputsT = absl::flat_hash_map; + XLS_ASSIGN_OR_RETURN(ChildOutputsT child_outputs, + InterpretModule(module, inputs)); + // We need to do the same here - map the NetRefs in the module's output + // to the NetRefs in this module, using pin names as the matching keys. + for (const auto& child_output : child_outputs) { + bool output_found = false; + for (const auto& cell_output : cell.outputs()) { + if (child_output.first->name() == cell_output.pin.name) { + (*processed_wires)[cell_output.netref] = child_output.second; + output_found = true; + break; + } + } + XLS_RET_CHECK(output_found); + XLS_RET_CHECK(output_found) << absl::StrFormat( + "Could not find cell output pin \"%s\" in cell \"%s\", referenced in " + "child module \"%s\"!", + child_output.first->name(), cell.name(), module->name()); + } + + return absl::OkStatus(); + } + + for (const rtl::Cell::Output& output : cell.outputs()) { + XLS_ASSIGN_OR_RETURN( + function::Ast ast, + function::Parser::ParseFunction( + cell.cell_library_entry()->output_pins()[0].function)); + XLS_ASSIGN_OR_RETURN(bool result, + InterpretFunction(cell, ast, *processed_wires)); + (*processed_wires)[output.netref] = result; + } + + return absl::OkStatus(); +} + +xabsl::StatusOr Interpreter::InterpretFunction( + const rtl::Cell& cell, const function::Ast& ast, + const absl::flat_hash_map& processed_wires) { + switch (ast.kind()) { + case function::Ast::Kind::kAnd: { + XLS_ASSIGN_OR_RETURN(bool lhs, InterpretFunction(cell, ast.children()[0], + processed_wires)); + XLS_ASSIGN_OR_RETURN(bool rhs, InterpretFunction(cell, ast.children()[1], + processed_wires)); + return lhs & rhs; + } + case function::Ast::Kind::kIdentifier: { + rtl::NetRef ref = nullptr; + for (const auto& input : cell.inputs()) { + if (input.pin_name == ast.name()) { + ref = input.netref; + } + } + + if (ref == nullptr) { + return absl::NotFoundError( + absl::StrFormat("Identifier \"%s\" not found in cell %s's inputs.", + ast.name(), cell.name())); + } + + return processed_wires.at(ref); + } + case function::Ast::Kind::kLiteralOne: + return 1; + case function::Ast::Kind::kLiteralZero: + return 0; + case function::Ast::Kind::kNot: { + XLS_ASSIGN_OR_RETURN( + bool value, + InterpretFunction(cell, ast.children()[0], processed_wires)); + return !value; + } + case function::Ast::Kind::kOr: { + XLS_ASSIGN_OR_RETURN(bool lhs, InterpretFunction(cell, ast.children()[0], + processed_wires)); + XLS_ASSIGN_OR_RETURN(bool rhs, InterpretFunction(cell, ast.children()[1], + processed_wires)); + return lhs | rhs; + } + case function::Ast::Kind::kXor: { + XLS_ASSIGN_OR_RETURN(bool lhs, InterpretFunction(cell, ast.children()[0], + processed_wires)); + XLS_ASSIGN_OR_RETURN(bool rhs, InterpretFunction(cell, ast.children()[1], + processed_wires)); + return lhs ^ rhs; + } + default: + return absl::InvalidArgumentError( + absl::StrCat("Unknown AST element type: ", ast.kind())); + } +} + +bool Interpreter::IsCellOutput(const rtl::Cell& cell, const rtl::NetRef ref) { + for (const auto& output : cell.outputs()) { + if (ref == output.netref) { + return true; + } + } + + return false; +} + +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/interpreter.h b/xls/netlist/interpreter.h new file mode 100644 index 0000000000..e9aa82e929 --- /dev/null +++ b/xls/netlist/interpreter.h @@ -0,0 +1,69 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef THIRD_PARTY_XLS_NETLIST_INTERPRETER_H_ +#define THIRD_PARTY_XLS_NETLIST_INTERPRETER_H_ + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/type.h" +#include "xls/ir/value.h" +#include "xls/netlist/function_parser.h" +#include "xls/netlist/netlist.h" + +namespace xls { +namespace netlist { + +// Interprets Netlists/Modules given a set of input values and returns the +// resulting value. +class Interpreter { + public: + explicit Interpreter(rtl::Netlist* netlist); + + // Calculates the output value of the given module when run with the specified + // inputs. + // The Module's inputs are determined at parsing time, and are represented by + // a vector of NetRefs. "inputs" should contain a bit for every + // corresponding element in module->inputs(). + xabsl::StatusOr InterpretModule(const std::string& module_name, + const Bits& inputs); + + // Interprets the given module without doing any XLS type conversion (inputs + // are given as flat bits, outputs are given as flat bits). + xabsl::StatusOr> InterpretModule( + const rtl::Module* module, + const absl::flat_hash_map& inputs); + + private: + // Returns true if the specified NetRef is an output of the given cell. + bool IsCellOutput(const rtl::Cell& cell, const rtl::NetRef ref); + + absl::Status InterpretCell( + const rtl::Cell& cell, + absl::flat_hash_map* processed_wires); + + xabsl::StatusOr InterpretFunction( + const rtl::Cell& cell, const function::Ast& ast, + const absl::flat_hash_map& processed_wires); + + rtl::Netlist* netlist_; +}; + +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_INTERPRETER_H_ diff --git a/xls/netlist/interpreter_test.cc b/xls/netlist/interpreter_test.cc new file mode 100644 index 0000000000..19bee8653d --- /dev/null +++ b/xls/netlist/interpreter_test.cc @@ -0,0 +1,205 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "xls/netlist/interpreter.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_map.h" +#include "absl/types/optional.h" +#include "absl/types/span.h" +#include "xls/common/status/matchers.h" +#include "xls/netlist/fake_cell_library.h" +#include "xls/netlist/netlist.h" +#include "xls/netlist/netlist_parser.h" + +namespace xls { +namespace netlist { +namespace { + +// Smoke test to make sure anything works. +TEST(InterpreterTest, BasicFunctionality) { + // Make a very simple A * B module. + auto module = std::make_unique("the_module"); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kInput, "A")); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kInput, "B")); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kOutput, "O")); + + CellLibrary library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(const CellLibraryEntry* entry, + library.GetEntry("AND")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef a_ref, module->ResolveNet("A")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef b_ref, module->ResolveNet("B")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef o_ref, module->ResolveNet("O")); + + absl::flat_hash_map params; + params["A"] = a_ref; + params["B"] = b_ref; + params["Z"] = o_ref; + + XLS_ASSERT_OK_AND_ASSIGN( + rtl::Cell tmp_cell, + rtl::Cell::Create(entry, "the_cell", params, absl::nullopt)); + XLS_ASSERT_OK_AND_ASSIGN(auto cell, module->AddCell(tmp_cell)); + a_ref->NoteConnectedCell(cell); + b_ref->NoteConnectedCell(cell); + o_ref->NoteConnectedCell(cell); + + rtl::Netlist netlist; + netlist.AddModule(std::move(module)); + Interpreter interpreter(&netlist); + + BitsRope rope(2); + rope.push_back(1); + rope.push_back(0); + XLS_ASSERT_OK_AND_ASSIGN( + Bits output, interpreter.InterpretModule("the_module", rope.Build())); + EXPECT_EQ(output.bit_count(), 1); + EXPECT_EQ(output.Get(0), 0); +} + +// Verifies that a simple XOR(AND(), OR()) tree is interpreted correctly. +TEST(InterpreterTest, Tree) { + // Make a very simple A * B module. + auto module = std::make_unique("the_module"); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kInput, "i0")); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kInput, "i1")); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kInput, "i2")); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kInput, "i3")); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kWire, "and_o")); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kWire, "or_o")); + XLS_ASSERT_OK(module->AddNetDecl(rtl::NetDeclKind::kOutput, "xor_o")); + + CellLibrary library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(const CellLibraryEntry* and_entry, + library.GetEntry("AND")); + XLS_ASSERT_OK_AND_ASSIGN(const CellLibraryEntry* or_entry, + library.GetEntry("OR")); + XLS_ASSERT_OK_AND_ASSIGN(const CellLibraryEntry* xor_entry, + library.GetEntry("XOR")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef i0, module->ResolveNet("i0")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef i1, module->ResolveNet("i1")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef i2, module->ResolveNet("i2")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef i3, module->ResolveNet("i3")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef and_o, module->ResolveNet("and_o")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef or_o, module->ResolveNet("or_o")); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef xor_o, module->ResolveNet("xor_o")); + + absl::flat_hash_map and_params; + and_params["A"] = i0; + and_params["B"] = i1; + and_params["Z"] = and_o; + + XLS_ASSERT_OK_AND_ASSIGN( + rtl::Cell tmp_cell, + rtl::Cell::Create(and_entry, "and", and_params, absl::nullopt)); + XLS_ASSERT_OK_AND_ASSIGN(auto and_cell, module->AddCell(tmp_cell)); + i0->NoteConnectedCell(and_cell); + i1->NoteConnectedCell(and_cell); + and_o->NoteConnectedCell(and_cell); + + absl::flat_hash_map or_params; + or_params["A"] = i2; + or_params["B"] = i3; + or_params["Z"] = or_o; + + XLS_ASSERT_OK_AND_ASSIGN( + tmp_cell, rtl::Cell::Create(or_entry, "or", or_params, absl::nullopt)); + XLS_ASSERT_OK_AND_ASSIGN(auto or_cell, module->AddCell(tmp_cell)); + i2->NoteConnectedCell(or_cell); + i3->NoteConnectedCell(or_cell); + or_o->NoteConnectedCell(or_cell); + + absl::flat_hash_map xor_params; + xor_params["A"] = and_o; + xor_params["B"] = or_o; + xor_params["Z"] = xor_o; + XLS_ASSERT_OK_AND_ASSIGN( + tmp_cell, rtl::Cell::Create(xor_entry, "xor", xor_params, absl::nullopt)); + XLS_ASSERT_OK_AND_ASSIGN(auto xor_cell, module->AddCell(tmp_cell)); + and_o->NoteConnectedCell(xor_cell); + or_o->NoteConnectedCell(xor_cell); + xor_o->NoteConnectedCell(xor_cell); + + rtl::Netlist netlist; + netlist.AddModule(std::move(module)); + Interpreter interpreter(&netlist); + BitsRope rope(4); + // AND inputs + rope.push_back(1); + rope.push_back(0); + + // OR inputs + rope.push_back(1); + rope.push_back(0); + XLS_ASSERT_OK_AND_ASSIGN( + Bits output, interpreter.InterpretModule("the_module", rope.Build())); + + EXPECT_EQ(output.bit_count(), 1); + EXPECT_EQ(output.Get(0), 1); +} + +TEST(InterpreterTest, Submodules) { + std::string module_text = R"( +module submodule_0 (i2_0, i2_1, o2_0); + input i2_0, i2_1; + output o2_0; + + AND and0( .A(i2_0), .B(i2_1), .Z(o2_0) ); +endmodule + +module submodule_1 (i2_2, i2_3, o2_1); + input i2_2, i2_3; + output o2_1; + + OR or0( .A(i2_2), .B(i2_3), .Z(o2_1) ); +endmodule + +module submodule_2 (i1_0, i1_1, i1_2, i1_3, o1_0); + input i1_0, i1_1, i1_2, i1_3; + output o1_0; + wire res0, res1; + + submodule_0 and0 ( .i2_0(i1_0), .i2_1(i1_1), .o2_0(res0) ); + submodule_1 or0 ( .i2_2(i1_2), .i2_3(i1_3), .o2_1(res1) ); + XOR xor0 ( .A(res0), .B(res1), .Z(o1_0) ); +endmodule + +module main (i0, i1, i2, i3, o0); + input i0, i1, i2, i3; + output o0; + + submodule_2 bleh( .i1_0(i0), .i1_1(i1), .i1_2(i2), .i1_3(i3), .o1_0(o0) ); +endmodule +)"; + + CellLibrary cell_library = MakeFakeCellLibrary(); + rtl::Scanner scanner(module_text); + XLS_ASSERT_OK_AND_ASSIGN(rtl::Netlist netlist, + rtl::Parser::ParseNetlist(&cell_library, &scanner)); + + Interpreter interpreter(&netlist); + BitsRope rope(4); + rope.push_back(1); + rope.push_back(0); + rope.push_back(1); + rope.push_back(0); + XLS_ASSERT_OK_AND_ASSIGN(Bits output, + interpreter.InterpretModule("main", rope.Build())); + EXPECT_EQ(output.bit_count(), 1); + EXPECT_EQ(output.Get(0), true); +} + +} // namespace +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/lib_parser.cc b/xls/netlist/lib_parser.cc new file mode 100644 index 0000000000..7df5e1293f --- /dev/null +++ b/xls/netlist/lib_parser.cc @@ -0,0 +1,377 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/lib_parser.h" + +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" + +namespace xls { +namespace netlist { +namespace cell_lib { + +/* static */ xabsl::StatusOr CharStream::FromPath( + absl::string_view path) { + std::ifstream file_stream{std::string(path)}; + if (file_stream.is_open()) { + return CharStream(std::move(file_stream)); + } + return absl::NotFoundError( + absl::StrCat("Could not open file at path: ", path)); +} + +/* static */ xabsl::StatusOr CharStream::FromText( + std::string text) { + return CharStream(std::move(text)); +} + +std::string TokenKindToString(TokenKind kind) { + switch (kind) { + case TokenKind::kIdentifier: + return "identifier"; + case TokenKind::kOpenParen: + return "open-paren"; + case TokenKind::kCloseParen: + return "close-paren"; + case TokenKind::kOpenCurl: + return "open-curl"; + case TokenKind::kCloseCurl: + return "close-curl"; + case TokenKind::kSemi: + return "semi"; + case TokenKind::kColon: + return "colon"; + case TokenKind::kQuotedString: + return "quoted-string"; + case TokenKind::kNumber: + return "number"; + case TokenKind::kComma: + return "comma"; + } + return absl::StrFormat("", static_cast(kind)); +} + +xabsl::StatusOr Scanner::ScanIdentifier() { + const Pos start_pos = cs_->GetPos(); + XLS_CHECK(IsIdentifierStart(cs_->PeekCharOrDie())); + absl::InlinedVector chars; + while (!cs_->AtEof() && IsIdentifierRest(cs_->PeekCharOrDie())) { + chars.push_back(cs_->PopCharOrDie()); + } + return Token::Identifier( + start_pos, std::string(absl::string_view(chars.data(), chars.size()))); +} + +// Scans a number token. +xabsl::StatusOr Scanner::ScanNumber() { + const Pos start_pos = cs_->GetPos(); + XLS_CHECK(std::isdigit(cs_->PeekCharOrDie())); + absl::InlinedVector chars; + while (!cs_->AtEof()) { + if (IsNumberRest(cs_->PeekCharOrDie())) { + chars.push_back(cs_->PopCharOrDie()); + } else if (cs_->TryDropChars('e', '-')) { + chars.push_back('e'); + chars.push_back('-'); + } else { + break; + } + } + return Token::Number( + start_pos, std::string(absl::string_view(chars.data(), chars.size()))); +} + +// Scans a string token. +xabsl::StatusOr Scanner::ScanQuotedString() { + const Pos start_pos = cs_->GetPos(); + XLS_CHECK(cs_->TryDropChar('"')); + absl::InlinedVector chars; + while (true) { + if (cs_->AtEof()) { + return absl::InvalidArgumentError( + "Unexpected end-of-file in string token starting @ " + + start_pos.ToHumanString()); + } + char c = cs_->PopCharOrDie(); + if (c == '"') { + break; + } + chars.push_back(c); + } + return Token::QuotedString( + start_pos, std::string(absl::string_view(chars.data(), chars.size()))); +} + +absl::Status Scanner::PeekInternal() { + XLS_DCHECK(!lookahead_.has_value()); + XLS_DCHECK(!cs_->AtEof()); + if (IsIdentifierStart(cs_->PeekCharOrDie())) { + XLS_ASSIGN_OR_RETURN(lookahead_, ScanIdentifier()); + DropWhitespaceAndComments(); + return absl::OkStatus(); + } + if (isdigit(cs_->PeekCharOrDie())) { + XLS_ASSIGN_OR_RETURN(lookahead_, ScanNumber()); + DropWhitespaceAndComments(); + return absl::OkStatus(); + } + const Pos start_pos = cs_->GetPos(); + switch (char c = cs_->PeekCharOrDie()) { + case '"': { + XLS_ASSIGN_OR_RETURN(lookahead_, ScanQuotedString()); + break; + } + case ':': + cs_->DropCharOrDie(); + lookahead_ = Token::Simple(start_pos, TokenKind::kColon); + break; + case ';': + cs_->DropCharOrDie(); + lookahead_ = Token::Simple(start_pos, TokenKind::kSemi); + break; + case '{': + cs_->DropCharOrDie(); + lookahead_ = Token::Simple(start_pos, TokenKind::kOpenCurl); + break; + case '}': + cs_->DropCharOrDie(); + lookahead_ = Token::Simple(start_pos, TokenKind::kCloseCurl); + break; + case '(': + cs_->DropCharOrDie(); + lookahead_ = Token::Simple(start_pos, TokenKind::kOpenParen); + break; + case ')': + cs_->DropCharOrDie(); + lookahead_ = Token::Simple(start_pos, TokenKind::kCloseParen); + break; + case ',': + cs_->DropCharOrDie(); + lookahead_ = Token::Simple(start_pos, TokenKind::kComma); + break; + default: + return absl::InvalidArgumentError( + absl::StrFormat("Unhandled character for scanning @ %s: '%c'=%#x", + start_pos.ToHumanString(), c, c)); + } + DropWhitespaceAndComments(); + return absl::OkStatus(); +} + +/* static */ std::string Block::EntryToString(const BlockEntry& entry) { + if (const KVEntry* kv = absl::get_if(&entry)) { + return absl::StrFormat("(%s \"%s\")", kv->key, kv->value); + } + const auto& block = absl::get>(entry); + return block->ToString(); +} + +std::string Block::ToString() const { + std::string result = + absl::StrCat("(block ", kind, " (", absl::StrJoin(args, " "), ")", " ("); + absl::StrAppend(&result, + absl::StrJoin(entries, " ", + [](std::string* out, const BlockEntry& entry) { + absl::StrAppend(out, EntryToString(entry)); + })); + absl::StrAppend(&result, "))"); + return result; +} + +std::vector Block::GetSubBlocks( + absl::optional target_kind) const { + std::vector results; + for (const BlockEntry& item : entries) { + if (const auto* block = absl::get_if>(&item)) { + if (!target_kind.has_value() || target_kind.value() == (*block)->kind) { + results.push_back(block->get()); + } + } + } + return results; +} + +const std::string& Block::GetKVOrDie(absl::string_view target_key) const { + for (const BlockEntry& item : entries) { + if (const KVEntry* kv_entry = absl::get_if(&item); + kv_entry->key == target_key) { + return kv_entry->value; + } + } + XLS_LOG(FATAL) << "Target key is not present in " << kind + << " block: " << target_key; +} + +int64 Block::CountEntries(absl::string_view target) const { + int64 count = 0; + for (const BlockEntry& entry : entries) { + if (const KVEntry* kv = absl::get_if(&entry)) { + count += kv->key == target; + } else { + const Block* block = absl::get>(entry).get(); + count += block->kind == target; + } + } + return count; +} + +xabsl::StatusOr Parser::TryDropToken(TokenKind target, Pos* pos) { + XLS_ASSIGN_OR_RETURN(const Token* peek, scanner_->Peek()); + if (peek->kind() == target) { + if (pos != nullptr) { + *pos = peek->pos(); + } + XLS_CHECK(scanner_->Pop().ok()); + return true; + } + return false; +} +absl::Status Parser::DropTokenOrError(TokenKind kind) { + XLS_ASSIGN_OR_RETURN(bool dropped, TryDropToken(kind)); + if (!dropped) { + return absl::InvalidArgumentError( + "Could not pop token with kind: " + TokenKindToString(kind) + " @ " + + scanner_->GetPos().ToHumanString()); + } + return absl::OkStatus(); +} +absl::Status Parser::DropIdentifierOrError(absl::string_view target) { + XLS_ASSIGN_OR_RETURN(std::string identifier, PopIdentifierOrError()); + if (identifier != target) { + return absl::InvalidArgumentError(absl::StrFormat( + "Expected identifier '%s'; got '%s'", target, identifier)); + } + return absl::OkStatus(); +} + +xabsl::StatusOr Parser::PopIdentifierOrError() { + XLS_ASSIGN_OR_RETURN(Token t, scanner_->Pop()); + if (t.kind() != TokenKind::kIdentifier) { + return absl::InvalidArgumentError( + absl::StrFormat("Expected an identifier; got %s @ %s", + TokenKindToString(t.kind()), t.pos().ToHumanString())); + } + return t.PopPayload(); +} + +xabsl::StatusOr Parser::PopValueOrError(Pos* last_pos) { + XLS_ASSIGN_OR_RETURN(Token t, scanner_->Pop()); + if (last_pos != nullptr) { + *last_pos = t.pos(); + } + switch (t.kind()) { + case TokenKind::kNumber: + case TokenKind::kQuotedString: + case TokenKind::kIdentifier: + return std::string(t.payload()); + default: + return absl::InvalidArgumentError(absl::StrFormat( + "Expected a value; got %s @ %s", TokenKindToString(t.kind()), + t.pos().ToHumanString())); + } +} + +xabsl::StatusOr> Parser::ParseEntries() { + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kOpenCurl)); + std::vector result; + while (true) { + XLS_ASSIGN_OR_RETURN(bool dropped_curl, + TryDropToken(TokenKind::kCloseCurl)); + if (dropped_curl) { + break; + } + XLS_ASSIGN_OR_RETURN(std::string identifier, PopIdentifierOrError()); + XLS_ASSIGN_OR_RETURN(bool dropped_colon, TryDropToken(TokenKind::kColon)); + if (dropped_colon) { + Pos last_pos; + XLS_ASSIGN_OR_RETURN(std::string value, PopValueOrError(&last_pos)); + result.push_back(KVEntry{identifier, value}); + XLS_ASSIGN_OR_RETURN(bool dropped_semi, TryDropToken(TokenKind::kSemi)); + if (!dropped_semi) { + if (scanner_->GetPos().lineno == last_pos.lineno) { + return absl::InvalidArgumentError( + "Expected semicolon or newline after entry @ " + + last_pos.ToHumanString()); + } + } + } else { + XLS_ASSIGN_OR_RETURN(std::unique_ptr block, + ParseBlock(identifier)); + result.push_back(std::move(block)); + } + } + return result; +} + +xabsl::StatusOr> Parser::ParseValues( + Pos* end_pos) { + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kOpenParen)); + absl::InlinedVector result; + while (true) { + Pos pos; + XLS_ASSIGN_OR_RETURN(bool dropped_close_paren, + TryDropToken(TokenKind::kCloseParen, &pos)); + if (dropped_close_paren) { + if (end_pos != nullptr) { + *end_pos = pos; + } + break; + } else if (!result.empty()) { + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kComma)); + } + XLS_ASSIGN_OR_RETURN(std::string value, PopValueOrError()); + result.push_back(value); + } + return result; +} + +xabsl::StatusOr> Parser::ParseBlock( + std::string identifier) { + auto block = absl::make_unique(); + block->kind = std::move(identifier); + + // Once we've seen the block kind we know whether it's whitelisted or not. + bool kind_whitelisted = + !kind_whitelist_.has_value() || kind_whitelist_->contains(block->kind); + + Pos last_pos; + XLS_ASSIGN_OR_RETURN(block->args, ParseValues(&last_pos)); + XLS_ASSIGN_OR_RETURN(bool dropped_semi, TryDropToken(TokenKind::kSemi)); + if (dropped_semi) { + return block; + } + + // We do this to hack around missing semicolons... + // + // Normally an identifier following the parens would be a syntax error, but + // we allow it to terminate the block because it happens at least once we've + // seen. + XLS_ASSIGN_OR_RETURN(const Token* peek_next, scanner_->Peek()); + if (peek_next->kind() == TokenKind::kIdentifier) { + if (peek_next->pos().lineno > last_pos.lineno) { + return block; + } + } + + XLS_ASSIGN_OR_RETURN(block->entries, ParseEntries()); + if (!kind_whitelisted) { + // Save memory on non-whitelisted blocks by clearing out its entries. + block->entries.clear(); + } + return block; +} + +} // namespace cell_lib +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/lib_parser.h b/xls/netlist/lib_parser.h new file mode 100644 index 0000000000..4b5f308676 --- /dev/null +++ b/xls/netlist/lib_parser.h @@ -0,0 +1,412 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Infrastructure for parsing ".lib" files (cell libraries). +// +// Note that these files can be quite large (on the order of gigabytes) so we +// performance optimize this a bit. + +#ifndef THIRD_PARTY_XLS_NETLIST_LIB_PARSER_H_ +#define THIRD_PARTY_XLS_NETLIST_LIB_PARSER_H_ + +#include +#include + +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" + +namespace xls { +namespace netlist { +namespace cell_lib { + +// Represents a position in the cell library text file. +struct Pos { + int64 lineno; + int64 colno; + + std::string ToHumanString() const { + return absl::StrFormat("%d:%d", lineno + 1, colno + 1); + } +}; + +// Wraps a file as a character stream with a 1- or 2-character lookahead +// interface. +class CharStream { + public: + static xabsl::StatusOr FromPath(absl::string_view path); + static xabsl::StatusOr FromText(std::string text); + + ~CharStream() { + if (if_.has_value()) { + if_->close(); + } + } + + CharStream(CharStream&& other) = default; + + Pos GetPos() const { return pos_; } + bool AtEof() const { + if (if_.has_value()) { + return if_->eof(); + } + return cursor_ >= text_.size(); + } + char PeekCharOrDie() { + if (if_.has_value()) { + XLS_DCHECK(!if_->eof()); + return if_->peek(); + } + XLS_DCHECK_LT(cursor_, text_.size()); + return text_[cursor_]; + } + char PopCharOrDie() { + char c = PeekCharOrDie(); + BumpPos(c); + return c; + } + void DropCharOrDie() { (void)PopCharOrDie(); } + + // Attempts to drop character "c" from the character stream and returns true + // if it can. + bool TryDropChar(char c) { + if (!AtEof() && PeekCharOrDie() == c) { + DropCharOrDie(); + return true; + } + return false; + } + + // Attempts to pop c0 followed by c1 in an atomic fashion. + bool TryDropChars(char c0, char c1) { + if (AtEof()) { + return false; + } + if (TryDropChar(c0)) { + if (AtEof() || PeekCharOrDie() != c1) { + Unget(c0); + return false; + } + DropCharOrDie(); + return true; + } + return false; + } + + private: + CharStream(std::ifstream file_stream) : if_(std::move(file_stream)) {} + CharStream(std::string text) : text_(std::move(text)) {} + + void Unget(char c) { + cursor_--; + if (c == '\n') { + pos_.lineno--; + pos_.colno = last_colno_; + } else { + pos_.colno--; + } + if (if_.has_value()) { + if_->unget(); + } + } + + void BumpPos(char c) { + cursor_++; + last_colno_ = pos_.colno; + if (c == '\n') { + pos_.lineno++; + pos_.colno = 0; + } else { + pos_.colno++; + } + } + + Pos pos_ = {0, 0}; + + // We have both ifstream mode and text mode data, we store both inline. + + // ifstream mode + absl::optional if_; + + // text mode + std::string text_; + int64 cursor_ = 0; + int64 last_colno_ = 0; +}; + +enum class TokenKind { + kIdentifier, + kOpenParen, + kCloseParen, + kOpenCurl, + kCloseCurl, + kSemi, + kColon, + kQuotedString, + kNumber, + kComma, +}; + +std::string TokenKindToString(TokenKind kind); + +// Represents a token in the file's token stream. +class Token { + public: + static Token Identifier(Pos pos, std::string s) { + return Token(TokenKind::kIdentifier, pos, s); + } + static Token QuotedString(Pos pos, std::string s) { + return Token(TokenKind::kQuotedString, pos, s); + } + static Token Number(Pos pos, std::string s) { + return Token(TokenKind::kNumber, pos, s); + } + static Token Simple(Pos pos, TokenKind kind) { return Token(kind, pos); } + + Token(TokenKind kind, Pos pos, + absl::optional payload = absl::nullopt) + : kind_(kind), pos_(pos), payload_(payload) {} + + std::string ToString() const { + return absl::StrFormat("Token(%s, %s, \"%s\")", pos_.ToHumanString(), + TokenKindToString(kind_), + payload_.has_value() ? payload_.value() : "null"); + } + + TokenKind kind() const { return kind_; } + const Pos& pos() const { return pos_; } + absl::string_view payload() const { return payload_.value(); } + std::string PopPayload() { return std::move(payload_.value()); } + + private: + TokenKind kind_; + Pos pos_; + absl::optional payload_; +}; + +inline std::ostream& operator<<(std::ostream& os, const Token& token) { + os << token.ToString(); + return os; +} + +// Converts a stream of characters to a stream of tokens. +class Scanner { + public: + explicit Scanner(CharStream* cs) : cs_(cs) { DropWhitespaceAndComments(); } + + // Pops a token off of the token stream. + xabsl::StatusOr Pop() { + XLS_RETURN_IF_ERROR(Peek().status()); + Token result = std::move(lookahead_.value()); + lookahead_ = absl::nullopt; + return result; + } + + xabsl::StatusOr Peek() { + if (lookahead_.has_value()) { + return &lookahead_.value(); + } + XLS_RETURN_IF_ERROR(PeekInternal()); + return Peek(); + } + + bool AtEof() const { return !lookahead_.has_value() && cs_->AtEof(); } + + Pos GetPos() { + if (lookahead_.has_value()) { + return lookahead_.value().pos(); + } + return cs_->GetPos(); + } + + private: + static bool IsIdentifierStart(char c) { return std::isalpha(c); } + static bool IsIdentifierRest(char c) { + return std::isalpha(c) || std::isdigit(c) || c == '_'; + } + static bool IsWhitespace(char c) { return c == ' ' || c == '\n'; } + static bool IsNumberRest(char c) { + return std::isdigit(c) || c == '.' || c == 'e' || c == '-'; + } + + // Scans an identifier token. + xabsl::StatusOr ScanIdentifier(); + + // Scans a number token. + xabsl::StatusOr ScanNumber(); + + // Scans a quoted string token. Character stream cursor should be over the + // starting quote character. + xabsl::StatusOr ScanQuotedString(); + + // Drops whitespace, comments, and line continuation characters. + void DropWhitespaceAndComments() { + restart: + while (!cs_->AtEof() && IsWhitespace(cs_->PeekCharOrDie())) { + cs_->DropCharOrDie(); + } + if (cs_->TryDropChars('/', '*')) { + while (!cs_->AtEof() && !cs_->TryDropChars('*', '/')) { + cs_->DropCharOrDie(); + } + goto restart; + } + if (cs_->TryDropChars('\\', '\n')) { + goto restart; + } + } + + // Peeks at a token and populates it as lookahead_. + absl::Status PeekInternal(); + + absl::optional lookahead_; + CharStream* cs_; +}; + +// Grammar looks like: +// +// top ::= "library" "(" identifier ")" "{" +// entry * +// "}" +// +// entry ::= +// | key_value +// | block +// +// key_value ::= identifier ":" value [";"] +// +// block ::= identifier "(" params ")" ";" +// | identifier "(" params ")" "{" +// entry * +// "}" +// +// value ::= string | number | identifier +// params ::= value ["," value]* +// +// Note that the semicolons appears to be optional empirically, so we support +// newlines implicitly delimiting the end of a key/value entry. +// +// This was determined empirically but see also the liberty reference manual: +// https://people.eecs.berkeley.edu/~alanmi/publications/other/liberty07_03.pdf + +// A key/value entry that can be contained inside of a block. +struct KVEntry { + std::string key; + std::string value; +}; + +struct Block; + +// Inside of a block there are either key/value items or sub-blocks. +using BlockEntry = absl::variant>; + +// Represents a hierarchical entity in the cell library description, as shown in +// the grammar above. +struct Block { + // The "kind" of this block that is given as a leading prefix; e.g. "library", + // "cell", "pin". + std::string kind; + + // Values in the parenthesized set; e.g. {"o"} in `pin (o) { ... }`. + absl::InlinedVector args; + + // Data contained within the block; KV pairs and sub-blocks. + std::vector entries; + + // Retrieves sub-blocks contained within this block. + // + // If target_kind is provided it is used as a filter (the only blocks returned + // have subblock->kind == target_kind). + std::vector GetSubBlocks( + absl::optional target_kind = absl::nullopt) const; + + // Retrieves the first key/value pair in this block that corresponds to + // target_key. + const std::string& GetKVOrDie(absl::string_view target_key) const; + + // Counts the number of entries with either the key of "target" for a + // key/value entry or a kind of "target" for a block entry. + int64 CountEntries(absl::string_view target) const; + + // Helper used for converting entries contained within the block into strings. + static std::string EntryToString(const BlockEntry& entry); + + // Converts this block to an AST-like string representation. + std::string ToString() const; +}; + +class Parser { + public: + // See comment on kind_whitelist_ member below for details. + explicit Parser(Scanner* scanner, + absl::optional> + kind_whitelist = absl::nullopt) + : scanner_(scanner), kind_whitelist_(std::move(kind_whitelist)) {} + + xabsl::StatusOr> ParseLibrary() { + XLS_RETURN_IF_ERROR(DropIdentifierOrError("library")); + return ParseBlock("library"); + } + + private: + xabsl::StatusOr TryDropToken(TokenKind target, Pos* pos = nullptr); + absl::Status DropTokenOrError(TokenKind kind); + absl::Status DropIdentifierOrError(absl::string_view target); + + // Pops an identifier token and returns its payload, or errors. + xabsl::StatusOr PopIdentifierOrError(); + + // Pops a value token and returns its payload, or errors. + // + // If last_pos is provided it is populated with the position of the last value + // token. (This is useful for checking for newline termination in lieu of + // semicolons.) + xabsl::StatusOr PopValueOrError(Pos* last_pos = nullptr); + + // Parses all of the entries contained within a block -- includes key/value + // entries as well as sub-blocks. + xabsl::StatusOr> ParseEntries(); + + // Parses a comma-delimited sequence of values and returns their payloads. + xabsl::StatusOr> ParseValues( + Pos* end_pos = nullptr); + + // Parses a block per the grammar above. + // + // If the identifier is provided by the caller it is not scanned out of the + // token stream. + xabsl::StatusOr> ParseBlock(std::string identifier); + + Scanner* scanner_; + + // Optional whitelist of keys (including block kinds) that we're interested in + // keeping in the result data structure. Non-whitelist block kinds are still + // parsed properly, but then may have incomplete data (or not be present at + // all) in the resulting data structure. + // + // This is very useful for minimizing memory usage when we're interested in + // just a subset of particular fields, e.g. as part of a query. + absl::optional> kind_whitelist_; +}; + +} // namespace cell_lib +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_LIB_PARSER_H_ diff --git a/xls/netlist/lib_parser_test.cc b/xls/netlist/lib_parser_test.cc new file mode 100644 index 0000000000..0eafc59023 --- /dev/null +++ b/xls/netlist/lib_parser_test.cc @@ -0,0 +1,164 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/lib_parser.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" + +namespace xls { +namespace netlist { +namespace cell_lib { +namespace { + +TEST(LibParserTest, ScanSimple) { + std::string text = "{}()"; + XLS_ASSERT_OK_AND_ASSIGN(auto cs, CharStream::FromText(text)); + Scanner scanner(&cs); + EXPECT_EQ(scanner.Peek().value()->kind(), TokenKind::kOpenCurl); + EXPECT_EQ(scanner.Peek().value()->kind(), TokenKind::kOpenCurl); + EXPECT_EQ(scanner.Pop().value().kind(), TokenKind::kOpenCurl); + EXPECT_EQ(scanner.Peek().value()->kind(), TokenKind::kCloseCurl); + EXPECT_EQ(scanner.Pop().value().kind(), TokenKind::kCloseCurl); + + EXPECT_EQ(scanner.Pop().value().kind(), TokenKind::kOpenParen); + EXPECT_EQ(scanner.Pop().value().kind(), TokenKind::kCloseParen); + EXPECT_TRUE(scanner.AtEof()); +} + +// Helper that parses the given text as a library block and returns the +// block structure. +xabsl::StatusOr> Parse( + std::string text, + absl::optional> whitelist = + absl::nullopt) { + XLS_ASSIGN_OR_RETURN(auto cs, CharStream::FromText(text)); + Scanner scanner(&cs); + Parser parser(&scanner, std::move(whitelist)); + return parser.ParseLibrary(); +} + +xabsl::StatusOr ParseToString(std::string text) { + XLS_ASSIGN_OR_RETURN(auto block, Parse(text)); + return block->ToString(); +} + +TEST(LibParserTest, EmptyLibrary) { + XLS_ASSERT_OK_AND_ASSIGN(std::string parsed, + ParseToString("library (foo) {}")); + EXPECT_EQ(parsed, "(block library (foo) ())"); +} + +TEST(LibParserTest, EmptyLibraryWithComment) { + XLS_ASSERT_OK_AND_ASSIGN( + std::string parsed, + ParseToString("library (foo) {/* this is a comment */}")); + EXPECT_EQ(parsed, "(block library (foo) ())"); +} + +TEST(LibParserTest, And2CellWithPins) { + std::string text = R"( +library (foo) { + cell (AND2) { + pin (a); + pin (b); + pin (o) { + function: "a&b"; + } + } +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::string parsed, ParseToString(text)); + EXPECT_EQ(parsed, + "(block library (foo) ((block cell (AND2) ((block pin (a) ()) " + "(block pin (b) ()) (block pin (o) ((function \"a&b\")))))))"); + EXPECT_EQ(1, Parse(text).value()->CountEntries("cell")); +} + +TEST(LibParserTest, KeyValues) { + std::string text = R"( +library (foo) { + some_number: 1.234; + tiny_pi: 3.1415926535e-10; + big_pi: 3.14e10; +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::string parsed, ParseToString(text)); + EXPECT_EQ(parsed, + "(block library (foo) ((some_number \"1.234\") (tiny_pi " + "\"3.1415926535e-10\") (big_pi \"3.14e10\")))"); +} + +TEST(LibParserTest, KeyValuesNoSemis) { + std::string text = R"( +library (foo) { + some_number: 1.234 + tiny_pi: 3.1415926535e-10 + big_pi: 3.14e10 +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::string parsed, ParseToString(text)); + EXPECT_EQ(parsed, + "(block library (foo) ((some_number \"1.234\") (tiny_pi " + "\"3.1415926535e-10\") (big_pi \"3.14e10\")))"); + EXPECT_EQ("1.234", Parse(text).value()->GetKVOrDie("some_number")); +} + +TEST(LibParserTest, SubBlockMultiValue) { + std::string text = R"( +library (foo) { + my_block (1.234, "string", some_ident) ; + my_block (); +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(std::string parsed, ParseToString(text)); + EXPECT_EQ(parsed, + "(block library (foo) ((block my_block (1.234 string some_ident) " + "()) (block my_block () ())))"); + EXPECT_EQ(2, Parse(text).value()->CountEntries("my_block")); + EXPECT_EQ(2, Parse(text).value()->GetSubBlocks("my_block").size()); +} + +TEST(LibParserTest, WhitelistKind) { + std::string text = R"( +library (foo) { + foo () { + foo_key: foo_value; + } + bar () { + bar_key: bar_value; + } + baz () { + baz_key: baz_value; + } +} +)"; + XLS_ASSERT_OK_AND_ASSIGN( + std::unique_ptr library, + Parse(text, absl::flat_hash_set{"library", "bar"})); + EXPECT_EQ(library->ToString(), + "(block library (foo) (" + "(block foo () ()) " + "(block bar () ((bar_key \"bar_value\"))) " + "(block baz () ())" + "))"); +} + +} // namespace +} // namespace cell_lib +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/logical_effort.cc b/xls/netlist/logical_effort.cc new file mode 100644 index 0000000000..0a3b583df4 --- /dev/null +++ b/xls/netlist/logical_effort.cc @@ -0,0 +1,181 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/logical_effort.h" + +#include "absl/strings/str_format.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" + +namespace xls { +namespace netlist { +namespace logical_effort { + +xabsl::StatusOr GetLogicalEffort(CellKind kind, int64 input_count) { + // Table 1.1 in the Logical Effort book. + switch (kind) { + case CellKind::kInverter: + XLS_RET_CHECK_EQ(input_count, 1); + return 1; + case CellKind::kNand: + XLS_RET_CHECK_GE(input_count, 2); + return (input_count + 2.0) / 3.0; + case CellKind::kNor: + XLS_RET_CHECK_GE(input_count, 2); + return (2.0 * input_count + 1.0) / 3.0; + case CellKind::kMultiplexer: + XLS_RET_CHECK_GE(input_count, 2); + return 2; + case CellKind::kXor: + XLS_RET_CHECK_GE(input_count, 2); + // Section 4.5.4 provides the formula for XOR: N * 2**(N-1) + // TODO(meheff): add pass which splits many operand XORs up to avoid the + // exponential delay. + return input_count * std::pow(2.0, input_count - 1.0); + default: + return absl::UnimplementedError( + absl::StrFormat("Unhandled cell kind for logical effort: %s", + CellKindToString(kind))); + } +} + +xabsl::StatusOr GetParasiticDelay(CellKind kind, int64 input_count) { + // Table 1.2 in the Logical Effort book. + switch (kind) { + case CellKind::kInverter: + XLS_RET_CHECK_EQ(input_count, 1); + return 1.0; + case CellKind::kNand: + XLS_RET_CHECK_GE(input_count, 2); + return input_count; + case CellKind::kNor: + XLS_RET_CHECK_GE(input_count, 2); + return input_count; + case CellKind::kMultiplexer: + XLS_RET_CHECK_GE(input_count, 2); + return 2.0 * input_count; + case CellKind::kXor: + // case CellKind::kXnor: + XLS_RET_CHECK_GE(input_count, 2); + return 4.0; + default: + return absl::UnimplementedError( + absl::StrFormat("Unhandled cell kind for parasitic delay: %s", + CellKindToString(kind))); + } +} + +xabsl::StatusOr ComputeElectricalEffort(const rtl::Cell& cell) { + int64 output_load_same_kind = 0; + for (const auto& iter : cell.outputs()) { + rtl::NetRef output = iter.netref; + for (const rtl::Cell* connected_cell : output->connected_cells()) { + if (connected_cell == &cell) { + continue; + } + if (connected_cell->kind() == cell.kind()) { + output_load_same_kind++; + } else { + return absl::FailedPreconditionError( + "Cannot compute eletric effort for cell, driving a cell of a " + "different kind (absolute capacitance values are required)."); + } + } + } + // TODO(leary): 2019-08-16 Need to have output pin capacitances. + XLS_RET_CHECK_NE(output_load_same_kind, 0) + << "Output of cell " << cell.name() << " appears to be unconnected."; + return output_load_same_kind; +} + +xabsl::StatusOr ComputeDelay(rtl::Cell* cell) { + XLS_ASSIGN_OR_RETURN(double g, + GetLogicalEffort(cell->kind(), cell->inputs().size())); + XLS_ASSIGN_OR_RETURN(double h, ComputeElectricalEffort(*cell)); + XLS_ASSIGN_OR_RETURN(double p, + GetParasiticDelay(cell->kind(), cell->inputs().size())); + double d = g * h + p; + XLS_VLOG(4) << absl::StreamFormat("g: %f h: %f p: %f d=gh+p: %f", g, h, p, d); + return d; +} + +xabsl::StatusOr ComputePathLogicalEffort( + absl::Span path) { + if (path.empty()) { + return absl::InvalidArgumentError( + "Cannot compute logical effort of empty path."); + } + double effort = 1.0; + for (const rtl::Cell* cell : path) { + XLS_ASSIGN_OR_RETURN(double cell_effort, + GetLogicalEffort(cell->kind(), cell->inputs().size())); + effort *= cell_effort; + } + return effort; +} + +xabsl::StatusOr ComputePathParasiticDelay( + absl::Span path) { + double sum = 0.0; + for (const rtl::Cell* cell : path) { + XLS_ASSIGN_OR_RETURN( + double p, GetParasiticDelay(cell->kind(), cell->inputs().size())); + sum += p; + } + return sum; +} + +xabsl::StatusOr ComputePathBranchingEffort( + absl::Span path) { + double branching_effort = 1.0; + for (int64 i = 0; i < path.size() - 1; ++i) { + if (path[i]->outputs().size() != 1) { + return absl::UnimplementedError( + "More than one output net, cannot compute branching effort."); + } + rtl::NetRef out = path[i]->outputs().begin()->netref; + XLS_ASSIGN_OR_RETURN(std::vector driven_cells, + out->GetConnectedCellsSans(path[i])); + if (driven_cells.size() != 1) { + return absl::UnimplementedError("Compute C_total / C_useful."); + } + XLS_RET_CHECK_EQ(*driven_cells.begin(), path[i + 1]); + // Only a single path, so branching effort is multiplied by 1.0. + } + return branching_effort; +} + +xabsl::StatusOr ComputePathEffort(absl::Span path, + double input_pin_capacitance, + double output_pin_capacitance) { + XLS_ASSIGN_OR_RETURN(double G, ComputePathLogicalEffort(path)); + XLS_ASSIGN_OR_RETURN(double B, ComputePathBranchingEffort(path)); + double H = output_pin_capacitance / input_pin_capacitance; + return G * B * H; +} + +xabsl::StatusOr ComputePathLeastDelayAchievable( + absl::Span path, double input_pin_capacitance, + double output_pin_capacitance) { + double N = path.size(); + XLS_ASSIGN_OR_RETURN(double F, ComputePathEffort(path, input_pin_capacitance, + output_pin_capacitance)); + XLS_ASSIGN_OR_RETURN(double P, ComputePathParasiticDelay(path)); + return N * std::pow(F, 1.0 / N) + P; +} + +} // namespace logical_effort +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/logical_effort.h b/xls/netlist/logical_effort.h new file mode 100644 index 0000000000..1cb10d7cc0 --- /dev/null +++ b/xls/netlist/logical_effort.h @@ -0,0 +1,74 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Helpers for calculating "method of logical effort" based delay, as reflected +// in the blue Logical Effort book by Ivan Sutherland, et al. + +#ifndef THIRD_PARTY_XLS_NETLIST_LOGICAL_EFFORT_H_ +#define THIRD_PARTY_XLS_NETLIST_LOGICAL_EFFORT_H_ + +#include "xls/common/integral_types.h" +#include "xls/common/status/statusor.h" +#include "xls/netlist/netlist.h" + +namespace xls { +namespace netlist { +namespace logical_effort { + +// Also referred to as "g". +xabsl::StatusOr GetLogicalEffort(CellKind kind, int64 input_count); + +// Also referred to as "p". +// +// Returns a value in (coefficient) units of $p_{inv}$, the parasitic delay of +// an inverter. Note a typical value convenient for analysis is 1.0 (in units of +// \tao) for $p_{inv}$. +xabsl::StatusOr GetParasiticDelay(CellKind kind, int64 input_count); + +// Also referred to as "h". +xabsl::StatusOr ComputeElectricalEffort(const rtl::Cell& cell); + +// Computes $d = gh + p$. +xabsl::StatusOr ComputeDelay(rtl::Cell* cell); + +// Computes $G = \Pi g$ (product of the cell logical efforts in the path). +xabsl::StatusOr ComputePathLogicalEffort( + absl::Span path); + +// Computes $P = \Sigma p$ (sum of the cell parasitic delays in the path). +xabsl::StatusOr ComputePathParasiticDelay( + absl::Span path); + +// Computes $B = \Pi b_i$ (product of branching efforts for each stage in the +// path, where $b = \frac{C_{total}}{C_{useful}}$). +xabsl::StatusOr ComputePathBranchingEffort( + absl::Span path); + +// Computes $F = G B H$ (incorporates logical path effort, branching effort, and +// path electrical effort). +xabsl::StatusOr ComputePathEffort(absl::Span path, + double input_pin_capacitance, + double output_pin_capacitance); + +// Computes $D \hat = N F^{1/N} + P$, the minimal delay achievable along the +// path. +xabsl::StatusOr ComputePathLeastDelayAchievable( + absl::Span path, double input_pin_capacitance, + double output_pin_capacitance); + +} // namespace logical_effort +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_LOGICAL_EFFORT_H_ diff --git a/xls/netlist/logical_effort_test.cc b/xls/netlist/logical_effort_test.cc new file mode 100644 index 0000000000..fe9647ee88 --- /dev/null +++ b/xls/netlist/logical_effort_test.cc @@ -0,0 +1,136 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/logical_effort.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "xls/common/status/matchers.h" +#include "xls/netlist/fake_cell_library.h" +#include "xls/netlist/netlist_parser.h" + +namespace xls { +namespace netlist { +namespace { + +TEST(LogicalEffortTest, FO4Delay) { + std::string netlist = R"(module fo4(i, o); + input i; + wire i_n; + output [3:0] o; + + INV inv_0(.A(i), .ZN(i_n)); + // Fanned-out-to inverters. + INV inv_fo0(.A(i_n), .ZN(o[0])); + INV inv_fo1(.A(i_n), .ZN(o[1])); + INV inv_fo2(.A(i_n), .ZN(o[2])); + INV inv_fo3(.A(i_n), .ZN(o[3])); +endmodule)"; + rtl::Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + rtl::Parser::ParseModule(&cell_library, &scanner)); + XLS_ASSERT_OK_AND_ASSIGN(rtl::Cell * inv_0, m->ResolveCell("inv_0")); + XLS_ASSERT_OK_AND_ASSIGN(double delay, logical_effort::ComputeDelay(inv_0)); + // Per Logical Effort book example 1.2. + EXPECT_FLOAT_EQ(5.0, delay); +} + +// Per Logical Effort book example 1.3. +TEST(LogicalEffortTest, FourInputNorDriving10Identical) { + std::string netlist = R"(module test(i, i_aux, o); + input [3:0] i; + input i_aux; + wire fo; + output [9:0] o; + + // Nor4 that produces the fanned-out signal. + NOR4 nor_with_fo(.A(i[3]), .B(i[2]), .C(i[1]), .D(i[3]), .ZN(fo)); + + // Fanned-out-to nor4s. + NOR4 fo0(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[0])); + NOR4 fo1(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[1])); + NOR4 fo2(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[2])); + NOR4 fo3(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[3])); + NOR4 fo4(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[4])); + NOR4 fo5(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[5])); + NOR4 fo6(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[6])); + NOR4 fo7(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[7])); + NOR4 fo8(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[8])); + NOR4 fo9(.A(fo), .B(i_aux), .C(i_aux), .D(i_aux), .ZN(o[9])); +endmodule)"; + rtl::Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + rtl::Parser::ParseModule(&cell_library, &scanner)); + XLS_ASSERT_OK_AND_ASSIGN(rtl::Cell * nor_with_fo, + m->ResolveCell("nor_with_fo")); + XLS_ASSERT_OK_AND_ASSIGN(double delay, + logical_effort::ComputeDelay(nor_with_fo)); + EXPECT_FLOAT_EQ(34.0, delay); +} + +TEST(LogicalEffortTest, PathDelay) { + std::string netlist = R"(module test(ai, i_aux, bo); + input ai; + wire i_aux, y, z; + output bo; + + NAND nand_0(.A(ai), .B(i_aux), .ZN(y)); + NAND nand_1(.A(y), .B(i_aux), .ZN(z)); + NAND nand_2(.A(z), .B(i_aux), .ZN(bo)); +endmodule)"; + rtl::Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + rtl::Parser::ParseModule(&cell_library, &scanner)); + std::vector path = { + m->ResolveCell("nand_0").value(), + m->ResolveCell("nand_1").value(), + m->ResolveCell("nand_2").value(), + }; + XLS_ASSERT_OK_AND_ASSIGN(double path_logical_effort, + logical_effort::ComputePathLogicalEffort(path)); + EXPECT_NEAR(2.37, path_logical_effort, 1e-3); + XLS_ASSERT_OK_AND_ASSIGN(double path_parasitic_delay, + logical_effort::ComputePathParasiticDelay(path)); + EXPECT_FLOAT_EQ(6.0, path_parasitic_delay); + constexpr double kInputPinCapacitance = 1.0; + constexpr double kOutputPinCapacitance = 1.0; + // Logical Effort book example 1.4. + { + XLS_ASSERT_OK_AND_ASSIGN( + double least_delay, + logical_effort::ComputePathLeastDelayAchievable( + path, kInputPinCapacitance, kOutputPinCapacitance)); + EXPECT_NEAR(10.0, least_delay, 1e-3); + } + + // Logical Effort book example 1.5. + { + constexpr double kOutputPinCapacitance = 8.0; + XLS_ASSERT_OK_AND_ASSIGN( + double least_delay, + logical_effort::ComputePathLeastDelayAchievable( + path, kInputPinCapacitance, kOutputPinCapacitance)); + EXPECT_NEAR(14.0, least_delay, 1e-3); + } +} + +} // namespace +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/netlist.cc b/xls/netlist/netlist.cc new file mode 100644 index 0000000000..b2e5b03242 --- /dev/null +++ b/xls/netlist/netlist.cc @@ -0,0 +1,200 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/netlist.h" + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "xls/common/status/status_macros.h" +#include "xls/netlist/cell_library.h" + +namespace xls { +namespace netlist { +namespace rtl { + +const CellLibraryEntry* Module::AsCellLibraryEntry() const { + if (!cell_library_entry_.has_value()) { + std::vector input_names; + input_names.reserve(inputs_.size()); + for (const auto& input : inputs_) { + input_names.push_back(input->name()); + } + std::vector output_pins; + output_pins.reserve(outputs_.size()); + for (const auto& output : outputs_) { + OutputPin output_pin; + output_pin.name = output->name(); + output_pins.push_back(output_pin); + } + cell_library_entry_.emplace(CellLibraryEntry( + CellKind::kOther, name_, input_names, output_pins, absl::nullopt)); + } + return &cell_library_entry_.value(); +} + +xabsl::StatusOr Module::AddOrResolveNumber(int64 number) { + auto status_or_ref = ResolveNumber(number); + if (status_or_ref.ok()) { + return status_or_ref.value(); + } + + std::string wire_name = absl::StrFormat("", number); + XLS_RETURN_IF_ERROR(AddNetDecl(NetDeclKind::kWire, wire_name)); + return ResolveNet(wire_name); +} + +xabsl::StatusOr Module::ResolveNumber(int64 number) const { + std::string wire_name = absl::StrFormat("", number); + return ResolveNet(wire_name); +} + +xabsl::StatusOr Module::ResolveNet(absl::string_view name) const { + for (const auto& net : nets_) { + if (net->name() == name) { + return net.get(); + } + } + + return absl::NotFoundError(absl::StrCat("Could not find net: ", name)); +} + +xabsl::StatusOr Module::ResolveCell(absl::string_view name) { + for (const auto& cell : cells_) { + if (cell->name() == name) { + return cell.get(); + } + } + return absl::NotFoundError( + absl::StrCat("Could not find cell with name: ", name)); +} + +xabsl::StatusOr Module::AddCell(Cell cell) { + auto status_or_cell = ResolveCell(cell.name()); + if (status_or_cell.status().ok()) { + return absl::InvalidArgumentError( + absl::StrCat("Module already has a cell with name: ", cell.name())); + } + + cells_.push_back(absl::make_unique(cell)); + return cells_.back().get(); +} + +absl::Status Module::AddNetDecl(NetDeclKind kind, absl::string_view name) { + auto status_or_net = ResolveNet(name); + if (status_or_net.status().ok()) { + return absl::InvalidArgumentError( + absl::StrCat("Module already has a net/wire decl with name: ", name)); + } + + nets_.emplace_back(absl::make_unique(name)); + NetRef ref = nets_.back().get(); + switch (kind) { + case NetDeclKind::kInput: + inputs_.push_back(ref); + break; + case NetDeclKind::kOutput: + outputs_.push_back(ref); + break; + case NetDeclKind::kWire: + wires_.push_back(ref); + break; + } + return absl::OkStatus(); +} + +xabsl::StatusOr> NetDef::GetConnectedCellsSans( + Cell* to_remove) const { + std::vector new_cells; + new_cells.reserve(connected_cells_.size() - 1); + bool found = false; + for (int i = 0; i < connected_cells_.size(); i++) { + if (connected_cells_[i] == to_remove) { + found = true; + } else { + new_cells.push_back(connected_cells_[i]); + } + } + + if (!found) { + return absl::NotFoundError("Could not find cell in connected cell set: " + + to_remove->name()); + } + return new_cells; +} + +/* static */ xabsl::StatusOr Cell::Create( + const CellLibraryEntry* cell_library_entry, absl::string_view name, + const absl::flat_hash_map& named_parameter_assignments, + absl::optional clock) { + auto sorted_key_str = [named_parameter_assignments]() -> std::string { + std::vector keys; + for (const auto& item : named_parameter_assignments) { + keys.push_back(item.first); + } + std::sort(keys.begin(), keys.end()); + return "[" + absl::StrJoin(keys, ", ") + "]"; + }; + + std::vector cell_inputs; + for (const std::string& input : cell_library_entry->input_names()) { + auto it = named_parameter_assignments.find(input); + if (it == named_parameter_assignments.end()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Missing named input parameter in instantiation: %s; got: %s", input, + sorted_key_str())); + } + Input cell_input; + cell_input.pin_name = input; + cell_input.netref = it->second; + cell_inputs.push_back(cell_input); + } + + std::vector cell_outputs; + for (const OutputPin& output : cell_library_entry->output_pins()) { + auto it = named_parameter_assignments.find(output.name); + if (it != named_parameter_assignments.end()) { + Output cell_output; + cell_output.pin = output; + cell_output.netref = it->second; + cell_outputs.push_back(cell_output); + } + } + + if (cell_library_entry->clock_name().has_value() && !clock.has_value()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Missing clock parameter %s in instantiation; got inputs: %s.", + cell_library_entry->clock_name().value(), sorted_key_str())); + } + + return Cell(cell_library_entry, name, std::move(cell_inputs), + std::move(cell_outputs), clock); +} + +void Netlist::AddModule(std::unique_ptr module) { + modules_.emplace_back(std::move(module)); +} + +const Module* Netlist::GetModule(const std::string& module_name) const { + for (const auto& module : modules_) { + if (module->name() == module_name) { + return module.get(); + } + } + return nullptr; +} + +} // namespace rtl +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/netlist.h b/xls/netlist/netlist.h new file mode 100644 index 0000000000..c7c74f1db6 --- /dev/null +++ b/xls/netlist/netlist.h @@ -0,0 +1,196 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Data structure that represents netlists (e.g. ones that have been parsed in +// from the synthesis flow). + +#ifndef THIRD_PARTY_XLS_NETLIST_NETLIST_H_ +#define THIRD_PARTY_XLS_NETLIST_NETLIST_H_ + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "xls/common/integral_types.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/statusor.h" +#include "xls/netlist/cell_library.h" + +namespace xls { +namespace netlist { +namespace rtl { + +// Forward declaration for use in NetRef. +class NetDef; + +// Refers to an ID inside the module's wires_ array. +using NetRef = NetDef*; + +// Represents a cell instantiated in the netlist. +class Cell { + public: + // Simple utility struct to capture data for a Cell input. + struct Input { + // Name of the input pin in the cell's function description. + std::string pin_name; + + // The associated net from the netlist. + NetRef netref; + }; + + // Simple utility struct to capture data for a Cell output. + struct Output { + // The description of the pin from the Cell Library - the pin's name and + // calculated function. + OutputPin pin; + + // The associated net from the netlist. + NetRef netref; + }; + + // In this class, both "inputs" and "outputs" are maps of cell input/output + // pin name to the NetDef/Ref used as that input in a given instance. + // For outputs, if a pin isn't used, then it won't be present in the provided + // map. + static xabsl::StatusOr Create( + const CellLibraryEntry* cell_library_entry, absl::string_view name, + const absl::flat_hash_map& + named_parameter_assignments, + absl::optional clock); + + const CellLibraryEntry* cell_library_entry() const { + return cell_library_entry_; + } + const std::string& name() const { return name_; } + CellKind kind() const { return cell_library_entry_->kind(); } + + absl::Span inputs() const { return inputs_; } + absl::Span outputs() const { return outputs_; } + const absl::optional& clock() const { return clock_; } + + private: + Cell(const CellLibraryEntry* cell_library_entry, absl::string_view name, + const std::vector& inputs, const std::vector& outputs, + absl::optional clock) + : cell_library_entry_(cell_library_entry), + name_(name), + inputs_(std::move(inputs)), + outputs_(std::move(outputs)), + clock_(clock) {} + + const CellLibraryEntry* cell_library_entry_; + std::string name_; // Instance name. + std::vector inputs_; + std::vector outputs_; + absl::optional clock_; +}; + +// Definition of a net. Note this may be augmented with a def/use chain in the +// future. +class NetDef { + public: + explicit NetDef(absl::string_view name) : name_(name) {} + + const std::string& name() const { return name_; } + + // Called to note that a cell is connected to this net. + void NoteConnectedCell(Cell* cell) { connected_cells_.push_back(cell); } + + absl::Span connected_cells() const { return connected_cells_; } + + // Helper for getting the connected cells without one that is known to be + // connected (e.g. a driver). Note: could be optimized to give a smart + // view/iterator object that filters out to_remove without instantiating + // storage. + xabsl::StatusOr> GetConnectedCellsSans( + Cell* to_remove) const; + + private: + std::string name_; + std::vector connected_cells_; +}; + +// Kinds of wire declarations that can be made in the netlist module. +enum class NetDeclKind { + kInput, + kOutput, + kWire, +}; + +// Represents the module containing the netlist info. +class Module { + public: + explicit Module(absl::string_view name) : name_(name) { + // Zero and one values are present in netlists as cell inputs (which we + // interpret as wires), but aren't explicitly declared, so we create them as + // wires here. + zero_ = AddOrResolveNumber(0).value(); + one_ = AddOrResolveNumber(1).value(); + } + + const std::string& name() const { return name_; } + + // Returns a representation of this module as a CellLibraryEntry. + const CellLibraryEntry* AsCellLibraryEntry() const; + + xabsl::StatusOr AddCell(Cell cell); + + absl::Status AddNetDecl(NetDeclKind kind, absl::string_view name); + + // Returns a NetRef to the given number, creating a NetDef if necessary. + xabsl::StatusOr AddOrResolveNumber(int64 number); + + xabsl::StatusOr ResolveNumber(int64 number) const; + + xabsl::StatusOr ResolveNet(absl::string_view name) const; + + xabsl::StatusOr ResolveCell(absl::string_view name); + + absl::Span> nets() const { return nets_; } + absl::Span> cells() const { return cells_; } + + const std::vector& inputs() const { return inputs_; } + const std::vector& outputs() const { return outputs_; } + + private: + std::string name_; + std::vector inputs_; + std::vector outputs_; + std::vector wires_; + std::vector> nets_; + std::vector> cells_; + NetRef zero_; + NetRef one_; + + mutable absl::optional cell_library_entry_; +}; + +// A Netlist contains all modules present in a single file. +class Netlist { + public: + void AddModule(std::unique_ptr module); + const Module* GetModule(const std::string& module_name) const; + const absl::Span> modules() { return modules_; } + + private: + std::vector> modules_; +}; + +} // namespace rtl +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_NETLIST_H_ diff --git a/xls/netlist/netlist.proto b/xls/netlist/netlist.proto new file mode 100644 index 0000000000..9b65232453 --- /dev/null +++ b/xls/netlist/netlist.proto @@ -0,0 +1,45 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package xls.netlist; + +enum CellKindProto { + INVALID = 0; + FLOP = 1; + INVERTER = 2; + BUFFER = 3; + NAND = 4; + NOR = 5; + MULTIPLEXER = 6; + XOR = 7; + OTHER = 8; +} + +message OutputPinProto { + optional string name = 1; + optional string function = 2; +} + +message CellLibraryEntryProto { + optional CellKindProto kind = 1; + optional string name = 2; + repeated string input_names = 3; + repeated OutputPinProto output_pins = 4; +} + +message CellLibraryProto { + repeated CellLibraryEntryProto entries = 1; +} diff --git a/xls/netlist/netlist_parser.cc b/xls/netlist/netlist_parser.cc new file mode 100644 index 0000000000..8d3e20a490 --- /dev/null +++ b/xls/netlist/netlist_parser.cc @@ -0,0 +1,541 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/netlist_parser.h" + +#include "absl/status/status.h" +#include "absl/strings/ascii.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_format.h" +#include "absl/types/variant.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits.h" +#include "re2/re2.h" + +namespace xls { +namespace netlist { +namespace rtl { + +std::string Pos::ToHumanString() const { + return absl::StrFormat("%d:%d", lineno + 1, colno + 1); +} + +std::string TokenKindToString(TokenKind kind) { + switch (kind) { + case TokenKind::kOpenParen: + return "open-paren"; + case TokenKind::kCloseParen: + return "close-paren"; + case TokenKind::kOpenBracket: + return "open-bracket"; + case TokenKind::kCloseBracket: + return "close-bracket"; + case TokenKind::kDot: + return "dot"; + case TokenKind::kComma: + return "comma"; + case TokenKind::kSemicolon: + return "semicolon"; + case TokenKind::kColon: + return "colon"; + case TokenKind::kName: + return "name"; + case TokenKind::kNumber: + return "number"; + } + return absl::StrCat("", static_cast(kind)); +} + +std::string Token::ToString() const { + if (kind == TokenKind::kName) { + return absl::StrFormat("Token{kName, @%s, \"%s\"}", pos.ToHumanString(), + value); + } + return absl::StrFormat("Token{%s, @%s}", TokenKindToString(kind), + pos.ToHumanString()); +} + +char Scanner::PeekCharOrDie() const { + XLS_CHECK(!AtEofInternal()); + return text_[index_]; +} + +char Scanner::PeekChar2OrDie() const { + XLS_CHECK_GT(text_.size(), index_ + 1); + return text_[index_ + 1]; +} + +char Scanner::PopCharOrDie() { + XLS_CHECK(!AtEofInternal()); + char c = text_[index_++]; + if (c == '\n') { + lineno_++; + colno_ = 0; + } else { + colno_++; + } + return c; +} + +void Scanner::DropCommentsAndWhitespace() { + auto drop_to_eol_or_eof = [this] { + while (!AtEofInternal() && PeekCharOrDie() != '\n') { + DropCharOrDie(); + } + }; + while (!AtEofInternal()) { + switch (PeekCharOrDie()) { + case '/': { + if (PeekChar2OrDie() == '/') { + DropCharOrDie(); + DropCharOrDie(); + drop_to_eol_or_eof(); + continue; + } + return; + } + case ' ': + case '\n': + case '\t': + DropCharOrDie(); + break; + default: + return; + } + } +} + +xabsl::StatusOr Scanner::Peek() { + if (lookahead_.has_value()) { + return lookahead_.value(); + } + XLS_ASSIGN_OR_RETURN(Token token, PeekInternal()); + lookahead_.emplace(token); + return lookahead_.value(); +} + +xabsl::StatusOr Scanner::Pop() { + XLS_ASSIGN_OR_RETURN(Token result, Peek()); + lookahead_.reset(); + XLS_VLOG(3) << "Popping token: " << result.ToString(); + return result; +} + +xabsl::StatusOr Scanner::ScanNumber(char startc, Pos pos) { + std::string chars; + chars.push_back(startc); + bool seen_separator = false; + auto is_hex_char = [](char c) { + return absl::ascii_isxdigit(absl::ascii_toupper(c)); + }; + + // This isn't quite right - if there's an apostrophe (i.e., if this is a sized + // number), then the size (the first component) must be decimal-only. + // It's probably fine to ignore that restriction, though. + // + // This also can't handle reals (no decimal or sign support)...but we don't + // expect them to show up in netlists. + while (!AtEofInternal()) { + char c = PeekCharOrDie(); + if (is_hex_char(c)) { + chars.push_back(PopCharOrDie()); + } else if (c == '\'' && !seen_separator) { + // If we see a base separator, pop it, then the optional signedness + // indicator (s|S), then the base indicator (d|b|o|h|D|B|O|H). + chars.push_back(PopCharOrDie()); + XLS_RET_CHECK(!AtEofInternal()) << "Saw EOF while scanning number base!"; + chars.push_back(PopCharOrDie()); + if (chars.back() == 's' || chars.back() == 'S') { + XLS_RET_CHECK(!AtEofInternal()) + << "Saw EOF while scanning number base (post-signedness)!"; + chars.push_back(PopCharOrDie()); + } + + c = chars.back(); + XLS_RET_CHECK(c == 'd' || c == 'b' || c == 'o' || c == 'h' || c == 'D' || + c == 'B' || c == 'O' || c == 'H') + << "Expected [dbohDBOH], saw '" << c << "'"; + + seen_separator = true; + } else { + break; + } + } + + return Token{TokenKind::kNumber, pos, chars}; +} + +xabsl::StatusOr Scanner::ScanName(char startc, Pos pos, + bool is_escaped) { + std::string chars; + chars.push_back(startc); + while (!AtEofInternal()) { + char c = PeekCharOrDie(); + bool is_whitespace = c == ' ' || c == '\t' || c == '\n'; + if ((is_escaped && !is_whitespace) || isalpha(c) || isdigit(c) || + c == '_') { + chars.push_back(PopCharOrDie()); + } else { + break; + } + } + return Token{TokenKind::kName, pos, chars}; +} + +xabsl::StatusOr Scanner::PeekInternal() { + DropCommentsAndWhitespace(); + if (index_ >= text_.size()) { + return absl::FailedPreconditionError("Scan has reached EOF."); + } + auto pos = GetPos(); + char c = text_[index_++]; + switch (c) { + case '(': + return Token{TokenKind::kOpenParen, pos}; + case ')': + return Token{TokenKind::kCloseParen, pos}; + case '[': + return Token{TokenKind::kOpenBracket, pos}; + case ']': + return Token{TokenKind::kCloseBracket, pos}; + case '.': + return Token{TokenKind::kDot, pos}; + case ',': + return Token{TokenKind::kComma, pos}; + case ';': + return Token{TokenKind::kSemicolon, pos}; + case ':': + return Token{TokenKind::kColon, pos}; + default: + if (isdigit(c)) { + return ScanNumber(c, pos); + } + if (isalpha(c) || c == '\\') { + return ScanName(c, pos, c == '\\'); + } + return absl::UnimplementedError(absl::StrFormat( + "Unsupported character: '%c' (%#x) @ %s", c, c, pos.ToHumanString())); + } +} + +xabsl::StatusOr Parser::PopNameOrError() { + XLS_ASSIGN_OR_RETURN(Token token, scanner_->Pop()); + if (token.kind == TokenKind::kName) { + return token.value; + } + return absl::InvalidArgumentError("Expected name token; got: " + + token.ToString()); +} + +xabsl::StatusOr Parser::PopNumberOrError() { + // We're assuming we won't see > 64b values. Fine for now, at least. + XLS_ASSIGN_OR_RETURN(Token token, scanner_->Pop()); + if (token.kind == TokenKind::kNumber) { + // Check for the big version first. + std::string width_string, signed_string, base_string, value_string; + if (RE2::FullMatch( + token.value, R"(([0-9]+)'([Ss]?)([bodhBODH])([0-9a-f]+))", + &width_string, &signed_string, &base_string, &value_string)) { + int64 width; + XLS_RET_CHECK(absl::numbers_internal::safe_strto64_base( + width_string, reinterpret_cast(&width), 10)) + << "Unable to parse number width: " << width_string; + int base; + if (base_string == "b" || base_string == "B") { + base = 2; + } else if (base_string == "o" || base_string == "O") { + base = 8; + } else if (base_string == "d" || base_string == "D") { + base = 10; + } else if (base_string == "h" || base_string == "H") { + base = 16; + } else { + return absl::InvalidArgumentError( + absl::StrCat("Invalid numeric base: ", base_string)); + } + + uint64_t temp; + XLS_RET_CHECK( + absl::numbers_internal::safe_strtou64_base(value_string, &temp, base)) + << "Unable to parse number value: " << value_string; + if (signed_string.empty()) { + return static_cast(temp); + } + + // If the number is actually signed, then throw it into a Bits for sign + // conversion. + XLS_ASSIGN_OR_RETURN(Bits bits, UBitsWithStatus(temp, width)); + return bits.ToInt64(); + } + + int64 result; + if (!absl::SimpleAtoi(token.value, &result)) { + return absl::InternalError( + "Number token's value cannot be parsed as an int64: " + token.value); + } + return result; + } + return absl::InvalidArgumentError("Expected number token; got: " + + token.ToString()); +} + +xabsl::StatusOr> +Parser::PopNameOrNumberOrError() { + TokenKind kind = scanner_->Peek()->kind; + if (kind == TokenKind::kName) { + XLS_ASSIGN_OR_RETURN(Token token, scanner_->Pop()); + return token.value; + } else if (kind == TokenKind::kNumber) { + return PopNumberOrError(); + } + return absl::InvalidArgumentError(absl::StrCat( + "Expected name or number token; got: ", static_cast(kind))); +} + +absl::Status Parser::DropTokenOrError(TokenKind target) { + XLS_ASSIGN_OR_RETURN(Token token, scanner_->Pop()); + if (token.kind == target) { + return absl::OkStatus(); + } + return absl::UnimplementedError(absl::StrFormat( + "Want token %s; got %s.", TokenKindToString(target), token.ToString())); +} + +xabsl::StatusOr> Parser::PopParenNameList() { + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kOpenParen)); + std::vector results; + bool must_end = false; + while (true) { + if (TryDropToken(TokenKind::kCloseParen)) { + break; + } + if (must_end) { + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kCloseParen)); + break; + } + XLS_ASSIGN_OR_RETURN(std::string name, PopNameOrError()); + results.push_back(name); + must_end = !TryDropToken(TokenKind::kComma); + } + return results; +} + +absl::Status Parser::DropKeywordOrError(absl::string_view target) { + XLS_ASSIGN_OR_RETURN(Token token, scanner_->Pop()); + if (token.kind == TokenKind::kName && token.value == target) { + return absl::OkStatus(); + } + return absl::InvalidArgumentError( + absl::StrFormat("Want keyword '%s', got: %s", target, token.ToString())); +} + +xabsl::StatusOr Parser::ParseCellModule( + const Netlist& netlist) { + XLS_ASSIGN_OR_RETURN(std::string name, PopNameOrError()); + const Module* module = netlist.GetModule(name); + if (module != nullptr) { + return module->AsCellLibraryEntry(); + } + return cell_library_->GetEntry(name); +} + +xabsl::StatusOr Parser::ParseNetRef(Module* module) { + using TokenT = absl::variant; + XLS_ASSIGN_OR_RETURN(TokenT token, PopNameOrNumberOrError()); + if (absl::holds_alternative(token)) { + int64 value = absl::get(token); + return module->AddOrResolveNumber(value); + } + + std::string name = absl::get(token); + if (TryDropToken(TokenKind::kOpenBracket)) { + XLS_ASSIGN_OR_RETURN(int64 index, PopNumberOrError()); + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kCloseBracket)); + absl::StrAppend(&name, "[", index, "]"); + } + return module->ResolveNet(name); +} + +absl::Status Parser::ParseInstance(Module* module, const Netlist& netlist) { + XLS_ASSIGN_OR_RETURN(Token peek, scanner_->Peek()); + const Pos pos = peek.pos; + + XLS_ASSIGN_OR_RETURN(const CellLibraryEntry* cle, ParseCellModule(netlist)); + XLS_ASSIGN_OR_RETURN(std::string name, PopNameOrError()); + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kOpenParen)); + // LRM 23.3.2 Calls these "named parameter assignments". + absl::flat_hash_map named_parameter_assignments; + while (true) { + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kDot)); + XLS_ASSIGN_OR_RETURN(std::string pin_name, PopNameOrError()); + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kOpenParen)); + XLS_ASSIGN_OR_RETURN(NetRef net, ParseNetRef(module)); + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kCloseParen)); + XLS_VLOG(3) << "Adding named parameter assignment: " << pin_name; + bool is_new = named_parameter_assignments.insert({pin_name, net}).second; + if (!is_new) { + return absl::InvalidArgumentError("Duplicate port seen: " + pin_name); + } + if (!TryDropToken(TokenKind::kComma)) { + break; + } + } + absl::optional clock; + if (cle->clock_name().has_value()) { + auto it = named_parameter_assignments.find(cle->clock_name().value()); + if (it == named_parameter_assignments.end()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Cell %s named %s requires a clock connection %s but none was found.", + cle->name(), name, cle->clock_name().value())); + } + clock = it->second; + named_parameter_assignments.erase(it); + } + XLS_ASSIGN_OR_RETURN(Cell cell, + Cell::Create(cle, name, named_parameter_assignments, clock), + _ << " @ " << pos.ToHumanString()); + XLS_ASSIGN_OR_RETURN(Cell * cell_ptr, module->AddCell(std::move(cell))); + for (auto& item : named_parameter_assignments) { + item.second->NoteConnectedCell(cell_ptr); + } + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kCloseParen)); + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kSemicolon)); + return absl::OkStatus(); +} + +bool Parser::TryDropToken(TokenKind target) { + if (scanner_->AtEof()) { + return false; + } + if (scanner_->Peek().value().kind == target) { + XLS_CHECK_OK(scanner_->Pop().status()); + return true; + } + return false; +} + +bool Parser::TryDropKeyword(absl::string_view target) { + if (scanner_->AtEof()) { + return false; + } + Token peek = scanner_->Peek().value(); + if (peek.kind == TokenKind::kName && peek.value == target) { + XLS_CHECK_OK(scanner_->Pop().status()); + return true; + } + return false; +} + +absl::Status Parser::ParseNetDecl(Module* module, NetDeclKind kind) { + absl::optional> range; + if (TryDropToken(TokenKind::kOpenBracket)) { + XLS_ASSIGN_OR_RETURN(int64 high, PopNumberOrError()); + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kColon)); + XLS_ASSIGN_OR_RETURN(int64 low, PopNumberOrError()); + if (high < low) { + return absl::InvalidArgumentError( + absl::StrFormat("Expected net range to be [high:low] with low <= " + "high, got low: %d; high: %d", + low, high)); + } + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kCloseBracket)); + range = {high, low}; + } + + std::vector names; + do { + XLS_ASSIGN_OR_RETURN(std::string name, PopNameOrError()); + names.push_back(name); + } while (TryDropToken(TokenKind::kComma)); + + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kSemicolon)); + + if (names.size() > 1 && range.has_value()) { + // Note: we could support this but not sure if the netlist ever contains + // such a construct. + return absl::UnimplementedError( + "Multiple declarations for a ranged net is not yet supported."); + } + + for (const std::string& name : names) { + if (range.has_value()) { + for (int64 i = range->second; i <= range->first; ++i) { + XLS_RETURN_IF_ERROR( + module->AddNetDecl(kind, absl::StrFormat("%s[%d]", name, i))); + } + } else { + XLS_RETURN_IF_ERROR(module->AddNetDecl(kind, name)); + } + } + return absl::OkStatus(); +} + +absl::Status Parser::ParseModuleStatement(Module* module, + const Netlist& netlist) { + if (TryDropKeyword("input")) { + return ParseNetDecl(module, NetDeclKind::kInput); + } + if (TryDropKeyword("output")) { + return ParseNetDecl(module, NetDeclKind::kOutput); + } + if (TryDropKeyword("wire")) { + return ParseNetDecl(module, NetDeclKind::kWire); + } + return ParseInstance(module, netlist); +} + +xabsl::StatusOr> Parser::ParseModule( + const Netlist& netlist) { + XLS_RETURN_IF_ERROR(DropKeywordOrError("module")); + XLS_ASSIGN_OR_RETURN(std::string module_name, PopNameOrError()); + auto module = std::make_unique(module_name); + XLS_ASSIGN_OR_RETURN(std::vector ports, PopParenNameList()); + XLS_RETURN_IF_ERROR(DropTokenOrError(TokenKind::kSemicolon)); + + while (true) { + if (TryDropKeyword("endmodule")) { + break; + } + XLS_RETURN_IF_ERROR(ParseModuleStatement(module.get(), netlist)); + } + return module; +} + +/* static */ xabsl::StatusOr> Parser::ParseModule( + CellLibrary* cell_library, Scanner* scanner) { + Parser p(cell_library, scanner); + XLS_ASSIGN_OR_RETURN(std::unique_ptr module, p.ParseModule()); + if (!scanner->AtEof()) { + return absl::InvalidArgumentError("Unexpected characters at end of file."); + } + return module; +} + +xabsl::StatusOr Parser::ParseNetlist(CellLibrary* cell_library, + Scanner* scanner) { + Netlist netlist; + Parser p(cell_library, scanner); + while (!scanner->AtEof()) { + XLS_ASSIGN_OR_RETURN(std::unique_ptr module, + p.ParseModule(netlist)); + netlist.AddModule(std::move(module)); + } + return std::move(netlist); +} + +} // namespace rtl +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/netlist_parser.h b/xls/netlist/netlist_parser.h new file mode 100644 index 0000000000..ff783b886f --- /dev/null +++ b/xls/netlist/netlist_parser.h @@ -0,0 +1,179 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_NETLIST_NETLIST_PARSER_H_ +#define THIRD_PARTY_XLS_NETLIST_NETLIST_PARSER_H_ + +#include "absl/status/status.h" +#include "xls/common/status/statusor.h" +#include "xls/netlist/netlist.h" + +namespace xls { +namespace netlist { +namespace rtl { + +// Kinds of tokens the scanner emits. +enum class TokenKind { + kOpenParen, // ( + kCloseParen, // ) + kOpenBracket, // [ + kCloseBracket, // ] + kDot, + kComma, + kColon, + kSemicolon, + kName, + kNumber, +}; + +// Returns a string representation of "kind" suitable for debugging. +std::string TokenKindToString(TokenKind kind); + +// Represents a position in input text. +struct Pos { + int64 lineno; + int64 colno; + + std::string ToHumanString() const; +}; + +// Represents a scanned token (that comes from scanning a character stream). +struct Token { + TokenKind kind; + Pos pos; + std::string value; + + std::string ToString() const; +}; + +// Token scanner for netlist files. +class Scanner { + public: + explicit Scanner(absl::string_view text) : text_(text) {} + + xabsl::StatusOr Peek(); + + xabsl::StatusOr Pop(); + + bool AtEof() { + DropCommentsAndWhitespace(); + return index_ >= text_.size(); + } + + private: + xabsl::StatusOr ScanName(char startc, Pos pos, bool is_escaped); + xabsl::StatusOr ScanNumber(char startc, Pos pos); + xabsl::StatusOr PeekInternal(); + + void DropCommentsAndWhitespace(); + + char PeekCharOrDie() const; + char PeekChar2OrDie() const; + char PopCharOrDie(); + void DropCharOrDie() { (void)PopCharOrDie(); } + Pos GetPos() const { return Pos{lineno_, colno_}; } + + // Internal version of EOF checking that doesn't attempt to discard the + // comments/whitespace as the public AtEof() does above -- this simply checks + // whether the character stream index has reached the end of the text. + bool AtEofInternal() const { return index_ >= text_.size(); } + + absl::string_view text_; + int64 index_ = 0; + int64 lineno_ = 0; + int64 colno_ = 0; + absl::optional lookahead_; +}; + +class Parser { + public: + // Parses a netlist module with the given cell library and token scanner. + // Returns a status on parse error. + static xabsl::StatusOr> ParseModule( + CellLibrary* cell_library, Scanner* scanner); + + static xabsl::StatusOr ParseNetlist(CellLibrary* cell_library, + Scanner* scanner); + + private: + explicit Parser(CellLibrary* cell_library, Scanner* scanner) + : cell_library_(cell_library), scanner_(scanner) {} + + // Parses a cell instantiation (e.g. in module scope). + absl::Status ParseInstance(Module* module, const Netlist& netlist); + + // Parses a cell module name out of the token stream and returns the + // corresponding CellLibraryEntry for that module name. + xabsl::StatusOr ParseCellModule( + const Netlist& netlist); + + // Parses a wire declaration at the module scope. + absl::Status ParseNetDecl(Module* module, NetDeclKind kind); + + // Parses a module-level statement (e.g. wire decl or cell instantiation). + absl::Status ParseModuleStatement(Module* module, const Netlist& netlist); + + // Parses a module definition (e.g. at the top of the file). + xabsl::StatusOr> ParseModule( + const Netlist& netlist = {}); + + // Parses a reference to an already- declared net. + xabsl::StatusOr ParseNetRef(Module* module); + + // Pops a name token and returns its contents or gives an error status if a + // name token is not immediately present in the stream. + xabsl::StatusOr PopNameOrError(); + + // Pops a name token and returns its value or gives an error status if a + // number token is not immediately present in the stream. + xabsl::StatusOr PopNumberOrError(); + + // Pops either a name or number token or returns an error. + xabsl::StatusOr> PopNameOrNumberOrError(); + + // Drops a token of kind target from the head of the stream or gives an error + // status. + absl::Status DropTokenOrError(TokenKind target); + + // Drops a keyword token from the head of the stream or gives an error status. + absl::Status DropKeywordOrError(absl::string_view target); + + // Attempts to drop a token of the target kind, or returns false if that + // target token kind is not at the head of the token stream. + bool TryDropToken(TokenKind target); + + // Attempts to drop a keyword token with the value "target" from the head of + // the token stream, or returns false if it cannot. + bool TryDropKeyword(absl::string_view target); + + // Pops a parenthesized name list from the token stream and returns it as a + // vector of those names. + xabsl::StatusOr> PopParenNameList(); + + // Cell library definitions are resolved against. + CellLibrary* cell_library_; + + // Set of (already-parsed) Modules that may be present in the Module currently + // being processed as Cell-type references. + absl::flat_hash_map modules_; + + // Scanner used for scanning out tokens (in a stream sequence). + Scanner* scanner_; +}; + +} // namespace rtl +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_NETLIST_PARSER_H_ diff --git a/xls/netlist/netlist_parser_test.cc b/xls/netlist/netlist_parser_test.cc new file mode 100644 index 0000000000..6c18765c32 --- /dev/null +++ b/xls/netlist/netlist_parser_test.cc @@ -0,0 +1,177 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/netlist_parser.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "absl/strings/substitute.h" +#include "xls/common/status/matchers.h" +#include "xls/netlist/fake_cell_library.h" + +namespace xls { +namespace netlist { +namespace rtl { +namespace { + +TEST(NetlistParserTest, EmptyModule) { + std::string netlist = R"(module main(); endmodule)"; + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + EXPECT_EQ("main", m->name()); +} + +TEST(NetlistParserTest, EmptyModuleWithComment) { + std::string netlist = R"( +// This is a module named main. +module main(); + // This area left intentionally blank. +endmodule)"; + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + EXPECT_EQ("main", m->name()); +} + +TEST(NetlistParserTest, WireMultiDecl) { + std::string netlist = R"(module main(); + wire foo, bar, baz; +endmodule)"; + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + EXPECT_EQ("main", m->name()); + + XLS_ASSERT_OK_AND_ASSIGN(NetRef foo, m->ResolveNet("foo")); + EXPECT_EQ("foo", foo->name()); + XLS_ASSERT_OK_AND_ASSIGN(NetRef bar, m->ResolveNet("bar")); + EXPECT_EQ("bar", bar->name()); + XLS_ASSERT_OK_AND_ASSIGN(NetRef baz, m->ResolveNet("baz")); + EXPECT_EQ("baz", baz->name()); +} + +TEST(NetlistParserTest, InverterModule) { + std::string netlist = R"(module main(a, z); + input a; + output z; + INV inv_0(.A(a), .ZN(z)); +endmodule)"; + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + EXPECT_EQ("main", m->name()); + XLS_ASSERT_OK_AND_ASSIGN(NetRef a, m->ResolveNet("a")); + EXPECT_EQ("a", a->name()); + XLS_ASSERT_OK_AND_ASSIGN(NetRef z, m->ResolveNet("z")); + EXPECT_EQ("z", z->name()); + + XLS_ASSERT_OK_AND_ASSIGN(Cell * c, m->ResolveCell("inv_0")); + EXPECT_EQ(cell_library.GetEntry("INV").value(), c->cell_library_entry()); + EXPECT_EQ("inv_0", c->name()); +} + +TEST(NetlistParserTest, AOI21WithMultiBitInput) { + std::string netlist = R"(module main(i, o); + input [2:0] i; + output o; + AOI21 aoi21_0(.A(i[2]), .B(i[1]), .C(i[0]), .ZN(o)); +endmodule)"; + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + EXPECT_EQ("main", m->name()); + XLS_ASSERT_OK_AND_ASSIGN(NetRef i0, m->ResolveNet("i[0]")); + EXPECT_EQ("i[0]", i0->name()); + XLS_ASSERT_OK_AND_ASSIGN(NetRef i1, m->ResolveNet("i[1]")); + EXPECT_EQ("i[1]", i1->name()); + XLS_ASSERT_OK_AND_ASSIGN(NetRef i2, m->ResolveNet("i[2]")); + EXPECT_EQ("i[2]", i2->name()); + EXPECT_THAT(m->ResolveNet("i[3]"), + status_testing::StatusIs( + absl::StatusCode::kNotFound, + ::testing::HasSubstr("Could not find net: i[3]"))); + + XLS_ASSERT_OK_AND_ASSIGN(Cell * c, m->ResolveCell("aoi21_0")); + EXPECT_EQ(cell_library.GetEntry("AOI21").value(), c->cell_library_entry()); + EXPECT_EQ("aoi21_0", c->name()); +} + +TEST(NetlistParserTest, NumberFormats) { + std::string netlist = R"(module main(); + wire z0, z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11; + INV inv_0(.A(10), .ZN(z0)); + INV inv_1(.A(1'b1), .ZN(z1)); + INV inv_2(.A(1'o1), .ZN(z2)); + INV inv_3(.A(1'd1), .ZN(z3)); + INV inv_4(.A(1'h1), .ZN(z4)); + INV inv_5(.A(1'B1), .ZN(z5)); + INV inv_6(.A(1'O1), .ZN(z6)); + INV inv_7(.A(1'D1), .ZN(z7)); + INV inv_8(.A(1'H1), .ZN(z8)); + INV inv_9(.A(10'o777), .ZN(z9)); + INV inv_10(.A(20'd100), .ZN(z10)); + INV inv_11(.A(30'hbeef), .ZN(z11)); +endmodule)"; + + Scanner scanner(netlist); + CellLibrary cell_library = MakeFakeCellLibrary(); + + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + EXPECT_EQ("main", m->name()); +} + +TEST(NetlistParserTest, MoreNumberFormats) { + auto make_module = [](const std::string& number) { + std::string module_base = R"(module main(); +wire z0; +INV inv_0(.A($0), .ZN(z0)); +endmodule)"; + return absl::Substitute(module_base, number); + }; + + std::vector> test_cases({ + {"1'b1", 1}, + {"1'o1", 1}, + {"8'd255", 255}, + {"8'sd127", 127}, + {"8'sd255", -1}, + {"8'sd253", -3}, + }); + + // For each test case, make sure we can find a netlist for the given number + // (matching the Verilog number string) in the module. + for (const auto& test_case : test_cases) { + std::string module_text = make_module(test_case.first); + Scanner scanner(module_text); + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + Parser::ParseModule(&cell_library, &scanner)); + XLS_ASSERT_OK(m->ResolveNumber(test_case.second).status()); + } +} + +} // namespace +} // namespace rtl +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/parse_netlist_main.cc b/xls/netlist/parse_netlist_main.cc new file mode 100644 index 0000000000..f139ea4450 --- /dev/null +++ b/xls/netlist/parse_netlist_main.cc @@ -0,0 +1,85 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/flag.h" +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "xls/common/file/filesystem.h" +#include "xls/common/init_xls.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/netlist/find_logic_clouds.h" +#include "xls/netlist/netlist_parser.h" + +ABSL_FLAG(bool, show_clusters, false, "Show the logic clusters found."); + +namespace xls { +namespace { + +absl::Status RealMain(absl::string_view netlist_path, + absl::string_view cell_library_path) { + XLS_ASSIGN_OR_RETURN(std::string netlist, GetFileContents(netlist_path)); + XLS_ASSIGN_OR_RETURN( + netlist::CellLibraryProto cell_library_proto, + ParseTextProtoFile(cell_library_path)); + XLS_ASSIGN_OR_RETURN(auto cell_library, + netlist::CellLibrary::FromProto(cell_library_proto)); + netlist::rtl::Scanner scanner(netlist); + XLS_ASSIGN_OR_RETURN( + std::unique_ptr module, + netlist::rtl::Parser::ParseModule(&cell_library, &scanner)); + std::cout << "nets: " << module->nets().size() << std::endl; + std::cout << "cells: " << module->cells().size() << std::endl; + absl::flat_hash_map cell_kind_to_count; + for (const auto& name_and_cell : module->cells()) { + cell_kind_to_count[name_and_cell->kind()]++; + } + std::cout << "cell-kind breakdown:" << std::endl; + for (int64 i = static_cast(netlist::CellKind::kFlop); + i <= static_cast(netlist::CellKind::kOther); ++i) { + netlist::CellKind cell_kind = static_cast(i); + std::cout << absl::StreamFormat(" %8s: %d", + netlist::CellKindToString(cell_kind), + cell_kind_to_count[cell_kind]) + << std::endl; + } + + std::vector clusters = + netlist::rtl::FindLogicClouds(*module); + std::cout << "logic clusters: " << clusters.size() << std::endl; + if (absl::GetFlag(FLAGS_show_clusters)) { + std::cout << netlist::rtl::ClustersToString(clusters) << std::endl; + } + + return absl::OkStatus(); +} + +} // namespace +} // namespace xls + +int main(int argc, char** argv) { + std::vector positional_arguments = + xls::InitXls(argv[0], argc, argv); + + if (positional_arguments.size() < 2) { + std::cerr << "Usage: " << argv[0] << " " + << std::endl; + return EXIT_FAILURE; + } + + XLS_QCHECK_OK( + xls::RealMain(positional_arguments[0], positional_arguments[1])); + + return EXIT_SUCCESS; +} diff --git a/xls/netlist/parse_netlist_main_test.sh b/xls/netlist/parse_netlist_main_test.sh new file mode 100755 index 0000000000..50514adcf6 --- /dev/null +++ b/xls/netlist/parse_netlist_main_test.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Find input files +echo 'entries: { kind: INVERTER, name: "INV" input_names: "A" output_pins { name: "ZN" function: "F" } }' > "${TEST_TMPDIR}/fake_cell_library.pbtxt" +echo 'module main(i, o); input i; output o; INV inv_0(.A(i), .ZN(o)); endmodule' > "${TEST_TMPDIR}/netlist.v" + +BINPATH=./xls/netlist/parse_netlist_main +$BINPATH "${TEST_TMPDIR}/netlist.v" "${TEST_TMPDIR}/fake_cell_library.pbtxt" || exit -1 + +echo "PASS" diff --git a/xls/netlist/z3_translator.cc b/xls/netlist/z3_translator.cc new file mode 100644 index 0000000000..ed0b874e12 --- /dev/null +++ b/xls/netlist/z3_translator.cc @@ -0,0 +1,277 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/z3_translator.h" + +#include "absl/flags/flag.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/netlist/function_parser.h" +#include "xls/netlist/netlist.h" +#include "../z3/src/api/z3_api.h" + +// TODO(rspringer): Flags in libraries are evil! ...but sometimes necessary. +// This one is a hidden temporary flag until we can properly handle cells +// with state_function attributes (e.g., some latches). +ABSL_FLAG(std::string, high_cells, "", + "Comma-separated list of cells for which to assume \"1\" values on " + "all outputs."); + +namespace xls { +namespace netlist { + +using function::Ast; + +xabsl::StatusOr> Z3Translator::CreateAndTranslate( + Z3_context ctx, const rtl::Module* module, + const absl::flat_hash_map& module_refs, + const absl::flat_hash_map& inputs) { + auto translator = + absl::WrapUnique(new Z3Translator(ctx, module, module_refs)); + XLS_RETURN_IF_ERROR(translator->Init(inputs)); + XLS_RETURN_IF_ERROR(translator->Translate()); + return translator; +} + +xabsl::StatusOr Z3Translator::GetTranslation(rtl::NetRef ref) { + XLS_RET_CHECK(translated_.contains(ref)); + return translated_.at(ref); +} + +Z3Translator::Z3Translator( + Z3_context ctx, const rtl::Module* module, + const absl::flat_hash_map& module_refs) + : ctx_(ctx), module_(module), module_refs_(module_refs) { + for (auto cell : absl::StrSplit(absl::GetFlag(FLAGS_high_cells), ',')) { + high_cells_.insert(static_cast(cell)); + } +} + +absl::Status Z3Translator::Init( + const absl::flat_hash_map& inputs) { + // Associate each input with its NetRef and make it available for lookup. + for (const auto& pair : inputs) { + XLS_VLOG(2) << "Processing input : " << pair.first; + XLS_ASSIGN_OR_RETURN(rtl::NetRef ref, module_->ResolveNet(pair.first)); + translated_[ref] = pair.second; + } + + // We only have to syntheize "clk" and "input_valid" symbols for XLS-generated + // modules; they're automatically added in the translation between DSLX and + // the netlist. + Z3_sort sort = Z3_mk_bv_sort(ctx_, 1); + auto status_or_clk = module_->ResolveNet("clk"); + if (status_or_clk.ok()) { + translated_[status_or_clk.value()] = Z3_mk_int(ctx_, 1, sort); + } + auto status_or_input_valid = module_->ResolveNet("input_valid"); + if (status_or_input_valid.ok()) { + translated_[status_or_input_valid.value()] = Z3_mk_int(ctx_, 1, sort); + } + + translated_[module_->ResolveNumber(0).value()] = Z3_mk_int(ctx_, 0, sort); + translated_[module_->ResolveNumber(1).value()] = Z3_mk_int(ctx_, 1, sort); + + return absl::OkStatus(); +} + +// General idea: construct an AST by iterating over all the Cells in the module. +// 1. First, collect all input wires and put them on an "active" list. +// 2. Iterate through the active wire list and examine all cells they're +// connected to, removing each as examined. +// 3. For any cell for which all inputs have been seen (are "active"), +// translate that cell into Z3 space, and move its output wires +// (the resulting Z3 nodes) onto the back of the active wire list. +// 4. Repeat until the active wire list is empty. +absl::Status Z3Translator::Translate() { + // Utility structure so we don't have to iterate through a cell's inputs and + // outputs every time it's examined. + absl::flat_hash_map> cell_inputs; + for (const auto& cell : module_->cells()) { + absl::flat_hash_set inputs; + for (const auto& input : cell->inputs()) { + inputs.insert(input.netref); + } + cell_inputs[cell.get()] = std::move(inputs); + } + + // Double-buffer the active/next active wire lists. + // Remember - we pre-populated translated_ with the set of module inputs. + std::deque active_wires; + for (const auto& pair : translated_) { + active_wires.push_back(pair.first); + } + + // For every active wire, check to see if all of its inputs are satisfied. + // If so, then that cell's outputs are now active and should be considered + // newly active on the next pass. + while (!active_wires.empty()) { + rtl::NetRef ref = active_wires.front(); + active_wires.pop_front(); + XLS_VLOG(2) << "Processing wire " << ref->name(); + + // Check every connected cell to see if all of its inputs are now + // available. + for (auto& cell : ref->connected_cells()) { + // Skip if this cell if the wire is its output! + bool is_output = false; + for (const auto& output : cell->outputs()) { + if (output.netref == ref) { + is_output = true; + break; + } + } + if (is_output) { + continue; + } + + cell_inputs[cell].erase(ref); + if (cell_inputs[cell].empty()) { + XLS_VLOG(2) << "Processing cell " << cell->name(); + XLS_RETURN_IF_ERROR(TranslateCell(*cell)); + + for (const auto& output : cell->outputs()) { + active_wires.push_back(output.netref); + } + } + } + } + + // Sanity check that we've processed all cells (i.e., that there aren't + // unsatisfiable cells). + for (const auto& cell : module_->cells()) { + for (const auto& output : cell->outputs()) { + if (!translated_.contains(output.netref)) { + return absl::InvalidArgumentError(absl::StrFormat( + "Netlist contains unconnected subgraphs and cannot be translated. " + "Example: cell %s, output %s.", + cell->name(), output.netref->name())); + } + } + } + + return absl::Status(); +} + +absl::Status Z3Translator::TranslateCell(const rtl::Cell& cell) { + using function::Ast; + + // If this cell is actually a reference to a module defined in this netlist, + // then translate it into Z3-space here and grab its output nodes. + std::string entry_name = cell.cell_library_entry()->name(); + if (module_refs_.contains(entry_name)) { + absl::flat_hash_map inputs; + for (const auto& input : cell.inputs()) { + inputs[input.pin_name] = translated_[input.netref]; + } + + const rtl::Module* module_ref = module_refs_.at(entry_name); + XLS_ASSIGN_OR_RETURN(auto subtranslator, + Z3Translator::CreateAndTranslate( + ctx_, module_ref, module_refs_, inputs)); + + // Now match the module outputs to the corresponding netref in this module's + // corresponding cell. + for (const auto& module_output : module_ref->outputs()) { + XLS_ASSIGN_OR_RETURN(Z3_ast translation, + subtranslator->GetTranslation(module_output)); + for (const auto& cell_output : cell.outputs()) { + if (cell_output.pin.name == module_output->name()) { + translated_[cell_output.netref] = translation; + break; + } + } + } + return absl::OkStatus(); + } + + if (high_cells_.contains(cell.cell_library_entry()->name())) { + // Set each output for the fixed-high cells to 1. + for (const auto& output : cell.outputs()) { + translated_[output.netref] = Z3_mk_int(ctx_, 1, Z3_mk_bv_sort(ctx_, 1)); + } + } else { + for (const auto& output : cell.outputs()) { + XLS_ASSIGN_OR_RETURN(function::Ast ast, function::Parser::ParseFunction( + output.pin.function)); + XLS_ASSIGN_OR_RETURN(Z3_ast result, TranslateFunction(cell, ast)); + translated_[output.netref] = result; + } + } + + return absl::OkStatus(); +} + +// After all the above, this is the spot where any _ACTUAL_ translation happens. +xabsl::StatusOr Z3Translator::TranslateFunction(const rtl::Cell& cell, + function::Ast ast) { + switch (ast.kind()) { + case Ast::Kind::kAnd: { + XLS_ASSIGN_OR_RETURN(Z3_ast lhs, + TranslateFunction(cell, ast.children()[0])); + XLS_ASSIGN_OR_RETURN(Z3_ast rhs, + TranslateFunction(cell, ast.children()[1])); + return Z3_mk_bvand(ctx_, lhs, rhs); + } + case Ast::Kind::kIdentifier: { + rtl::NetRef ref = nullptr; + for (const auto& input : cell.inputs()) { + if (input.pin_name == ast.name()) { + ref = input.netref; + break; + } + } + if (ref == nullptr) { + return absl::NotFoundError(absl::StrFormat( + "Identifier \"%s\", was not found in cell %s's inputs.", ast.name(), + cell.name())); + } + return translated_.at(ref); + } + case Ast::Kind::kLiteralOne: { + return Z3_mk_true(ctx_); + } + case Ast::Kind::kLiteralZero: { + return Z3_mk_false(ctx_); + } + case Ast::Kind::kNot: { + XLS_ASSIGN_OR_RETURN(Z3_ast child, + TranslateFunction(cell, ast.children()[0])); + return Z3_mk_bvnot(ctx_, child); + } + case Ast::Kind::kOr: { + XLS_ASSIGN_OR_RETURN(Z3_ast lhs, + TranslateFunction(cell, ast.children()[0])); + XLS_ASSIGN_OR_RETURN(Z3_ast rhs, + TranslateFunction(cell, ast.children()[1])); + return Z3_mk_bvor(ctx_, lhs, rhs); + } + case Ast::Kind::kXor: { + XLS_ASSIGN_OR_RETURN(Z3_ast lhs, + TranslateFunction(cell, ast.children()[0])); + XLS_ASSIGN_OR_RETURN(Z3_ast rhs, + TranslateFunction(cell, ast.children()[1])); + return Z3_mk_bvxor(ctx_, lhs, rhs); + } + default: + return absl::InvalidArgumentError(absl::StrFormat( + "Unknown AST kind: %d", static_cast(ast.kind()))); + } +} + +} // namespace netlist +} // namespace xls diff --git a/xls/netlist/z3_translator.h b/xls/netlist/z3_translator.h new file mode 100644 index 0000000000..cc9d7dfeb4 --- /dev/null +++ b/xls/netlist/z3_translator.h @@ -0,0 +1,80 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_NETLIST_Z3_TRANSLATOR_H_ +#define THIRD_PARTY_XLS_NETLIST_Z3_TRANSLATOR_H_ + +#include + +#include "absl/container/flat_hash_map.h" +#include "xls/common/status/statusor.h" +#include "xls/netlist/function_parser.h" +#include "xls/netlist/netlist.h" +#include "../z3/src/api/z3.h" + +namespace xls { +namespace netlist { + +// Z3Translator converts a netlist into a Z3 AST suitable for use in Z3 proofs +// (correctness, equality, etc.). +// It does this by converting the logical ops described in a Cell's "function" +// attribute into trees Z3 operations, then combining those trees, as described +// in the Module's nets, into one comprehensive tree. +class Z3Translator { + public: + // Inputs must be provided here so that the same "values" can be used for + // other trees, e.g., in an equivalence check (we need the same input nodes to + // feed into two separate trees in that case). + // - module_refs is a collection of modules that may be present as Cell + // references in the module being processed. + // - inputs is a map of wire/net name to Z3 one-bit vectors; this requires + // "exploding" values, such as a bits[8] into 8 single-bit inputs. + static xabsl::StatusOr> CreateAndTranslate( + Z3_context ctx, const rtl::Module* module, + const absl::flat_hash_map& module_refs, + const absl::flat_hash_map& inputs); + + // Returns the Z3 equivalent for the specified net. + xabsl::StatusOr GetTranslation(rtl::NetRef ref); + + private: + Z3Translator( + Z3_context ctx, const rtl::Module* module, + const absl::flat_hash_map& module_refs); + absl::Status Init(const absl::flat_hash_map& inputs); + + // Translates the module, cell, or cell function, respectively, into Z3-space. + absl::Status Translate(); + absl::Status TranslateCell(const rtl::Cell& cell); + xabsl::StatusOr TranslateFunction(const rtl::Cell& cell, + const function::Ast ast); + + Z3_context ctx_; + const rtl::Module* module_; + + // Maps a NetDef to a Z3 entity. + absl::flat_hash_map translated_; + + const absl::flat_hash_map& module_refs_; + + // TODO(rspringer): Eliminate the need for this by properly handling cells + // with state_function attributes. + // List of cells for which all outputs are unconditionally set to 1. + absl::flat_hash_set high_cells_; +}; + +} // namespace netlist +} // namespace xls + +#endif // THIRD_PARTY_XLS_NETLIST_Z3_TRANSLATOR_H_ diff --git a/xls/netlist/z3_translator_test.cc b/xls/netlist/z3_translator_test.cc new file mode 100644 index 0000000000..d0f665fbe7 --- /dev/null +++ b/xls/netlist/z3_translator_test.cc @@ -0,0 +1,370 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/netlist/z3_translator.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_map.h" +#include "absl/random/random.h" +#include "absl/types/optional.h" +#include "xls/common/cleanup.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/netlist/fake_cell_library.h" +#include "xls/netlist/netlist.h" +#include "../z3/src/api/z3_api.h" + +namespace xls { +namespace netlist { +namespace { + +absl::flat_hash_map CreateInputs(Z3_context ctx, + int num_inputs) { + absl::flat_hash_map inputs; + Z3_sort input_sort = Z3_mk_bv_sort(ctx, /*sz=*/1); + for (int i = 0; i < num_inputs; i++) { + std::string input_name = absl::StrCat("input_", i); + inputs[input_name] = Z3_mk_const( + ctx, Z3_mk_string_symbol(ctx, input_name.c_str()), input_sort); + } + return inputs; +} + +// Creates a netlist for testing purposes. +// This function creates a fake cell library and creates a "random" netlist from +// it, using the provided Z3_ast elements as potential inputs. +absl::Status CreateNetList( + absl::BitGen* bitgen, + const absl::flat_hash_map& inputs, int num_cells, + const CellLibrary& cell_library, rtl::Module* module) { + // Maintain a set of nets that can be used as inputs for new cells. Initially, + // this is just the set of inputs, but will be expanded as cells are defined. + // We could maintain a vector of strings and resolve them, or store a vector + // of already-resolved string -> NetRefs. Turns out to be about the same in + // practice. + std::vector available_inputs; + for (const auto& input : inputs) { + XLS_RETURN_IF_ERROR( + module->AddNetDecl(rtl::NetDeclKind::kInput, input.first)); + available_inputs.push_back(input.first); + } + + std::string clk_name = "wow_what_an_amazing_clock"; + XLS_RETURN_IF_ERROR(module->AddNetDecl(rtl::NetDeclKind::kWire, clk_name)); + XLS_ASSIGN_OR_RETURN(rtl::NetRef clk, module->ResolveNet(clk_name)); + + // Names of the fake cells: + // - INV + // - DFF + // - AND + // - NAND + // - NOR4 + // - AOI21 + // I'm currently skipping DFF, since they don't have formulae in the cell + // library I've examined. I'll cross that bridge in time. + std::vector cell_names = {"INV", "AND", "NAND", "NOR4", "AOI21"}; + + for (int cell_index = 0; cell_index < num_cells; cell_index++) { + // Pick a cell at random. + std::string cell_name = + cell_names[absl::Uniform(*bitgen, 0u, cell_names.size())]; + XLS_ASSIGN_OR_RETURN(const CellLibraryEntry* entry, + cell_library.GetEntry(cell_name)); + absl::StrAppend(&cell_name, "_", cell_index); + + // Assign a random available net to each cell input. + absl::flat_hash_map param_assignments; + absl::Span input_names = entry->input_names(); + for (int input_index = 0; input_index < input_names.size(); input_index++) { + int available_index = absl::Uniform(*bitgen, 0u, available_inputs.size()); + XLS_ASSIGN_OR_RETURN( + rtl::NetRef input_ref, + module->ResolveNet(available_inputs[available_index])); + param_assignments[input_names[input_index]] = input_ref; + } + + // And associate the output with a new NetRef. + absl::Span output_pins = entry->output_pins(); + for (int output_index = 0; output_index < output_pins.size(); + output_index++) { + std::string output_net_name = absl::StrCat(cell_name, "_out"); + XLS_RETURN_IF_ERROR( + module->AddNetDecl(rtl::NetDeclKind::kOutput, output_net_name)); + + XLS_ASSIGN_OR_RETURN(rtl::NetRef output_ref, + module->ResolveNet(output_net_name)); + param_assignments[output_pins[output_index].name] = output_ref; + available_inputs.push_back(output_net_name); + } + + XLS_ASSIGN_OR_RETURN( + rtl::Cell cell, + rtl::Cell::Create(entry, cell_name, param_assignments, clk)); + XLS_ASSIGN_OR_RETURN(rtl::Cell * module_cell, module->AddCell(cell)); + XLS_VLOG(2) << "Added cell: " << module_cell->name(); + XLS_VLOG(2) << " - Inputs"; + for (const auto& input : module_cell->inputs()) { + XLS_VLOG(2) << " - " << input.netref->name(); + } + XLS_VLOG(2) << " - Outputs"; + for (const auto& output : module_cell->outputs()) { + XLS_VLOG(2) << " - " << output.netref->name(); + } + for (auto& pair : param_assignments) { + pair.second->NoteConnectedCell(module_cell); + } + } + + return absl::OkStatus(); +} + +// Simple test to make sure we can translate anything at all. +TEST(Z3TranslatorTest, BasicFunctionality) { + constexpr int kNumCells = 16; + constexpr int kNumInputs = 4; + + Z3_config config = Z3_mk_config(); + Z3_context ctx = Z3_mk_context(config); + auto cleanup = xabsl::MakeCleanup([config, ctx] { + Z3_del_context(ctx); + Z3_del_config(config); + }); + rtl::Module module("the_module"); + CellLibrary cell_library = MakeFakeCellLibrary(); + absl::BitGen bitgen; + absl::flat_hash_map inputs = + CreateInputs(ctx, kNumInputs); + XLS_ASSERT_OK( + CreateNetList(&bitgen, inputs, kNumCells, cell_library, &module)); + + XLS_ASSERT_OK_AND_ASSIGN(auto translator, Z3Translator::CreateAndTranslate( + ctx, &module, {}, inputs)); +} + +// Tests that a simple (single-cell) netlist is translated correctly. +TEST(Z3TranslatorTest, SimpleNet) { + Z3_config config = Z3_mk_config(); + Z3_context ctx = Z3_mk_context(config); + auto cleanup = xabsl::MakeCleanup([config, ctx] { + Z3_del_context(ctx); + Z3_del_config(config); + }); + + rtl::Module module("the_module"); + + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(const CellLibraryEntry* and_entry, + cell_library.GetEntry("AND")); + absl::flat_hash_map param_assignments; + absl::flat_hash_map inputs; + Z3_sort input_sort = Z3_mk_bv_sort(ctx, /*sz=*/1); + for (const auto& input_name : and_entry->input_names()) { + XLS_ASSERT_OK(module.AddNetDecl(rtl::NetDeclKind::kInput, input_name)); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef ref, module.ResolveNet(input_name)); + param_assignments[input_name] = ref; + + Z3_ast z3_input = Z3_mk_const( + ctx, Z3_mk_string_symbol(ctx, input_name.c_str()), input_sort); + inputs[input_name] = z3_input; + } + + std::string output_name = and_entry->output_pins().begin()->name; + XLS_ASSERT_OK(module.AddNetDecl(rtl::NetDeclKind::kOutput, output_name)); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef output_ref, + module.ResolveNet(output_name)); + param_assignments[output_name] = output_ref; + + std::string clk_name = "wow_what_an_amazing_clock"; + XLS_ASSERT_OK(module.AddNetDecl(rtl::NetDeclKind::kWire, clk_name)); + XLS_ASSERT_OK_AND_ASSIGN(rtl::NetRef clk, module.ResolveNet(clk_name)); + + XLS_ASSERT_OK_AND_ASSIGN( + rtl::Cell tmp_cell, + rtl::Cell::Create(and_entry, "Rob's magic cell", param_assignments, clk)); + XLS_ASSERT_OK_AND_ASSIGN(rtl::Cell * cell, module.AddCell(tmp_cell)); + for (auto& pair : param_assignments) { + pair.second->NoteConnectedCell(cell); + } + XLS_ASSERT_OK_AND_ASSIGN(auto translator, Z3Translator::CreateAndTranslate( + ctx, &module, {}, inputs)); + + rtl::NetRef cell_output = cell->outputs().begin()->netref; + XLS_ASSERT_OK_AND_ASSIGN(Z3_ast z3_output, + translator->GetTranslation(cell_output)); + EXPECT_EQ(Z3_get_bv_sort_size(ctx, Z3_get_sort(ctx, z3_output)), 1); + + // I promise I tried, but I've not found a way in the Z3 API to extract the + // operation that an AST node represents, so we're left with string + // examination. + std::string ast_text = Z3_ast_to_string(ctx, z3_output); + EXPECT_NE(ast_text.find("bvand A B"), std::string::npos); +} + +// Create a module that instantiates all child cells and combines them with a +// cell with the specified name. +xabsl::StatusOr CreateModule( + const CellLibrary& cell_library, const std::string& module_name, + const std::string& cell_name, const std::vector& child_cells) { + // Instantiate all child cells, collect their inputs and outputs + // Synthesize any missing parent cell inputs (if we have too many, abort) + // - Each child cell output is a parent cell input + // - Any missing are new module inputs + // Apply inputs to cell in a left-to-right order + rtl::Module module(module_name); + + absl::flat_hash_map parent_params; + std::vector child_outputs; + for (int i = 0; i < child_cells.size(); i++) { + const std::string& child_name = child_cells[i]; + XLS_ASSIGN_OR_RETURN(const CellLibraryEntry* entry, + cell_library.GetEntry(child_name)); + + absl::flat_hash_map child_params; + absl::Span entry_input_names = entry->input_names(); + for (const std::string& entry_input_name : entry_input_names) { + std::string module_input_name = + absl::StrCat(module_name, "_c", i, "_", entry_input_name); + XLS_VLOG(2) << "Creating module input : " << module_input_name; + XLS_RET_CHECK_OK( + module.AddNetDecl(rtl::NetDeclKind::kInput, module_input_name)); + child_params[entry_input_name] = + module.ResolveNet(module_input_name).value(); + } + + std::string child_output_name = absl::StrCat(module_name, "_c", i, "_o"); + XLS_VLOG(2) << "Creating child output : " << child_output_name; + XLS_RET_CHECK_OK( + module.AddNetDecl(rtl::NetDeclKind::kWire, child_output_name)); + child_params[entry->output_pins()[0].name] = + module.ResolveNet(child_output_name).value(); + child_outputs.push_back(child_output_name); + + XLS_ASSIGN_OR_RETURN( + rtl::Cell temp_cell, + rtl::Cell::Create(entry, absl::StrCat(module_name, "_", i), + child_params, absl::nullopt)); + XLS_ASSIGN_OR_RETURN(rtl::Cell * cell, module.AddCell(temp_cell)); + for (auto& pair : child_params) { + pair.second->NoteConnectedCell(cell); + } + } + + // If we're short parent cell inputs, then synthesize some. + XLS_ASSIGN_OR_RETURN(const CellLibraryEntry* entry, + cell_library.GetEntry(cell_name)); + absl::Span entry_input_names = entry->input_names(); + XLS_RET_CHECK(child_outputs.size() <= entry_input_names.size()) + << "Too many inputs for cell: " << cell_name << "! " + << entry_input_names.size() << " vs. " << child_outputs.size() << "."; + while (child_outputs.size() < entry_input_names.size()) { + std::string new_input_name = + absl::StrCat(module_name, "_", + entry_input_names.size() - child_outputs.size(), "_o"); + XLS_VLOG(2) << "Synthesizing module input: " << new_input_name; + XLS_RET_CHECK_OK( + module.AddNetDecl(rtl::NetDeclKind::kInput, new_input_name)); + child_outputs.push_back(new_input_name); + } + + for (int i = 0; i < entry_input_names.size(); i++) { + parent_params[entry_input_names[i]] = + module.ResolveNet(child_outputs[i]).value(); + } + + for (int i = 0; i < entry->output_pins().size(); i++) { + std::string output_name = absl::StrCat(module_name, "_o", i); + XLS_RETURN_IF_ERROR( + module.AddNetDecl(rtl::NetDeclKind::kOutput, output_name)); + parent_params[entry->output_pins()[i].name] = + module.ResolveNet(output_name).value(); + } + + XLS_ASSIGN_OR_RETURN( + rtl::Cell temp_cell, + rtl::Cell::Create(entry, absl::StrCat(module_name, "_", cell_name), + parent_params, absl::nullopt)); + XLS_ASSIGN_OR_RETURN(rtl::Cell * cell, module.AddCell(temp_cell)); + for (auto& pair : parent_params) { + pair.second->NoteConnectedCell(cell); + } + + return std::move(module); +} + +TEST(Z3TranslatorTest, HandlesSubmodules) { + // Create four modules: + // - module_0: self-contained AND cell. + // - module_1: self-contained OR cell. + // - module_2: Takes four inputs, AND of module_0 and module_1 outputs. + // - module_3: references module_2. + Z3_config config = Z3_mk_config(); + Z3_context ctx = Z3_mk_context(config); + auto cleanup = xabsl::MakeCleanup([config, ctx] { + Z3_del_context(ctx); + Z3_del_config(config); + }); + + CellLibrary cell_library = MakeFakeCellLibrary(); + XLS_ASSERT_OK_AND_ASSIGN(rtl::Module module_0, + CreateModule(cell_library, "m0", "AND", {})); + XLS_ASSERT_OK_AND_ASSIGN(rtl::Module module_1, + CreateModule(cell_library, "m1", "OR", {})); + + XLS_ASSERT_OK(cell_library.AddEntry(*module_0.AsCellLibraryEntry())); + XLS_ASSERT_OK(cell_library.AddEntry(*module_1.AsCellLibraryEntry())); + XLS_ASSERT_OK_AND_ASSIGN(rtl::Module module_2, + CreateModule(cell_library, "m2", "AND", + {module_0.name(), module_1.name()})); + XLS_ASSERT_OK(cell_library.AddEntry(*module_2.AsCellLibraryEntry())); + + XLS_ASSERT_OK_AND_ASSIGN( + rtl::Module module_3, + CreateModule(cell_library, "m3", "INV", {module_2.name()})); + + // Create inputs for the top-level cell/module. + absl::flat_hash_map inputs; + Z3_sort input_sort = Z3_mk_bv_sort(ctx, 1); + for (const auto& input : module_3.inputs()) { + inputs[input->name()] = Z3_mk_const( + ctx, Z3_mk_string_symbol(ctx, input->name().c_str()), input_sort); + } + absl::flat_hash_map module_refs({ + {"m0", &module_0}, + {"m1", &module_1}, + {"m2", &module_2}, + }); + XLS_ASSERT_OK_AND_ASSIGN( + auto translator, + Z3Translator::CreateAndTranslate(ctx, &module_3, module_refs, inputs)); + + XLS_ASSERT_OK_AND_ASSIGN(Z3_ast z3_output, + translator->GetTranslation(module_3.outputs()[0])); + std::string ast_text = Z3_ast_to_string(ctx, z3_output); + XLS_VLOG(1) << "Z3 AST:" << std::endl << ast_text; + int not_pos = ast_text.find("bvnot"); + int and_pos = ast_text.find("bvand"); + int or_pos = ast_text.find("bvor"); + EXPECT_NE(not_pos, std::string::npos); + EXPECT_NE(and_pos, std::string::npos); + EXPECT_NE(or_pos, std::string::npos); + EXPECT_LT(not_pos, and_pos); + EXPECT_LT(and_pos, or_pos); +} + +} // namespace +} // namespace netlist +} // namespace xls diff --git a/xls/passes/BUILD b/xls/passes/BUILD new file mode 100644 index 0000000000..d46b8d3fac --- /dev/null +++ b/xls/passes/BUILD @@ -0,0 +1,976 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Optimization passes, pass managers. + +package( + default_visibility = ["//xls:xls_internal"], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "standard_pipeline", + srcs = ["standard_pipeline.cc"], + hdrs = ["standard_pipeline.h"], + deps = [ + ":arith_simplification_pass", + ":bdd_cse_pass", + ":bdd_simplification_pass", + ":bit_slice_simplification_pass", + ":boolean_simplification_pass", + ":canonicalization_pass", + ":concat_simplification_pass", + ":constant_folding_pass", + ":cse_pass", + ":dce_pass", + ":dfe_pass", + ":identity_removal_pass", + ":inlining_pass", + ":literal_uncommoning_pass", + ":map_inlining_pass", + ":narrowing_pass", + ":passes", + ":reassociation_pass", + ":select_simplification_pass", + ":strength_reduction_pass", + ":tuple_simplification_pass", + ":unroll_pass", + ":verifier_checker", + "//xls/common/status:statusor", + "//xls/scheduling:pipeline_scheduling_pass", + "//xls/scheduling:scheduling_checker", + "//xls/scheduling:scheduling_pass", + ], +) + +cc_library( + name = "verifier_checker", + srcs = ["verifier_checker.cc"], + hdrs = ["verifier_checker.h"], + deps = [ + ":passes", + "@com_google_absl//absl/status", + "//xls/ir", + ], +) + +cc_test( + name = "passes_test", + srcs = ["passes_test.cc"], + deps = [ + ":passes", + "@com_google_absl//absl/strings:str_format", + "//xls/common:casts", + "//xls/common/logging", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/examples:sample_packages", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:function_builder", + "//xls/ir:ir_parser", + "//xls/ir:type", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "identity_removal_pass_test", + srcs = ["identity_removal_pass_test.cc"], + deps = [ + ":dce_pass", + ":identity_removal_pass", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "arith_simplification_pass_test", + srcs = ["arith_simplification_pass_test.cc"], + deps = [ + ":arith_simplification_pass", + "//xls/common/status:matchers", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "standard_pipeline_test", + srcs = ["standard_pipeline_test.cc"], + deps = [ + ":arith_simplification_pass", + ":dce_pass", + ":dump_pass", + ":standard_pipeline", + "@com_google_absl//absl/strings:str_format", + "//xls/common/status:matchers", + "//xls/examples:sample_packages", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:function_builder", + "//xls/ir:ir_matcher", + "//xls/ir:ir_parser", + "//xls/ir:ir_test_base", + "//xls/ir:type", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "canonicalization_pass_test", + srcs = ["canonicalization_pass_test.cc"], + deps = [ + ":canonicalization_pass", + "//xls/common/status:matchers", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "unroll_pass", + srcs = ["unroll_pass.cc"], + hdrs = ["unroll_pass.h"], + deps = [ + ":passes", + "@com_google_absl//absl/status", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_test( + name = "unroll_pass_test", + srcs = ["unroll_pass_test.cc"], + deps = [ + ":dce_pass", + ":unroll_pass", + "//xls/common/status:matchers", + "//xls/ir:ir_parser", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "inlining_pass", + srcs = ["inlining_pass.cc"], + hdrs = ["inlining_pass.h"], + deps = [ + ":passes", + "@com_google_absl//absl/status", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "literal_uncommoning_pass", + srcs = ["literal_uncommoning_pass.cc"], + hdrs = ["literal_uncommoning_pass.h"], + deps = [ + ":passes", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "map_inlining_pass", + srcs = ["map_inlining_pass.cc"], + hdrs = ["map_inlining_pass.h"], + deps = [ + ":pass_base", + ":passes", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/time", + "//xls/common:math_util", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:function_builder", + ], +) + +cc_test( + name = "map_inlining_pass_test", + srcs = ["map_inlining_pass_test.cc"], + deps = [ + ":map_inlining_pass", + "//xls/common/status:matchers", + "//xls/ir", + "//xls/ir:ir_matcher", + "//xls/ir:ir_parser", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "cse_pass", + srcs = ["cse_pass.cc"], + hdrs = ["cse_pass.h"], + deps = [ + ":passes", + "@com_google_absl//absl/hash", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "constant_folding_pass", + srcs = ["constant_folding_pass.cc"], + hdrs = ["constant_folding_pass.h"], + deps = [ + ":passes", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:ir_interpreter", + ], +) + +cc_library( + name = "bit_slice_simplification_pass", + srcs = ["bit_slice_simplification_pass.cc"], + hdrs = ["bit_slice_simplification_pass.h"], + deps = [ + ":passes", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits_ops", + ], +) + +cc_library( + name = "concat_simplification_pass", + srcs = ["concat_simplification_pass.cc"], + hdrs = ["concat_simplification_pass.h"], + deps = [ + ":passes", + "@com_google_absl//absl/status", + "@com_google_absl//absl/types:span", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits_ops", + "//xls/ir:op", + ], +) + +cc_library( + name = "boolean_simplification_pass", + srcs = ["boolean_simplification_pass.cc"], + hdrs = ["boolean_simplification_pass.h"], + deps = [ + ":passes", + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits_ops", + "//xls/ir:node_util", + "//xls/netlist:logical_effort", + ], +) + +cc_library( + name = "arith_simplification_pass", + srcs = ["arith_simplification_pass.cc"], + hdrs = ["arith_simplification_pass.h"], + deps = [ + ":passes", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits_ops", + "//xls/ir:node_util", + "//xls/ir:value_helpers", + ], +) + +cc_library( + name = "dfe_pass", + srcs = ["dfe_pass.cc"], + hdrs = ["dfe_pass.h"], + deps = [ + ":passes", + "//xls/common/logging", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "canonicalization_pass", + srcs = ["canonicalization_pass.cc"], + hdrs = ["canonicalization_pass.h"], + deps = [ + ":passes", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "dump_pass", + srcs = ["dump_pass.cc"], + hdrs = ["dump_pass.h"], + deps = [ + ":passes", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "passes", + srcs = ["passes.cc"], + hdrs = ["passes.h"], + deps = [ + ":pass_base", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/time", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "identity_removal_pass", + srcs = ["identity_removal_pass.cc"], + hdrs = ["identity_removal_pass.h"], + deps = [ + ":passes", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "dce_pass", + srcs = ["dce_pass.cc"], + hdrs = ["dce_pass.h"], + deps = [ + ":passes", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "tuple_simplification_pass", + srcs = ["tuple_simplification_pass.cc"], + hdrs = ["tuple_simplification_pass.h"], + deps = [ + ":passes", + "@com_google_absl//absl/status", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "strength_reduction_pass", + srcs = ["strength_reduction_pass.cc"], + hdrs = [ + "dce_pass.h", + "strength_reduction_pass.h", + ], + deps = [ + ":passes", + ":query_engine", + ":ternary_query_engine", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:bits_ops", + "//xls/ir:node_util", + ], +) + +cc_library( + name = "ternary_query_engine", + srcs = ["ternary_query_engine.cc"], + hdrs = ["ternary_query_engine.h"], + deps = [ + ":query_engine", + ":ternary_logic", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/strings", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/data_structures:leaf_type_tree", + "//xls/ir", + "//xls/ir:abstract_node_evaluator", + "//xls/ir:bits", + "//xls/ir:bits_ops", + ], +) + +cc_library( + name = "select_simplification_pass", + srcs = ["select_simplification_pass.cc"], + hdrs = ["select_simplification_pass.h"], + deps = [ + ":passes", + ":ternary_query_engine", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/strings", + "//xls/common:visitor", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/logging:vlog_is_on", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/data_structures:algorithm", + "//xls/ir", + "//xls/ir:bits_ops", + "//xls/ir:node_util", + ], +) + +cc_library( + name = "ternary_logic", + hdrs = ["ternary_logic.h"], + deps = [ + "@com_google_absl//absl/strings", + "//xls/common/status:statusor", + "//xls/ir:abstract_evaluator", + "//xls/ir:bits", + "//xls/ir:ternary", + ], +) + +cc_library( + name = "bdd_function", + srcs = ["bdd_function.cc"], + hdrs = ["bdd_function.h"], + deps = [ + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/data_structures:binary_decision_diagram", + "//xls/data_structures:leaf_type_tree", + "//xls/ir", + "//xls/ir:abstract_evaluator", + "//xls/ir:abstract_node_evaluator", + "//xls/ir:ir_interpreter", + ], +) + +cc_library( + name = "query_engine", + srcs = ["query_engine.cc"], + hdrs = ["query_engine.h"], + deps = [ + "@com_google_absl//absl/types:variant", + "//xls/common/logging", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits", + ], +) + +cc_library( + name = "bdd_query_engine", + srcs = ["bdd_query_engine.cc"], + hdrs = ["bdd_query_engine.h"], + deps = [ + ":bdd_function", + ":query_engine", + "//xls/common/logging", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits", + ], +) + +cc_library( + name = "bdd_simplification_pass", + srcs = ["bdd_simplification_pass.cc"], + hdrs = ["bdd_simplification_pass.h"], + deps = [ + ":bdd_query_engine", + ":passes", + "@com_google_absl//absl/container:inlined_vector", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/logging:vlog_is_on", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits", + ], +) + +cc_library( + name = "pass_base", + hdrs = ["pass_base.h"], + deps = [ + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/time", + "//xls/common/file:filesystem", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + ], +) + +cc_library( + name = "narrowing_pass", + srcs = ["narrowing_pass.cc"], + hdrs = ["narrowing_pass.h"], + deps = [ + ":passes", + ":query_engine", + ":ternary_query_engine", + "//xls/common/logging", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:node_util", + "//xls/ir:op", + ], +) + +cc_library( + name = "bdd_cse_pass", + srcs = ["bdd_cse_pass.cc"], + hdrs = ["bdd_cse_pass.h"], + deps = [ + ":bdd_function", + ":passes", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/hash", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/delay_model:delay_estimator", + "//xls/delay_model:delay_estimators", + "//xls/ir", + ], +) + +cc_library( + name = "reassociation_pass", + srcs = ["reassociation_pass.cc"], + hdrs = ["reassociation_pass.h"], + deps = [ + ":pass_base", + ":passes", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/strings", + "//xls/common:math_util", + "//xls/common/logging", + "//xls/common/logging:log_lines", + "//xls/common/status:ret_check", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:node_util", + "//xls/ir:op", + ], +) + +cc_test( + name = "inlining_pass_test", + srcs = ["inlining_pass_test.cc"], + deps = [ + ":dce_pass", + ":inlining_pass", + "//xls/common/status:matchers", + "//xls/ir:ir_parser", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "literal_uncommoning_pass_test", + srcs = ["literal_uncommoning_pass_test.cc"], + deps = [ + ":literal_uncommoning_pass", + "//xls/common/status:matchers", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "cse_pass_test", + srcs = ["cse_pass_test.cc"], + deps = [ + ":cse_pass", + ":dce_pass", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "constant_folding_pass_test", + srcs = ["constant_folding_pass_test.cc"], + deps = [ + ":constant_folding_pass", + ":dce_pass", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:ir_test_base", + "//xls/ir:value", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "bit_slice_simplification_pass_test", + srcs = ["bit_slice_simplification_pass_test.cc"], + deps = [ + ":bit_slice_simplification_pass", + ":dce_pass", + "@com_google_absl//absl/strings", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:ir_interpreter", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "//xls/ir:value", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "boolean_simplification_pass_test", + srcs = ["boolean_simplification_pass_test.cc"], + deps = [ + ":boolean_simplification_pass", + ":dce_pass", + "@com_google_absl//absl/memory", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/ir:bits_ops", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "concat_simplification_pass_test", + srcs = ["concat_simplification_pass_test.cc"], + deps = [ + ":concat_simplification_pass", + ":dce_pass", + "@com_google_absl//absl/strings", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:ir_interpreter", + "//xls/ir:ir_test_base", + "//xls/ir:value", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "tuple_simplification_pass_test", + srcs = ["tuple_simplification_pass_test.cc"], + deps = [ + ":dce_pass", + ":tuple_simplification_pass", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "strength_reduction_pass_test", + srcs = ["strength_reduction_pass_test.cc"], + deps = [ + ":strength_reduction_pass", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/ir", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "ternary_query_engine_test", + srcs = ["ternary_query_engine_test.cc"], + deps = [ + ":ternary_logic", + ":ternary_query_engine", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/strings", + "//xls/common/logging", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:bits_ops", + "//xls/ir:function_builder", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "select_simplification_pass_test", + srcs = ["select_simplification_pass_test.cc"], + deps = [ + ":select_simplification_pass", + "@com_google_absl//absl/strings", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "ternary_logic_test", + srcs = ["ternary_logic_test.cc"], + deps = [ + ":ternary_logic", + "@com_google_absl//absl/strings:str_format", + "//xls/common/logging", + "//xls/common/status:matchers", + "//xls/ir:bits", + "//xls/ir:bits_ops", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "dce_pass_test", + srcs = ["dce_pass_test.cc"], + deps = [ + ":dce_pass", + "//xls/common/status:matchers", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "bdd_function_test", + srcs = ["bdd_function_test.cc"], + tags = ["optonly"], + deps = [ + ":bdd_function", + "//xls/common/status:matchers", + "//xls/examples:sample_packages", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:ir_interpreter", + "//xls/ir:ir_test_base", + "//xls/ir:value_helpers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "bdd_query_engine_test", + srcs = ["bdd_query_engine_test.cc"], + deps = [ + ":bdd_query_engine", + "//xls/common/status:matchers", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:function_builder", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "query_engine_test", + srcs = ["query_engine_test.cc"], + deps = [ + ":bdd_query_engine", + ":query_engine", + ":ternary_logic", + ":ternary_query_engine", + "@com_google_absl//absl/container:inlined_vector", + "@com_google_absl//absl/strings", + "//xls/common/logging", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/ir", + "//xls/ir:bits_ops", + "//xls/ir:function_builder", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "bdd_simplification_pass_test", + srcs = ["bdd_simplification_pass_test.cc"], + deps = [ + ":bdd_simplification_pass", + "//xls/common/status:matchers", + "//xls/common/status:status_macros", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "narrowing_pass_test", + srcs = ["narrowing_pass_test.cc"], + deps = [ + ":narrowing_pass", + ":pass_base", + "//xls/common/status:matchers", + "//xls/ir:function_builder", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "dfe_pass_test", + srcs = ["dfe_pass_test.cc"], + deps = [ + ":dfe_pass", + ":pass_base", + "@com_google_absl//absl/strings", + "//xls/common/status:matchers", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "bdd_cse_pass_test", + srcs = ["bdd_cse_pass_test.cc"], + deps = [ + ":bdd_cse_pass", + ":pass_base", + "//xls/common/status:matchers", + "//xls/common/status:statusor", + "//xls/ir", + "//xls/ir:function_builder", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "reassociation_pass_test", + srcs = ["reassociation_pass_test.cc"], + deps = [ + ":reassociation_pass", + "//xls/common/status:matchers", + "//xls/common/status:statusor", + "//xls/ir:function_builder", + "//xls/ir:ir_matcher", + "//xls/ir:ir_test_base", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/xls/passes/arith_simplification_pass.cc b/xls/passes/arith_simplification_pass.cc new file mode 100644 index 0000000000..ad12df9ac8 --- /dev/null +++ b/xls/passes/arith_simplification_pass.cc @@ -0,0 +1,547 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/arith_simplification_pass.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/node_iterator.h" +#include "xls/ir/node_util.h" +#include "xls/ir/nodes.h" +#include "xls/ir/value_helpers.h" + +namespace xls { +namespace { + +// For the given comparison Op, returns the op op_inverse for which the +// following identity holds: +// op(x, y) == !op_inverse(x, y) +Op CompareOpInverse(Op op) { + switch (op) { + case Op::kEq: + return Op::kNe; + case Op::kNe: + return Op::kEq; + case Op::kSGe: + return Op::kSLt; + case Op::kUGe: + return Op::kULt; + case Op::kSGt: + return Op::kSLe; + case Op::kUGt: + return Op::kULe; + case Op::kSLe: + return Op::kSGt; + case Op::kULe: + return Op::kUGt; + case Op::kSLt: + return Op::kSGe; + case Op::kULt: + return Op::kUGe; + default: + XLS_LOG(FATAL) << "Op is not comparison: " << OpToString(op); + } +} + +// MatchArithPatterns matches simple tree patterns to find opportunities +// for simplification, such as adding a zero, multiplying by 1, etc. +// +// Return 'true' if the IR was modified. +xabsl::StatusOr MatchArithPatterns(Node* n) { + absl::Span ops = n->operands(); + + // Pattern: Add/Sub/Or/Xor/Shift a value with 0 on the RHS. + if ((n->op() == Op::kAdd || n->op() == Op::kSub || n->op() == Op::kShll || + n->op() == Op::kShrl || n->op() == Op::kShra) && + IsLiteralZero(ops[1])) { + XLS_VLOG(2) << "FOUND: Useless operation of value with zero"; + return n->ReplaceUsesWith(ops[0]); + } + + const Op op = n->op(); + if (n->Is() && (op == Op::kAnd || op == Op::kOr || op == Op::kXor) && + ops.size() == 1) { + return n->ReplaceUsesWith(ops[0]); + } + + // Replaces uses of n with a new node by eliminating operands for which the + // "predicate" holds. If the predicate holds for all operands, the + // NaryOpNullaryResult is used as a replacement. + auto eliminate_operands_where = + [n](std::function predicate) -> xabsl::StatusOr { + XLS_RET_CHECK(n->Is()); + std::vector new_operands; + for (Node* operand : n->operands()) { + if (!predicate(operand)) { + new_operands.push_back(operand); + } + } + if (new_operands.size() == n->operand_count()) { + return false; + } + if (new_operands.empty()) { + XLS_RETURN_IF_ERROR( + n + ->ReplaceUsesWithNew(Value(DoLogicalOp( + n->op(), {LogicalOpIdentity(n->op(), n->BitCountOrDie())}))) + .status()); + } else { + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(new_operands, n->op()).status()); + } + return true; + }; + + // Or(x, 0, y) => Or(x, y) + // Xor(x, 0, y) => Xor(x, y) + // Nor(x, 0, y) => Nor(x, y) + if ((n->op() == Op::kOr || n->op() == Op::kXor || n->op() == Op::kNor)) { + XLS_ASSIGN_OR_RETURN(bool changed, eliminate_operands_where(IsLiteralZero)); + if (changed) { + return true; + } + } + + // Or(x, -1, y) => -1 + if (n->op() == Op::kOr && AnyOperandWhere(n, IsLiteralAllOnes)) { + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(AllOnesOfType(n->GetType())).status()); + return true; + } + + // Nor(x, -1, y) => 0 + if (n->op() == Op::kNor && AnyOperandWhere(n, IsLiteralAllOnes)) { + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(ZeroOfType(n->GetType())).status()); + return true; + } + + // And(x, -1, y) => And(x, y) + // Nand(x, -1, y) => Nand(x, y) + if (n->op() == Op::kAnd || n->op() == Op::kNand) { + XLS_ASSIGN_OR_RETURN(bool changed, + eliminate_operands_where(IsLiteralAllOnes)); + if (changed) { + return true; + } + } + + // And(x, 0) => 0 + if (n->op() == Op::kAnd && AnyOperandWhere(n, IsLiteralZero)) { + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(ZeroOfType(n->GetType())).status()); + return true; + } + + // And(x, x) => x + // Or(x, x) => x + // TODO(rhundt): This should be subsumed by BDD-based optimizations. + if (n->op() == Op::kAnd || n->op() == Op::kOr) { + bool all_the_same = true; + for (int i = 1; i < ops.size(); ++i) { + if (ops[0] != ops[i]) { + all_the_same = false; + break; + } + } + if (all_the_same) { + XLS_RETURN_IF_ERROR(n->ReplaceUsesWith(ops[0]).status()); + return true; + } + } + + // Nand(x, 0) => 1 + if (n->op() == Op::kNand && AnyOperandWhere(n, IsLiteralZero)) { + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(AllOnesOfType(n->GetType())).status()); + return true; + } + + auto has_inverted_operand = [&] { + for (Node* operand : ops) { + if (operand->op() == Op::kNot && + std::find(ops.begin(), ops.end(), operand->operand(0)) != ops.end()) { + return true; + } + } + return false; + }; + + // And(x, Not(x)) => 0 + // And(Not(x), x) => 0 + // + // Note that this won't be found through the ternary query engine because + // conservatively it determines `not(UNKNOWN) = UNKNOWN`. + if (n->op() == Op::kAnd && has_inverted_operand()) { + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(Value(UBits(0, n->BitCountOrDie()))) + .status()); + return true; + } + + // Xor(x, -1) => Not(x) + if (n->op() == Op::kXor && n->operand_count() == 2 && + IsLiteralAllOnes(ops[1])) { + XLS_VLOG(2) << "FOUND: Found xor with all ones"; + XLS_RETURN_IF_ERROR(n->ReplaceUsesWithNew(ops[0], Op::kNot).status()); + return true; + } + + auto is_same_opcode = [&](Node* other) { return n->op() == other->op(); }; + + // Flatten nested associative nary ops into their "dependent" op. + if (OpIsAssociative(n->op()) && n->Is() && + AnyOperandWhere(n, is_same_opcode)) { + std::vector new_operands; + for (Node* operand : ops) { + if (operand->op() == n->op()) { + for (Node* suboperand : operand->operands()) { + new_operands.push_back(suboperand); + } + } else { + new_operands.push_back(operand); + } + } + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(new_operands, n->op()).status()); + return true; + } + + // Fold the literal values presented to the nary op. + if (OpIsCommutative(n->op()) && OpIsAssociative(n->op()) && n->Is() && + AnyTwoOperandsWhere(n, IsLiteral)) { + std::vector new_operands; + Bits bits = LogicalOpIdentity(n->op(), n->BitCountOrDie()); + for (Node* operand : ops) { + if (operand->Is()) { + bits = DoLogicalOp(n->op(), + {bits, operand->As()->value().bits()}); + } else { + new_operands.push_back(operand); + } + } + Function* f = n->function(); + XLS_ASSIGN_OR_RETURN(Node * literal, + f->MakeNode(n->loc(), Value(bits))); + new_operands.push_back(literal); + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(new_operands, n->op()).status()); + return true; + } + + // Replace Nary ops with a single-operand with their operand (or inverse in + // the case of nand and nor). + if (n->Is() && n->operand_count() == 1) { + switch (n->op()) { + case Op::kAnd: + case Op::kOr: + case Op::kXor: + return n->ReplaceUsesWith(n->operand(0)); + case Op::kNand: + case Op::kNor: + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(n->operand(0), Op::kNot).status()); + break; + default: + XLS_LOG(FATAL) << "Expected nary op op: " << OpToString(n->op()); + } + return true; + } + + // Returns a value which is extended or narrowed to the given bit count. + // Possible cases: + // + // (1) node width == bit_count: return node + // + // (2) node width < bit_count: return node truncated to bit_count + // + // (3) node width > bit_count and is_signed: return node sign-extended to + // bit_count + // + // (4) node width > bit_count and !is_signed: return node zero-extended to + // bit_count + auto maybe_extend_or_trunc = [](Node* node, int64 bit_count, + bool is_signed) -> xabsl::StatusOr { + if (node->BitCountOrDie() == bit_count) { + return node; + } + if (node->BitCountOrDie() > bit_count) { + return node->function()->MakeNode(node->loc(), node, + /*start=*/0, + /*width=*/bit_count); + } + return node->function()->MakeNode( + node->loc(), node, + /*new_bit_count=*/bit_count, is_signed ? Op::kSignExt : Op::kZeroExt); + }; + + // Pattern: Mul or div by 1 + if ((n->op() == Op::kSMul && IsLiteralSignedOne(ops[1])) || + ((n->op() == Op::kUMul || n->op() == Op::kUDiv) && + IsLiteralUnsignedOne(ops[1]))) { + XLS_VLOG(2) << "FOUND: Mul/Div by 1"; + XLS_ASSIGN_OR_RETURN( + Node * replacement, + maybe_extend_or_trunc(ops[0], n->BitCountOrDie(), + /*is_signed=*/n->op() == Op::kSMul)); + return n->ReplaceUsesWith(replacement); + } + + // Pattern: Mul by 0 + if ((n->op() == Op::kSMul || n->op() == Op::kUMul) && IsLiteralZero(ops[1])) { + XLS_VLOG(2) << "FOUND: Mul by 0"; + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(Value(UBits(0, n->BitCountOrDie()))) + .status()); + return true; + } + + // Pattern: Not(Not(x)) => x + if (n->op() == Op::kNot && ops[0]->op() == Op::kNot) { + return n->ReplaceUsesWith(ops[0]->operand(0)); + } + + // Logical shift by a constant can be replaced by a slice and concat. + // (val << lit) -> Concat(BitSlice(val, ...), UBits(0, ...)) + // (val >> lit) -> Concat(UBits(0, ...), BitSlice(val, ...)) + // If the shift amount is greater than or equal to the bit width the + // expression can be replaced with zero. + // + // This simplification is desirable as in the canonical lower-level IR a shift + // implies a barrel shifter which is not necessary for a shift by a constant + // amount. + if ((n->op() == Op::kShll || n->op() == Op::kShrl) && ops[1]->Is()) { + int64 bit_count = n->BitCountOrDie(); + const Bits& shift_bits = ops[1]->As()->value().bits(); + if (shift_bits.IsAllZeros()) { + // A shift by zero is a nop. + return n->ReplaceUsesWith(ops[0]); + } + if (bits_ops::UGreaterThanOrEqual(shift_bits, bit_count)) { + // Replace with zero. + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(Value(UBits(0, bit_count))).status()); + return true; + } + XLS_ASSIGN_OR_RETURN(uint64 shift_amount, shift_bits.ToUint64()); + XLS_ASSIGN_OR_RETURN(Node * zero, + n->function()->MakeNode( + n->loc(), Value(UBits(0, shift_amount)))); + int64 slice_start = (n->op() == Op::kShll) ? 0 : shift_amount; + int64 slice_width = bit_count - shift_amount; + XLS_ASSIGN_OR_RETURN(Node * slice, + n->function()->MakeNode( + n->loc(), ops[0], slice_start, slice_width)); + auto concat_operands = (n->op() == Op::kShll) + ? std::vector{slice, zero} + : std::vector{zero, slice}; + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(concat_operands).status()); + return true; + } + + // SignExt(SignExt(x, w_0), w_1) => SignExt(x, w_1) + if (n->op() == Op::kSignExt && ops[0]->op() == Op::kSignExt) { + XLS_RETURN_IF_ERROR(n->ReplaceUsesWithNew(ops[0]->operand(0), + n->BitCountOrDie(), + Op::kSignExt) + .status()); + return true; + } + + // An arithmetic shift right by a constant can be replaced by slice, concat, + // and a sequence of sign bits of the input. + // + // (val >>> lit) -> Concat({sign_extend(sign_bit), BitSlice(val, ...)}) + // + // If the shift amount is greater than or equal to the bit width the + // expression can be replaced with the sign-extended sign bit. + // + // This simplification is desirable because in the canonical lower-level IR a + // shift implies a barrel shifter which is not necessary for a shift by a + // constant amount. + if (n->op() == Op::kShra && ops[1]->Is()) { + const int64 bit_count = n->BitCountOrDie(); + const Bits& shift_bits = ops[1]->As()->value().bits(); + if (shift_bits.IsAllZeros()) { + // A shift by zero is a nop. + return n->ReplaceUsesWith(ops[0]); + } + XLS_ASSIGN_OR_RETURN( + Node * sign_bit, + n->function()->MakeNode( + n->loc(), ops[0], /*start=*/bit_count - 1, /*width=*/1)); + if (bits_ops::UGreaterThanOrEqual(shift_bits, bit_count)) { + // Replace with all sign bits. + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(sign_bit, bit_count, Op::kSignExt) + .status()); + return true; + } + XLS_ASSIGN_OR_RETURN(uint64 shift_amount, shift_bits.ToUint64()); + XLS_RET_CHECK_LT(shift_amount, bit_count); + XLS_ASSIGN_OR_RETURN(Node * all_sign_bits, + n->function()->MakeNode( + n->loc(), sign_bit, shift_amount, Op::kSignExt)); + XLS_ASSIGN_OR_RETURN(Node * slice, + n->function()->MakeNode( + n->loc(), ops[0], /*start=*/shift_amount, + /*width=*/bit_count - shift_amount)); + std::vector concat_args = {all_sign_bits, slice}; + XLS_RETURN_IF_ERROR(n->ReplaceUsesWithNew(concat_args).status()); + return true; + } + + // Pattern: Double negative. + // -(-expr) + if (n->op() == Op::kNeg && ops[0]->op() == Op::kNeg) { + XLS_VLOG(2) << "FOUND: Double negative"; + return n->ReplaceUsesWith(ops[0]->operand(0)); + } + + // Patterns (where x is a bits[1] type): + // eq(x, 1) => x + // eq(x, 0) => not(x) + // + // Because eq is commutative, we can rely on the literal being on the right + // because of canonicalization. + if (n->op() == Op::kEq && ops[0]->BitCountOrDie() == 1 && + ops[1]->Is()) { + if (IsLiteralZero(ops[1])) { + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(ops[0], Op::kNot).status()); + return true; + } + XLS_RET_CHECK(IsLiteralUnsignedOne(ops[1])); + return n->ReplaceUsesWith(ops[0]); + } + + // Or(Or(x, y), z) => NaryOr(x, y, z) + // Or(x, Or(y, z)) => NaryOr(x, y, z) + // Or(Or(x, y), Or(z, a)) => NaryOr(x, y, z, a) + auto has_operand_with_same_opcode = [&](Op op) { + return n->op() == op && std::any_of(ops.begin(), ops.end(), [op](Node* n) { + return n->op() == op; + }); + }; + if (has_operand_with_same_opcode(Op::kOr) || + has_operand_with_same_opcode(Op::kAnd) || + has_operand_with_same_opcode(Op::kXor)) { + std::vector new_operands; + for (Node* operand : ops) { + if (operand->op() == n->op()) { + for (Node* sub_operand : operand->operands()) { + new_operands.push_back(sub_operand); + } + } else { + new_operands.push_back(operand); + } + } + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(new_operands, n->op()).status()); + return true; + } + + // If either x or y is zero width: + // [US]Mul(x, y) => 0 + // This can arise due to narrowing of multiplies. + if (n->op() == Op::kUMul || n->op() == Op::kSMul) { + for (Node* operand : n->operands()) { + if (operand->BitCountOrDie() == 0) { + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(Value(UBits(0, n->BitCountOrDie()))) + .status()); + return true; + } + } + } + + // Slt(x, 0) -> msb(x) + // SGe(x, 0) -> not(msb(x)) + // + // Canonicalization puts the literal on the right for comparisons. + if (OpIsCompare(n->op()) && IsLiteralZero(ops[1])) { + if (n->op() == Op::kSLt) { + XLS_VLOG(2) << "FOUND: SLt(x, 0)"; + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew( + ops[0], /*start=*/ops[0]->BitCountOrDie() - 1, /*width=*/1) + .status()); + return true; + } + if (n->op() == Op::kSGe) { + XLS_VLOG(2) << "FOUND: SGe(x, 0)"; + XLS_ASSIGN_OR_RETURN( + Node * sign_bit, + n->function()->MakeNode( + n->loc(), ops[0], + /*start=*/ops[0]->BitCountOrDie() - 1, /*width=*/1)); + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(sign_bit, Op::kNot).status()); + return true; + } + } + + // Not(comparison_op(x, y)) => comparison_op_inverse(x, y) + if (n->op() == Op::kNot && OpIsCompare(ops[0]->op())) { + XLS_VLOG(2) << "FOUND: Not(CompareOp(x, y))"; + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(ops[0]->operand(0), ops[0]->operand(1), + CompareOpInverse(ops[0]->op())) + .status()); + return true; + } + + int64 leading_zeros, trailing_ones; + if (OpIsCompare(n->op()) && + IsLiteralMask(ops[1], &leading_zeros, &trailing_ones)) { + XLS_VLOG(2) << "Found comparison to literal mask; leading zeros: " + << leading_zeros << " trailing ones: " << trailing_ones + << " :: " << n; + if (n->op() == Op::kULt) { + XLS_ASSIGN_OR_RETURN(Node * or_red, + OrReduceLeading(n->operand(0), leading_zeros)); + XLS_ASSIGN_OR_RETURN(Node * and_trail, + AndReduceTrailing(n->operand(0), trailing_ones)); + std::vector args = {or_red, and_trail}; + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(args, Op::kNor).status()); + return true; + } else if (n->op() == Op::kUGt) { + XLS_ASSIGN_OR_RETURN(Node * or_red, + OrReduceLeading(n->operand(0), leading_zeros)); + XLS_RETURN_IF_ERROR(n->ReplaceUsesWith(or_red).status()); + return true; + } + } + + return false; +} + +} // namespace + +xabsl::StatusOr ArithSimplificationPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + bool modified = false; + bool local_modified = false; + for (Node* node : TopoSort(f)) { + XLS_ASSIGN_OR_RETURN(local_modified, MatchArithPatterns(node)); + modified |= local_modified; + } + return modified; +} + +} // namespace xls diff --git a/xls/passes/arith_simplification_pass.h b/xls/passes/arith_simplification_pass.h new file mode 100644 index 0000000000..536bdc1495 --- /dev/null +++ b/xls/passes/arith_simplification_pass.h @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_ARITH_SIMPLIFICATION_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_ARITH_SIMPLIFICATION_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// class ArithSimplificationPass analyzes the IR and finds some +// simple patterns it can simplify, e.g., things like mul by 1, +// add of 0, etc. +class ArithSimplificationPass : public FunctionPass { + public: + ArithSimplificationPass() + : FunctionPass("arith_simp", "Arithmetic Simplifications") {} + ~ArithSimplificationPass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_ARITH_SIMPLIFICATION_PASS_H_ diff --git a/xls/passes/arith_simplification_pass_test.cc b/xls/passes/arith_simplification_pass_test.cc new file mode 100644 index 0000000000..a89e4604fd --- /dev/null +++ b/xls/passes/arith_simplification_pass_test.cc @@ -0,0 +1,607 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/arith_simplification_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" + +namespace m = ::xls::op_matchers; + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class ArithSimplificationPassTest : public IrTestBase { + protected: + ArithSimplificationPassTest() = default; + + xabsl::StatusOr Run(Package* p) { + PassResults results; + return ArithSimplificationPass().Run(p, PassOptions(), &results); + } +}; + +TEST_F(ArithSimplificationPassTest, Arith1) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn double_shift(x:bits[32]) -> bits[32] { + three:bits[32] = literal(value=3) + two:bits[32] = literal(value=2) + xshrl3:bits[32] = shrl(x, three) + xshrl3_shrl2:bits[32] = shrl(xshrl3, two) + ret add: bits[32] = add(xshrl3_shrl2, xshrl3_shrl2) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Add(m::Concat(m::Literal(0), m::BitSlice()), + m::Concat(m::Literal(0), m::BitSlice()))); +} + +TEST_F(ArithSimplificationPassTest, DoubleNeg) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn simple_neg(x:bits[2]) -> bits[2] { + neg1:bits[2] = neg(x) + ret neg2: bits[2] = neg(neg1) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param()); +} + +TEST_F(ArithSimplificationPassTest, MulBy0) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn mul_zero(x:bits[8]) -> bits[8] { + zero:bits[8] = literal(value=0) + ret ret_mul: bits[8] = umul(x, zero) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Literal(0)); +} + +TEST_F(ArithSimplificationPassTest, MulBy1SignExtendedResult) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn mul_zero(x:bits[8]) -> bits[16] { + one: bits[8] = literal(value=1) + ret ret_mul: bits[16] = smul(x, one) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::SignExt(m::Param("x"))); +} + +TEST_F(ArithSimplificationPassTest, MulBy1ZeroExtendedResult) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn mul_zero(x:bits[8]) -> bits[16] { + one: bits[8] = literal(value=1) + ret ret_mul: bits[16] = umul(x, one) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::ZeroExt(m::Param("x"))); +} + +TEST_F(ArithSimplificationPassTest, MulBy1NarrowedResult) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn mul_zero(x:bits[8]) -> bits[3] { + one: bits[8] = literal(value=1) + ret ret_mul: bits[3] = umul(x, one) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::BitSlice(m::Param("x"), /*start=*/0, /*width=*/3)); +} + +TEST_F(ArithSimplificationPassTest, SMulByMinusOne) { + // A single-bit value of 1 is a -1 when interpreted as a signed number. The + // Mul-by-1 optimization should not kick in in this case. + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn mul_zero(x:bits[8]) -> bits[3] { + one: bits[1] = literal(value=1) + ret ret_mul: bits[3] = smul(x, one) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_THAT(f->return_value(), m::SMul()); +} + +TEST_F(ArithSimplificationPassTest, UDivBy1) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn div_one(x:bits[8]) -> bits[8] { + one:bits[8] = literal(value=1) + ret ret_mul: bits[8] = udiv(x, one) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param()); +} + +TEST_F(ArithSimplificationPassTest, MulBy1) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn mul_zero(x:bits[8]) -> bits[8] { + one:bits[8] = literal(value=1) + ret ret_mul: bits[8] = umul(x, one) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param()); +} + +TEST_F(ArithSimplificationPassTest, CanonicalizeXorAllOnes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x:bits[2]) -> bits[2] { + literal.1: bits[2] = literal(value=3) + ret xor.2: bits[2] = xor(x, literal.1) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Not(m::Param())); +} + +TEST_F(ArithSimplificationPassTest, OverlargeShiftAfterSimp) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x:bits[2]) -> bits[2] { + literal.1: bits[2] = literal(value=2) + shrl.2: bits[2] = shrl(x, literal.1) + ret shrl.3: bits[2] = shrl(shrl.2, literal.1) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Literal(0)); +} + +TEST_F(ArithSimplificationPassTest, ShiftRightArithmeticByLiteral) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x:bits[16]) -> bits[16] { + literal.1: bits[16] = literal(value=13) + ret shra.3: bits[16] = shra(x, literal.1) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Concat(m::SignExt(), m::BitSlice())); +} + +TEST_F(ArithSimplificationPassTest, OverlargeShiftRightArithmeticByLiteral) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x:bits[16]) -> bits[16] { + literal.1: bits[16] = literal(value=1234) + ret shra.3: bits[16] = shra(x, literal.1) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::SignExt(m::BitSlice(/*start=*/15, /*width=*/1))); +} + +TEST_F(ArithSimplificationPassTest, CompareBoolAgainstOne) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x:bits[1]) -> bits[1] { + literal.1: bits[1] = literal(value=1) + ret eq.2: bits[1] = eq(x, literal.1) + } + )", + p.get())); + EXPECT_TRUE(f->return_value()->Is()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_TRUE(f->return_value()->Is()); +} + +TEST_F(ArithSimplificationPassTest, CompareBoolAgainstZero) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x:bits[1]) -> bits[1] { + literal.1: bits[1] = literal(value=0) + ret eq.2: bits[1] = eq(x, literal.1) + } + )", + p.get())); + EXPECT_TRUE(f->return_value()->Is()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Not(m::Param())); +} + +TEST_F(ArithSimplificationPassTest, DoubleNegation) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[42]) -> bits[42] { + not.2: bits[42] = not(x) + ret not.3: bits[42] = not(not.2) + } + )", + p.get())); + EXPECT_TRUE(f->return_value()->Is()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param("x")); +} + +TEST_F(ArithSimplificationPassTest, NaryOrEliminateSeveralZeros) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[8]) -> bits[8] { + literal.3: bits[8] = literal(value=0) + literal.4: bits[8] = literal(value=0) + ret or.5: bits[8] = or(x, literal.3, y, literal.4) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Or(m::Param("x"), m::Param("y"))); +} + +TEST_F(ArithSimplificationPassTest, NaryAndEliminateSeveralOnes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[8]) -> bits[8] { + literal.3: bits[8] = literal(value=0xff) + literal.4: bits[8] = literal(value=0xff) + ret and.5: bits[8] = and(x, literal.3, y, literal.4) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::And(m::Param("x"), m::Param("y"))); +} + +TEST_F(ArithSimplificationPassTest, NaryAndEliminateAllOnes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f() -> bits[8] { + literal.1: bits[8] = literal(value=0xff) + literal.2: bits[8] = literal(value=0xff) + ret and.3: bits[8] = and(literal.1, literal.2) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Literal(255)); +} + +TEST_F(ArithSimplificationPassTest, NaryFlattening) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[8], z: bits[8]) -> bits[8] { + literal.3: bits[8] = literal(value=0x1f) + literal.4: bits[8] = literal(value=0x0f) + and.5: bits[8] = and(z, literal.3) + ret and.6: bits[8] = and(x, y, literal.4, and.5) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::And(m::Param("x"), m::Param("y"), m::Literal(15), + m::Param("z"), m::Literal(31))); +} + +TEST_F(ArithSimplificationPassTest, NaryLiteralConsolidation) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[8], y: bits[8], z: bits[8]) -> bits[8] { + literal.4: bits[8] = literal(value=15) + literal.5: bits[8] = literal(value=31) + ret and.6: bits[8] = and(x, y, literal.4, z, literal.5) +} + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::And(m::Param("x"), m::Param("y"), + m::Param("z"), m::Literal(15))); +} + +TEST_F(ArithSimplificationPassTest, XAndNotX) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[32]) -> bits[32] { + not.2: bits[32] = not(x) + ret and.3: bits[32] = and(x, not.2) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Literal(0)); +} + +TEST_F(ArithSimplificationPassTest, NotXAndX) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[32]) -> bits[32] { + not.2: bits[32] = not(x) + ret and.3: bits[32] = and(not.2, x) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Literal(0)); +} + +TEST_F(ArithSimplificationPassTest, SignExtTwice) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8]) -> bits[32] { + sign_ext.2: bits[24] = sign_ext(x, new_bit_count=24) + ret sign_ext.3: bits[32] = sign_ext(sign_ext.2, new_bit_count=32) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::SignExt(m::Param())); +} + +TEST_F(ArithSimplificationPassTest, CollapseToNaryOr) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(w: bits[8], x: bits[8], y: bits[8], z: bits[8]) -> bits[8] { + or.5: bits[8] = or(w, x) + or.6: bits[8] = or(y, z) + ret or.7: bits[8] = or(or.5, or.6) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Or(m::Param("w"), m::Param("x"), + m::Param("y"), m::Param("z"))); +} + +TEST_F(ArithSimplificationPassTest, CollapseOneSideToNaryOr) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[8], z: bits[8]) -> bits[8] { + or.4: bits[8] = or(y, z) + ret or.5: bits[8] = or(x, or.4) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Or(m::Param("x"), m::Param("y"), m::Param("z"))); +} + +TEST_F(ArithSimplificationPassTest, NorWithLiteralZeroOperands) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[8], z: bits[8]) -> bits[8] { + literal.1: bits[8] = literal(value=0) + literal.2: bits[8] = literal(value=0) + ret nor.3: bits[8] = nor(x, literal.1, y, literal.2, z) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Nor(m::Param("x"), m::Param("y"), m::Param("z"))); +} + +TEST_F(ArithSimplificationPassTest, NandWithLiteralAllOnesOperands) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[8], z: bits[8]) -> bits[8] { + literal.1: bits[8] = literal(value=255) + ret nand.2: bits[8] = nand(x, literal.1, y, z) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Nand(m::Param("x"), m::Param("y"), m::Param("z"))); +} + +TEST_F(ArithSimplificationPassTest, SingleOperandNand) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.AddNaryOp(Op::kNand, {fb.Param("x", p->GetBitsType(32))}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Not(m::Param("x"))); +} + +TEST_F(ArithSimplificationPassTest, SingleOperandOr) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.AddNaryOp(Op::kOr, {fb.Param("x", p->GetBitsType(32))}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param("x")); +} + +TEST_F(ArithSimplificationPassTest, RedundantAnd) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn id_and(x: bits[32], y: bits[32]) -> bits[32] { + ret and.3: bits[32] = and(x, x, pos=0,1,5) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param("x")); +} + +TEST_F(ArithSimplificationPassTest, RedundantOr) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn id_or(x: bits[32], y: bits[32]) -> bits[32] { + ret and.3: bits[32] = or(x, x, pos=0,1,5) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param("x")); +} + +TEST_F(ArithSimplificationPassTest, AddWithZero) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn add_zero(x: bits[32]) -> bits[32] { + literal.1: bits[32] = literal(value=0) + ret slt.2: bits[32] = add(x, literal.1) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param("x")); +} + +TEST_F(ArithSimplificationPassTest, ZeroWidthMulOperand) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn id_or(x: bits[0], y: bits[32]) -> bits[32] { + ret smul.1: bits[32] = smul(x, y, pos=0,1,5) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Literal(UBits(0, 32))); +} + +TEST_F(ArithSimplificationPassTest, SltZero) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn slt_zero(x: bits[32]) -> bits[1] { + literal.1: bits[32] = literal(value=0) + ret slt.2: bits[1] = slt(x, literal.1) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::BitSlice(m::Param("x"), /*start=*/31, /*width=*/1)); +} + +TEST_F(ArithSimplificationPassTest, SGeZero) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn sge_zero(x: bits[32]) -> bits[1] { + literal.1: bits[32] = literal(value=0) + ret sge.2: bits[1] = sge(x, literal.1) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Not(m::BitSlice(m::Param("x"), /*start=*/31, /*width=*/1))); +} + +TEST_F(ArithSimplificationPassTest, InvertedComparison) { + { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Not(fb.ULt(fb.Param("x", u32), fb.Param("y", u32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::UGe(m::Param("x"), m::Param("y"))); + } + { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Not(fb.SGe(fb.Param("x", u32), fb.Param("y", u32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::SLt(m::Param("x"), m::Param("y"))); + } + { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Not(fb.Eq(fb.Param("x", u32), fb.Param("y", u32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Ne(m::Param("x"), m::Param("y"))); + } +} + +TEST_F(ArithSimplificationPassTest, ULtMask) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[4]) -> bits[1] { + literal.1: bits[4] = literal(value=0b0011) + ret ult.2: bits[1] = ult(x, literal.1) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + m::Nor(m::Or(m::BitSlice(m::Param("x"), /*start=*/2, /*width=*/1), + m::BitSlice(m::Param("x"), /*start=*/3, /*width=*/1)), + m::And(m::BitSlice(m::Param("x"), /*start=*/0, /*width=*/1), + m::BitSlice(m::Param("x"), /*start=*/1, /*width=*/1)))); +} + +TEST_F(ArithSimplificationPassTest, UGtMask) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[4]) -> bits[1] { + literal.1: bits[4] = literal(value=0b0011) + ret res: bits[1] = ugt(x, literal.1) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Or(m::BitSlice(m::Param("x"), /*start=*/2, /*width=*/1), + m::BitSlice(m::Param("x"), /*start=*/3, /*width=*/1))); +} + +TEST_F(ArithSimplificationPassTest, UGtMaskAllOnes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[4]) -> bits[1] { + literal.1: bits[4] = literal(value=0b1111) + ret res: bits[1] = ugt(x, literal.1) + } +)", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Literal(0)); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/bdd_cse_pass.cc b/xls/passes/bdd_cse_pass.cc new file mode 100644 index 0000000000..ebe9d37a76 --- /dev/null +++ b/xls/passes/bdd_cse_pass.cc @@ -0,0 +1,168 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bdd_cse_pass.h" + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/hash/hash.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/delay_model/delay_estimator.h" +#include "xls/delay_model/delay_estimators.h" +#include "xls/ir/node.h" +#include "xls/ir/node_iterator.h" +#include "xls/passes/bdd_function.h" + +namespace xls { + +namespace { + +// Returns the order in which to visit the nodes when performing the +// optimization. If a pair of equivalent nodes is found during the optimization +// then the earlier visited node replaces the later visited node so this order +// is constructed with the following properties: +// +// (1) Order is a topological sort. This is necessary to avoid introducing +// cycles in the graph. +// +// (2) Critical-path delay through the graph to the node increases monotonically +// in the list. This ensures that the CSE replacement does not increase +// critical-path +// +xabsl::StatusOr> GetNodeOrder(Function* f) { + // Index of each node in the topological sort. + absl::flat_hash_map topo_index; + // Critical-path distance from root in the graph to each node. + absl::flat_hash_map node_cp_delay; + int64 i = 0; + + // Return an estimate of the delay of the given node. Because BDD-CSE may be + // run at any point in the pipeline, some nodes with no delay model may be + // present (these would be eliminated before codegen) so return zero for these + // cases. + // TODO(meheff): Replace with the actual model being used when the delay model + // is threaded through the pass pipeline. + auto get_node_delay = [&](Node* n) { + xabsl::StatusOr delay_status = + GetStandardDelayEstimator().GetOperationDelayInPs(n); + return delay_status.ok() ? delay_status.value() : 0; + }; + for (Node* node : TopoSort(f)) { + topo_index[node] = i; + int64 node_start = 0; + for (Node* operand : node->operands()) { + node_start = std::max( + node_start, node_cp_delay.at(operand) + get_node_delay(operand)); + } + node_cp_delay[node] = node_start + get_node_delay(node); + ++i; + } + std::vector nodes(f->nodes().begin(), f->nodes().end()); + std::sort(nodes.begin(), nodes.end(), [&](Node* a, Node* b) { + return (node_cp_delay.at(a) < node_cp_delay.at(b) || + (node_cp_delay.at(a) == node_cp_delay.at(b) && + topo_index.at(a) < topo_index.at(b))); + }); + // The node order must be a topological sort in order to avoid introducing + // cycles in the graph. + for (Node* node : nodes) { + for (Node* operand : node->operands()) { + XLS_RET_CHECK(topo_index.at(operand) < topo_index.at(node)); + } + } + return nodes; +} + +} // namespace + +xabsl::StatusOr BddCsePass::RunOnFunction(Function* f, + const PassOptions& options, + PassResults* results) const { + XLS_VLOG(2) << "Running BDD CSE on function " << f->name(); + XLS_VLOG(3) << "Before:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + // TODO(meheff): Try tuning the minterm limit. + XLS_ASSIGN_OR_RETURN(std::unique_ptr bdd_function, + BddFunction::Run(f, /*minterm_limit=*/4096)); + + // To improve efficiency, bucket potentially common nodes together. The + // bucketing is done via a int64 hash value of the BDD node indices of each + // bit of the node. + auto hasher = absl::Hash>(); + auto node_hash = [&](Node* n) { + XLS_CHECK(n->GetType()->IsBits()); + std::vector values_to_hash; + for (int64 i = 0; i < n->BitCountOrDie(); ++i) { + values_to_hash.push_back(bdd_function->GetBddNode(n, i).value()); + } + return hasher(values_to_hash); + }; + + auto is_same_value = [&](Node* a, Node* b) { + if (a->BitCountOrDie() != b->BitCountOrDie()) { + return false; + } + for (int64 i = 0; i < a->BitCountOrDie(); ++i) { + if (bdd_function->GetBddNode(a, i) != bdd_function->GetBddNode(b, i)) { + return false; + } + } + return true; + }; + + bool changed = false; + absl::flat_hash_map> node_buckets; + node_buckets.reserve(f->node_count()); + XLS_ASSIGN_OR_RETURN(std::vector node_order, GetNodeOrder(f)); + for (Node* node : node_order) { + if (!node->GetType()->IsBits() || node->Is()) { + continue; + } + + int64 hash = node_hash(node); + if (!node_buckets.contains(hash)) { + node_buckets[hash].push_back(node); + continue; + } + bool replaced = false; + for (Node* candidate : node_buckets.at(hash)) { + if (is_same_value(node, candidate)) { + XLS_ASSIGN_OR_RETURN(bool node_changed, + node->ReplaceUsesWith(candidate)); + XLS_VLOG(4) << "Found identical value:"; + XLS_VLOG(4) << " Node: " << node->ToString(); + XLS_VLOG(4) << " Replacement: " << candidate->ToString(); + changed |= node_changed; + replaced = true; + break; + } + } + if (!replaced) { + node_buckets[hash].push_back(node); + } + } + + XLS_VLOG(3) << "After:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + return changed; +} + +} // namespace xls diff --git a/xls/passes/bdd_cse_pass.h b/xls/passes/bdd_cse_pass.h new file mode 100644 index 0000000000..4db57f74fc --- /dev/null +++ b/xls/passes/bdd_cse_pass.h @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_BDD_CSE_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_BDD_CSE_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// Pass which commons equivalent expressions in the graph using binary decision +// diagrams. +class BddCsePass : public FunctionPass { + public: + explicit BddCsePass() + : FunctionPass("bdd_cse", "BDD-based Common Subexpression Elimination") {} + ~BddCsePass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_BDD_CSE_PASS_H_ diff --git a/xls/passes/bdd_cse_pass_test.cc b/xls/passes/bdd_cse_pass_test.cc new file mode 100644 index 0000000000..23e1782147 --- /dev/null +++ b/xls/passes/bdd_cse_pass_test.cc @@ -0,0 +1,90 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bdd_cse_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_test_base.h" +#include "xls/passes/pass_base.h" + +namespace m = ::xls::op_matchers; + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class BddCsePassTest : public IrTestBase { + protected: + BddCsePassTest() = default; + + xabsl::StatusOr Run(Function* f) { + PassResults results; + return BddCsePass().RunOnFunction(f, PassOptions(), &results); + } +}; + +TEST_F(BddCsePassTest, EqEquivalentToNotNe) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(16)); + BValue forty_two = fb.Literal(UBits(42, 16)); + BValue x_eq_42 = fb.Eq(x, forty_two); + BValue forty_two_not_ne_x = fb.Not(fb.Ne(forty_two, x)); + fb.Tuple({x_eq_42, forty_two_not_ne_x}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Tuple(m::Eq(m::Param("x"), m::Literal(42)), + m::Eq(m::Param("x"), m::Literal(42)))); +} + +TEST_F(BddCsePassTest, DifferentExpressions) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(16)); + BValue y = fb.Param("y", p->GetBitsType(16)); + BValue forty_two = fb.Literal(UBits(42, 16)); + BValue x_eq_42 = fb.Eq(x, forty_two); + BValue forty_two_not_ne_y = fb.Not(fb.Ne(forty_two, y)); + fb.Tuple({x_eq_42, forty_two_not_ne_y}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(Run(f), IsOkAndHolds(false)); +} + +TEST_F(BddCsePassTest, DecodeEquivalentToDeconstructedDecode) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(2)); + BValue decode_x = fb.Decode(x); + BValue hacky_decode_x = fb.Concat( + {fb.Eq(x, fb.Literal(UBits(3, 2))), fb.Eq(x, fb.Literal(UBits(2, 2))), + fb.Eq(x, fb.Literal(UBits(1, 2))), fb.Eq(x, fb.Literal(UBits(0, 2)))}); + fb.Tuple({decode_x, hacky_decode_x}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Tuple(m::Decode(), m::Decode())); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/bdd_function.cc b/xls/passes/bdd_function.cc new file mode 100644 index 0000000000..2add466208 --- /dev/null +++ b/xls/passes/bdd_function.cc @@ -0,0 +1,348 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bdd_function.h" + +#include + +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/abstract_evaluator.h" +#include "xls/ir/abstract_node_evaluator.h" +#include "xls/ir/dfs_visitor.h" +#include "xls/ir/ir_interpreter.h" +#include "xls/ir/node.h" +#include "xls/ir/node_iterator.h" + +namespace xls { +namespace { + +// Construct a BDD-based abstract evaluator. The expressions in the BDD +// saturates at a particular number of minterms. When the minterm limit is met, +// a new BDD variable is created in its place effective forgetting any +// information about the value. This avoids exponential blowup problems when +// constructing the BDD at the cost of precision. The primitive bit element of +// the abstract evaluator is a sum type consisting of a BDD node and a sentinel +// value TooManyMinterms. The TooManyMinterms value is produced if the number of +// minterms in the computed expression exceed some limit. Any logical operation +// performed with a TooManyMinterms value produces a TooManyMinterms value. +struct TooManyMinterms {}; +using SaturatingBddNodeIndex = absl::variant; +using SaturatingBddNodeVector = std::vector; + +// The AbstractEvaluator requires equals to and not equals to operations on the +// primitive element. +bool operator==(const SaturatingBddNodeIndex& a, + const SaturatingBddNodeIndex& b) { + if (absl::holds_alternative(a) || + absl::holds_alternative(b)) { + return false; + } + return absl::get(a) == absl::get(b); +} + +bool operator!=(const SaturatingBddNodeIndex& a, + const SaturatingBddNodeIndex& b) { + return !(a == b); +} + +// Converts the given saturating BDD vector to a normal vector of BDD nodes. The +// input vector must not contain any TooManyMinterms values. +BddNodeVector ToBddNodeVector(const SaturatingBddNodeVector& input) { + BddNodeVector result(input.size()); + for (int64 i = 0; i < input.size(); ++i) { + XLS_CHECK(absl::holds_alternative(input[i])); + result[i] = absl::get(input[i]); + } + return result; +} + +// The abstract evaluator based on a BDD with minterm-saturating logic. +class SaturatingBddEvaluator + : public AbstractEvaluator { + public: + SaturatingBddEvaluator(int64 minterm_limit, BinaryDecisionDiagram* bdd) + : minterm_limit_(minterm_limit), bdd_(bdd) {} + + SaturatingBddNodeIndex One() const override { return bdd_->one(); } + + SaturatingBddNodeIndex Zero() const override { return bdd_->zero(); } + + SaturatingBddNodeIndex Not( + const SaturatingBddNodeIndex& input) const override { + if (absl::holds_alternative(input)) { + return TooManyMinterms(); + } + BddNodeIndex result = bdd_->Not(absl::get(input)); + if (minterm_limit_ > 0 && bdd_->minterm_count(result) > minterm_limit_) { + return TooManyMinterms(); + } + return result; + } + + SaturatingBddNodeIndex And(const SaturatingBddNodeIndex& a, + const SaturatingBddNodeIndex& b) const override { + if (absl::holds_alternative(a) || + absl::holds_alternative(b)) { + return TooManyMinterms(); + } + BddNodeIndex result = + bdd_->And(absl::get(a), absl::get(b)); + if (minterm_limit_ > 0 && bdd_->minterm_count(result) > minterm_limit_) { + return TooManyMinterms(); + } + return result; + } + + SaturatingBddNodeIndex Or(const SaturatingBddNodeIndex& a, + const SaturatingBddNodeIndex& b) const override { + if (absl::holds_alternative(a) || + absl::holds_alternative(b)) { + return TooManyMinterms(); + } + BddNodeIndex result = + bdd_->Or(absl::get(a), absl::get(b)); + if (minterm_limit_ > 0 && bdd_->minterm_count(result) > minterm_limit_) { + return TooManyMinterms(); + } + return result; + } + + private: + int64 minterm_limit_; + BinaryDecisionDiagram* bdd_; +}; + +// Returns whether the given op should be included in BDD computations. +bool ShouldEvaluate(Node* node) { + if (!node->GetType()->IsBits()) { + return false; + } + switch (node->op()) { + // Logical ops. + case Op::kAnd: + case Op::kNand: + case Op::kNor: + case Op::kNot: + case Op::kOr: + case Op::kXor: + return true; + + // Extension ops. + case Op::kSignExt: + case Op::kZeroExt: + return true; + + case Op::kLiteral: + return true; + + // Bit moving ops. + case Op::kBitSlice: + case Op::kConcat: + case Op::kReverse: + case Op::kIdentity: + return true; + + // Select operations. + case Op::kOneHot: + case Op::kOneHotSel: + case Op::kSel: + return true; + + // Encode/decode operations: + case Op::kDecode: + case Op::kEncode: + return true; + + // Comparison operation are only expressed if at least one of the operands + // is a literal. This avoids the potential exponential explosion of BDD + // nodes which can occur with pathological variable ordering. + case Op::kUGe: + case Op::kUGt: + case Op::kULe: + case Op::kULt: + case Op::kEq: + case Op::kNe: + return node->operand(0)->Is() || node->operand(1)->Is(); + + // Arithmetic ops + case Op::kAdd: + case Op::kSMul: + case Op::kUMul: + case Op::kNeg: + case Op::kSDiv: + case Op::kSub: + case Op::kUDiv: + return false; + + // Reduction ops. + case Op::kAndReduce: + case Op::kOrReduce: + case Op::kXorReduce: + return true; + + // Weirdo ops. + case Op::kArray: + case Op::kArrayIndex: + case Op::kCountedFor: + case Op::kInvoke: + case Op::kMap: + case Op::kParam: + case Op::kTuple: + case Op::kTupleIndex: + return false; + + // Unsupported comparison operations. + case Op::kSGt: + case Op::kSGe: + case Op::kSLe: + case Op::kSLt: + return false; + + // Shift operations. + // Shifts are very intensive to compute because they decompose into many, + // many gates and they don't seem to provide much benefit. Turn-off for now. + // TODO(meheff): Consider enabling shifts. + case Op::kShll: + case Op::kShra: + case Op::kShrl: + return false; + } +} + +} // namespace + +/* static */ xabsl::StatusOr> BddFunction::Run( + Function* f, int64 minterm_limit) { + XLS_VLOG(1) << absl::StreamFormat("BddFunction::Run(%s):", f->name()); + XLS_VLOG_LINES(5, f->DumpIr()); + + auto bdd_function = absl::WrapUnique(new BddFunction(f)); + SaturatingBddEvaluator evaluator(minterm_limit, &bdd_function->bdd()); + + // Create and return a vector containing newly defined BDD variables. + auto create_new_node_vector = [&](Node* n) { + SaturatingBddNodeVector v; + for (int64 i = 0; i < n->BitCountOrDie(); ++i) { + v.push_back(bdd_function->bdd().NewVariable()); + } + bdd_function->saturated_expressions_.insert(n); + return v; + }; + + XLS_VLOG(3) << "BDD expressions:"; + absl::flat_hash_map values; + for (Node* node : TopoSort(f)) { + if (!node->GetType()->IsBits()) { + continue; + } + // If we shouldn't evaluate this node or the node includes some + // non-bits-typed operands, then just create a vector of new BDD variables + // for this node. + if (!ShouldEvaluate(node) || + std::any_of(node->operands().begin(), node->operands().end(), + [](Node* o) { return !o->GetType()->IsBits(); })) { + values[node] = create_new_node_vector(node); + } else { + std::vector operand_values; + for (Node* operand : node->operands()) { + operand_values.push_back(values.at(operand)); + } + XLS_ASSIGN_OR_RETURN( + values[node], + AbstractEvaluate(node, operand_values, &evaluator, + /*default_handler=*/create_new_node_vector)); + + // Associate a new BDD variable with each bit that exceeded the minterm + // limit. + for (SaturatingBddNodeIndex& value : values.at(node)) { + if (absl::holds_alternative(value)) { + bdd_function->saturated_expressions_.insert(node); + value = bdd_function->bdd().NewVariable(); + } + } + } + XLS_VLOG(5) << " " << node->GetName() << ":"; + for (int64 i = 0; i < node->BitCountOrDie(); ++i) { + XLS_VLOG(5) << absl::StreamFormat( + " bit %d : %s", i, + bdd_function->bdd().ToStringDnf( + absl::get(values.at(node)[i]), + /*minterm_limit=*/15)); + } + } + + // Copy over the vector and BDD variables into the node map which is exposed + // via the BddFunction interface. At this point any TooManyMinterm sentinel + // values have been replaced with new Bdd variables. + for (const auto& pair : values) { + bdd_function->node_map_[pair.first] = ToBddNodeVector(pair.second); + } + return std::move(bdd_function); +} + +xabsl::StatusOr BddFunction::Evaluate( + absl::Span args) const { + // Map containing the result of each node. + absl::flat_hash_map values; + // Map of the BDD variable values. + absl::flat_hash_map bdd_variable_values; + XLS_RET_CHECK_EQ(args.size(), func_->params().size()); + for (Node* node : TopoSort(func_)) { + XLS_VLOG(2) << "node: " << node; + Value result; + if (node->Is()) { + XLS_ASSIGN_OR_RETURN(int64 param_index, + func_->GetParamIndex(node->As())); + result = args.at(param_index); + } else if (!node->GetType()->IsBits() || + saturated_expressions_.contains(node)) { + std::vector operand_values; + for (Node* operand : node->operands()) { + operand_values.push_back(&values.at(operand)); + } + XLS_ASSIGN_OR_RETURN(result, + ir_interpreter::EvaluateNode(node, operand_values)); + } else { + const BddNodeVector& bdd_vector = node_map_.at(node); + absl::InlinedVector bits; + for (int64 i = 0; i < bdd_vector.size(); ++i) { + XLS_ASSIGN_OR_RETURN(bool bit_result, + bdd_.Evaluate(bdd_vector[i], bdd_variable_values)); + bits.push_back(bit_result); + } + result = Value(Bits(bits)); + } + values[node] = result; + + XLS_VLOG(2) << " result: " << result; + // Write BDD variable values into the map used for evaluation. + if (node_map_.contains(node)) { + const BddNodeVector& bdd_vector = node_map_.at(node); + for (int64 i = 0; i < bdd_vector.size(); ++i) { + if (bdd_.IsVariableBaseNode(bdd_vector.at(i))) { + bdd_variable_values[bdd_vector.at(i)] = result.bits().Get(i); + } + } + } + } + return values.at(func_->return_value()); +} + +} // namespace xls diff --git a/xls/passes/bdd_function.h b/xls/passes/bdd_function.h new file mode 100644 index 0000000000..77dfcbfd7c --- /dev/null +++ b/xls/passes/bdd_function.h @@ -0,0 +1,84 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_BDD_FUNCTION_H_ +#define THIRD_PARTY_XLS_PASSES_BDD_FUNCTION_H_ + +#include "absl/container/flat_hash_map.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/statusor.h" +#include "xls/data_structures/binary_decision_diagram.h" +#include "xls/data_structures/leaf_type_tree.h" +#include "xls/ir/function.h" + +namespace xls { + +using BddNodeVector = std::vector; +using NodeMap = absl::flat_hash_map; + +// A class which represents an XLS function using a binary decision diagram +// (BDD). The BDD is constructed by an abstract evaluation of the operations in +// the function using compositions of the And/Or/Not functions of the BDD. Only +// a subset of the function's nodes are evaluated with the BDD (defined by +// BddFunction::IsExpressedInBdd). For example, arithmetic operations are not +// evaluated as they generally produce very large BDDs. Non-bits types are +// skipped as well. +// +// For each bits-typed XLS Node, BddFunction holds a BddNodeVector which is a +// vector of BDD nodes corresponding to the expression for each bit in the XLS +// Node output. +class BddFunction { + public: + // Construct a BDD representing the given function. 'minterm_limit' is an + // upper bound on the number of minterms in an expression. If a BDD node + // associated with a particular bit in the function ({Node*, bit index} pair) + // exceeds this value the bit's representation in the BDD is replaced with a + // new BDD variable. + static xabsl::StatusOr> Run( + Function* f, int64 minterm_limit = 0); + + // Returns the underlying BDD. + const BinaryDecisionDiagram& bdd() const { return bdd_; } + BinaryDecisionDiagram& bdd() { return bdd_; } + + // Returns the node associated with the given bit. + BddNodeIndex GetBddNode(Node* node, int64 bit_index) const { + XLS_CHECK(node->GetType()->IsBits()); + return node_map_.at(node).at(bit_index); + } + + // Evaluates the function using the BDD with the given argument values. + // Operations such as arithmetic operations which are not expressed in the BDD + // are evaluated using the IR interpreter. This method is for testing purposes + // only for verifying that the BDD is properly constructed. + xabsl::StatusOr Evaluate(absl::Span args) const; + + private: + explicit BddFunction(Function* f) : func_(f) {} + + Function* func_; + BinaryDecisionDiagram bdd_; + + // A map from XLS Node to vector of BDD nodes representing the XLS Node's + // expression. + NodeMap node_map_; + + // Map containing the Nodes whose expressions exceeded the maximum number of + // minterms. + absl::flat_hash_set saturated_expressions_; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_BDD_FUNCTION_H_ diff --git a/xls/passes/bdd_function_test.cc b/xls/passes/bdd_function_test.cc new file mode 100644 index 0000000000..505c9acda4 --- /dev/null +++ b/xls/passes/bdd_function_test.cc @@ -0,0 +1,133 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bdd_function.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/examples/sample_packages.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_interpreter.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" +#include "xls/ir/value_helpers.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class BddFunctionTest : public IrTestBase {}; + +TEST_F(BddFunctionTest, SimpleOr) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* t = p->GetBitsType(8); + fb.Or(fb.Param("x", t), fb.Param("y", t)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr bdd_function, + BddFunction::Run(f)); + EXPECT_THAT(bdd_function->Evaluate( + {Value(UBits(0b11110000, 8)), Value(UBits(0b10101010, 8))}), + IsOkAndHolds(Value(UBits(0b11111010, 8)))); +} + +TEST_F(BddFunctionTest, JustAParam) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Param("x", p->GetBitsType(8)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr bdd_function, + BddFunction::Run(f)); + EXPECT_THAT(bdd_function->Evaluate({Value(UBits(0b11011011, 8))}), + IsOkAndHolds(Value(UBits(0b11011011, 8)))); +} + +TEST_F(BddFunctionTest, AndNot) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* t = p->GetBitsType(8); + BValue x = fb.Param("x", t); + fb.And(x, fb.Not(x)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr bdd_function, + BddFunction::Run(f)); + // AND of a value and its inverse should be zero. + for (int64 i = 0; i < 8; ++i) { + EXPECT_EQ(bdd_function->GetBddNode(f->return_value(), i), + bdd_function->bdd().zero()); + } + + EXPECT_THAT(bdd_function->Evaluate({Value(UBits(0b11000011, 8))}), + IsOkAndHolds(Value(UBits(0, 8)))); +} + +TEST_F(BddFunctionTest, Parity) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(32)); + BValue parity = fb.Literal(UBits(0, 1)); + for (int64 i = 0; i < 32; ++i) { + parity = fb.Xor(parity, fb.BitSlice(x, i, 1)); + } + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + const int64 kNumSamples = 100; + std::minstd_rand engine; + for (int64 minterm_limit : {0, 10, 1000, 10000}) { + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr bdd_function, + BddFunction::Run(f, minterm_limit)); + EXPECT_THAT(bdd_function->Evaluate({Value(UBits(0, 32))}), + IsOkAndHolds(Value(UBits(0, 1)))); + EXPECT_THAT(bdd_function->Evaluate({Value(UBits(1, 32))}), + IsOkAndHolds(Value(UBits(1, 1)))); + EXPECT_THAT(bdd_function->Evaluate({Value(UBits(0xffffffffLL, 32))}), + IsOkAndHolds(Value(UBits(0, 1)))); + + for (int64 i = 0; i < kNumSamples; ++i) { + std::vector inputs = RandomFunctionArguments(f, &engine); + XLS_ASSERT_OK_AND_ASSIGN(Value expected, ir_interpreter::Run(f, inputs)); + XLS_ASSERT_OK_AND_ASSIGN(Value actual, bdd_function->Evaluate(inputs)); + EXPECT_EQ(expected, actual); + } + } +} + +TEST_F(BddFunctionTest, BenchmarkTest) { + // Run samples through various bechmarks and verify against the interpreter. + for (std::string benchmark : {"crc32", "sha256"}) { + XLS_ASSERT_OK_AND_ASSIGN( + std::unique_ptr p, + sample_packages::GetBenchmark(benchmark, /*optimized=*/true)); + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, p->EntryFunction()); + + std::minstd_rand engine; + const int64 kSampleCount = 32; + for (int64 minterm_limit : {10, 100, 1000}) { + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr bdd_function, + BddFunction::Run(entry, minterm_limit)); + for (int64 i = 0; i < kSampleCount; ++i) { + std::vector inputs = RandomFunctionArguments(entry, &engine); + XLS_ASSERT_OK_AND_ASSIGN(Value expected, + ir_interpreter::Run(entry, inputs)); + XLS_ASSERT_OK_AND_ASSIGN(Value actual, bdd_function->Evaluate(inputs)); + EXPECT_EQ(expected, actual); + } + } + } +} + +} // namespace +} // namespace xls diff --git a/xls/passes/bdd_query_engine.cc b/xls/passes/bdd_query_engine.cc new file mode 100644 index 0000000000..aaff6f463f --- /dev/null +++ b/xls/passes/bdd_query_engine.cc @@ -0,0 +1,120 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bdd_query_engine.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" + +namespace xls { + +/* static */ +xabsl::StatusOr> BddQueryEngine::Run( + Function* f, int64 minterm_limit) { + auto query_engine = absl::WrapUnique(new BddQueryEngine(minterm_limit)); + XLS_ASSIGN_OR_RETURN(query_engine->bdd_function_, + BddFunction::Run(f, minterm_limit)); + // Construct the Bits objects indication which bit values are statically known + // for each node and what those values are (0 or 1) if known. + BinaryDecisionDiagram& bdd = query_engine->bdd(); + for (Node* node : f->nodes()) { + if (node->GetType()->IsBits()) { + absl::InlinedVector known_bits; + absl::InlinedVector bits_values; + for (int64 i = 0; i < node->BitCountOrDie(); ++i) { + if (query_engine->GetBddNode(BitLocation(node, i)) == bdd.zero()) { + known_bits.push_back(true); + bits_values.push_back(false); + } else if (query_engine->GetBddNode(BitLocation(node, i)) == + bdd.one()) { + known_bits.push_back(true); + bits_values.push_back(true); + } else { + known_bits.push_back(false); + bits_values.push_back(false); + } + } + query_engine->known_bits_[node] = Bits(known_bits); + query_engine->bits_values_[node] = Bits(bits_values); + } + } + return std::move(query_engine); +} + +bool BddQueryEngine::AtMostOneTrue(absl::Span bits) const { + BddNodeIndex result = bdd().zero(); + for (int64 i = 0; i < bits.size(); ++i) { + if (!IsTracked(bits[i].node)) { + return false; + } + } + // Compute the OR-reduction of a pairwise AND of all bits. If this value is + // zero then no two bits can be simultaneously true. Equivalently: at most one + // bit is true. + for (int64 i = 0; i < bits.size(); ++i) { + for (int64 j = i + 1; j < bits.size(); ++j) { + result = + bdd().Or(result, bdd().And(GetBddNode(bits[i]), GetBddNode(bits[j]))); + if (ExceedsMintermLimit(result)) { + XLS_VLOG(3) << "AtMostOneTrue exceeded minterm limit of " + << minterm_limit_; + return false; + } + } + } + return result == bdd().zero(); +} + +bool BddQueryEngine::AtLeastOneTrue(absl::Span bits) const { + BddNodeIndex result = bdd().zero(); + // At least one bit is true is equivalent to an OR-reduction of all the bits. + for (const BitLocation& location : bits) { + if (!IsTracked(location.node)) { + return false; + } + result = bdd().Or(result, GetBddNode(location)); + if (ExceedsMintermLimit(result)) { + XLS_VLOG(3) << "AtLeastOneTrue exceeded minterm limit of " + << minterm_limit_; + return false; + } + } + return result == bdd().one(); +} + +bool BddQueryEngine::Implies(const BitLocation& a, const BitLocation& b) const { + if (!IsTracked(a.node) || !IsTracked(b.node)) { + return false; + } + // A implies B <=> !(A && !B) + return bdd().And(GetBddNode(a), bdd().Not(GetBddNode(b))) == bdd().zero(); +} + +bool BddQueryEngine::KnownEquals(const BitLocation& a, + const BitLocation& b) const { + if (!IsTracked(a.node) || !IsTracked(b.node)) { + return false; + } + return GetBddNode(a) == GetBddNode(b); +} + +bool BddQueryEngine::KnownNotEquals(const BitLocation& a, + const BitLocation& b) const { + if (!IsTracked(a.node) || !IsTracked(b.node)) { + return false; + } + return GetBddNode(a) == bdd().Not(GetBddNode(b)); +} + +} // namespace xls diff --git a/xls/passes/bdd_query_engine.h b/xls/passes/bdd_query_engine.h new file mode 100644 index 0000000000..5143cf1978 --- /dev/null +++ b/xls/passes/bdd_query_engine.h @@ -0,0 +1,101 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_BDD_QUERY_ENGINE_H_ +#define THIRD_PARTY_XLS_PASSES_BDD_QUERY_ENGINE_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/function.h" +#include "xls/ir/nodes.h" +#include "xls/passes/bdd_function.h" +#include "xls/passes/query_engine.h" + +namespace xls { + +// A query engine which uses binary decision diagrams (BDDs) to analyze an XLS +// function. BDDs provide sharp analysis of bits values and relationships +// between bit values in the function (relative to ternary abstract evaluation). +// The downside is that BDDs can be slow in general and exponentially slow in +// particular for some operations such as arithmetic and comparison +// operations. For this reason, these operations are generally excluded from the +// analysis. +class BddQueryEngine : public QueryEngine { + public: + // 'minterm_limit' is the maximum number of minterms to allow in a BDD + // expression before truncating it. See BddFunction for details. + static xabsl::StatusOr> Run( + Function* f, int64 minterm_limit = 0); + + bool IsTracked(Node* node) const override { + return known_bits_.contains(node); + } + + const Bits& GetKnownBits(Node* node) const override { + return known_bits_.at(node); + } + const Bits& GetKnownBitsValues(Node* node) const override { + return bits_values_.at(node); + } + + bool AtMostOneTrue(absl::Span bits) const override; + bool AtLeastOneTrue(absl::Span bits) const override; + bool Implies(const BitLocation& a, const BitLocation& b) const override; + bool KnownEquals(const BitLocation& a, const BitLocation& b) const override; + bool KnownNotEquals(const BitLocation& a, + const BitLocation& b) const override; + + // Returns the underlying BddFunction representing the XLS function. + const BddFunction& bdd_function() const { return *bdd_function_; } + + private: + explicit BddQueryEngine(int64 minterm_limit) + : minterm_limit_(minterm_limit) {} + + // Returns the underlying BDD. This method is const, but queries on a BDD + // generally mutate the object. We sneakily avoid conflicts with C++ const + // because the BDD is only held indirectly via pointers. + // TODO(meheff): Enable queries on a BDD with out mutating the BDD itself. + BinaryDecisionDiagram& bdd() const { return bdd_function_->bdd(); } + + // Returns the BDD node associated with the given bit. + BddNodeIndex GetBddNode(const BitLocation& location) const { + return bdd_function_->GetBddNode(location.node, location.bit_index); + } + + // Returns true if the expression of the given BDD node exceeds the minterm + // limit. + // TODO(meheff): This should be part of the BDD itself where a query can be + // performed and the BDD method returns a union of minterm limit exceeded or + // the result of the query. + bool ExceedsMintermLimit(BddNodeIndex node) const { + return minterm_limit_ > 0 && + bdd().GetNode(node).minterm_count > minterm_limit_; + } + + // The maximum number of minterms in expression in the BDD before truncating. + int64 minterm_limit_; + + // Indicates the bits at the output of each node which have known values. + absl::flat_hash_map known_bits_; + + // Indicates the values of bits at the output of each node (if known) + absl::flat_hash_map bits_values_; + + std::unique_ptr bdd_function_; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_BDD_QUERY_ENGINE_H_ diff --git a/xls/passes/bdd_query_engine_test.cc b/xls/passes/bdd_query_engine_test.cc new file mode 100644 index 0000000000..13cde5a67d --- /dev/null +++ b/xls/passes/bdd_query_engine_test.cc @@ -0,0 +1,109 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bdd_query_engine.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/bits.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" + +namespace xls { +namespace { + +class BddQueryEngineTest : public IrTestBase { + protected: + // Convenience methods for testing implication, equality, and inverse for + // single-bit node values. + bool Implies(const QueryEngine& engine, Node* a, Node* b) { + return engine.Implies(BitLocation(a, 0), BitLocation(b, 0)); + } + bool KnownEquals(const QueryEngine& engine, Node* a, Node* b) { + return engine.KnownEquals(BitLocation(a, 0), BitLocation(b, 0)); + } + bool KnownNotEquals(const QueryEngine& engine, Node* a, Node* b) { + return engine.KnownNotEquals(BitLocation(a, 0), BitLocation(b, 0)); + } +}; + +TEST_F(BddQueryEngineTest, EqualToPredicates) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(8)); + BValue y = fb.Param("y", p->GetBitsType(8)); + BValue x_eq_0 = fb.Eq(x, fb.Literal(UBits(0, 8))); + BValue x_eq_0_2 = fb.Eq(x, fb.Literal(UBits(0, 8))); + BValue x_ne_0 = fb.Not(x_eq_0); + BValue x_eq_42 = fb.Eq(x, fb.Literal(UBits(7, 8))); + BValue y_eq_42 = fb.Eq(y, fb.Literal(UBits(7, 8))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto query_engine, BddQueryEngine::Run(f)); + + EXPECT_TRUE(query_engine->AtMostOneNodeTrue({})); + EXPECT_FALSE(query_engine->AtMostOneBitTrue(x.node())); + EXPECT_TRUE(query_engine->AtMostOneNodeTrue({x_eq_0.node(), x_eq_42.node()})); + EXPECT_TRUE(query_engine->AtLeastOneNodeTrue({x_eq_0.node(), x_ne_0.node()})); + + EXPECT_TRUE(KnownEquals(*query_engine, x_eq_0.node(), x_eq_0.node())); + EXPECT_TRUE(KnownEquals(*query_engine, x_eq_0.node(), x_eq_0_2.node())); + EXPECT_FALSE(KnownNotEquals(*query_engine, x_eq_0.node(), x_eq_0_2.node())); + EXPECT_TRUE(KnownNotEquals(*query_engine, x_eq_0.node(), x_ne_0.node())); + + EXPECT_TRUE(Implies(*query_engine, x_eq_0.node(), x_eq_0.node())); + EXPECT_TRUE(Implies(*query_engine, x_eq_0.node(), x_eq_0_2.node())); + EXPECT_FALSE(Implies(*query_engine, x_eq_0.node(), x_eq_42.node())); + + // Unrelated values 'x' and 'y' should have no relationships. + EXPECT_FALSE(Implies(*query_engine, x_eq_42.node(), y_eq_42.node())); + EXPECT_FALSE(KnownEquals(*query_engine, x_eq_42.node(), y_eq_42.node())); + EXPECT_FALSE(KnownNotEquals(*query_engine, x_eq_42.node(), y_eq_42.node())); + EXPECT_FALSE( + query_engine->AtMostOneNodeTrue({x_eq_42.node(), y_eq_42.node()})); + EXPECT_FALSE( + query_engine->AtLeastOneNodeTrue({x_eq_42.node(), y_eq_42.node()})); +} + +TEST_F(BddQueryEngineTest, VariousComparisonPredicates) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(32)); + BValue x_eq_42 = fb.Eq(x, fb.Literal(UBits(42, 32))); + BValue x_lt_42 = fb.ULt(x, fb.Literal(UBits(42, 32))); + BValue x_ge_20 = fb.UGe(x, fb.Literal(UBits(20, 32))); + BValue x_lt_20 = fb.ULt(x, fb.Literal(UBits(20, 32))); + BValue x_eq_7 = fb.Eq(x, fb.Literal(UBits(7, 32))); + BValue x_eq_999 = fb.Eq(x, fb.Literal(UBits(999, 32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + XLS_ASSERT_OK_AND_ASSIGN(auto query_engine, BddQueryEngine::Run(f)); + + EXPECT_TRUE(query_engine->AtMostOneNodeTrue( + {x_eq_42.node(), x_eq_7.node(), x_eq_999.node()})); + EXPECT_FALSE( + query_engine->AtMostOneNodeTrue({x_lt_42.node(), x_ge_20.node()})); + + EXPECT_TRUE( + query_engine->AtLeastOneNodeTrue({x_lt_42.node(), x_ge_20.node()})); + EXPECT_TRUE( + query_engine->AtLeastOneNodeTrue({x_ge_20.node(), x_lt_20.node()})); + + EXPECT_TRUE(Implies(*query_engine, x_eq_7.node(), x_lt_42.node())); + EXPECT_FALSE(Implies(*query_engine, x_lt_42.node(), x_eq_7.node())); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/bdd_simplification_pass.cc b/xls/passes/bdd_simplification_pass.cc new file mode 100644 index 0000000000..ea2dac2fdc --- /dev/null +++ b/xls/passes/bdd_simplification_pass.cc @@ -0,0 +1,334 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bdd_simplification_pass.h" + +#include "absl/container/inlined_vector.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/logging/vlog_is_on.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits.h" +#include "xls/ir/node.h" +#include "xls/ir/node_iterator.h" +#include "xls/ir/nodes.h" +#include "xls/passes/bdd_query_engine.h" + +namespace xls { + +namespace { + +// Returns a conscise string representation of the given node if it is a +// comparator. For example, a kEq with a literal operand might produce: +// "x == 42". +std::string SelectorToString(Node* node) { + std::string op; + switch (node->op()) { + case (Op::kEq): + op = "=="; + break; + case (Op::kULt): + op = "<"; + break; + case (Op::kUGt): + op = ">"; + break; + case (Op::kULe): + op = "<="; + break; + case (Op::kUGe): + op = ">="; + break; + default: + return ""; + } + auto node_to_string = [](Node* n) { + return n->Is() ? n->As()->value().ToString() + : n->GetName(); + }; + return absl::StrFormat("%s %s %s", node_to_string(node->operand(0)), op, + node_to_string(node->operand(1))); +} + +// Collapse chain of selects with disjoint (one-hot or zero) selectors into a +// single one-hot-select. +xabsl::StatusOr CollapseSelectChains(Function* f, + const QueryEngine& query_engine) { + auto is_binary_select = [](Node* node) { + if (!node->Is(); + return (sel->cases().size() == 2 && !sel->default_value().has_value()); + }; + // A set containing the select instructions collapsed so far so we don't waste + // time considering selects which have already been optimized. + absl::flat_hash_set collapsed_selects; + bool modified = false; + + // Walk the graph in reverse order looking for chains of binary selects where + // case 0 of one select is another binary select. The diagram below shows the + // relationships in the graph between the nodes in the vectors as the chain is + // built: + // 'select_chain' : vector of binary kSelects + // 'selectors' : vector of selectors for the kSelects in 'select_chains' + // 'cases' : vector of the case=1 operands of the kSelects. + // + // | cases[n-1] + // | | + // V V + // selectors[n-1] -> select_chain[n-1] + // | + // ... + // | cases[1] + // | | + // V V + // selectors[1]} -> select_chain[1] + // | + // | cases[0] + // | | + // V V + // selectors[0] -> select_chain[0] + // | + // V + // TODO(meheff): Also merge OneHotSelects. + for (Node* node : ReverseTopoSort(f)) { + if (!is_binary_select(node) || + collapsed_selects.contains(node->As()->cases()[0]) { + select_chain.push_back(s->As( + /*selector=*/bit0_selector, + std::vector{ohs->cases()[1], ohs->cases()[0]}, + /*default_value*/ absl::nullopt) + .status()); + } + return true; + } + + // Remove kOneHot operations with an input that is already one-hot. + if (node->Is() && query_engine.AtMostOneBitTrue(node->operand(0))) { + XLS_ASSIGN_OR_RETURN( + Node * zero, + node->function()->MakeNode( + node->loc(), + Value(UBits(0, /*bit_count=*/node->operand(0)->BitCountOrDie())))); + XLS_ASSIGN_OR_RETURN(Node * operand_eq_zero, + node->function()->MakeNode( + node->loc(), node->operand(0), zero, Op::kEq)); + XLS_RETURN_IF_ERROR(node->ReplaceUsesWithNew( + std::vector{operand_eq_zero, node->operand(0)}) + .status()); + return true; + } + + return false; +} + +} // namespace + +xabsl::StatusOr BddSimplificationPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + XLS_VLOG(2) << "Running BDD simplifier on function " << f->name(); + XLS_VLOG(3) << "Before:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + // TODO(meheff): Try tuning the minterm limit. + XLS_ASSIGN_OR_RETURN(std::unique_ptr query_engine, + BddQueryEngine::Run(f, /*minterm_limit=*/4096)); + bool modified = false; + for (Node* node : TopoSort(f)) { + XLS_ASSIGN_OR_RETURN(bool node_modified, + SimplifyNode(node, *query_engine, split_ops_)); + modified |= node_modified; + } + XLS_ASSIGN_OR_RETURN(bool selects_collapsed, + CollapseSelectChains(f, *query_engine)); + + XLS_VLOG(3) << "After:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + return modified || selects_collapsed; +} + +} // namespace xls diff --git a/xls/passes/bdd_simplification_pass.h b/xls/passes/bdd_simplification_pass.h new file mode 100644 index 0000000000..20e479ad20 --- /dev/null +++ b/xls/passes/bdd_simplification_pass.h @@ -0,0 +1,45 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_BDD_SIMPLIFICATION_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_BDD_SIMPLIFICATION_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// Runs BDD-based simplifications on the function. Currently this is a very +// limited set of optimization including one-hot removal and replacement of +// statically known values with literals. +// TODO(meheff): Add more BDD-based optimizations. +class BddSimplificationPass : public FunctionPass { + public: + explicit BddSimplificationPass(bool split_ops) + : FunctionPass("bdd_simp", "BDD-based Simplification"), + split_ops_(split_ops) {} + ~BddSimplificationPass() override {} + + // Run all registered passes in order of registration. + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; + + private: + bool split_ops_; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_BDD_SIMPLIFICATION_PASS_H_ diff --git a/xls/passes/bdd_simplification_pass_test.cc b/xls/passes/bdd_simplification_pass_test.cc new file mode 100644 index 0000000000..49e00e8cde --- /dev/null +++ b/xls/passes/bdd_simplification_pass_test.cc @@ -0,0 +1,184 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bdd_simplification_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" + +namespace m = ::xls::op_matchers; + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class BddSimplificationPassTest : public IrTestBase { + protected: + xabsl::StatusOr Run(Function* f) { + PassResults results; + XLS_ASSIGN_OR_RETURN(bool changed, + BddSimplificationPass(/*split_ops=*/true) + .RunOnFunction(f, PassOptions(), &results)); + return changed; + } +}; + +TEST_F(BddSimplificationPassTest, ReplaceAllKnownValues) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(4)); + BValue y = fb.Param("y", p->GetBitsType(4)); + BValue x_or_not_x = fb.Or(x, fb.Not(x)); + BValue y_and_not_y = fb.And(y, fb.Not(y)); + fb.Concat({x_or_not_x, y_and_not_y}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + + EXPECT_THAT(f->return_value(), m::Literal(0b11110000)); +} + +TEST_F(BddSimplificationPassTest, ReplaceKnownPrefix) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(16)); + BValue y = fb.Param("y", p->GetBitsType(9)); + fb.And(x, fb.Concat({fb.Literal(UBits(0, 7)), y})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + + EXPECT_THAT(f->return_value(), + m::Concat(m::Literal(0), m::BitSlice(m::And()))); +} + +TEST_F(BddSimplificationPassTest, ReplaceKnownSuffix) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(32)); + BValue y = fb.Param("y", p->GetBitsType(31)); + fb.Or(x, fb.Concat({y, fb.Literal(UBits(1, 1))})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + + EXPECT_THAT(f->return_value(), + m::Concat(m::BitSlice(m::Or()), m::Literal(1))); +} + +TEST_F(BddSimplificationPassTest, KnownSuffixButNotReplaced) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(32)); + // The suffix (least-significant bits) of the expression is known the + // expression is not simplified because the "simplification" is the same as + // the expression itself (concat of a literal). + fb.Concat({x, fb.Literal(UBits(123, 10))}); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(Run(f), IsOkAndHolds(false)); + + EXPECT_THAT(f->return_value(), m::Concat(m::Param("x"), m::Literal(123))); +} + +TEST_F(BddSimplificationPassTest, RemoveRedundantOneHot) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue x = fb.Param("x", p->GetBitsType(8)); + BValue x_eq_0 = fb.Eq(x, fb.Literal(UBits(0, 8))); + BValue x_eq_42 = fb.Eq(x, fb.Literal(UBits(42, 8))); + BValue x_gt_123 = fb.UGt(x, fb.Literal(UBits(123, 8))); + fb.OneHot(fb.Concat({x_eq_0, x_eq_42, x_gt_123}), LsbOrMsb::kLsb); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Concat(m::Eq(), m::Concat())); +} + +TEST_F(BddSimplificationPassTest, ConvertTwoWayOneHotSelect) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(p: bits[1], x: bits[32], y: bits[32]) -> bits[32] { + not.1: bits[1] = not(p) + concat.2: bits[2] = concat(p, not.1) + ret one_hot_sel.3: bits[32] = one_hot_sel(concat.2, cases=[x, y]) + } + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Select(m::BitSlice(), /*cases=*/{ + m::Param("y"), m::Param("x")})); +} + +TEST_F(BddSimplificationPassTest, SelectChainOneHot) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue s = fb.Param("s", p->GetBitsType(2)); + BValue pred0 = fb.Eq(s, fb.Literal(UBits(0, 2))); + BValue pred1 = fb.Eq(s, fb.Literal(UBits(1, 2))); + BValue pred2 = fb.Eq(s, fb.Literal(UBits(2, 2))); + BValue pred3 = fb.Eq(s, fb.Literal(UBits(3, 2))); + auto param = [&](absl::string_view s) { + return fb.Param(s, p->GetBitsType(8)); + }; + fb.Select(pred3, param("x3"), + fb.Select(pred2, param("x2"), + fb.Select(pred1, param("x1"), + fb.Select(pred0, param("x0"), param("y"))))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::OneHotSelect(m::Concat(m::Eq(m::Param("s"), m::Literal(3)), + m::Eq(m::Param("s"), m::Literal(2)), + m::Eq(m::Param("s"), m::Literal(1)), + m::Eq(m::Param("s"), m::Literal(0))), + {m::Param("x0"), m::Param("x1"), m::Param("x2"), + m::Param("x3")})); +} + +TEST_F(BddSimplificationPassTest, SelectChainOneHotOrZeroSelectors) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + BValue s = fb.Param("s", p->GetBitsType(8)); + BValue pred0 = fb.UGt(s, fb.Literal(UBits(42, 8))); + BValue pred1 = fb.Eq(s, fb.Literal(UBits(11, 8))); + BValue pred2 = fb.ULt(s, fb.Literal(UBits(7, 8))); + auto param = [&](absl::string_view s) { + return fb.Param(s, p->GetBitsType(8)); + }; + fb.Select( + pred2, param("x2"), + fb.Select(pred1, param("x1"), fb.Select(pred0, param("x0"), param("y")))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::OneHotSelect(m::Concat(m::ULt(m::Param("s"), m::Literal(7)), + m::Eq(m::Param("s"), m::Literal(11)), + m::UGt(m::Param("s"), m::Literal(42)), + m::Nor(m::ULt(), m::Eq(), m::UGt())), + {m::Param("y"), m::Param("x0"), m::Param("x1"), + m::Param("x2")})); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/bit_slice_simplification_pass.cc b/xls/passes/bit_slice_simplification_pass.cc new file mode 100644 index 0000000000..24d0e4a81a --- /dev/null +++ b/xls/passes/bit_slice_simplification_pass.cc @@ -0,0 +1,273 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bit_slice_simplification_pass.h" + +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/node_iterator.h" + +namespace xls { +namespace { + +// Attempts to replace the given bit slice with a simpler or more canonical +// form. Returns true if the bit slice was replaced. Any newly created +// bit-slices are added to the worklist. +xabsl::StatusOr SimplifyBitSlice(BitSlice* bit_slice, + std::deque* worklist) { + Node* operand = bit_slice->operand(0); + BitsType* operand_type = operand->GetType()->AsBitsOrDie(); + + // Creates a new bit slice and adds it to the worklist. + auto make_bit_slice = [&](absl::optional loc, Node* operand, + int64 start, + int64 width) -> xabsl::StatusOr { + XLS_ASSIGN_OR_RETURN( + BitSlice * new_bit_slice, + bit_slice->function()->MakeNode(loc, operand, start, width)); + worklist->push_back(new_bit_slice); + return new_bit_slice; + }; + + // A full width slice is a nop. + if (bit_slice->start() == 0 && + bit_slice->width() == operand_type->bit_count()) { + return bit_slice->ReplaceUsesWith(operand); + } + + // A slice of a slice can be replaced by a single slice. + // BitSlice(BitSlice(val, ...), ...) -> BitSlice(val, ...) + if (operand->Is()) { + BitSlice* operand_bit_slice = operand->As(); + XLS_RETURN_IF_ERROR( + bit_slice + ->ReplaceUsesWithNew( + operand->operand(0), + /*start=*/bit_slice->start() + operand_bit_slice->start(), + /*width=*/bit_slice->width()) + .status()); + return true; + } + + // A slice can be hoisted above a concat. The new concat will have a subset of + // the original concats operands. The first and last operand of the concat may + // themselves be sliced. Example: + // + // BitSlice(Concat(a, b, c, d), ..) -> Concat(BitSlice(b), c, BitSlice(d)) + // + if (operand->Is()) { + Concat* concat = operand->As(); + std::vector new_operands; + // Inclusive bounds of the start/end of the bit slice. Values are bit + // indices within the output of the concat. + const int64 slice_start = bit_slice->start(); + const int64 slice_end = bit_slice->start() + bit_slice->width() - 1; + + // Start index of the current operand in the iteration. + int64 operand_start = 0; + + // Iterate through the operands in reverse order because this is the + // increasing order of bit indices. + for (int64 i = concat->operand_count() - 1; i >= 0; --i) { + Node* concat_operand = concat->operand(i); + const int64 concat_operand_width = concat_operand->BitCountOrDie(); + // Inclusive bound on the bit offset of the operand in the concat + // operation. + const int64 operand_end = operand_start + concat_operand_width - 1; + + if (operand_start > slice_end) { + // Operand (and all subsequent operands) is entirely after the end of + // the slice. + break; + } + + if (slice_start > operand_end) { + // Operand is entirely before the beginning of the slice (lower bit + // index). + } else if (slice_start <= operand_start && slice_end >= operand_end) { + // Operand is entirely within the slice. + new_operands.push_back(concat_operand); + } else { + // Operand is partially within the slice. Slice out the part of the + // operand which is within the slice and add it to the list of operands + // for the replacement concat. + const int64 operand_slice_start = std::max(slice_start, operand_start); + const int64 operand_slice_end = std::min(slice_end, operand_end); + XLS_ASSIGN_OR_RETURN( + Node * operand_slice, + make_bit_slice(bit_slice->loc(), concat_operand, + /*start=*/operand_slice_start - operand_start, + operand_slice_end - operand_slice_start + 1)); + new_operands.push_back(operand_slice); + } + operand_start += concat_operand_width; + } + std::reverse(new_operands.begin(), new_operands.end()); + XLS_RETURN_IF_ERROR( + bit_slice->ReplaceUsesWithNew(new_operands).status()); + return true; + } + + // Bit slice that is the sole consumer of an op can often lead the slice to + // propagate into the operands to reduce the work the op has to do. + if (operand->users().size() <= 1) { + // For bitwise operations we can always slice the operands (because it's a + // bit-parallel operation). + // + // For arithmetic operations that carry low bits into high bits, where the + // slice starts at the LSb, we can slice the operands instead of the output. + // (Note this is not possible for operations that carry high bits into low + // bits like right shifts.) + // + // Note we can't simplify a shll without clamping the RHS; e.g. + // + // x: bits[4] + // y: bits[4] + // (x << y)[2:0] + // + // Where "y = 8" would cause the shift amount to go from "total" to "zero" + // if we sliced the "y" value. + bool low_bits_of_arith_output = + bit_slice->start() == 0 && + (operand->op() == Op::kAdd || operand->op() == Op::kSub || + operand->op() == Op::kNeg); + if (OpIsBitWise(operand->op()) || low_bits_of_arith_output) { + std::vector sliced_operands; + Function* f = bit_slice->function(); + for (Node* o : operand->operands()) { + XLS_ASSIGN_OR_RETURN(Node * new_operand, + make_bit_slice(o->loc(), o, bit_slice->start(), + bit_slice->width())); + sliced_operands.push_back(new_operand); + } + XLS_ASSIGN_OR_RETURN(Node * pre_sliced, + operand->Clone(sliced_operands, f)); + XLS_RETURN_IF_ERROR(bit_slice->ReplaceUsesWith(pre_sliced).status()); + return true; + } + } + + // Hoist slices above sign-extends. Let: + // + // X = ... + // ext = sign_ext(X, new_width=nw) + // slice = bit_slice(ext, start=s, width=w) + // + // Represent ext as 'ssssssssXXXXXXXX' where the 's's are the extended sign + // bits and the 'X's are the bits of the value being extended. There are three + // possibilities depending upon where the slice falls relative to the sign + // bit. + // + // + // (1) Slice entirely in sign-extend operand: + // + // ssssssssssssXXXXXXXXXXXX + // | slice | + // + // Transformation: replace the slice the sign-extend with a slice of X + // (the sign-extend's operand). + // + // (2) Slice spans the sign bit of the sign-extend operand. + // + // ssssssssssssXXXXXXXXXXXX + // | slice | + // + // Transformation: slice the most-significant bits from X and sign-extend + // the result. + // + // (3) Slice is entirely within the sign extended bits. + // + // ssssssssssssXXXXXXXXXXXX + // | slice | + // + // Transformation: slice the sign bit from X and sign-extend the result. + // + // To avoid introducing an additional sign-extension cases (2) and (3) should + // only be performed if the bit-slice is the only user of the sign-extend. + if (bit_slice->operand(0)->op() == Op::kSignExt) { + ExtendOp* ext = bit_slice->operand(0)->As(); + Node* x = ext->operand(0); + int64 x_bit_count = x->BitCountOrDie(); + if (bit_slice->start() + bit_slice->width() <= x_bit_count) { + // Case (1), replace with slice of sign-extend's operand. + XLS_ASSIGN_OR_RETURN( + Node * replacement, + make_bit_slice(bit_slice->loc(), x, bit_slice->start(), + bit_slice->width())); + XLS_RETURN_IF_ERROR(bit_slice->ReplaceUsesWith(replacement).status()); + return true; + } else if (ext->users().size() == 1) { + if (bit_slice->start() < x_bit_count) { + // Case (2), slice straddles the sign bit. + XLS_ASSIGN_OR_RETURN( + Node * x_slice, + make_bit_slice(bit_slice->loc(), x, /*start=*/bit_slice->start(), + /*width=*/x_bit_count - bit_slice->start())); + XLS_RETURN_IF_ERROR( + bit_slice + ->ReplaceUsesWithNew( + x_slice, + /*new_bit_count=*/bit_slice->BitCountOrDie(), Op::kSignExt) + .status()); + } else { + // Case (3), slice includes only the extended bits. + XLS_ASSIGN_OR_RETURN( + Node * x_sign_bit, + make_bit_slice(bit_slice->loc(), x, + /*start=*/x_bit_count - 1, /*width=*/1)); + XLS_RETURN_IF_ERROR( + bit_slice + ->ReplaceUsesWithNew( + x_sign_bit, + /*new_bit_count=*/bit_slice->BitCountOrDie(), Op::kSignExt) + .status()); + } + return true; + } + } + return false; +} + +} // namespace + +xabsl::StatusOr BitSliceSimplificationPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + XLS_VLOG(2) << "Running bit-slice simplifier on function " << f->name(); + XLS_VLOG(3) << "Before:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + std::deque worklist; + for (Node* node : f->nodes()) { + if (node->Is()) { + worklist.push_back(node->As()); + } + } + bool changed = false; + while (!worklist.empty()) { + BitSlice* bit_slice = worklist.front(); + worklist.pop_front(); + XLS_ASSIGN_OR_RETURN(bool node_changed, + SimplifyBitSlice(bit_slice, &worklist)); + changed = changed || node_changed; + } + + XLS_VLOG(3) << "After:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + return changed; +} + +} // namespace xls diff --git a/xls/passes/bit_slice_simplification_pass.h b/xls/passes/bit_slice_simplification_pass.h new file mode 100644 index 0000000000..d69a07ef7d --- /dev/null +++ b/xls/passes/bit_slice_simplification_pass.h @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_BIT_SLICE_SIMPLIFICATION_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_BIT_SLICE_SIMPLIFICATION_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// Pass which simplifies bit-slices. This includes collapsing sequential +// bit-slices, eliminating degenerate full-width slices, and others. +class BitSliceSimplificationPass : public FunctionPass { + public: + BitSliceSimplificationPass() + : FunctionPass("bitslice_simp", "Bit-slice simplification") {} + ~BitSliceSimplificationPass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_BIT_SLICE_SIMPLIFICATION_PASS_H_ diff --git a/xls/passes/bit_slice_simplification_pass_test.cc b/xls/passes/bit_slice_simplification_pass_test.cc new file mode 100644 index 0000000000..c44eb80f38 --- /dev/null +++ b/xls/passes/bit_slice_simplification_pass_test.cc @@ -0,0 +1,254 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/bit_slice_simplification_pass.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/substitute.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_interpreter.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/value.h" +#include "xls/passes/dce_pass.h" + +namespace m = ::xls::op_matchers; + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class BitSliceSimplificationPassTest : public IrTestBase { + protected: + BitSliceSimplificationPassTest() = default; + + xabsl::StatusOr Run(Function* f) { + PassResults results; + XLS_ASSIGN_OR_RETURN( + bool changed, + BitSliceSimplificationPass().RunOnFunction(f, PassOptions(), &results)); + XLS_RETURN_IF_ERROR(DeadCodeEliminationPass() + .RunOnFunction(f, PassOptions(), &results) + .status()); + return changed; + } +}; + +TEST_F(BitSliceSimplificationPassTest, FullWidthSlice) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn FullWidthSlice(x: bits[42]) -> bits[42] { + ret full_slice: bits[42] = bit_slice(x, start=0, width=42) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 2); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 1); + EXPECT_TRUE(f->return_value()->Is()); +} + +TEST_F(BitSliceSimplificationPassTest, SliceOfASlice) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn SliceOfASlice(x: bits[123]) -> bits[32] { + slice.1: bits[74] = bit_slice(x, start=30, width=74) + ret slice.2: bits[32] = bit_slice(slice.1, start=15, width=32) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 3); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 2); + EXPECT_THAT(f->return_value(), + m::BitSlice(m::Param(), /*start=*/45, /*width=*/32)); +} + +TEST_F(BitSliceSimplificationPassTest, SliceOfTrivialConcat) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn SliceOfTrivialConcat(x: bits[42]) -> bits[10] { + concat.1: bits[42] = concat(x) + ret slice.2: bits[10] = bit_slice(concat.1, start=5, width=10) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 3); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 3); + EXPECT_THAT(f->return_value(), + m::Concat(m::BitSlice(m::Param(), /*start=*/5, /*width=*/10))); +} + +TEST_F(BitSliceSimplificationPassTest, SliceOfConcats) { + const char fn_template[] = R"( +package SliceOfConcats + +fn main(x: bits[4], y: bits[1], z: bits[4]) -> bits[$1] { + concat.1: bits[9] = concat(x, y, z) + ret slice.2: bits[$1] = bit_slice(concat.1, start=$0, width=$1) +})"; + auto gen_fn = [&](int64 start, int64 width) { + return absl::Substitute(fn_template, start, width); + }; + + const int64 kInputWidth = 9; + const Value x = Value(UBits(0xa, 4)); + const Value y = Value(UBits(1, 1)); + const Value z = Value(UBits(0x5, 4)); + + // Try all possible combinations of start and width. Verify that the + // interpreted IR generates the same value before and after. + for (int64 start = 0; start < kInputWidth; ++start) { + for (int64 width = 1; width < kInputWidth - start; ++width) { + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackage(gen_fn(start, width))); + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, p->EntryFunction()); + XLS_ASSERT_OK_AND_ASSIGN(Value expected, + ir_interpreter::Run(entry, {x, y, z})); + + EXPECT_TRUE(entry->return_value()->Is()); + EXPECT_THAT(Run(entry), IsOkAndHolds(true)); + EXPECT_TRUE(entry->return_value()->Is()); + + XLS_ASSERT_OK_AND_ASSIGN(Value actual, + ir_interpreter::Run(entry, {x, y, z})); + EXPECT_EQ(expected, actual); + } + } +} + +TEST_F(BitSliceSimplificationPassTest, SoleSliceOfAnd) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[42], y: bits[42]) -> bits[32] { + and.3: bits[42] = and(x, y) + ret slice.4: bits[32] = bit_slice(and.3, start=7, width=32) + } + )", + p.get())); + EXPECT_TRUE(f->return_value()->Is()); + XLS_ASSERT_OK(Run(f)); + EXPECT_THAT(f->return_value(), + m::And(m::BitSlice(/*start=*/7, /*width=*/32), + m::BitSlice(/*start=*/7, /*width=*/32))); +} + +TEST_F(BitSliceSimplificationPassTest, SoleSliceLowBitsOfAdd) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[42], y: bits[42]) -> bits[32] { + add.3: bits[42] = add(x, y) + ret slice.4: bits[32] = bit_slice(add.3, start=0, width=32) + } + )", + p.get())); + EXPECT_TRUE(f->return_value()->Is()); + XLS_ASSERT_OK(Run(f)); + EXPECT_THAT(f->return_value(), + m::Add(m::BitSlice(/*start=*/0, /*width=*/32), + m::BitSlice(/*start=*/0, /*width=*/32))); +} + +TEST_F(BitSliceSimplificationPassTest, + SoleSliceNotLowBitsOfAddDoesNotOptimize) { + auto p = CreatePackage(); + const std::string program = R"(fn f(x: bits[42], y: bits[42]) -> bits[32] { + add.3: bits[42] = add(x, y) + ret bit_slice.4: bits[32] = bit_slice(add.3, start=1, width=32) +} +)"; + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(program, p.get())); + EXPECT_TRUE(f->return_value()->Is()); + XLS_ASSERT_OK(Run(f)); + EXPECT_EQ(f->DumpIr(), program); +} + +TEST_F(BitSliceSimplificationPassTest, SliceOfSignExtCaseOne) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8]) -> bits[5] { + sign_ext.2: bits[24] = sign_ext(x, new_bit_count=24) + ret bit_slice.3: bits[5] = bit_slice(sign_ext.2, start=2, width=5) + } + )", + p.get())); + ASSERT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::BitSlice(m::Param())); +} + +TEST_F(BitSliceSimplificationPassTest, SliceOfSignExtCaseOneStartingAtZero) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8]) -> bits[16] { + sign_ext.2: bits[24] = sign_ext(x, new_bit_count=24) + ret bit_slice.3: bits[16] = bit_slice(sign_ext.2, start=0, width=16) + } + )", + p.get())); + ASSERT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::SignExt(m::Param())); +} + +TEST_F(BitSliceSimplificationPassTest, SliceOfSignExtCaseTwo) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8]) -> bits[5] { + sign_ext.2: bits[24] = sign_ext(x, new_bit_count=24) + ret bit_slice.3: bits[5] = bit_slice(sign_ext.2, start=4, width=5) + } + )", + p.get())); + ASSERT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::SignExt(m::BitSlice(m::Param(), /*start=*/4, /*width=*/4))); +} + +TEST_F(BitSliceSimplificationPassTest, SliceOfSignExtCaseTwoExactlyTheSignBit) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8]) -> bits[5] { + sign_ext.2: bits[24] = sign_ext(x, new_bit_count=24) + ret bit_slice.3: bits[5] = bit_slice(sign_ext.2, start=7, width=5) + } + )", + p.get())); + ASSERT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::SignExt(m::BitSlice(m::Param(), /*start=*/7, /*width=*/1))); +} + +TEST_F(BitSliceSimplificationPassTest, SliceOfSignExtCaseCaseThree) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8]) -> bits[5] { + sign_ext.2: bits[24] = sign_ext(x, new_bit_count=24) + ret bit_slice.3: bits[5] = bit_slice(sign_ext.2, start=12, width=5) + } + )", + p.get())); + ASSERT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::SignExt(m::BitSlice(m::Param(), /*start=*/7, /*width=*/1))); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/boolean_simplification_pass.cc b/xls/passes/boolean_simplification_pass.cc new file mode 100644 index 0000000000..adae2ffbcd --- /dev/null +++ b/xls/passes/boolean_simplification_pass.cc @@ -0,0 +1,484 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/boolean_simplification_pass.h" + +#include "absl/status/status.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/node_util.h" +#include "xls/netlist/logical_effort.h" + +namespace xls { +namespace { + +// We store up to two "frontier nodes" on the frontier of the boolean +// computation -- if we have more than this many "frontier nodes", we switch +// over to "TooMany" via the TooManySentinel type below. +constexpr int64 kMaxFrontierNodes = 3; + +} // namespace + +namespace internal { + +TruthTable::TruthTable(const Bits& xyz_present, const Bits& xyz_negated, + absl::optional logical_op) + : xyz_present_(xyz_present), + xyz_negated_(xyz_negated), + logical_op_(logical_op) { + XLS_CHECK(!xyz_present.IsAllZeros()); + XLS_CHECK_EQ(3, xyz_present.bit_count()); + XLS_CHECK_EQ(3, xyz_negated.bit_count()); + if (!logical_op.has_value()) { + XLS_CHECK_EQ(1, xyz_present.PopCount()); + XLS_CHECK_LE(xyz_negated.PopCount(), 1); + } + // Check for not-present bits that are negated. + XLS_CHECK( + bits_ops::And(bits_ops::Not(xyz_present), xyz_negated).IsAllZeros()); +} + +/* static */ Bits TruthTable::GetInitialVector(int64 i) { + XLS_CHECK_LT(i, kMaxFrontierNodes); + switch (i) { + case 0: + return UBits(0b00001111, /*bit_count=*/8); + case 1: + return UBits(0b00110011, /*bit_count=*/8); + case 2: + return UBits(0b01010101, /*bit_count=*/8); + } + XLS_LOG(FATAL) << "Unreachable."; +} + +/* static */ Bits TruthTable::RunNaryOp(Op op, + absl::Span operands) { + switch (op) { + case Op::kAnd: + return bits_ops::NaryAnd(operands); + case Op::kOr: + return bits_ops::NaryOr(operands); + case Op::kNand: + return bits_ops::NaryNand(operands); + case Op::kNor: + return bits_ops::NaryNor(operands); + case Op::kXor: + return bits_ops::NaryXor(operands); + default: + XLS_LOG(FATAL) << "Unhandled nary logical operation: " << op; + } +} + +Bits TruthTable::ComputeTruthTable() const { + std::vector operands; + for (int64 i = 0; i < kMaxFrontierNodes; ++i) { + if (xyz_present_.GetFromMsb(i)) { + Bits bit_vector = GetInitialVector(i); + if (xyz_negated_.GetFromMsb(i)) { + bit_vector = bits_ops::Not(bit_vector); + } + operands.push_back(bit_vector); + } + } + if (logical_op_.has_value()) { + return RunNaryOp(logical_op_.value(), operands); + } + XLS_CHECK_EQ(1, operands.size()); + return operands[0]; +} + +bool TruthTable::MatchesVector(const Bits& table) const { + return ComputeTruthTable() == table; +} + +bool TruthTable::MatchesSymmetrical( + Node* original, absl::Span operands) const { + if (logical_op_.has_value()) { + if (original->op() != logical_op_.value()) { + return false; + } + for (int64 i = 0; i < kMaxFrontierNodes; ++i) { + if (!xyz_present_.GetFromMsb(i)) { + continue; // Don't care about this operand. + } + if (i >= operands.size()) { + // Not enough operands to match this truth table. + return false; + } + if (xyz_negated_.GetFromMsb(i)) { + if (!AnyOperandWhere( + original, [&](Node* o) { return IsNotOf(o, operands[i]); })) { + return false; + } + } else { // Present, not negated. + if (!AnyOperandWhere(original, + [&](Node* o) { return o == operands[i]; })) { + return false; + } + } + } + return true; + } + // When there is no logical function, only one of the "present" bits may be + // set. + XLS_CHECK_EQ(1, xyz_present_.PopCount()); + for (int64 i = 0; i < kMaxFrontierNodes; ++i) { + if (!xyz_present_.GetFromMsb(i)) { + continue; // Don't care about this operand. + } + if (xyz_negated_.GetFromMsb(i)) { + return IsNotOf(original, operands[i]); + } else { + return original == operands[i]; + } + } + XLS_LOG(FATAL) << "Unreachable."; +} + +xabsl::StatusOr TruthTable::CreateReplacement( + const absl::optional& original_loc, + absl::Span operands, Function* f) const { + XLS_CHECK_LE(operands.size(), kMaxFrontierNodes); + std::vector this_operands; + for (int64 i = 0; i < kMaxFrontierNodes; ++i) { + if (!xyz_present_.GetFromMsb(i)) { + continue; + } + Node* operand = operands[i]; + if (xyz_negated_.GetFromMsb(i)) { + XLS_ASSIGN_OR_RETURN(operand, + f->MakeNode(original_loc, operand, Op::kNot)); + } + this_operands.push_back(operand); + } + if (!logical_op_.has_value()) { + XLS_CHECK_EQ(1, this_operands.size()); + return this_operands[0]; + } + return f->MakeNode(original_loc, this_operands, logical_op_.value()); +} + +} // namespace internal + +namespace { + +using xls::internal::TruthTable; + +// Indicates more than kMaxFrontierNodes are involved in a boolean expression. +struct TooManySentinel {}; + +// Type that notes the frontier nodes involved in a boolean expression. +using FrontierVector = absl::InlinedVector; +using Frontier = absl::variant; + +// Convenient converter of the Frontier type to string for debugging. +std::string ToString(const Frontier& x) { + if (absl::holds_alternative(x)) { + return "Frontier()"; + } + const auto& nodes = absl::get(x); + return absl::StrFormat("Frontier([%s])", + absl::StrJoin(nodes, ", ", NodeFormatter)); +} + +bool HasFrontierVector(const Frontier& frontier) { + return absl::holds_alternative(frontier); +} +const FrontierVector& GetFrontierVector(const Frontier& frontier) { + return absl::get(frontier); +} + +// Adds a frontier "node" to the set -- note this may push it over to the +// "TooManySentinel" mode. +void AddNonBool(Node* node, Frontier* frontier) { + if (absl::holds_alternative(*frontier)) { + return; + } + auto& nodes = absl::get(*frontier); + if (std::find(nodes.begin(), nodes.end(), node) != nodes.end()) { + return; // Already present. + } + if (nodes.size() >= kMaxFrontierNodes) { + *frontier = TooManySentinel{}; + return; + } + nodes.push_back(node); +} + +// Unions the "arg" frontier set into the "out" frontier set. Note this may push +// it over to the "TooManySentinel" mode. +void Union(const Frontier& arg, Frontier* out) { + if (absl::holds_alternative(*out)) { + return; + } + if (absl::holds_alternative(arg)) { + *out = TooManySentinel{}; + return; + } + for (Node* node : absl::get(arg)) { + AddNonBool(node, out); + } +} + +// We arbitrarily call the node at index 0 in the frontier "X", and the one at +// index 1 "Y". +Node* GetX(const Frontier& frontier) { + return absl::get(frontier)[0]; +} +Node* GetY(const Frontier& frontier) { + return absl::get(frontier)[1]; +} +Node* GetZ(const Frontier& frontier) { + const auto& v = absl::get(frontier); + return v.size() > 2 ? v[2] : nullptr; +} + +// DFS tracker for boolean value flow. +// +// When we arrive at a node we potentially replace it with a simpler boolean +// expression. +// +// * Performing the flow to find the composite logical operation is done by +// FlowFromFrontierToNode(). +// * Determining what sub-expression to replace the composite logical operation +// with is done by ResolveTruthTable(). +// +// Note that because we aggressively rewrite all intermediate logical operations +// to their simplest known form, we may increase the amount of gates required by +// the computation overall (we're breaking dependencies and simplifying +// composite expression instead of reusing all intermediaries in the way +// originally specified). Since we expect bitwise / boolean operations to be +// relatively cheap, this seems worthwhile. +class BooleanFlowTracker : public DfsVisitorWithDefault { + public: + absl::Status HandleNaryAnd(NaryOp* and_op) override { + return HandleLogicOp(and_op); + } + absl::Status HandleNaryNand(NaryOp* nand_op) override { + return HandleLogicOp(nand_op); + } + absl::Status HandleNaryNor(NaryOp* nor_op) override { + return HandleLogicOp(nor_op); + } + absl::Status HandleNot(UnOp* not_op) override { + return HandleLogicOp(not_op); + } + absl::Status HandleNaryOr(NaryOp* or_op) override { + return HandleLogicOp(or_op); + } + + static absl::flat_hash_map* CreateTruthTables() { + auto results = new absl::flat_hash_map; + auto add = [results](TruthTable table) { + uint64 truth_table_bits = table.ComputeTruthTable().ToUint64().value(); + // Note: we don't update to the lowest-cost table, because other passes + // (e.g. simplification passes) seem to do better with simpler operations + // (e.g. or(x, y, z) instead of nand(~x, ~y, ~z) -- we also don't have a + // very many overlapping truth tables that fail to follow the simple cost + // ordering defined in the loops below. + results->insert({truth_table_bits, table}); + }; + + // Approximately in cheapest-to-more-expensive order. + const auto kLogicOps = {Op::kNand, Op::kNor, Op::kXor, Op::kAnd, Op::kOr}; + + // First populate the truth tables for single values and their negations. + for (int64 presence = 0b0001; presence < 0b1000; presence <<= 1) { + for (bool negate : {false, true}) { + add(TruthTable(UBits(presence, /*bit_count=*/3), + UBits(negate ? presence : 0LL, /*bit_count=*/3), + absl::nullopt)); + } + } + + // Then populate the truth tables for the operations, approximately + // sequenced from cheap to more expensive. + for (Op op : kLogicOps) { + for (int64 presence : {0b011, 0b101, 0b110, 0b111}) { + const uint64 negation = 0; + add(TruthTable(UBits(presence, /*bit_count=*/3), + UBits(negation, /*bit_count=*/3), op)); + } + } + // Now add operations that negate their operands. + for (Op op : kLogicOps) { + for (int64 presence : {0b011, 0b101, 0b110, 0b111}) { + for (int64 negation = 1; negation < 0b1000; ++negation) { + if ((~presence & negation)) { + // Don't negate things that are not present. + continue; + } + add(TruthTable(UBits(presence, /*bit_count=*/3), + UBits(negation, /*bit_count=*/3), op)); + } + } + } + return results; + } + + // Returns a memoized (constant) version of all the truth tables. + static const absl::flat_hash_map& GetTruthTables() { + static absl::flat_hash_map* tables = + CreateTruthTables(); + return *tables; + } + + // "operands" are the nodes on the frontier of the logical operations. + xabsl::StatusOr ResolveTruthTable(const Bits& bits, + absl::Span operands, + Node* original) { + XLS_RET_CHECK(2 <= operands.size() && operands.size() <= 3); + Function* f = original->function(); + if (bits.IsAllOnes()) { + return f->MakeNode(original->loc(), + Value(SBits(-1, original->BitCountOrDie()))); + } + if (bits.IsAllZeros()) { + return f->MakeNode(original->loc(), + Value(UBits(0, original->BitCountOrDie()))); + } + + const auto& truth_tables = GetTruthTables(); + auto it = truth_tables.find(bits.ToUint64().value()); + if (it == truth_tables.end()) { + return nullptr; // No match. + } + const TruthTable& table = it->second; + if (table.MatchesSymmetrical(original, operands)) { + // Already in minimal form. + return nullptr; + } + return table.CreateReplacement(original->loc(), operands, f); + } + + absl::Status DefaultHandler(Node* node) override { return absl::OkStatus(); } + + absl::Span> node_replacements() { + return node_replacements_; + } + + private: + // Flows a truth table from the frontier to the output node "node". + // + // Starting at node, recursively invokes itself until it gets to the "frontier + // nodes" at the frontier of the boolean computation. Those get initialized + // with the full truth table of possibiliites for two variables: + // + // X: 0 0 1 1 0 0 1 1 + // Y: 0 1 0 1 0 1 0 1 + // Z: 0 0 0 0 1 1 1 1 + // + // Once we've pushed this vector of possiblities though all the intermediate + // bitwise nodes what we're left with at "node" is the resulting logical + // function. At that point we can just look up whether there's a simplified + // expression of that logical function is and replace it accordingly, as we do + // in ResolveTruthTable(). + xabsl::StatusOr FlowFromFrontierToNode(const Frontier& frontier, + Node* node) { + if (node == GetX(frontier)) { + return TruthTable::GetInitialVector(0); + } + if (node == GetY(frontier)) { + return TruthTable::GetInitialVector(1); + } + if (node == GetZ(frontier)) { + return TruthTable::GetInitialVector(2); + } + std::vector operands; + for (Node* operand : node->operands()) { + XLS_ASSIGN_OR_RETURN(Bits result, + FlowFromFrontierToNode(frontier, operand)); + operands.push_back(result); + } + switch (node->op()) { + case Op::kAnd: + return bits_ops::NaryAnd(operands); + case Op::kOr: + return bits_ops::NaryOr(operands); + case Op::kXor: + return bits_ops::NaryXor(operands); + case Op::kNand: + return bits_ops::NaryNand(operands); + case Op::kNor: + return bits_ops::NaryNor(operands); + case Op::kNot: + XLS_RET_CHECK(operands.size() == 1); + return bits_ops::Not(operands[0]); + default: + XLS_LOG(FATAL) << "Expected node to be logical bitwise: " << node; + } + } + + absl::Status HandleLogicOp(Node* node) { + // If there are >= 2 frontier nodes we flow the vectors from those to this + // node to figure out its aggregate truth table. + const Frontier& frontier = UnionOperandFrontier(node); + if (HasFrontierVector(frontier) && + GetFrontierVector(frontier).size() >= 2) { + const auto& operands = GetFrontierVector(frontier); + XLS_ASSIGN_OR_RETURN(Bits result, FlowFromFrontierToNode(frontier, node)); + XLS_VLOG(3) << "Flow result for " << node << ": " + << result.ToString(FormatPreference::kBinary, true); + XLS_ASSIGN_OR_RETURN(Node * replacement, + ResolveTruthTable(result, operands, node)); + if (replacement == nullptr) { + return absl::OkStatus(); + } + node_replacements_.push_back({node, replacement}); + } + return absl::OkStatus(); + } + + // If a node's operand is not found in the node_to_frontier_ mapping, we + // assume it is itself a frontier node. + const Frontier& UnionOperandFrontier(Node* node) { + Frontier accum; + for (Node* operand : node->operands()) { + auto it = node_to_frontier_.find(operand); + if (it == node_to_frontier_.end()) { + AddNonBool(operand, &accum); + } else { + Union(it->second, &accum); + } + } + node_to_frontier_[node] = accum; + return node_to_frontier_[node]; + } + + // Either a node has some limited number of "frontier nodes" on its frontier, + // or we exceed the number and stop trying to track them. + absl::flat_hash_map node_to_frontier_; + + std::vector> node_replacements_; +}; + +} // namespace + +xabsl::StatusOr BooleanSimplificationPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + BooleanFlowTracker visitor; + XLS_RETURN_IF_ERROR(f->Accept(&visitor)); + for (auto& pair : visitor.node_replacements()) { + Node* node = pair.first; + Node* replacement = pair.second; + XLS_VLOG(3) << "Replacing " << node << " with " << replacement; + XLS_RETURN_IF_ERROR(node->ReplaceUsesWith(replacement).status()); + } + return !visitor.node_replacements().empty(); +} + +} // namespace xls diff --git a/xls/passes/boolean_simplification_pass.h b/xls/passes/boolean_simplification_pass.h new file mode 100644 index 0000000000..70bb3828bb --- /dev/null +++ b/xls/passes/boolean_simplification_pass.h @@ -0,0 +1,78 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_BOOLEAN_SIMPLIFICATION_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_BOOLEAN_SIMPLIFICATION_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { +namespace internal { + +// Exposed in the header for testing. +class TruthTable { + public: + // xyz_present and xyz_negated must be bit_count == 3 for each of the operands + // on the input frontier (see .cc file for details). A value like xyz_present + // == 0b100 indicates 'x' is present, 0b011 indicates 'y' and 'z' are present, + // etc. + TruthTable(const Bits& xyz_present, const Bits& xyz_negated, + absl::optional logical_op); + + // Computes the result vector for this operation, as specified in the + // constructor. + Bits ComputeTruthTable() const; + + bool MatchesVector(const Bits& table) const; + + // Returns whether the original node matches this logical function with the + // given operands (note all logical operations we express in this way are + // symmetrical with respect to permutations in the input operands). + bool MatchesSymmetrical(Node* original, + absl::Span operands) const; + + // Creates a replacement node to use in lieu of the original that corresponds + // to this truth table with the given input frontier operands. + xabsl::StatusOr CreateReplacement( + const absl::optional& original_loc, + absl::Span operands, Function* f) const; + + // Gets the truth table (input) vector for operand "i". + static Bits GetInitialVector(int64 i); + static Bits RunNaryOp(Op op, absl::Span operands); + + private: + Bits xyz_present_; + Bits xyz_negated_; + absl::optional logical_op_; +}; + +} // namespace internal + +// Attempts to simplify bitwise / boolean expressions (e.g. of multiple +// variables). +class BooleanSimplificationPass : public FunctionPass { + public: + BooleanSimplificationPass() + : FunctionPass("bool_simp", "boolean simplification") {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_BOOLEAN_SIMPLIFICATION_PASS_H_ diff --git a/xls/passes/boolean_simplification_pass_test.cc b/xls/passes/boolean_simplification_pass_test.cc new file mode 100644 index 0000000000..df717322d3 --- /dev/null +++ b/xls/passes/boolean_simplification_pass_test.cc @@ -0,0 +1,300 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/boolean_simplification_pass.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/ir_test_base.h" +#include "xls/passes/dce_pass.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class BooleanSimplificationPassTest : public IrTestBase { + protected: + BooleanSimplificationPassTest() = default; + + xabsl::StatusOr Run(Function* f) { + PassResults results; + XLS_ASSIGN_OR_RETURN( + bool changed, + BooleanSimplificationPass().RunOnFunction(f, PassOptions(), &results)); + // Run dce to clean things up. + XLS_RETURN_IF_ERROR(DeadCodeEliminationPass() + .RunOnFunction(f, PassOptions(), &results) + .status()); + // Return whether boolean simplification changed anything. + return changed; + } +}; + +TEST_F(BooleanSimplificationPassTest, TruthTableTestSingleVariableNegated) { + internal::TruthTable not_x(UBits(0b100, /*bit_count=*/3), + UBits(0b100, /*bit_count=*/3), absl::nullopt); + EXPECT_EQ(not_x.ComputeTruthTable(), + bits_ops::Not(not_x.GetInitialVector(0))); + EXPECT_TRUE(not_x.MatchesVector(not_x.ComputeTruthTable())); +} + +TEST_F(BooleanSimplificationPassTest, TruthTableTestAndOfNxAndNz) { + internal::TruthTable table(UBits(0b101, /*bit_count=*/3), + UBits(0b101, /*bit_count=*/3), Op::kAnd); + EXPECT_EQ(table.ComputeTruthTable(), + bits_ops::And(bits_ops::Not(table.GetInitialVector(0)), + bits_ops::Not(table.GetInitialVector(2)))); +} + +TEST_F(BooleanSimplificationPassTest, TruthTableMatchesAndOfNxAndNy) { + internal::TruthTable table(UBits(0b110, /*bit_count=*/3), + UBits(0b110, /*bit_count=*/3), Op::kAnd); + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[42], y: bits[42]) -> bits[42] { + nx: bits[42] = not(x) + ny: bits[42] = not(y) + ret and: bits[42] = and(nx, ny) +} + )", + p.get())); + EXPECT_TRUE( + table.MatchesSymmetrical(f->return_value(), {f->param(0), f->param(1)})); + EXPECT_TRUE( + table.MatchesSymmetrical(f->return_value(), {f->param(1), f->param(0)})); +} + +TEST_F(BooleanSimplificationPassTest, TruthTableMatchesNotX) { + internal::TruthTable table(UBits(0b100, /*bit_count=*/3), + UBits(0b100, /*bit_count=*/3), absl::nullopt); + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[42], y: bits[42]) -> bits[42] { + ret nx: bits[42] = not(x) +} + )", + p.get())); + EXPECT_TRUE(table.MatchesSymmetrical(f->return_value(), {f->param(0)})); +} + +TEST_F(BooleanSimplificationPassTest, DoubleAnd) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[42], y: bits[42]) -> bits[42] { + and.3: bits[42] = and(x, y) + ret and.4: bits[42] = and(and.3, y) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[42], y: bits[42]) -> bits[42] { + ret and.5: bits[42] = and(x, y) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, NotAndOr) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[42], y: bits[42]) -> bits[42] { + not.3: bits[42] = not(x) + and.4: bits[42] = and(not.3, y) + ret or.5: bits[42] = or(and.4, x) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[42], y: bits[42]) -> bits[42] { + ret or.8: bits[42] = or(x, y) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, TwoVarsMakingAllOnes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[7], y: bits[7]) -> bits[7] { + nor: bits[7] = nor(x, y) + xn: bits[7] = not(x) + x_or_y: bits[7] = or(x, y) + and: bits[7] = and(xn, x_or_y) + x_nor_y: bits[7] = nor(x, y) + ret or: bits[7] = or(x, x_nor_y, and) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[7], y: bits[7]) -> bits[7] { + ret literal.11: bits[7] = literal(value=127) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, ConvertToNand) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[42], y: bits[42]) -> bits[42] { + and: bits[42] = and(x, y) + ret not: bits[42] = not(and) +})", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[42], y: bits[42]) -> bits[42] { + ret nand.5: bits[42] = nand(x, y) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, ThreeVarsZero) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[42], y: bits[42], z: bits[42]) -> bits[42] { + not_z: bits[42] = not(z) + x_or_y: bits[42] = or(x, y) + not__x_or_y: bits[42] = not(x_or_y) + ret and: bits[42] = and(y, not_z, not__x_or_y) +})", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), + R"(fn f(x: bits[42], y: bits[42], z: bits[42]) -> bits[42] { + ret literal.9: bits[42] = literal(value=0) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, ThreeVarsNor) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[42], y: bits[42], z: bits[42]) -> bits[42] { + xn: bits[42] = not(x) + yn: bits[42] = not(y) + zn: bits[42] = not(z) + y_or_z: bits[42] = or(y, z) + not_y_or_z: bits[42] = not(y_or_z) + ret final_and: bits[42] = and(xn, yn, not_y_or_z) +})", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), + R"(fn f(x: bits[42], y: bits[42], z: bits[42]) -> bits[42] { + ret nor.11: bits[42] = nor(x, y, z) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, ConvertToNor) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[42], y: bits[42]) -> bits[42] { + or: bits[42] = or(x, y) + ret not: bits[42] = not(or) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[42], y: bits[42]) -> bits[42] { + ret nor.5: bits[42] = nor(x, y) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, SimplifyToXOrNotZEquivalent) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[3], y: bits[3], z: bits[3]) -> bits[3] { + nor: bits[3] = nor(x, y, z) + zn: bits[3] = not(z) + ret or: bits[3] = or(x, nor, zn) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), + R"(fn f(x: bits[3], y: bits[3], z: bits[3]) -> bits[3] { + not.7: bits[3] = not(x) + ret nand.8: bits[3] = nand(not.7, z) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, SimplifyToNotYAndZEquivalent) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[3], y: bits[3], z: bits[3]) -> bits[3] { + y_or_z: bits[3] = or(y, z) + yn: bits[3] = not(y) + ret and: bits[3] = and(yn, y_or_z) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), + R"(fn f(x: bits[3], y: bits[3], z: bits[3]) -> bits[3] { + not.7: bits[3] = not(z) + ret nor.8: bits[3] = nor(y, not.7) +} +)"); +} + +TEST_F(BooleanSimplificationPassTest, SimplifyRealWorld) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[3], y: bits[3], z: bits[3]) -> bits[3] { + zn: bits[3] = not(z) + zn_or_y: bits[3] = or(zn, y) + nor_xyz: bits[3] = nor(x, y, z) + ret or: bits[3] = or(zn_or_y, nor_xyz) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), + R"(fn f(x: bits[3], y: bits[3], z: bits[3]) -> bits[3] { + not.10: bits[3] = not(y) + ret nand.11: bits[3] = nand(z, not.10) +} +)"); +} + +// TODO(leary): 2019-10-11 Needs AOI21 logical function to map against. +#if 0 +TEST_F(BooleanSimplificationPassTest, SimplifyRealWorld2) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[3], y: bits[3], z: bits[3]) -> bits[3] { + yn: bits[3] = not(y) + yn_nor_x: bits[3] = nor(yn, x) + nor_xyz: bits[3] = nor(x, y, z) + ret or: bits[3] = or(yn_nor_x, nor_xyz) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), + R"(fn f(x: bits[3], y: bits[3], z: bits[3]) -> bits[3] { +} +)"); +} +#endif + +} // namespace +} // namespace xls diff --git a/xls/passes/canonicalization_pass.cc b/xls/passes/canonicalization_pass.cc new file mode 100644 index 0000000000..20cb4ce23b --- /dev/null +++ b/xls/passes/canonicalization_pass.cc @@ -0,0 +1,133 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/canonicalization_pass.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/node_iterator.h" + +namespace xls { + +namespace { + +// For the given comparison Op, returns the op op_commuted for which the +// following identity holds: +// op(x, y) == op_commuted(y, x) +Op CompareOpCommuted(Op op) { + switch (op) { + case Op::kEq: + case Op::kNe: + return op; + case Op::kSGe: + return Op::kSLe; + case Op::kUGe: + return Op::kULe; + case Op::kSGt: + return Op::kSLt; + case Op::kUGt: + return Op::kULt; + case Op::kSLe: + return Op::kSGe; + case Op::kULe: + return Op::kUGe; + case Op::kSLt: + return Op::kSGt; + case Op::kULt: + return Op::kUGt; + default: + XLS_LOG(FATAL) << "Op is not comparison: " << OpToString(op); + } +} + +} // namespace + +// CanonicalizeNodes performs simple canonicalization of expressions, +// such as moving a literal in an associative expression to the right. +// Being able to rely on the shape of such nodes greatly simplifies +// the implementation of transformation passes, as only one pattern needs +// to be matched, instead of two. +xabsl::StatusOr CanonicalizeNodes(Node* n, Function* f) { + // Always move kLiteral to right for commutative operators. + Op op = n->op(); + if (OpIsCommutative(op) && n->operand_count() == 2) { + if (n->operand(0)->Is() && !n->operand(1)->Is()) { + XLS_VLOG(2) << "Replaced 'op(literal, x) with op(x, literal)"; + n->SwapOperands(0, 1); + return true; + } + } + + // Move kLiterals to the right for comparison operators. + if (OpIsCompare(n->op()) && n->operand(0)->Is() && + !n->operand(1)->Is()) { + Op commuted_op = CompareOpCommuted(n->op()); + XLS_VLOG(2) << absl::StreamFormat( + "Replaced %s(literal, x) with %s(x, literal)", OpToString(n->op()), + OpToString(commuted_op)); + XLS_RETURN_IF_ERROR(n->ReplaceUsesWithNew( + n->operand(1), n->operand(0), commuted_op) + .status()); + return true; + } + + // Replace (x - lit) with x + (-literal) + if (n->op() == Op::kSub && n->operand(1)->Is()) { + XLS_ASSIGN_OR_RETURN(Node * neg_rhs, + f->MakeNode(n->loc(), n->operand(1), Op::kNeg)); + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(n->operand(0), neg_rhs, Op::kAdd) + .status()); + XLS_VLOG(2) << "Replaced 'sub(lhs, rhs)' with 'add(lhs, neg(rhs))'"; + return true; + } + + if (n->Is() && + n->BitCountOrDie() == n->operand(0)->BitCountOrDie()) { + return n->ReplaceUsesWith(n->operand(0)); + } + + // Replace zero-extend operations with concat with zero. + if (n->op() == Op::kZeroExt) { + const int64 operand_bit_count = n->operand(0)->BitCountOrDie(); + const int64 bit_count = n->BitCountOrDie(); + // The optimization above should have caught any degenerate non-extending + // zero-extend ops. + XLS_RET_CHECK_GT(bit_count, operand_bit_count); + XLS_ASSIGN_OR_RETURN( + Node * zero, + f->MakeNode(n->loc(), + Value(UBits(0, bit_count - operand_bit_count)))); + std::vector concat_operands = {zero, n->operand(0)}; + XLS_RETURN_IF_ERROR( + n->ReplaceUsesWithNew(concat_operands).status()); + return true; + } + + return false; +} + +xabsl::StatusOr CanonicalizationPass::RunOnFunction( + Function* func, const PassOptions& options, PassResults* results) const { + bool changed = false; + for (Node* node : TopoSort(func)) { + XLS_ASSIGN_OR_RETURN(bool node_changed, CanonicalizeNodes(node, func)); + changed = changed | node_changed; + } + return changed; +} + +} // namespace xls diff --git a/xls/passes/canonicalization_pass.h b/xls/passes/canonicalization_pass.h new file mode 100644 index 0000000000..161d4c55a5 --- /dev/null +++ b/xls/passes/canonicalization_pass.h @@ -0,0 +1,42 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// IR Canonicalization. + +#ifndef THIRD_PARTY_XLS_PASSES_CANONICALIZATION_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_CANONICALIZATION_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// class CanonicalizationPass iterates over nodes and tries +// to canonicalize the expressions found. For example, for an add +// between a node and a literal, the literal should only be the +// 2nd operand. This preprocessing of the IR helps to simplify +// later passes. +class CanonicalizationPass : public FunctionPass { + public: + explicit CanonicalizationPass() : FunctionPass("canon", "Canonicalization") {} + ~CanonicalizationPass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_CANONICALIZATION_PASS_H_ diff --git a/xls/passes/canonicalization_pass_test.cc b/xls/passes/canonicalization_pass_test.cc new file mode 100644 index 0000000000..6fa6dab733 --- /dev/null +++ b/xls/passes/canonicalization_pass_test.cc @@ -0,0 +1,133 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/canonicalization_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" + +namespace m = ::xls::op_matchers; + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class CanonicalizePassTest : public IrTestBase { + protected: + xabsl::StatusOr Run(Package* p) { + PassResults results; + return CanonicalizationPass().Run(p, PassOptions(), &results); + } +}; + +TEST_F(CanonicalizePassTest, Canonicalize) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn simple_canon(x:bits[2]) -> bits[2] { + one:bits[2] = literal(value=1) + ret addval: bits[2] = add(one, x) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Add(m::Param(), m::Literal())); +} + +TEST_F(CanonicalizePassTest, SubToAddNegate) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn simple_neg(x:bits[2]) -> bits[2] { + one:bits[2] = literal(value=1) + ret subval: bits[2] = sub(x, one) + } + )", + p.get())); + + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Add(m::Param(), m::Neg())); +} + +TEST_F(CanonicalizePassTest, NopZeroExtend) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn nop_zero_ext(x:bits[16]) -> bits[16] { + ret zero_ext: bits[16] = zero_ext(x, new_bit_count=16) + } + )", + p.get())); + + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param()); +} + +TEST_F(CanonicalizePassTest, ZeroExtendReplacedWithConcat) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn zero_ext(x:bits[33]) -> bits[42] { + ret zero_ext: bits[42] = zero_ext(x, new_bit_count=42) + } + )", + p.get())); + + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Concat(m::Literal(0), m::Param())); +} + +TEST_F(CanonicalizePassTest, ComparisonWithLiteralCanonicalization) { + { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.ULt(fb.Literal(UBits(42, 32)), fb.Param("x", p->GetBitsType(32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::UGt(m::Param(), m::Literal(42))); + } + + { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.UGe(fb.Literal(UBits(42, 32)), fb.Param("x", p->GetBitsType(32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::ULe(m::Param(), m::Literal(42))); + } + + { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.SGt(fb.Literal(UBits(42, 32)), fb.Param("x", p->GetBitsType(32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::SLt(m::Param(), m::Literal(42))); + } + + { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Eq(fb.Literal(UBits(42, 32)), fb.Param("x", p->GetBitsType(32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Eq(m::Param(), m::Literal(42))); + } +} + +} // namespace +} // namespace xls diff --git a/xls/passes/concat_simplification_pass.cc b/xls/passes/concat_simplification_pass.cc new file mode 100644 index 0000000000..0a6886138b --- /dev/null +++ b/xls/passes/concat_simplification_pass.cc @@ -0,0 +1,313 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/concat_simplification_pass.h" + +#include + +#include "absl/status/status.h" +#include "absl/types/span.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/node_iterator.h" +#include "xls/ir/nodes.h" +#include "xls/ir/op.h" + +namespace xls { +namespace { + +// Returns true if the given concat as consecutive literal operands. +bool HasConsecutiveLiteralOperands(Concat* concat) { + for (int64 i = 1; i < concat->operand_count(); ++i) { + if (concat->operand(i - 1)->Is() && + concat->operand(i)->Is()) { + return true; + } + } + return false; +} + +// Replaces any consecutive literal operands with a single merged literal +// operand. Returns the newly created concat which never aliases the given +// concat. +xabsl::StatusOr ReplaceConsecutiveLiteralOperands(Concat* concat) { + std::vector new_operands; + std::vector consecutive_literals; + + auto add_consecutive_literals_to_operands = [&]() -> absl::Status { + if (consecutive_literals.size() > 1) { + std::vector literal_bits(consecutive_literals.size()); + std::transform(consecutive_literals.begin(), consecutive_literals.end(), + literal_bits.begin(), + [](Literal* l) { return l->value().bits(); }); + XLS_ASSIGN_OR_RETURN( + Node * new_literal, + concat->function()->MakeNode( + concat->loc(), Value(bits_ops::Concat(literal_bits)))); + new_operands.push_back(new_literal); + } else if (consecutive_literals.size() == 1) { + new_operands.push_back(consecutive_literals.front()); + } + consecutive_literals.clear(); + return absl::OkStatus(); + }; + + for (Node* operand : concat->operands()) { + if (operand->Is()) { + consecutive_literals.push_back(operand->As()); + } else { + XLS_RETURN_IF_ERROR(add_consecutive_literals_to_operands()); + new_operands.push_back(operand); + } + } + XLS_RETURN_IF_ERROR(add_consecutive_literals_to_operands()); + return concat->ReplaceUsesWithNew(new_operands); +} + +// Replaces the given concat and any of its operand which are concats (and any +// of *their* operands which are concats, etc) with a single concat +// operation. For example: +// +// Concat(a, b, Concat(c, Concat(d, e))) => Concat(a, b, c, d, e) +// +// Returns the newly created concat which never aliases the given concat. +xabsl::StatusOr FlattenConcatTree(Concat* concat) { + std::vector new_operands; + std::deque worklist(concat->operands().begin(), + concat->operands().end()); + while (!worklist.empty()) { + Node* node = worklist.front(); + worklist.pop_front(); + if (node->Is()) { + worklist.insert(worklist.begin(), node->operands().begin(), + node->operands().end()); + } else { + new_operands.push_back(node); + } + } + return concat->ReplaceUsesWithNew(new_operands); +} + +// Attempts to replace the given concat with a simpler or more canonical +// form. Returns true if the concat was replaced. +xabsl::StatusOr SimplifyConcat(Concat* concat, + std::deque* worklist) { + absl::Span operands = concat->operands(); + + // Concat with a single operand can be replaced with its operand. + if (concat->operand_count() == 1) { + return concat->ReplaceUsesWith(operands[0]); + } + + // Tree of concats can be flattened to a single concat. + if (std::any_of(operands.begin(), operands.end(), + [](Node* op) { return op->Is(); })) { + XLS_ASSIGN_OR_RETURN(Concat * new_concat, FlattenConcatTree(concat)); + worklist->push_back(new_concat); + return true; + } + + // Consecutive literal operands of a concat can be merged into a single + // literal. + if (HasConsecutiveLiteralOperands(concat)) { + XLS_ASSIGN_OR_RETURN(Concat * new_concat, + ReplaceConsecutiveLiteralOperands(concat)); + worklist->push_back(new_concat); + return true; + } + + // Eliminate any zero-bit operands that get concatenated. + if (std::any_of(operands.begin(), operands.end(), + [](Node* op) { return op->BitCountOrDie() == 0; })) { + std::vector new_operands; + for (Node* operand : operands) { + if (operand->BitCountOrDie() != 0) { + new_operands.push_back(operand); + } + } + XLS_ASSIGN_OR_RETURN(Concat * new_concat, + concat->ReplaceUsesWithNew(new_operands)); + worklist->push_back(new_concat); + return true; + } + + return false; +} + +// Tries to hoist the given bitwise operation above it's concat +// operations. Example: +// +// Xor(Concat(a, b), Concat(c, d)) => Concat(Xor(a, c), Xor(b, d)) +// +// Hosting the bitwise operations presents more opportunity for optimization and +// simplification. +// +// Preconditions: +// * All operands of the bitwise operation are concats. +// * The concats each have the same number of operands, and they are the same +// size. +xabsl::StatusOr TryHoistBitWiseOperation(Node* node) { + XLS_RET_CHECK(OpIsBitWise(node->op())); + if (node->operand_count() == 0 || + !std::all_of(node->operands().begin(), node->operands().end(), + [](Node* op) { return op->Is(); })) { + return false; + } + + Concat* concat_0 = node->operand(0)->As(); + for (int i = 1; i < node->operand_count(); ++i) { + Concat* concat_i = node->operand(i)->As(); + if (concat_0->operand_count() != concat_i->operand_count()) { + return false; + } + for (int j = 0; j < concat_0->operand_count(); ++j) { + if (concat_0->operand(j)->BitCountOrDie() != + concat_i->operand(j)->BitCountOrDie()) { + return false; + } + } + } + + std::vector new_concat_operands; + for (int64 i = 0; i < concat_0->operand_count(); ++i) { + std::vector bitwise_operands; + for (int64 j = 0; j < node->operand_count(); ++j) { + bitwise_operands.push_back(node->operand(j)->operand(i)); + } + XLS_ASSIGN_OR_RETURN(Node * new_bitwise, + node->Clone(bitwise_operands, node->function())); + new_concat_operands.push_back(new_bitwise); + } + + XLS_RETURN_IF_ERROR( + node->ReplaceUsesWithNew(new_concat_operands).status()); + return true; +} + +// Attempts to distribute a reducible operation into the operands (sub-slices) +// of a concat -- this helps the optimizer because the concat could otherwise +// act as something that's hard to "see through"; e.g. imagine you concatenate +// zeroes onto a value, and then check whether that concatenated value is equal +// to zero. By distributing the equality test to the operands, we'll see that +// for the concatenated zero bits there's no equality that needs to be +// performed, it's always true. +// +// So it can enable transforms like like: +// +// Eq(Concat(bits[7]:0, x), 0) => And(Eq(bits[7], 0), Eq(x, 0)) => Eq(x, 0) +xabsl::StatusOr TryDistributeReducibleOperation(Node* node) { + // For now we only handle eq and ne operations. + if (node->op() != Op::kEq && node->op() != Op::kNe) { + return false; + } + + auto get_concat_and_other = [&](Concat** concat, Node** other) -> bool { + if (node->operand(0)->Is()) { + *concat = node->operand(0)->As(); + *other = node->operand(1); + return true; + } + if (node->operand(1)->Is()) { + *concat = node->operand(1)->As(); + *other = node->operand(0); + return true; + } + return false; + }; + + Concat* concat; + Node* other; + if (!get_concat_and_other(&concat, &other)) { + return false; + } + + // For eq, the reduction is that all the sub-slices are equal (AND). + // For ne, the reduction is that any one of the sub-slices is not-equal (OR). + Op reducer = node->op() == Op::kEq ? Op::kAnd : Op::kOr; + + // Walk through the concat operands and grab the corresponding slice out of + // the "other" node, and distribute the operation to occur on those + // sub-slices. + Function* f = concat->function(); + Node* result = nullptr; + for (int64 i = 0; i < concat->operands().size(); ++i) { + SliceData concat_slice = concat->GetOperandSliceData(i); + XLS_ASSIGN_OR_RETURN( + Node * other_slice, + f->MakeNode(other->loc(), other, concat_slice.start, + concat_slice.width)); + XLS_ASSIGN_OR_RETURN(Node * slices_eq, + f->MakeNode(node->loc(), concat->operand(i), + other_slice, node->op())); + if (result == nullptr) { + result = slices_eq; + } else { + XLS_ASSIGN_OR_RETURN( + result, + f->MakeNode(node->loc(), + std::vector{result, slices_eq}, reducer)); + } + } + + XLS_RETURN_IF_ERROR(node->ReplaceUsesWith(result).status()); + return true; +} + +} // namespace + +xabsl::StatusOr ConcatSimplificationPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + XLS_VLOG(2) << "Running concat simplifier on function " << f->name(); + XLS_VLOG(3) << "Before:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + // For optimizations which replace concats with other concats use a worklist + // of unprocessed concats in the graphs. As new concats are created they are + // added to the worklist. + std::deque worklist; + for (Node* node : f->nodes()) { + if (node->Is()) { + worklist.push_back(node->As()); + } + } + bool changed = false; + while (!worklist.empty()) { + Concat* concat = worklist.front(); + worklist.pop_front(); + XLS_ASSIGN_OR_RETURN(bool node_changed, SimplifyConcat(concat, &worklist)); + changed = changed || node_changed; + } + + // For optimizations which optimize around concats, just iterate through once + // and find all opportunities. + for (Node* node : TopoSort(f)) { + bool node_changed = false; + if (OpIsBitWise(node->op())) { + XLS_ASSIGN_OR_RETURN(node_changed, TryHoistBitWiseOperation(node)); + } else { + XLS_ASSIGN_OR_RETURN(node_changed, TryDistributeReducibleOperation(node)); + } + changed = changed || node_changed; + } + + XLS_VLOG(3) << "After:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + return changed; +} + +} // namespace xls diff --git a/xls/passes/concat_simplification_pass.h b/xls/passes/concat_simplification_pass.h new file mode 100644 index 0000000000..4ac1b6d436 --- /dev/null +++ b/xls/passes/concat_simplification_pass.h @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_CONCAT_SIMPLIFICATION_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_CONCAT_SIMPLIFICATION_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// Pass which simplifies concats. This includes removing single-operand concats, +// flattening trees of dependent concats, and others. +class ConcatSimplificationPass : public FunctionPass { + public: + ConcatSimplificationPass() + : FunctionPass("concat_simp", "Concat simplification") {} + ~ConcatSimplificationPass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_CONCAT_SIMPLIFICATION_PASS_H_ diff --git a/xls/passes/concat_simplification_pass_test.cc b/xls/passes/concat_simplification_pass_test.cc new file mode 100644 index 0000000000..4c5b9bd92d --- /dev/null +++ b/xls/passes/concat_simplification_pass_test.cc @@ -0,0 +1,323 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/concat_simplification_pass.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/substitute.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_interpreter.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/value.h" +#include "xls/passes/dce_pass.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class ConcatSimplificationPassTest : public IrTestBase { + protected: + ConcatSimplificationPassTest() = default; + + xabsl::StatusOr Run(Function* f) { + PassResults results; + XLS_ASSIGN_OR_RETURN(bool changed, ConcatSimplificationPass().RunOnFunction( + f, PassOptions(), &results)); + // Run dce to clean things up. + XLS_RETURN_IF_ERROR(DeadCodeEliminationPass() + .RunOnFunction(f, PassOptions(), &results) + .status()); + // Return whether concat simplification changed anything. + return changed; + } +}; + +TEST_F(ConcatSimplificationPassTest, TrivialConcat) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn TrivialConcat(x: bits[42]) -> bits[42] { + ret concat: bits[42] = concat(x) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 2); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 1); + EXPECT_TRUE(f->return_value()->Is()); +} + +TEST_F(ConcatSimplificationPassTest, TowerOfConcats) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn TowerOfConcats(x: bits[42]) -> bits[42] { + concat.1: bits[42] = concat(x) + concat.2: bits[42] = concat(concat.1) + concat.3: bits[42] = concat(concat.2) + ret concat.4: bits[42] = concat(concat.3) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 5); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 1); + EXPECT_TRUE(f->return_value()->Is()); +} + +TEST_F(ConcatSimplificationPassTest, TreeOfConcats) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn TreeOfConcats(a: bits[16], b: bits[1], c: bits[4], d: bits[7]) -> bits[28] { + concat.1: bits[5] = concat(b, c) + concat.2: bits[21] = concat(a, concat.1) + concat.3: bits[21] = concat(concat.2) + ret concat.4: bits[28] = concat(concat.3, d) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 8); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 5); + EXPECT_TRUE(f->return_value()->Is()); + ASSERT_EQ(f->return_value()->operand_count(), 4); + ASSERT_TRUE(f->return_value()->operand(0)->Is()); + EXPECT_EQ(f->return_value()->operand(0)->GetName(), "a"); + + ASSERT_TRUE(f->return_value()->operand(1)->Is()); + EXPECT_EQ(f->return_value()->operand(1)->GetName(), "b"); + + ASSERT_TRUE(f->return_value()->operand(2)->Is()); + EXPECT_EQ(f->return_value()->operand(2)->GetName(), "c"); + + ASSERT_TRUE(f->return_value()->operand(3)->Is()); + EXPECT_EQ(f->return_value()->operand(3)->GetName(), "d"); +} + +TEST_F(ConcatSimplificationPassTest, TreeOfConcatsOfSameValue) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn TreeOfConcatsOfSameValue(a: bits[8]) -> bits[48] { + concat.1: bits[16] = concat(a, a) + concat.2: bits[24] = concat(a, concat.1) + concat.3: bits[40] = concat(a, concat.2, a) + ret concat.4: bits[48] = concat(concat.3, a) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 5); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 2); + EXPECT_TRUE(f->return_value()->Is()); + ASSERT_EQ(f->return_value()->operand_count(), 6); + for (int64 i = 0; i < 6; ++i) { + EXPECT_TRUE(f->return_value()->operand(i)->Is()); + EXPECT_EQ(f->return_value()->operand(i)->GetName(), "a"); + } +} + +TEST_F(ConcatSimplificationPassTest, ConsecutiveLiteralOperandsOfAConcat) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn ConsecutiveLiteralOperandsOfAConcat(a: bits[16], b: bits[1]) -> bits[33] { + literal.1: bits[2] = literal(value=1) + literal.2: bits[2] = literal(value=2) + literal.3: bits[4] = literal(value=0xa) + literal.4: bits[4] = literal(value=0xb) + literal.5: bits[4] = literal(value=0xc) + ret concat.6: bits[33] = concat(literal.1, literal.2, a, literal.3, b, literal.4, literal.5) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 8); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 6); + EXPECT_TRUE(f->return_value()->Is()); + ASSERT_EQ(f->return_value()->operand_count(), 5); + ASSERT_TRUE(f->return_value()->operand(0)->Is()); + EXPECT_EQ(f->return_value()->operand(0)->As()->value().bits(), + UBits(6, 4)); + + ASSERT_TRUE(f->return_value()->operand(1)->Is()); + EXPECT_EQ(f->return_value()->operand(1)->GetName(), "a"); + + ASSERT_TRUE(f->return_value()->operand(2)->Is()); + EXPECT_EQ(f->return_value()->operand(2)->As()->value().bits(), + UBits(0xa, 4)); + + ASSERT_TRUE(f->return_value()->operand(3)->Is()); + EXPECT_EQ(f->return_value()->operand(3)->GetName(), "b"); + + ASSERT_TRUE(f->return_value()->operand(4)->Is()); + EXPECT_EQ(f->return_value()->operand(4)->As()->value().bits(), + UBits(0xbc, 8)); +} + +TEST_F(ConcatSimplificationPassTest, NotOfConcat) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn ConsecutiveLiteralOperandsOfAConcat(a: bits[16], b: bits[10], c: bits[7]) -> bits[33] { + concat.1: bits[33] = concat(a, b, c) + ret not.2: bits[33] = not(concat.1) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 5); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 7); + EXPECT_TRUE(f->return_value()->Is()); + ASSERT_EQ(f->return_value()->operand_count(), 3); + + ASSERT_EQ(f->return_value()->operand(0)->op(), Op::kNot); + EXPECT_TRUE(f->return_value()->operand(0)->operand(0)->Is()); + EXPECT_EQ(f->return_value()->operand(0)->operand(0)->GetName(), "a"); + + ASSERT_EQ(f->return_value()->operand(1)->op(), Op::kNot); + EXPECT_TRUE(f->return_value()->operand(1)->operand(0)->Is()); + EXPECT_EQ(f->return_value()->operand(1)->operand(0)->GetName(), "b"); + + ASSERT_EQ(f->return_value()->operand(2)->op(), Op::kNot); + EXPECT_TRUE(f->return_value()->operand(2)->operand(0)->Is()); + EXPECT_EQ(f->return_value()->operand(2)->operand(0)->GetName(), "c"); +} + +TEST_F(ConcatSimplificationPassTest, XorOfConcat) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn ConsecutiveLiteralOperandsOfAConcat(a: bits[8], b: bits[10], c: bits[8], d: bits[10]) -> bits[18] { + concat.1: bits[18] = concat(a, b) + concat.2: bits[18] = concat(c, d) + ret xor.3: bits[18] = xor(concat.1, concat.2) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 7); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 7); + EXPECT_TRUE(f->return_value()->Is()); + ASSERT_EQ(f->return_value()->operand_count(), 2); + + ASSERT_EQ(f->return_value()->operand(0)->op(), Op::kXor); + EXPECT_TRUE(f->return_value()->operand(0)->operand(0)->Is()); + EXPECT_EQ(f->return_value()->operand(0)->operand(0)->GetName(), "a"); + EXPECT_TRUE(f->return_value()->operand(0)->operand(1)->Is()); + EXPECT_EQ(f->return_value()->operand(0)->operand(1)->GetName(), "c"); + + ASSERT_EQ(f->return_value()->operand(1)->op(), Op::kXor); + EXPECT_TRUE(f->return_value()->operand(1)->operand(0)->Is()); + EXPECT_EQ(f->return_value()->operand(1)->operand(0)->GetName(), "b"); + EXPECT_TRUE(f->return_value()->operand(1)->operand(1)->Is()); + EXPECT_EQ(f->return_value()->operand(1)->operand(1)->GetName(), "d"); +} + +TEST_F(ConcatSimplificationPassTest, EqOfConcatDistributes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[5], y: bits[10]) -> bits[1] { + concat.1: bits[15] = concat(x, y) + literal.2: bits[15] = literal(value=0) + ret eq.3: bits[1] = eq(concat.1, literal.2) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[5], y: bits[10]) -> bits[1] { + literal.2: bits[15] = literal(value=0) + bit_slice.6: bits[5] = bit_slice(literal.2, start=10, width=5) + bit_slice.8: bits[10] = bit_slice(literal.2, start=0, width=10) + eq.7: bits[1] = eq(x, bit_slice.6) + eq.9: bits[1] = eq(y, bit_slice.8) + ret and.10: bits[1] = and(eq.7, eq.9) +} +)"); +} + +TEST_F(ConcatSimplificationPassTest, EqOfConcatDistributes3Pieces) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[1], y: bits[2], z: bits[3]) -> bits[1] { + concat.1: bits[6] = concat(x, y, z) + literal.2: bits[6] = literal(value=0) + ret eq.3: bits[1] = eq(concat.1, literal.2) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), + R"(fn f(x: bits[1], y: bits[2], z: bits[3]) -> bits[1] { + literal.2: bits[6] = literal(value=0) + bit_slice.7: bits[1] = bit_slice(literal.2, start=5, width=1) + bit_slice.9: bits[2] = bit_slice(literal.2, start=3, width=2) + eq.8: bits[1] = eq(x, bit_slice.7) + eq.10: bits[1] = eq(y, bit_slice.9) + bit_slice.12: bits[3] = bit_slice(literal.2, start=0, width=3) + and.11: bits[1] = and(eq.8, eq.10) + eq.13: bits[1] = eq(z, bit_slice.12) + ret and.14: bits[1] = and(and.11, eq.13) +} +)"); +} + +TEST_F(ConcatSimplificationPassTest, NeOfConcatDistributes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[5], y: bits[10]) -> bits[1] { + concat.1: bits[15] = concat(x, y) + literal.2: bits[15] = literal(value=0) + ret eq.3: bits[1] = ne(concat.1, literal.2) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[5], y: bits[10]) -> bits[1] { + literal.2: bits[15] = literal(value=0) + bit_slice.6: bits[5] = bit_slice(literal.2, start=10, width=5) + bit_slice.8: bits[10] = bit_slice(literal.2, start=0, width=10) + ne.7: bits[1] = ne(x, bit_slice.6) + ne.9: bits[1] = ne(y, bit_slice.8) + ret or.10: bits[1] = or(ne.7, ne.9) +} +)"); +} + +TEST_F(ConcatSimplificationPassTest, ConcatOnRhs) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( +fn f(x: bits[5], y: bits[10]) -> bits[1] { + concat.1: bits[15] = concat(x, y) + literal.2: bits[15] = literal(value=0) + ret eq.3: bits[1] = eq(literal.2, concat.1) +} + )", + p.get())); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn f(x: bits[5], y: bits[10]) -> bits[1] { + literal.2: bits[15] = literal(value=0) + bit_slice.6: bits[5] = bit_slice(literal.2, start=10, width=5) + bit_slice.8: bits[10] = bit_slice(literal.2, start=0, width=10) + eq.7: bits[1] = eq(x, bit_slice.6) + eq.9: bits[1] = eq(y, bit_slice.8) + ret and.10: bits[1] = and(eq.7, eq.9) +} +)"); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/constant_folding_pass.cc b/xls/passes/constant_folding_pass.cc new file mode 100644 index 0000000000..166f6c368a --- /dev/null +++ b/xls/passes/constant_folding_pass.cc @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/constant_folding_pass.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/ir_interpreter.h" +#include "xls/ir/node_iterator.h" + +namespace xls { + +xabsl::StatusOr ConstantFoldingPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + XLS_VLOG(2) << "Running constant folding on function " << f->name(); + XLS_VLOG(3) << "Before:"; + XLS_VLOG_LINES(3, f->DumpIr()); + bool changed = false; + for (Node* node : TopoSort(f)) { + // TODO(meheff): 2019/6/26 Consider not folding loops with large trip counts + // to avoid hanging at compile time. + if (node->operand_count() > 0 && + std::all_of(node->operands().begin(), node->operands().end(), + [](Node* o) { return o->Is(); })) { + XLS_VLOG(2) << "Folding: " << *node; + XLS_ASSIGN_OR_RETURN( + Value result, ir_interpreter::EvaluateNodeWithLiteralOperands(node)); + XLS_RETURN_IF_ERROR(node->ReplaceUsesWithNew(result).status()); + changed = true; + } + } + + XLS_VLOG(3) << "After:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + return changed; +} + +} // namespace xls diff --git a/xls/passes/constant_folding_pass.h b/xls/passes/constant_folding_pass.h new file mode 100644 index 0000000000..f6424612da --- /dev/null +++ b/xls/passes/constant_folding_pass.h @@ -0,0 +1,37 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_CONSTANT_FOLDING_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_CONSTANT_FOLDING_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// Pass which performs constant folding. Every op with only literal operands is +// replaced by a equivalent literal. Runs DCE after constant folding. +class ConstantFoldingPass : public FunctionPass { + public: + ConstantFoldingPass() : FunctionPass("const_fold", "Constant folding") {} + ~ConstantFoldingPass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_CONSTANT_FOLDING_PASS_H_ diff --git a/xls/passes/constant_folding_pass_test.cc b/xls/passes/constant_folding_pass_test.cc new file mode 100644 index 0000000000..d472cec656 --- /dev/null +++ b/xls/passes/constant_folding_pass_test.cc @@ -0,0 +1,118 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/constant_folding_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" +#include "xls/ir/value.h" +#include "xls/passes/dce_pass.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class ConstantFoldingPassTest : public IrTestBase { + protected: + ConstantFoldingPassTest() = default; + + xabsl::StatusOr Run(Function* f) { + PassResults results; + XLS_ASSIGN_OR_RETURN(bool changed, ConstantFoldingPass().RunOnFunction( + f, PassOptions(), &results)); + // Run dce to clean things up. + XLS_RETURN_IF_ERROR(DeadCodeEliminationPass() + .RunOnFunction(f, PassOptions(), &results) + .status()); + // Return whether constant folding changed anything. + return changed; + } +}; + +TEST_F(ConstantFoldingPassTest, SingleLiteralNoChange) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn single_literal() -> bits[2] { + ret one: bits[2] = literal(value=1) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 1); + EXPECT_THAT(Run(f), IsOkAndHolds(false)); + EXPECT_EQ(f->node_count(), 1); +} + +TEST_F(ConstantFoldingPassTest, AddOfTwoLiterals) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn IdenticalLiterals() -> bits[8] { + literal.1: bits[8] = literal(value=42) + literal.2: bits[8] = literal(value=123) + ret add.3: bits[8] = add(literal.1, literal.2) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 3); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 1); + ASSERT_TRUE(f->return_value()->Is()); + EXPECT_EQ(f->return_value()->As()->value(), Value(UBits(165, 8))); +} + +TEST_F(ConstantFoldingPassTest, AddWithOnlyOneLiteral) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn IdenticalLiterals(x: bits[8]) -> bits[8] { + literal.1: bits[8] = literal(value=42) + ret add.2: bits[8] = add(literal.1, x) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 3); + EXPECT_THAT(Run(f), IsOkAndHolds(false)); + EXPECT_EQ(f->node_count(), 3); +} + +TEST_F(ConstantFoldingPassTest, CountedFor) { + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackage(R"( +package CountedFor + +fn body(x: bits[11], y: bits[11]) -> bits[11] { + ret add.3: bits[11] = add(x, y) +} + +fn main() -> bits[11] { + literal.1: bits[11] = literal(value=0) + ret counted_for.2: bits[11] = counted_for(literal.1, trip_count=7, stride=1, body=body) +} +)")); + + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, p->EntryFunction()); + EXPECT_EQ(entry->node_count(), 2); + EXPECT_THAT(Run(entry), IsOkAndHolds(true)); + EXPECT_EQ(entry->node_count(), 1); + ASSERT_TRUE(entry->return_value()->Is()); + EXPECT_EQ(entry->return_value()->As()->value(), + Value(UBits(21, 11))); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/cse_pass.cc b/xls/passes/cse_pass.cc new file mode 100644 index 0000000000..119f152908 --- /dev/null +++ b/xls/passes/cse_pass.cc @@ -0,0 +1,67 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/cse_pass.h" + +#include "absl/hash/hash.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/node_iterator.h" + +namespace xls { + +xabsl::StatusOr CsePass::RunOnFunction(Function* f, + const PassOptions& options, + PassResults* results) const { + // To improve efficiency, bucket potentially common nodes together. The + // bucketing is done via an int64 hash value which is constructed from the + // op() of the node and the uid's of the node's operands. + auto hasher = absl::Hash>(); + auto node_hash = [&](Node* n) { + std::vector values_to_hash = {static_cast(n->op())}; + for (Node* operand : n->operands()) { + values_to_hash.push_back(operand->id()); + } + // If this is slow because of many literals, the Literal values could be + // combined into the hash. As is, all literals get the same hash value. + return hasher(values_to_hash); + }; + bool changed = false; + absl::flat_hash_map> node_buckets; + node_buckets.reserve(f->node_count()); + for (Node* node : TopoSort(f)) { + int64 hash = node_hash(node); + if (!node_buckets.contains(hash)) { + node_buckets[hash].push_back(node); + continue; + } + bool replaced = false; + for (Node* candidate : node_buckets.at(hash)) { + if (node->operands() == candidate->operands() && + node->IsDefinitelyEqualTo(candidate)) { + XLS_ASSIGN_OR_RETURN(bool node_changed, + node->ReplaceUsesWith(candidate)); + changed |= node_changed; + replaced = true; + break; + } + } + if (!replaced) { + node_buckets[hash].push_back(node); + } + } + + return changed; +} + +} // namespace xls diff --git a/xls/passes/cse_pass.h b/xls/passes/cse_pass.h new file mode 100644 index 0000000000..3cabdd1893 --- /dev/null +++ b/xls/passes/cse_pass.h @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_CSE_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_CSE_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// Pass which performs common subexpression elimination. Equivalent ops with the +// same operands are commoned. The pass can find arbitrarily large common +// expressions. +class CsePass : public FunctionPass { + public: + CsePass() : FunctionPass("cse", "Common subexpression elimination") {} + ~CsePass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_CSE_PASS_H_ diff --git a/xls/passes/cse_pass_test.cc b/xls/passes/cse_pass_test.cc new file mode 100644 index 0000000000..f523b03e89 --- /dev/null +++ b/xls/passes/cse_pass_test.cc @@ -0,0 +1,179 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/cse_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" +#include "xls/passes/dce_pass.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class CsePassTest : public IrTestBase { + protected: + CsePassTest() = default; + + xabsl::StatusOr Run(Function* f) { + PassResults results; + XLS_ASSIGN_OR_RETURN(bool changed, + CsePass().RunOnFunction(f, PassOptions(), &results)); + // Run dce to clean things up. + XLS_RETURN_IF_ERROR(DeadCodeEliminationPass() + .RunOnFunction(f, PassOptions(), &results) + .status()); + // Return whether cse changed anything. + return changed; + } +}; + +TEST_F(CsePassTest, SingleLiteralNoChange) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn single_literal() -> bits[2] { + ret one: bits[2] = literal(value=1) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 1); + EXPECT_THAT(Run(f), IsOkAndHolds(false)); + EXPECT_EQ(f->node_count(), 1); +} + +TEST_F(CsePassTest, TwoIdenticalLiterals) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn IdenticalLiterals() -> (bits[2], bits[2]) { + literal.1: bits[2] = literal(value=1) + literal.2: bits[2] = literal(value=1) + ret tuple.3: (bits[2], bits[2]) = tuple(literal.1, literal.2) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 3); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 2); + EXPECT_EQ(f->return_value()->operand(0), f->return_value()->operand(1)); +} + +TEST_F(CsePassTest, NontrivialCommonSubexpressions) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn nontrivial(x: bits[8], y: bits[8], z: bits[8]) -> bits[8] { + and.1: bits[8] = and(x, y) + neg.2: bits[8] = neg(and.1) + or.3: bits[8] = or(neg.2, z) + + and.4: bits[8] = and(x, y) + neg.5: bits[8] = neg(and.4) + or.6: bits[8] = or(neg.5, z) + + ret add.7: bits[8] = add(or.3, or.6) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 10); + EXPECT_EQ(FindNode("x", f)->users().size(), 2); + EXPECT_EQ(FindNode("y", f)->users().size(), 2); + + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + + EXPECT_EQ(f->node_count(), 7); + EXPECT_EQ(f->return_value()->operand(0), f->return_value()->operand(1)); + EXPECT_EQ(FindNode("x", f)->users().size(), 1); + EXPECT_EQ(FindNode("y", f)->users().size(), 1); +} + +TEST_F(CsePassTest, CountedFor) { + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackage(R"( +package CountedFor + +fn body(x: bits[11], y: bits[11]) -> bits[11] { + ret add.3: bits[11] = add(x, y) +} + +fn same_as_body(a: bits[11], b: bits[11]) -> bits[11] { + ret add.102: bits[11] = add(a, b) +} + +fn main(x: bits[11]) -> (bits[11], bits[11], bits[11]) { + counted_for.6: bits[11] = counted_for(x, trip_count=7, stride=1, body=body) + counted_for.7: bits[11] = counted_for(x, trip_count=7, stride=1, body=body) + counted_for.8: bits[11] = counted_for(x, trip_count=7, stride=1, body=same_as_body) + ret tuple.4: (bits[11], bits[11], bits[11]) = tuple(counted_for.6, counted_for.7, counted_for.8) +} +)")); + + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, p->EntryFunction()); + EXPECT_EQ(entry->node_count(), 5); + + EXPECT_THAT(Run(entry), IsOkAndHolds(true)); + + EXPECT_EQ(entry->node_count(), 3); + EXPECT_EQ(entry->return_value()->operand(0), + entry->return_value()->operand(1)); + EXPECT_EQ(entry->return_value()->operand(0), + entry->return_value()->operand(2)); +} + +TEST_F(CsePassTest, BitSliceConcat) { + // Note: in constructing this test case we're careful to put a literal value + // with a different bitwidth ahead of the other literals in topological order + // (as a regression test for not expanding the node bucket for a distinct + // value). + XLS_ASSERT_OK_AND_ASSIGN(auto p, ParsePackage(R"( +package bit_slice_concat + +fn main(x: bits[11]) -> (bits[11], bits[11], bits[11]) { + bit_slice.2: bits[10] = bit_slice(x, start=1, width=10) + literal.42: bits[32] = literal(value=0) + concat.43: bits[42] = concat(literal.42, bit_slice.2) + bit_slice.44: bits[10] = bit_slice(concat.43, start=0, width=10) + literal.3: bits[1] = literal(value=0, pos=1,2,3) + literal.4: bits[1] = literal(value=0, pos=4,5,6) + literal.5: bits[1] = literal(value=0, pos=7,8,9) + concat.6: bits[11] = concat(bit_slice.44, literal.3, pos=10,11,12) + concat.7: bits[11] = concat(bit_slice.44, literal.4, pos=11,12,13) + concat.8: bits[11] = concat(bit_slice.44, literal.5, pos=12,13,14) + ret tuple.9: (bits[11], bits[11], bits[11]) = tuple(concat.6, concat.7, concat.8) +} +)")); + + XLS_ASSERT_OK_AND_ASSIGN(Function * entry, p->EntryFunction()); + + EXPECT_THAT(Run(entry), IsOkAndHolds(true)); + + EXPECT_EQ(R"(fn main(x: bits[11]) -> (bits[11], bits[11], bits[11]) { + literal.42: bits[32] = literal(value=0) + bit_slice.2: bits[10] = bit_slice(x, start=1, width=10) + concat.43: bits[42] = concat(literal.42, bit_slice.2) + bit_slice.44: bits[10] = bit_slice(concat.43, start=0, width=10) + literal.3: bits[1] = literal(value=0, pos=1,2,3) + concat.6: bits[11] = concat(bit_slice.44, literal.3, pos=10,11,12) + ret tuple.9: (bits[11], bits[11], bits[11]) = tuple(concat.6, concat.6, concat.6) +} +)", + entry->DumpIr()); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/dce_pass.cc b/xls/passes/dce_pass.cc new file mode 100644 index 0000000000..874b7875fa --- /dev/null +++ b/xls/passes/dce_pass.cc @@ -0,0 +1,57 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/dce_pass.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/dfs_visitor.h" +#include "xls/ir/node_iterator.h" + +namespace xls { + +xabsl::StatusOr DeadCodeEliminationPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + std::deque worklist; + for (Node* n : f->nodes()) { + if (n->users().empty() && n != f->return_value() && !n->Is()) { + worklist.push_back(n); + } + } + int64 removed_count = 0; + absl::flat_hash_set unique_operands; + while (!worklist.empty()) { + Node* node = worklist.front(); + worklist.pop_front(); + + // A node may appear more than once as an operand of 'node'. Keep track of + // which operands have been handled in a set. + unique_operands.clear(); + for (Node* operand : node->operands()) { + if (unique_operands.insert(operand).second) { + if (operand->users().size() == 1 && operand != f->return_value() && + !operand->Is()) { + worklist.push_back(operand); + } + } + } + XLS_RETURN_IF_ERROR(f->RemoveNode(node)); + removed_count++; + } + + XLS_VLOG(2) << "Removed " << removed_count << " dead nodes"; + return removed_count > 0; +} + +} // namespace xls diff --git a/xls/passes/dce_pass.h b/xls/passes/dce_pass.h new file mode 100644 index 0000000000..3e07fdb87c --- /dev/null +++ b/xls/passes/dce_pass.h @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Dead Code Elimination. +// +#ifndef THIRD_PARTY_XLS_PASSES_DCE_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_DCE_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// class DeadCodeEliminationPass iterates up from a functions result +// nodes and marks all visited node. After that, all unvisited nodes +// are considered dead. +class DeadCodeEliminationPass : public FunctionPass { + public: + DeadCodeEliminationPass() : FunctionPass("dce", "Dead Code Elimination") {} + ~DeadCodeEliminationPass() override {} + + // Iterate all nodes, mark and eliminate the unvisited nodes. + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_DCE_PASS_H_ diff --git a/xls/passes/dce_pass_test.cc b/xls/passes/dce_pass_test.cc new file mode 100644 index 0000000000..6828096ff1 --- /dev/null +++ b/xls/passes/dce_pass_test.cc @@ -0,0 +1,84 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/dce_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_test_base.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class DeadCodeEliminationPassTest : public IrTestBase { + protected: + DeadCodeEliminationPassTest() = default; + + xabsl::StatusOr Run(Function* f) { + PassResults results; + return DeadCodeEliminationPass().RunOnFunction(f, PassOptions(), &results); + } +}; + +TEST_F(DeadCodeEliminationPassTest, NoDeadCode) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn func(x: bits[42], y: bits[42]) -> bits[42] { + ret neg.1: bits[42] = neg(x) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 3); + EXPECT_THAT(Run(f), IsOkAndHolds(false)); + EXPECT_EQ(f->node_count(), 3); +} + +TEST_F(DeadCodeEliminationPassTest, SomeDeadCode) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn func(x: bits[42], y: bits[42]) -> bits[42] { + neg.1: bits[42] = neg(x) + add.2: bits[42] = add(x, y) + neg.3: bits[42] = neg(add.2) + ret sub.4: bits[42] = sub(neg.1, y) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 6); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 4); +} + +TEST_F(DeadCodeEliminationPassTest, RepeatedOperand) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn func(x: bits[42], y: bits[42]) -> bits[42] { + neg.1: bits[42] = neg(x) + add.2: bits[42] = add(neg.1, neg.1) + ret sub.3: bits[42] = sub(x, y) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 5); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 3); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/dfe_pass.cc b/xls/passes/dfe_pass.cc new file mode 100644 index 0000000000..784c27b49e --- /dev/null +++ b/xls/passes/dfe_pass.cc @@ -0,0 +1,76 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/dfe_pass.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/node_iterator.h" + +namespace xls { +namespace { + +void MarkReachedFunctions(Function* func, + absl::flat_hash_set* reached) { + reached->insert(func); + // iterate over statements and find invocations or references. + for (Node* node : TopoSort(func)) { + switch (node->op()) { + case Op::kCountedFor: + MarkReachedFunctions(node->As()->body(), reached); + break; + case Op::kInvoke: + MarkReachedFunctions(node->As()->to_apply(), reached); + break; + case Op::kMap: + MarkReachedFunctions(node->As()->to_apply(), reached); + break; + default: + break; + } + } + } + +} // namespace + +// Starting from the return_value(s), DFS over all nodes. Unvisited +// nodes, or parameters, are dead. +xabsl::StatusOr DeadFunctionEliminationPass::Run( + Package* p, const PassOptions& options, PassResults* results) const { + absl::flat_hash_set reached; + // TODO(meheff): Package:EntryFunction check fails if there is not a function + // named "main". Ideally as an invariant a Package should always have an entry + // function, but for now look for it and bail if it does not exist. + xabsl::StatusOr func = p->EntryFunction(); + if (!func.ok()) { + return false; + } + + MarkReachedFunctions(*func, &reached); + + // Accumulate a list of nodes to unlink. + std::vector to_unlink; + for (std::unique_ptr& f : p->functions()) { + if (!reached.contains(f)) { + XLS_VLOG(2) << "Dead Function Elimination: " << f->name(); + to_unlink.push_back(f.get()); + } + } + if (!to_unlink.empty()) { + p->DeleteDeadFunctions(to_unlink); + } + return !to_unlink.empty(); +} + +} // namespace xls diff --git a/xls/passes/dfe_pass.h b/xls/passes/dfe_pass.h new file mode 100644 index 0000000000..3b13aca980 --- /dev/null +++ b/xls/passes/dfe_pass.h @@ -0,0 +1,42 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Dead Function Elimination. +// +#ifndef THIRD_PARTY_XLS_PASSES_DFE_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_DFE_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// class DeadCodeEliminationPass iterates up from a functions result +// nodes and marks all visited node. After that, all unvisited nodes +// are considered dead. +class DeadFunctionEliminationPass : public Pass { + public: + explicit DeadFunctionEliminationPass() + : Pass("dfe", "Dead Function Elimination") {} + ~DeadFunctionEliminationPass() override {} + + // Iterate all nodes and mark and eliminate unreachable functions. + xabsl::StatusOr Run(Package* p, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_DFE_PASS_H_ diff --git a/xls/passes/dfe_pass_test.cc b/xls/passes/dfe_pass_test.cc new file mode 100644 index 0000000000..9f896d0e91 --- /dev/null +++ b/xls/passes/dfe_pass_test.cc @@ -0,0 +1,119 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/dfe_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_test_base.h" +#include "xls/passes/pass_base.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class DeadFunctionEliminationPassTest : public IrTestBase { + protected: + DeadFunctionEliminationPassTest() = default; + + xabsl::StatusOr Run(Package* p) { + PassResults results; + return DeadFunctionEliminationPass().Run(p, PassOptions(), &results); + } + + xabsl::StatusOr MakeFunction(absl::string_view name, Package* p) { + FunctionBuilder fb(name, p); + fb.Param("arg", p->GetBitsType(32)); + return fb.Build(); + } +}; + +TEST_F(DeadFunctionEliminationPassTest, NoDeadFunctions) { + auto p = absl::make_unique(TestName(), /*entry=*/"the_entry"); + XLS_ASSERT_OK_AND_ASSIGN(Function * a, MakeFunction("a", p.get())); + XLS_ASSERT_OK_AND_ASSIGN(Function * b, MakeFunction("b", p.get())); + FunctionBuilder fb("the_entry", p.get()); + BValue x = fb.Param("x", p->GetBitsType(32)); + fb.Add(fb.Invoke({x}, a), fb.Invoke({x}, b)); + XLS_ASSERT_OK(fb.Build().status()); + + EXPECT_EQ(p->functions().size(), 3); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_EQ(p->functions().size(), 3); +} + +TEST_F(DeadFunctionEliminationPassTest, OneDeadFunction) { + auto p = absl::make_unique(TestName(), /*entry=*/"the_entry"); + XLS_ASSERT_OK_AND_ASSIGN(Function * a, MakeFunction("a", p.get())); + XLS_ASSERT_OK(MakeFunction("dead", p.get()).status()); + FunctionBuilder fb("the_entry", p.get()); + BValue x = fb.Param("x", p->GetBitsType(32)); + fb.Add(fb.Invoke({x}, a), fb.Invoke({x}, a)); + XLS_ASSERT_OK(fb.Build().status()); + + EXPECT_EQ(p->functions().size(), 3); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_EQ(p->functions().size(), 2); +} + +TEST_F(DeadFunctionEliminationPassTest, OneDeadFunctionButNoEntry) { + // If no entry function is specified, then DFS cannot happen as all functions + // are live. + auto p = absl::make_unique(TestName()); + XLS_ASSERT_OK_AND_ASSIGN(Function * a, MakeFunction("a", p.get())); + XLS_ASSERT_OK(MakeFunction("dead", p.get()).status()); + FunctionBuilder fb("blah", p.get()); + BValue x = fb.Param("x", p->GetBitsType(32)); + fb.Add(fb.Invoke({x}, a), fb.Invoke({x}, a)); + XLS_ASSERT_OK(fb.Build().status()); + + EXPECT_EQ(p->functions().size(), 3); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_EQ(p->functions().size(), 3); +} + +TEST_F(DeadFunctionEliminationPassTest, MapAndCountedFor) { + // If no entry function is specified, then DFS cannot happen as all functions + // are live. + auto p = absl::make_unique(TestName(), /*entry=*/"the_entry"); + XLS_ASSERT_OK_AND_ASSIGN(Function * a, MakeFunction("a", p.get())); + Function* body; + { + FunctionBuilder fb("jesse_the_loop_body", p.get()); + fb.Param("arg", p->GetTupleType({p->GetBitsType(32), p->GetBitsType(32)})); + fb.Literal(UBits(123, 32)); + XLS_ASSERT_OK_AND_ASSIGN(body, fb.Build()); + } + FunctionBuilder fb("the_entry", p.get()); + BValue x = fb.Param("x", p->GetBitsType(32)); + BValue ar = fb.Param("ar", p->GetArrayType(42, p->GetBitsType(32))); + BValue mapped_ar = fb.Map(ar, a); + BValue for_loop = fb.CountedFor(x, /*trip_count=*/42, /*stride=*/1, body); + fb.Tuple({mapped_ar, for_loop}); + + XLS_ASSERT_OK(fb.Build().status()); + + EXPECT_EQ(p->functions().size(), 3); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_EQ(p->functions().size(), 3); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/dump_pass.cc b/xls/passes/dump_pass.cc new file mode 100644 index 0000000000..5ac8522314 --- /dev/null +++ b/xls/passes/dump_pass.cc @@ -0,0 +1,28 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/dump_pass.h" + +namespace xls { + +// Starting from the return_value(s), call the dumper function. +xabsl::StatusOr DumpPass::RunOnFunction(Function* f, + const PassOptions& options, + PassResults* results) const { + std::string func = f->DumpIr(); + std::cerr << std::endl << func << std::endl; + return true; +} + +} // namespace xls diff --git a/xls/passes/dump_pass.h b/xls/passes/dump_pass.h new file mode 100644 index 0000000000..e07c1a1458 --- /dev/null +++ b/xls/passes/dump_pass.h @@ -0,0 +1,40 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A pass that does nothing but dump the IR. +// +#ifndef THIRD_PARTY_XLS_PASSES_DUMP_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_DUMP_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// class DumpPass simply dumps the IR as is. This pass can be used +// for debugging purposes. +class DumpPass : public FunctionPass { + public: + DumpPass() : FunctionPass("DMP", "Dump IR") {} + ~DumpPass() override = default; + + // Dumps the IR and keeps it unmodified. + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_DUMP_PASS_H_ diff --git a/xls/passes/identity_removal_pass.cc b/xls/passes/identity_removal_pass.cc new file mode 100644 index 0000000000..c15765565b --- /dev/null +++ b/xls/passes/identity_removal_pass.cc @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/identity_removal_pass.h" + +#include "xls/common/status/status_macros.h" +#include "xls/ir/node_iterator.h" + +namespace xls { + +// Identity Removal performs one forward pass over the TopoSort'ed nodes +// and replaces identities with their respective operands. +xabsl::StatusOr IdentityRemovalPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + bool changed = false; + absl::flat_hash_map identity_map; + auto get_src_value = [&](Node* n) { + return n->op() == Op::kIdentity ? identity_map.at(n) : n; + }; + for (Node* node : TopoSort(f)) { + if (node->op() == Op::kIdentity) { + identity_map[node] = node->operand(0); + } + } + for (Node* node : TopoSort(f)) { + if (node->op() == Op::kIdentity) { + identity_map[node] = get_src_value(node->operand(0)); + XLS_ASSIGN_OR_RETURN(bool node_changed, + node->ReplaceUsesWith(identity_map.at(node))); + changed |= node_changed; + } + } + return changed; +} + +} // namespace xls diff --git a/xls/passes/identity_removal_pass.h b/xls/passes/identity_removal_pass.h new file mode 100644 index 0000000000..15e2c253bf --- /dev/null +++ b/xls/passes/identity_removal_pass.h @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Identity Removal - eliminate all identity() expressions. + +#ifndef THIRD_PARTY_XLS_PASSES_IDENTITY_REMOVAL_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_IDENTITY_REMOVAL_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// class IdentityRemovalPass eliminates all identity() expressions +// by forward substituting it's parameters to the uses of the +// identity's def. +class IdentityRemovalPass : public FunctionPass { + public: + IdentityRemovalPass() : FunctionPass("ident_remove", "Identity Removal") {} + ~IdentityRemovalPass() override {} + + // Iterate all nodes and eliminate identities. + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_IDENTITY_REMOVAL_PASS_H_ diff --git a/xls/passes/identity_removal_pass_test.cc b/xls/passes/identity_removal_pass_test.cc new file mode 100644 index 0000000000..5669bea1f5 --- /dev/null +++ b/xls/passes/identity_removal_pass_test.cc @@ -0,0 +1,95 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/identity_removal_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" +#include "xls/passes/dce_pass.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class IdentityRemovalPassTest : public IrTestBase { + protected: + IdentityRemovalPassTest() = default; + + xabsl::StatusOr Run(Package* p) { + PassResults results; + XLS_ASSIGN_OR_RETURN(bool changed, + IdentityRemovalPass().Run(p, PassOptions(), &results)); + // Run dce to clean things up. + XLS_RETURN_IF_ERROR( + DeadCodeEliminationPass().Run(p, PassOptions(), &results).status()); + // Return whether cse changed anything. + return changed; + } +}; + +TEST_F(IdentityRemovalPassTest, IdentityChainRemoval) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn simple_neg(x:bits[8]) -> bits[8] { + one: bits[8] = literal(value=1) + v1: bits[8] = identity(x) + add: bits[8] = add(v1, one) + v2: bits[8] = identity(add) + v3: bits[8] = identity(v2) + v4: bits[8] = identity(v3) + v5: bits[8] = identity(v4) + v6: bits[8] = identity(v5) + v7: bits[8] = identity(v6) + v8: bits[8] = identity(v7) + v9: bits[8] = identity(v8) + add2:bits[8] = sub(v9, one) + ret add2 + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn simple_neg(x: bits[8]) -> bits[8] { + literal.2: bits[8] = literal(value=1) + add.4: bits[8] = add(x, literal.2) + ret sub.13: bits[8] = sub(add.4, literal.2) +} +)"); + EXPECT_EQ(f->node_count(), 4); +} + +TEST_F(IdentityRemovalPassTest, IdentityRemovalFromParam) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn simple_param(x:bits[8]) -> bits[8] { + ret res: bits[8] = identity(x) + } + )", + p.get())); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_EQ(f->DumpIr(), R"(fn simple_param(x: bits[8]) -> bits[8] { + ret param.1: bits[8] = param(name=x) +} +)"); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/inlining_pass.cc b/xls/passes/inlining_pass.cc new file mode 100644 index 0000000000..28a497a083 --- /dev/null +++ b/xls/passes/inlining_pass.cc @@ -0,0 +1,92 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/inlining_pass.h" + +#include "absl/status/status.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/node_iterator.h" + +namespace xls { +namespace { + +bool ShouldInline(Invoke* invoke) { + // Inline all the things! + // + // TODO(xls-team): 2019-04-12 May want a more sophisticated policy than this + // one day. + return true; +} + +// Finds an "effectively used" (has users or is return value) invoke in the +// function f, or returns nullptr if none is found. +Invoke* FindInvoke(Function* f) { + for (Node* node : TopoSort(f)) { + if (node->Is() && + (node == f->return_value() || !node->users().empty()) && + ShouldInline(node->As())) { + return node->As(); + } + } + return nullptr; +} + +// Unrolls the node "loop" by replacing it with a sequence of dependent +// invocations. +absl::Status InlineInvoke(Invoke* invoke, Function* f) { + Function* invoked = invoke->to_apply(); + absl::flat_hash_map invoked_node_to_replacement; + for (int64 i = 0; i < invoked->params().size(); ++i) { + Node* param = invoked->params()[i]; + invoked_node_to_replacement[param] = invoke->operand(i); + } + for (Node* node : TopoSort(invoked)) { + if (invoked_node_to_replacement.find(node) != + invoked_node_to_replacement.end()) { + // Already taken care of (e.g. parameters above). + continue; + } + std::vector new_operands; + for (Node* operand : node->operands()) { + new_operands.push_back(invoked_node_to_replacement.at(operand)); + } + XLS_ASSIGN_OR_RETURN(Node * new_node, node->Clone(new_operands, f)); + invoked_node_to_replacement[node] = new_node; + } + + XLS_RETURN_IF_ERROR(invoke + ->ReplaceUsesWith(invoked_node_to_replacement.at( + invoked->return_value())) + .status()); + return f->RemoveNode(invoke); +} + +} // namespace + +xabsl::StatusOr InliningPass::RunOnFunction(Function* f, + const PassOptions& options, + PassResults* results) const { + bool changed = false; + while (true) { + Invoke* invoke = FindInvoke(f); + if (invoke == nullptr) { + break; + } + XLS_RETURN_IF_ERROR(InlineInvoke(invoke, f)); + changed = true; + } + return changed; +} + +} // namespace xls diff --git a/xls/passes/inlining_pass.h b/xls/passes/inlining_pass.h new file mode 100644 index 0000000000..ba3984b103 --- /dev/null +++ b/xls/passes/inlining_pass.h @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_INLINING_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_INLINING_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +class InliningPass : public FunctionPass { + public: + InliningPass() : FunctionPass("inlining", "Inlines invocations") {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_INLINING_PASS_H_ diff --git a/xls/passes/inlining_pass_test.cc b/xls/passes/inlining_pass_test.cc new file mode 100644 index 0000000000..a9ba4e15da --- /dev/null +++ b/xls/passes/inlining_pass_test.cc @@ -0,0 +1,95 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/inlining_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/ir_parser.h" +#include "xls/passes/dce_pass.h" + +namespace xls { +namespace { + +void Inline(absl::string_view program, std::string* output) { + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(program)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, p->GetFunction("caller")); + PassResults results; + XLS_ASSERT_OK_AND_ASSIGN( + bool changed, InliningPass().RunOnFunction(f, PassOptions(), &results)); + EXPECT_TRUE(changed); + XLS_ASSERT_OK(DeadCodeEliminationPass() + .RunOnFunction(f, PassOptions(), &results) + .status()); + *output = f->DumpIr(); +} + +TEST(InliningPassTest, AddWrapper) { + const std::string program = R"( +package some_package + +fn callee(x: bits[32], y: bits[32]) -> bits[32] { + ret add.1: bits[32] = add(x, y) +} + +fn caller() -> bits[32] { + literal.2: bits[32] = literal(value=2) + ret invoke.3: bits[32] = invoke(literal.2, literal.2, to_apply=callee) +} +)"; + + std::string output; + Inline(program, &output); + + const std::string expected = R"(fn caller() -> bits[32] { + literal.2: bits[32] = literal(value=2) + ret add.4: bits[32] = add(literal.2, literal.2) +} +)"; + EXPECT_EQ(expected, output); +} + +TEST(InliningPassTest, Transitive) { + const std::string program = R"( +package some_package + +fn callee2(x: bits[32], y: bits[32]) -> bits[32] { + ret add.1: bits[32] = add(x, y) +} + +fn callee1(x: bits[32], y: bits[32]) -> bits[32] { + ret invoke.2: bits[32] = invoke(x, y, to_apply=callee2) +} + +fn caller() -> bits[32] { + literal.3: bits[32] = literal(value=2) + ret invoke.4: bits[32] = invoke(literal.3, literal.3, to_apply=callee1) +} +)"; + + std::string output; + Inline(program, &output); + + const std::string expected = R"(fn caller() -> bits[32] { + literal.3: bits[32] = literal(value=2) + ret add.7: bits[32] = add(literal.3, literal.3) +} +)"; + EXPECT_EQ(expected, output); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/literal_uncommoning_pass.cc b/xls/passes/literal_uncommoning_pass.cc new file mode 100644 index 0000000000..6227085f7e --- /dev/null +++ b/xls/passes/literal_uncommoning_pass.cc @@ -0,0 +1,62 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/literal_uncommoning_pass.h" + +#include "xls/common/status/status_macros.h" +#include "xls/ir/dfs_visitor.h" +#include "xls/ir/node_iterator.h" + +namespace xls { + +xabsl::StatusOr LiteralUncommoningPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + // Construct separate list of the initial literals to avoid iterator + // invalidation problems because we will be adding additional nodes during + // the transformation. + std::vector literals; + for (const auto& node : f->nodes()) { + if (node->Is() && node->GetType()->IsBits()) { + literals.push_back(node->As()); + } + } + + bool changed = false; + for (Literal* literal : literals) { + bool is_first_use = true; + std::vector uses(literal->users().begin(), literal->users().end()); + for (Node* use : uses) { + // Iterate through the operands explicitly because we want a new literal + // for each operand slot. + for (int64 operand_no = 0; operand_no < use->operand_count(); + ++operand_no) { + if (use->operand(operand_no) == literal) { + if (is_first_use) { + // Keep the first use, and clone the literal for the remaining uses. + is_first_use = false; + continue; + } + XLS_ASSIGN_OR_RETURN(Node * clone, + literal->Clone(/*new_operands=*/{}, f)); + XLS_RETURN_IF_ERROR(use->ReplaceOperandNumber(operand_no, clone)); + changed = true; + } + } + } + } + + return changed; +} + +} // namespace xls diff --git a/xls/passes/literal_uncommoning_pass.h b/xls/passes/literal_uncommoning_pass.h new file mode 100644 index 0000000000..f504c03ba5 --- /dev/null +++ b/xls/passes/literal_uncommoning_pass.h @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_LITERAL_UNCOMMONING_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_LITERAL_UNCOMMONING_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// Pass which clones Literals such that each literal has only a single use +// (inverse of CSE). The motivation is that Literals are trivially and freely +// materializable so no need to share an instance. +class LiteralUncommoningPass : public FunctionPass { + public: + LiteralUncommoningPass() + : FunctionPass("literal_uncommon", "Literal uncommoning") {} + ~LiteralUncommoningPass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_LITERAL_UNCOMMONING_PASS_H_ diff --git a/xls/passes/literal_uncommoning_pass_test.cc b/xls/passes/literal_uncommoning_pass_test.cc new file mode 100644 index 0000000000..a7bd87d355 --- /dev/null +++ b/xls/passes/literal_uncommoning_pass_test.cc @@ -0,0 +1,141 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/literal_uncommoning_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class LiteralUncommoningPassTest : public IrTestBase { + protected: + LiteralUncommoningPassTest() = default; + + xabsl::StatusOr Run(Package* p) { + PassResults results; + return LiteralUncommoningPass().Run(p, PassOptions(), &results); + } +}; + +TEST_F(LiteralUncommoningPassTest, SingleLiteralNoChange) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn single_literal() -> bits[2] { + ret one: bits[2] = literal(value=1) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 1); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_EQ(f->node_count(), 1); +} + +TEST_F(LiteralUncommoningPassTest, TwoLiteralsNoChange) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn single_literal() -> bits[42] { + one: bits[42] = literal(value=1) + two: bits[42] = literal(value=2) + ret and: bits[42] = and(one, two) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 3); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_EQ(f->node_count(), 3); +} + +TEST_F(LiteralUncommoningPassTest, LiteralHasMultipleUses) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn single_literal() -> bits[42] { + literal.1: bits[42] = literal(value=123) + neg.2: bits[42] = neg(literal.1) + not.3: bits[42] = not(literal.1) + ret not.4: bits[42] = not(literal.1) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 4); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_EQ(f->node_count(), 6); +} + +TEST_F(LiteralUncommoningPassTest, LiteralDuplicatedInOperands) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn single_literal() -> bits[42] { + literal.1: bits[42] = literal(value=123) + ret and: bits[42] = and(literal.1, literal.1) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 2); + EXPECT_EQ(f->return_value()->operand(0), f->return_value()->operand(1)); + + EXPECT_THAT(Run(p.get()), IsOkAndHolds(true)); + + EXPECT_EQ(f->node_count(), 3); + ASSERT_TRUE(f->return_value()->operand(0)->Is()); + ASSERT_TRUE(f->return_value()->operand(1)->Is()); + EXPECT_NE(f->return_value()->operand(0), f->return_value()->operand(1)); + EXPECT_EQ(f->return_value()->operand(0)->As()->value().bits(), + UBits(123, 42)); + EXPECT_EQ(f->return_value()->operand(1)->As()->value().bits(), + UBits(123, 42)); +} + +TEST_F(LiteralUncommoningPassTest, DoNotUncommonArrays) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn single_literal(x: bits[32], y: bits[32]) -> bits[32] { + literal.1: bits[32][4] = literal(value=[1, 2, 3, 4]) + array_index.2: bits[32] = array_index(literal.1, x) + array_index.3: bits[32] = array_index(literal.1, y) + ret add.4: bits[32] = add(array_index.2, array_index.3) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 6); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_EQ(f->node_count(), 6); +} + +TEST_F(LiteralUncommoningPassTest, DoNotUncommonTuples) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn single_literal() -> bits[32] { + literal.1: (bits[32], bits[32]) = literal(value=(42, 2)) + tuple_index.2: bits[32] = tuple_index(literal.1, index=0) + tuple_index.3: bits[32] = tuple_index(literal.1, index=1) + ret add.4: bits[32] = add(tuple_index.2, tuple_index.3) + } + )", + p.get())); + EXPECT_EQ(f->node_count(), 4); + EXPECT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_EQ(f->node_count(), 4); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/map_inlining_pass.cc b/xls/passes/map_inlining_pass.cc new file mode 100644 index 0000000000..af8b900ce0 --- /dev/null +++ b/xls/passes/map_inlining_pass.cc @@ -0,0 +1,78 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/map_inlining_pass.h" + +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "absl/time/time.h" +#include "xls/common/math_util.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/function_builder.h" +#include "xls/passes/pass_base.h" + +namespace xls { + +MapInliningPass::MapInliningPass() + : FunctionPass("map_inlining", "Inline map operations") {} + +xabsl::StatusOr MapInliningPass::RunOnFunction( + Function* function, const PassOptions& options, + PassResults* results) const { + bool changed = false; + std::vector map_nodes; + for (Node* node : function->nodes()) { + if (node->Is()) { + map_nodes.push_back(node); + changed = true; + } + } + + for (Node* node : map_nodes) { + XLS_RETURN_IF_ERROR(ReplaceMap(node->As())); + } + + return changed; +} + +absl::Status MapInliningPass::ReplaceMap(Map* map) const { + Function* function = map->function(); + + int map_inputs_size = map->operand(0)->GetType()->AsArrayOrDie()->size(); + std::vector invocations; + invocations.reserve(map_inputs_size); + for (int i = 0; i < map_inputs_size; i++) { + Value index_value = + Value(UBits(i, Bits::MinBitCountUnsigned(map_inputs_size))); + XLS_ASSIGN_OR_RETURN(Node * index, + function->MakeNode(map->loc(), index_value)); + XLS_ASSIGN_OR_RETURN( + Node * array_index, + function->MakeNode(map->loc(), map->operand(0), index)); + XLS_ASSIGN_OR_RETURN( + Node * node, + function->MakeNode(map->loc(), absl::MakeSpan(&array_index, 1), + map->to_apply())); + invocations.push_back(node); + } + + Type* output_element_type = map->GetType()->AsArrayOrDie()->element_type(); + XLS_RETURN_IF_ERROR( + map->ReplaceUsesWithNew(invocations, output_element_type) + .status()); + return function->RemoveNode(map); +} + +} // namespace xls diff --git a/xls/passes/map_inlining_pass.h b/xls/passes/map_inlining_pass.h new file mode 100644 index 0000000000..a40c6f960c --- /dev/null +++ b/xls/passes/map_inlining_pass.h @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_MAP_INLINING_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_MAP_INLINING_PASS_H_ + +#include "absl/strings/string_view.h" +#include "xls/passes/passes.h" + +namespace xls { + +// A pass to convert map nodes to in-line Invoke nodes. We don't directly lower +// maps to Verilog. +class MapInliningPass : public FunctionPass { + public: + MapInliningPass(); + xabsl::StatusOr RunOnFunction(Function* function, + const PassOptions& options, + PassResults* results) const override; + + private: + // Replaces a single Map node with a CountedFor operation. + absl::Status ReplaceMap(Map* map) const; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_MAP_INLINING_PASS_H_ diff --git a/xls/passes/map_inlining_pass_test.cc b/xls/passes/map_inlining_pass_test.cc new file mode 100644 index 0000000000..3c960ed67d --- /dev/null +++ b/xls/passes/map_inlining_pass_test.cc @@ -0,0 +1,93 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/map_inlining_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_parser.h" + +namespace xls { +namespace { + +namespace m = ::xls::op_matchers; + +// "Smoke" test for a basic map transform. +TEST(MapInliningPassTest, BasicOperation) { + const char kPackage[] = R"( +package p + +fn map_fn(x: bits[32]) -> bits[16] { + ret bit_slice.1: bits[16] = bit_slice(x, start=0, width=16) +} + +fn main() -> bits[16][4] { + literal_1: bits[32] = literal(value=0x123) + literal_2: bits[32] = literal(value=0x456) + literal_3: bits[32] = literal(value=0x789) + literal_4: bits[32] = literal(value=0xabc) + array_1: bits[32][4] = array(literal_1, literal_2, literal_3, literal_4) + ret result: bits[16][4] = map(array_1, to_apply=map_fn) +} + +)"; + + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(kPackage)); + XLS_ASSERT_OK_AND_ASSIGN(auto func, package->GetFunction("main")); + MapInliningPass pass; + PassOptions options; + XLS_ASSERT_OK_AND_ASSIGN(bool changed, + pass.RunOnFunction(func, options, nullptr)); + ASSERT_TRUE(changed); + EXPECT_THAT(func->return_value(), + m::Array(m::Invoke(m::ArrayIndex(m::Array(), m::Literal(0))), + m::Invoke(m::ArrayIndex(m::Array(), m::Literal(1))), + m::Invoke(m::ArrayIndex(m::Array(), m::Literal(2))), + m::Invoke(m::ArrayIndex(m::Array(), m::Literal(3))))); + + XLS_VLOG(1) << package->DumpIr(); +} + +TEST(MapInliningPass, InputArrayOrLiteral) { + const char kPackage[] = R"( +package p + +fn map_fn(x: bits[32]) -> bits[16] { + ret bit_slice.1: bits[16] = bit_slice(x, start=0, width=16) +} + +fn main(a: bits[32][4]) -> bits[16][4] { + ret result: bits[16][4] = map(a, to_apply=map_fn) +} +)"; + + XLS_ASSERT_OK_AND_ASSIGN(auto package, Parser::ParsePackage(kPackage)); + XLS_ASSERT_OK_AND_ASSIGN(auto func, package->GetFunction("main")); + MapInliningPass pass; + PassOptions options; + XLS_ASSERT_OK_AND_ASSIGN(bool changed, + pass.RunOnFunction(func, options, nullptr)); + ASSERT_TRUE(changed); + + EXPECT_THAT(func->return_value(), + m::Array(m::Invoke(m::ArrayIndex(m::Param(), m::Literal(0))), + m::Invoke(m::ArrayIndex(m::Param(), m::Literal(1))), + m::Invoke(m::ArrayIndex(m::Param(), m::Literal(2))), + m::Invoke(m::ArrayIndex(m::Param(), m::Literal(3))))); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/narrowing_pass.cc b/xls/passes/narrowing_pass.cc new file mode 100644 index 0000000000..694365fb16 --- /dev/null +++ b/xls/passes/narrowing_pass.cc @@ -0,0 +1,484 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/narrowing_pass.h" + +#include "xls/common/logging/logging.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/node_iterator.h" +#include "xls/ir/node_util.h" +#include "xls/ir/op.h" +#include "xls/passes/query_engine.h" +#include "xls/passes/ternary_query_engine.h" + +namespace xls { + +namespace { + +// Return the number of leading known zeros in the given nodes values. +int64 CountLeadingKnownZeros(Node* node, const QueryEngine& query_engine) { + int64 leading_zeros = 0; + for (int64 i = node->BitCountOrDie() - 1; i >= 0; --i) { + if (!query_engine.IsZero(BitLocation{node, i})) { + break; + } + ++leading_zeros; + } + return leading_zeros; +} + +// Return the number of leading known ones in the given nodes values. +int64 CountLeadingKnownOnes(Node* node, const QueryEngine& query_engine) { + int64 leading_ones = 0; + for (int64 i = node->BitCountOrDie() - 1; i >= 0; --i) { + if (!query_engine.IsOne(BitLocation{node, i})) { + break; + } + ++leading_ones; + } + return leading_ones; +} + +// Try to narrow the operands of comparison operations. Returns true if the +// given compare operation was narrowed. +xabsl::StatusOr MaybeNarrowCompare(CompareOp* compare, + const QueryEngine& query_engine) { + // Returns the number of consecutive leading/trailing bits that are known to + // be equal between the LHS and RHS of the given compare operation. + auto matched_leading_operand_bits = [&](CompareOp* c) -> int64 { + int64 bit_count = c->operand(0)->BitCountOrDie(); + for (int64 i = 0; i < bit_count; ++i) { + int64 bit_index = bit_count - i - 1; + if (!query_engine.KnownEquals(BitLocation{c->operand(0), bit_index}, + BitLocation{c->operand(1), bit_index})) { + return i; + } + } + return bit_count; + }; + auto matched_trailing_operand_bits = [&](CompareOp* c) -> int64 { + int64 bit_count = c->operand(0)->BitCountOrDie(); + for (int64 i = 0; i < bit_count; ++i) { + if (!query_engine.KnownEquals(BitLocation{c->operand(0), i}, + BitLocation{c->operand(1), i})) { + return i; + } + } + return bit_count; + }; + + // Narrow the operands of the compare to the given bit count. Replace the + // given comparison operation with the new narrower compare operation. + auto narrow_compare_operands = [](CompareOp* c, int64 start, + int64 bit_count) -> absl::Status { + XLS_ASSIGN_OR_RETURN(Node * narrowed_lhs, + c->function()->MakeNode( + c->loc(), c->operand(0), start, bit_count)); + XLS_ASSIGN_OR_RETURN(Node * narrowed_rhs, + c->function()->MakeNode( + c->loc(), c->operand(1), start, bit_count)); + return c->ReplaceUsesWithNew(narrowed_lhs, narrowed_rhs, c->op()) + .status(); + }; + + int64 operand_width = compare->operand(0)->BitCountOrDie(); + + // Matched leading and trailing bits of operands for unsigned comparisons (and + // Eq and Ne) can be stripped away. For example: + // + // UGt(0110_0XXX_0011, 0110_0YYY_0011) == UGt(XXX, YYY) + // + // Skip this optimization if all bits match because the logic needs to be + // special cased for this, and that case is handled via other optimization + // passes. + int64 matched_leading_bits = matched_leading_operand_bits(compare); + int64 matched_trailing_bits = matched_trailing_operand_bits(compare); + bool all_bits_match = + matched_leading_bits == compare->operand(0)->BitCountOrDie(); + if ((IsUnsignedCompare(compare) || compare->op() == Op::kEq || + compare->op() == Op::kNe) && + (matched_leading_bits > 0 || matched_trailing_bits > 0) && + !all_bits_match) { + XLS_RETURN_IF_ERROR(narrow_compare_operands( + compare, /*start=*/matched_trailing_bits, + operand_width - matched_leading_bits - matched_trailing_bits)); + return true; + } + + // All but one of the leading known zeros (ones) on both sides of an signed + // compare can be sliced away except. The unsliced bit must remain as the sign + // bit. + int64 common_leading_ones_or_zeros = + std::min(CountLeadingKnownZeros(compare->operand(0), query_engine), + CountLeadingKnownZeros(compare->operand(1), query_engine)); + if (common_leading_ones_or_zeros == 0) { + common_leading_ones_or_zeros = + std::min(CountLeadingKnownOnes(compare->operand(0), query_engine), + CountLeadingKnownOnes(compare->operand(1), query_engine)); + } + if (IsSignedCompare(compare) && common_leading_ones_or_zeros > 1) { + XLS_RETURN_IF_ERROR(narrow_compare_operands( + compare, /*start=*/0, + /*bit_count=*/operand_width - common_leading_ones_or_zeros + 1)); + return true; + } + + // If both operands of a signed compare are sign-extensions we can narrow + // the compare to wider of the two operands *before* sign_extension. + if (IsSignedCompare(compare) && compare->operand(0)->op() == Op::kSignExt && + compare->operand(1)->op() == Op::kSignExt) { + int64 max_unextended_width = + std::max(compare->operand(0)->operand(0)->BitCountOrDie(), + compare->operand(1)->operand(0)->BitCountOrDie()); + if (max_unextended_width < operand_width) { + XLS_RETURN_IF_ERROR(narrow_compare_operands( + compare, /*start=*/0, /*bit_count=*/max_unextended_width)); + return true; + } + } + return false; +} + +// Try to narrow the shift amount of a shift node. +xabsl::StatusOr MaybeNarrowShiftAmount(Node* shift, + const QueryEngine& query_engine) { + XLS_RET_CHECK(shift->op() == Op::kShll || shift->op() == Op::kShrl || + shift->op() == Op::kShra); + int64 leading_zeros = CountLeadingKnownZeros(shift->operand(1), query_engine); + if (leading_zeros == shift->operand(1)->BitCountOrDie()) { + // Shift amount is zero. Replace with (slice of) input operand of shift. + if (shift->BitCountOrDie() == shift->operand(0)->BitCountOrDie()) { + XLS_RETURN_IF_ERROR(shift->ReplaceUsesWith(shift->operand(0)).status()); + } else { + // Shift instruction is narrower than its input operand. Replace with + // slice of input. + XLS_RET_CHECK_LE(shift->BitCountOrDie(), + shift->operand(0)->BitCountOrDie()); + XLS_RETURN_IF_ERROR( + shift + ->ReplaceUsesWithNew(shift->operand(0), /*start=*/0, + /*width=*/shift->BitCountOrDie()) + .status()); + } + return true; + } else if (leading_zeros > 0) { + // Prune the leading zeros from the shift amount. + XLS_ASSIGN_OR_RETURN( + Node * narrowed_shift_amount, + shift->function()->MakeNode( + shift->loc(), shift->operand(1), /*start=*/0, + /*width=*/shift->operand(1)->BitCountOrDie() - leading_zeros)); + XLS_RETURN_IF_ERROR(shift + ->ReplaceUsesWithNew(shift->operand(0), + narrowed_shift_amount, + shift->op()) + .status()); + return true; + } + return false; +} + +// Try to narrow the index value of an array index operation. +xabsl::StatusOr MaybeNarrowArrayIndex(ArrayIndex* array_index, + const QueryEngine& query_engine) { + Node* index = array_index->operand(1); + // TODO(b/148457283): Unconditionally narrow the width of the index to the + // minimum number of bits require to index the entire array. + if (index->Is()) { + return false; + } + int64 index_width = index->BitCountOrDie(); + int64 leading_zeros = CountLeadingKnownZeros(index, query_engine); + if (leading_zeros == index_width) { + XLS_ASSIGN_OR_RETURN(Node * zero, + array_index->function()->MakeNode( + array_index->loc(), Value(UBits(0, index_width)))); + XLS_RETURN_IF_ERROR(array_index->ReplaceOperandNumber(1, zero)); + return true; + } else if (leading_zeros > 0) { + XLS_ASSIGN_OR_RETURN(Node * narrowed_index, + array_index->function()->MakeNode( + array_index->loc(), index, /*start=*/0, + /*width=*/index_width - leading_zeros)); + XLS_RETURN_IF_ERROR(array_index + ->ReplaceUsesWithNew( + array_index->operand(0), narrowed_index) + .status()); + return true; + } + return false; +} + +// Try to narrow an add with known bits. +xabsl::StatusOr MaybeNarrowAdd(Node* add, + const QueryEngine& query_engine) { + XLS_VLOG(3) << "Trying to narrow add: " << add->ToString(); + + XLS_RET_CHECK_EQ(add->op(), Op::kAdd); + + Node* lhs = add->operand(0); + Node* rhs = add->operand(1); + const int64 bit_count = add->BitCountOrDie(); + if (lhs->BitCountOrDie() != rhs->BitCountOrDie()) { + return false; + } + + int64 common_leading_zeros = std::min( + CountLeadingKnownZeros(lhs, query_engine), + CountLeadingKnownZeros(rhs, query_engine)); + + if (common_leading_zeros > 1) { + // Narrow the add removing all but one of the known-zero leading + // bits. Example: + // + // 000XXX + 0000YY => { 00, 0XXX + 00YY } + // + if (common_leading_zeros == bit_count) { + // All of the bits of both operands are zero. This case is handled + // elsewhere by replacing the operands with literal zeros. + return false; + } + int64 narrowed_bit_count = bit_count - common_leading_zeros + 1; + XLS_ASSIGN_OR_RETURN( + Node * narrowed_lhs, + lhs->function()->MakeNode(lhs->loc(), lhs, /*start=*/0, + /*width=*/narrowed_bit_count)); + XLS_ASSIGN_OR_RETURN( + Node * narrowed_rhs, + rhs->function()->MakeNode(rhs->loc(), rhs, /*start=*/0, + /*width=*/narrowed_bit_count)); + XLS_ASSIGN_OR_RETURN(Node * narrowed_add, + add->function()->MakeNode( + add->loc(), narrowed_lhs, narrowed_rhs, Op::kAdd)); + XLS_RETURN_IF_ERROR( + add->ReplaceUsesWithNew(narrowed_add, bit_count, Op::kZeroExt) + .status()); + return true; + } + + return false; +} + +// Try to narrow the operands and/or the result of a multiply. +xabsl::StatusOr MaybeNarrowMultiply(ArithOp* mul, + const QueryEngine& query_engine) { + XLS_VLOG(3) << "Trying to narrow multiply: " << mul->ToString(); + + XLS_RET_CHECK(mul->op() == Op::kSMul || mul->op() == Op::kUMul); + + Node* lhs = mul->operand(0); + Node* rhs = mul->operand(1); + const int64 result_bit_count = mul->BitCountOrDie(); + const int64 lhs_bit_count = lhs->BitCountOrDie(); + const int64 rhs_bit_count = rhs->BitCountOrDie(); + XLS_VLOG(3) << absl::StreamFormat( + " result_bit_count = %d, lhs_bit_count = %d, rhs_bit_count = %d", + result_bit_count, lhs_bit_count, rhs_bit_count); + + // Return the given node sign-extended (if 'mul' is Op::kSMul) or + // zero-extended (if 'mul' is Op::kUMul) to the given bit count. If the node + // is already of the given width, then the node is returned. + auto maybe_extend = [&](Node* node, + int64 bit_count) -> xabsl::StatusOr { + XLS_RET_CHECK(node->BitCountOrDie() <= bit_count); + if (node->BitCountOrDie() == bit_count) { + return node; + } + return node->function()->MakeNode( + node->loc(), node, + /*new_bit_count=*/bit_count, + /*op=*/mul->op() == Op::kSMul ? Op::kSignExt : Op::kZeroExt); + }; + + // Return the given node narrowed to the given bit count. If the node + // is already of the given width, then the node is returned. + auto maybe_narrow = [&](Node* node, + int64 bit_count) -> xabsl::StatusOr { + XLS_RET_CHECK(node->BitCountOrDie() >= bit_count); + if (node->BitCountOrDie() == bit_count) { + return node; + } + return node->function()->MakeNode(node->loc(), node, /*start=*/0, + /*width=*/bit_count); + }; + + // The result can be unconditionally narrowed to the sum of the operand + // widths, then zero/sign extended. + if (result_bit_count > lhs_bit_count + rhs_bit_count) { + XLS_VLOG(3) << "Result is wider than sum of operands. Narrowing multiply."; + XLS_ASSIGN_OR_RETURN( + Node * narrowed_mul, + mul->function()->MakeNode( + mul->loc(), lhs, rhs, + /*width=*/lhs_bit_count + rhs_bit_count, mul->op())); + XLS_ASSIGN_OR_RETURN(Node * replacement, + maybe_extend(narrowed_mul, result_bit_count)); + return mul->ReplaceUsesWith(replacement); + } + + // The operands can be unconditionally narrowed to the result width. + if (lhs_bit_count > result_bit_count || rhs_bit_count > result_bit_count) { + Node* narrowed_lhs = lhs; + Node* narrowed_rhs = rhs; + if (lhs_bit_count > result_bit_count) { + XLS_ASSIGN_OR_RETURN(narrowed_lhs, maybe_narrow(lhs, result_bit_count)); + } + if (rhs_bit_count > result_bit_count) { + XLS_ASSIGN_OR_RETURN(narrowed_rhs, maybe_narrow(rhs, result_bit_count)); + } + XLS_RETURN_IF_ERROR( + mul->ReplaceUsesWithNew(narrowed_lhs, narrowed_rhs, + result_bit_count, mul->op()) + .status()); + return true; + } + + // A multiply where the result and both operands are the same width is the + // same operation whether it is signed or unsigned. + bool is_sign_agnostic = + result_bit_count == lhs_bit_count && result_bit_count == rhs_bit_count; + + // Zero-extended operands of unsigned multiplies can be narrowed. + if (mul->op() == Op::kUMul || is_sign_agnostic) { + bool operand_narrowed = false; + auto maybe_narrow_operand = [&](Node* operand) -> xabsl::StatusOr { + int64 leading_zeros = CountLeadingKnownZeros(operand, query_engine); + if (leading_zeros == 0) { + return operand; + } + operand_narrowed = true; + return mul->function()->MakeNode( + mul->loc(), operand, /*start=*/0, + /*width=*/operand->BitCountOrDie() - leading_zeros); + }; + XLS_ASSIGN_OR_RETURN(Node * operand0, + maybe_narrow_operand(mul->operand(0))); + XLS_ASSIGN_OR_RETURN(Node * operand1, + maybe_narrow_operand(mul->operand(1))); + if (operand_narrowed) { + XLS_RETURN_IF_ERROR(mul->ReplaceUsesWithNew(operand0, operand1, + result_bit_count, + Op::kUMul) + .status()); + return true; + } + } + + // Sign-extended operands of signed multiplies can be narrowed by replacing + // the operand of the multiply with the value before sign-extension. + if (mul->op() == Op::kSMul || is_sign_agnostic) { + bool operand_narrowed = false; + auto maybe_narrow_operand = [&](Node* operand) -> xabsl::StatusOr { + if (operand->op() == Op::kSignExt) { + // Operand is a sign-extended value. Just use the value before + // sign-extension. + operand_narrowed = true; + return operand->operand(0); + } + if (CountLeadingKnownZeros(operand, query_engine) > 1) { + // Operand has more than one leading zero, something like: + // operand = 0000XXXX + // This is equivalent to: + // operand = signextend(0XXXX) + // So we can replace the operand with 0XXXX. + operand_narrowed = true; + return maybe_narrow( + operand, operand->BitCountOrDie() - + CountLeadingKnownZeros(operand, query_engine) + 1); + } + return operand; + }; + XLS_ASSIGN_OR_RETURN(Node * operand0, + maybe_narrow_operand(mul->operand(0))); + XLS_ASSIGN_OR_RETURN(Node * operand1, + maybe_narrow_operand(mul->operand(1))); + if (operand_narrowed) { + XLS_RETURN_IF_ERROR(mul->ReplaceUsesWithNew(operand0, operand1, + result_bit_count, + Op::kSMul) + .status()); + return true; + } + } + + // TODO(meheff): If either lhs or rhs has trailing zeros, the multiply can be + // narrowed and the result concatenated with trailing zeros. + + return false; +} + +} // namespace + +xabsl::StatusOr NarrowingPass::RunOnFunction(Function* f, + const PassOptions& options, + PassResults* results) const { + XLS_ASSIGN_OR_RETURN(std::unique_ptr query_engine, + TernaryQueryEngine::Run(f)); + + bool modified = false; + for (Node* node : TopoSort(f)) { + // Narrow the shift-amount operand of shift operations if the shift-amount + // has leading zeros. + bool node_modified = false; + switch (node->op()) { + case Op::kShll: + case Op::kShrl: + case Op::kShra: { + XLS_ASSIGN_OR_RETURN(node_modified, + MaybeNarrowShiftAmount(node, *query_engine)); + break; + } + case Op::kArrayIndex: { + XLS_ASSIGN_OR_RETURN( + node_modified, + MaybeNarrowArrayIndex(node->As(), *query_engine)); + break; + } + case Op::kSMul: + case Op::kUMul: { + XLS_ASSIGN_OR_RETURN( + node_modified, + MaybeNarrowMultiply(node->As(), *query_engine)); + break; + } + case Op::kULe: + case Op::kULt: + case Op::kUGe: + case Op::kUGt: + case Op::kSLe: + case Op::kSLt: + case Op::kSGe: + case Op::kSGt: + case Op::kEq: + case Op::kNe: { + XLS_ASSIGN_OR_RETURN( + node_modified, + MaybeNarrowCompare(node->As(), *query_engine)); + break; + } + case Op::kAdd: { + XLS_ASSIGN_OR_RETURN(node_modified, + MaybeNarrowAdd(node, *query_engine)); + break; + } + default: + break; + } + modified |= node_modified; + } + return modified; +} + +} // namespace xls diff --git a/xls/passes/narrowing_pass.h b/xls/passes/narrowing_pass.h new file mode 100644 index 0000000000..9d77ffedab --- /dev/null +++ b/xls/passes/narrowing_pass.h @@ -0,0 +1,37 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_NARROWING_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_NARROWING_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/passes.h" + +namespace xls { + +// A pass which reduces the width of operations eliminating redundant or unused +// bits. +class NarrowingPass : public FunctionPass { + public: + NarrowingPass() : FunctionPass("narrow", "Narrowing") {} + ~NarrowingPass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_NARROWING_PASS_H_ diff --git a/xls/passes/narrowing_pass_test.cc b/xls/passes/narrowing_pass_test.cc new file mode 100644 index 0000000000..617842f2f3 --- /dev/null +++ b/xls/passes/narrowing_pass_test.cc @@ -0,0 +1,326 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/narrowing_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_test_base.h" +#include "xls/passes/pass_base.h" + +namespace m = ::xls::op_matchers; + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; +using ::testing::AllOf; + +class NarrowingPassTest : public IrTestBase { + protected: + NarrowingPassTest() = default; + + xabsl::StatusOr Run(Package* p) { + PassResults results; + return NarrowingPass().Run(p, PassOptions(), &results); + } +}; + +TEST_F(NarrowingPassTest, UnnarrowableShift) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Shra(fb.Param("in", p->GetBitsType(32)), + fb.Param("amt", p->GetBitsType(32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_THAT(f->return_value(), m::Shra(m::Param("in"), m::Param("amt"))); +} + +TEST_F(NarrowingPassTest, NarrowableShift) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Shll( + fb.Param("in", p->GetBitsType(32)), + fb.ZeroExtend(fb.Param("amt", p->GetBitsType(3)), /*new_bit_count=*/123)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Shll(m::Param("in"), m::BitSlice(/*start=*/0, /*width=*/3))); +} + +TEST_F(NarrowingPassTest, ShiftWithKnownZeroShiftAmount) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Shll(fb.Param("in", p->GetBitsType(32)), fb.Literal(UBits(0, 27))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param("in")); +} + +TEST_F(NarrowingPassTest, ShiftWithKnownOnePrefix) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Shll(fb.Param("in", p->GetBitsType(32)), + fb.Concat({fb.Literal(UBits(0b111000, 6)), + fb.Param("amt", p->GetBitsType(3))})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_THAT(f->return_value(), m::Shll(m::Param("in"), m::Concat())); +} + +TEST_F(NarrowingPassTest, NarrowableArrayIndex) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.ArrayIndex( + fb.Param("a", p->GetArrayType(42, p->GetBitsType(32))), + fb.ZeroExtend(fb.Param("idx", p->GetBitsType(8)), /*new_bit_count=*/123)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + m::ArrayIndex(m::Param("a"), m::BitSlice(/*start=*/0, /*width=*/8))); +} + +TEST_F(NarrowingPassTest, NarrowableArrayIndexAllZeros) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.ArrayIndex(fb.Param("a", p->GetArrayType(42, p->GetBitsType(32))), + fb.And(fb.Param("idx", p->GetBitsType(8)), + fb.Literal(Value(UBits(0, 8))))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::ArrayIndex(m::Param("a"), m::Literal(0))); +} + +TEST_F(NarrowingPassTest, LiteralArrayIndex) { + // A literal array index should not be substituted. + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.ArrayIndex(fb.Param("a", p->GetArrayType(42, p->GetBitsType(32))), + fb.Literal(Value(UBits(0x0f, 8)))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); + EXPECT_THAT(f->return_value(), + m::ArrayIndex(m::Param("a"), m::Literal(0x0f))); +} + +TEST_F(NarrowingPassTest, MultiplyWiderThanSumOfOperands) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u8 = p->GetBitsType(8); + fb.SMul(fb.Param("lhs", u8), fb.Param("rhs", u8), /*result_width=*/42); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + AllOf(m::Type("bits[42]"), + m::SignExt(AllOf(m::Type("bits[16]"), + m::SMul(m::Param("lhs"), m::Param("rhs")))))); +} + +TEST_F(NarrowingPassTest, MultiplyOperandsWiderThanResult) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u17 = p->GetBitsType(17); + Type* u42 = p->GetBitsType(42); + fb.UMul(fb.Param("lhs", u17), fb.Param("rhs", u42), /*result_width=*/9); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + m::UMul(AllOf(m::Type("bits[9]"), + m::BitSlice(m::Param("lhs"), /*start=*/0, /*width=*/9)), + AllOf(m::Type("bits[9]"), + m::BitSlice(m::Param("rhs"), /*start=*/0, /*width=*/9)))); +} + +TEST_F(NarrowingPassTest, ExtendedUMulOperands) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u17 = p->GetBitsType(17); + fb.UMul(fb.ZeroExtend(fb.Param("lhs", u17), 32), + fb.SignExtend(fb.Param("rhs", u17), 54), + /*result_width=*/62); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + // Only the zero-extend should have been elided. + EXPECT_THAT(f->return_value(), + m::UMul(m::BitSlice(m::ZeroExt(m::Param("lhs")), /*start=*/0, + /*width=*/17), + m::SignExt(m::Param("rhs")))); +} + +TEST_F(NarrowingPassTest, ExtendedSMulOperands) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u17 = p->GetBitsType(17); + fb.SMul(fb.ZeroExtend(fb.Param("lhs", u17), 21), + fb.SignExtend(fb.Param("rhs", u17), 23), + /*result_width=*/29); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + // The zero-extended operand should be sliced down to the (zero) sign bit. + EXPECT_THAT(f->return_value(), + m::SMul(m::BitSlice(m::ZeroExt(m::Param("lhs")), /*start=*/0, + /*width=*/18), + m::Param("rhs"))); +} + +TEST_F(NarrowingPassTest, LeadingZerosOfUnsignedCompare) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.UGt(fb.ZeroExtend(fb.Param("lhs", p->GetBitsType(17)), 42), + fb.ZeroExtend(fb.Param("rhs", p->GetBitsType(23)), 42)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + m::UGt( + m::BitSlice(m::ZeroExt(m::Param("lhs")), /*start=*/0, /*width=*/23), + m::BitSlice(m::ZeroExt(m::Param("rhs")), /*start=*/0, /*width=*/23))); +} + +TEST_F(NarrowingPassTest, LeadingZerosOneOneSideOfUnsignedCompare) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + // Leading zeros on only one side of the unsigned comparison should not result + // in any transformation. + fb.UGt(fb.Param("lhs", p->GetBitsType(42)), + fb.ZeroExtend(fb.Param("rhs", p->GetBitsType(23)), 42)); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +TEST_F(NarrowingPassTest, MatchedLeadingBitsOfUnsignedCompare) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.ULe(fb.Concat({fb.Literal(UBits(0b001101, 6)), + fb.Param("lhs", p->GetBitsType(13))}), + fb.Concat({fb.Literal(UBits(0b0011, 4)), + fb.Param("rhs", p->GetBitsType(15))})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::ULe(m::BitSlice(m::Concat(), /*start=*/0, /*width=*/15), + m::BitSlice(m::Concat(), /*start=*/0, /*width=*/15))); +} + +TEST_F(NarrowingPassTest, MatchedLeadingAndTrailingBitsOfUnsignedCompare) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + // Four matching leading bits and three matching trailing bits. + fb.ULe(fb.Concat({fb.Literal(UBits(0b001101, 6)), + fb.Param("lhs", p->GetBitsType(13)), + fb.Literal(UBits(0b10101, 5))}), + fb.Concat({fb.Literal(UBits(0b0011, 4)), + fb.Param("rhs", p->GetBitsType(15)), + fb.Literal(UBits(0b11101, 5))})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::ULe(m::BitSlice(m::Concat(), /*start=*/3, /*width=*/17), + m::BitSlice(m::Concat(), /*start=*/3, /*width=*/17))); +} + +TEST_F(NarrowingPassTest, LeadingZerosSignedCompare) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.SGt(fb.ZeroExtend(fb.Param("lhs", p->GetBitsType(17)), 42), + fb.ZeroExtend(fb.Param("rhs", p->GetBitsType(23)), 42)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + m::SGt( + m::BitSlice(m::ZeroExt(m::Param("lhs")), /*start=*/0, /*width=*/24), + m::BitSlice(m::ZeroExt(m::Param("rhs")), /*start=*/0, /*width=*/24))); +} + +TEST_F(NarrowingPassTest, LeadingOnesOfSignedCompare) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.SLe(fb.Concat({fb.Literal(UBits(0b111101, 6)), + fb.Param("lhs", p->GetBitsType(12))}), + fb.Concat({fb.Literal(UBits(0b111, 3)), + fb.Param("rhs", p->GetBitsType(15))})); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::SLe(m::BitSlice(m::Concat(), /*start=*/0, /*width=*/16), + m::BitSlice(m::Concat(), /*start=*/0, /*width=*/16))); +} + +TEST_F(NarrowingPassTest, SignExtendedOperandsOfSignedCompare) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.SGe(fb.SignExtend(fb.Param("lhs", p->GetBitsType(17)), 42), + fb.SignExtend(fb.Param("rhs", p->GetBitsType(23)), 42)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + m::SGe( + m::BitSlice(m::SignExt(m::Param("lhs")), /*start=*/0, /*width=*/23), + m::BitSlice(m::SignExt(m::Param("rhs")), /*start=*/0, /*width=*/23))); +} + +TEST_F(NarrowingPassTest, CompareOfIdenticalLiterals) { + // Identical literals should not be narrowed by this pass. Those are handled + // elsewhere. + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.UGt(fb.Literal(Value(UBits(42, 32))), fb.Literal(Value(UBits(42, 32)))); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +TEST_F(NarrowingPassTest, AddWithLeadingZeros) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Add(fb.ZeroExtend(fb.Param("lhs", p->GetBitsType(10)), 42), + fb.ZeroExtend(fb.Param("rhs", p->GetBitsType(30)), 42)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::ZeroExt(m::Add(m::BitSlice(m::ZeroExt(m::Param("lhs")), + /*start=*/0, /*width=*/31), + m::BitSlice(m::ZeroExt(m::Param("rhs")), + /*start=*/0, /*width=*/31)))); +} + +TEST_F(NarrowingPassTest, AddWithOnlyOneOperandLeadingZeros) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Add(fb.Param("lhs", p->GetBitsType(42)), + fb.ZeroExtend(fb.Param("rhs", p->GetBitsType(30)), 42)); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +TEST_F(NarrowingPassTest, AddWithAllZeroOperands) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + fb.Add(fb.Literal(Value(UBits(0, 16))), + fb.Literal(Value(UBits(0, 16)))); + // There shouldn't be narrowing because this special case of all known zero is + // handled elsewhere in the pipeline. + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/pass_base.h b/xls/passes/pass_base.h new file mode 100644 index 0000000000..c63c8738de --- /dev/null +++ b/xls/passes/pass_base.h @@ -0,0 +1,369 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_PASS_BASE_H_ +#define THIRD_PARTY_XLS_PASSES_PASS_BASE_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "xls/common/file/filesystem.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/package.h" + +namespace xls { + +// This file defines a set of base classes for building XLS compiler passes and +// pass pipelines. The base classes are templated allowing polymorphism of the +// data types the pass operates on. + +// Options data structure passed to each pass run invocation. This data +// structure is passed by const reference to PassBase::Run and should contain +// options which affect how passes are run. +struct PassOptions { + // If non-empty, this is the path to the directory in which to dump + // intermediate IR files. + std::filesystem::path ir_dump_path; + + // If present, only passes whose short names are in this list will be run. + absl::optional> run_only_passes; + + // If present, passes whose short names are in this list will be skipped. If + // both run_only_passes and skip_passes are present, then only passes which + // are present in run_only_passes and not present in skip_passes will be run. + std::vector skip_passes; +}; + +// An object containing information about the invocation of a pass (single call +// to PassBase::Run). +struct PassInvocation { + // The name of the pass. + std::string pass_name; + + // Whether the IR was changed by the pass. + bool ir_changed; + + // The run duration of the pass. + absl::Duration run_duration; +}; + +// A object to which metadata may be written in each pass invocation. This data +// structure is passed by mutable pointer to PassBase::Run. +struct PassResults { + // This vector contains and entry for each invocation of each pass. + std::vector invocations; +}; + +// Base class for all compiler passes. Template parameters: +// +// IrT : The data type that the pass operates on (e.g., xls::Package). The +// type should define 'DumpIr' and 'name' methods used for dumping and +// logging in compound passes. A pass which strictly operate on the XLS IR +// may use the xls::Package type as the IrT template argument. Passes which +// operate on the IR and a schedule may be instantiated on a data structure +// containing both an xls::Package and a schedule. +// +// OptionsT : Options type passed as an immutable object to each invocation of +// PassBase::Run. This type should be derived from PassOptions because +// PassOptions contains fields required by CompoundPassBase when executing +// pass pipelines. +// +// ResultsT : Results type passed as a mutable object to each invocation of +// PassBase::Run. This type should be derived from PassResults because +// PassResults contains fields required by CompoundPassBase when executing +// pass pipelines. +template +class PassBase { + public: + PassBase(absl::string_view short_name, absl::string_view long_name) + : short_name_(short_name), long_name_(long_name) {} + + virtual ~PassBase() = default; + + const std::string& short_name() const { return short_name_; } + const std::string& long_name() const { return long_name_; } + + // Run the specific pass. Returns true if the graph was changed by the pass. + // Typically the "changed" indicator is used to determine when to terminate + // fixed point computation. + virtual xabsl::StatusOr Run(IrT* ir, const OptionsT& options, + ResultsT* results) const = 0; + + // Returns true if this is a compound pass. + virtual bool IsCompound() const { return false; } + + protected: + const std::string short_name_; + const std::string long_name_; +}; + +// A base class for abstractions which check invariants of the IR. These +// checkers are added to compound passes (pass pipelines) and run before and +// after each pass in the pipeline. +template +class InvariantCheckerBase { + public: + virtual ~InvariantCheckerBase() = default; + virtual absl::Status Run(IrT* ir, const OptionsT& options, + ResultsT* results) const = 0; +}; + +// CompoundPass is a container for other passes. For example, the scalar +// optimizer can be a compound pass holding many passes for scalar +// optimizations. +template +class CompoundPassBase : public PassBase { + public: + using Pass = PassBase; + using InvariantChecker = InvariantCheckerBase; + + CompoundPassBase(absl::string_view short_name, absl::string_view long_name) + : Pass(short_name, long_name) {} + ~CompoundPassBase() override = default; + + // Add a new pass to this compound pass. Arguments to method are the arguments + // to the pass constructor. Example usage: + // + // pass->Add(bar, qux); + // + // Returns a pointer to the newly constructed pass. + template + T* Add(Args&&... args) { + auto* pass = new T(std::forward(args)...); + passes_.emplace_back(pass); + pass_ptrs_.push_back(pass); + return pass; + } + + absl::Span passes() const { return pass_ptrs_; } + absl::Span passes() { return absl::Span(pass_ptrs_); } + + // Adds an invariant checker to the compound pass. The invariant checker is + // run before and after each pass contained in the compound pass. The checkers + // are also run for nested compound passes. Arguments to method are the + // arguments to the invariant checker constructor. Example usage: + // + // pass->Add(foo); + // + // Returns a pointer to the newly constructed pass. + template + T* AddInvariantChecker(Args&&... args) { + auto* checker = new T(std::forward(args)...); + invariant_checkers_.emplace_back(checker); + invariant_checker_ptrs_.push_back(checker); + return checker; + } + + xabsl::StatusOr Run(IrT* ir, const OptionsT& options, + ResultsT* results) const override { + if (!options.ir_dump_path.empty()) { + // Start of the top-level pass. Dump IR. + XLS_RETURN_IF_ERROR(DumpIr(options.ir_dump_path, ir, this->short_name(), + "start", + /*ordinal=*/0, /*changed=*/false)); + } + return RunInternal(ir, options, results, this->short_name(), + /*invariant_checkers=*/{}); + } + + // Internal implementation of Run for compound passes. Invoked when a compound + // pass is nested within another compound pass. Enables passing of invariant + // checkers and name of the top-level pass to nested compound passes. + virtual xabsl::StatusOr RunInternal( + IrT* ir, const OptionsT& options, ResultsT* results, + absl::string_view top_level_name, + absl::Span invariant_checkers) const; + + bool IsCompound() const override { return true; } + + protected: + // Dump the IR to a file in the given directory. Name is determined by the + // various arguments passed in. File names will be lexographically ordered by + // package name and ordinal. + absl::Status DumpIr(const std::filesystem::path& ir_dump_path, IrT* ir, + absl::string_view top_level_name, absl::string_view tag, + int64 ordinal, bool changed) const { + std::filesystem::path path = + ir_dump_path / absl::StrFormat("%s.%s.%03d.%s.%s.ir", ir->name(), + top_level_name, ordinal, tag, + changed ? "changed" : "unchanged"); + return SetFileContents(path, ir->DumpIr()); + } + + std::vector> passes_; + std::vector pass_ptrs_; + + std::vector> invariant_checkers_; + std::vector invariant_checker_ptrs_; +}; + +// A compound pass which runs its set of passes to fixed point. +template +class FixedPointCompoundPassBase + : public CompoundPassBase { + public: + FixedPointCompoundPassBase(absl::string_view short_name, + absl::string_view long_name) + : CompoundPassBase(short_name, long_name) {} + + xabsl::StatusOr RunInternal( + IrT* ir, const OptionsT& options, ResultsT* results, + absl::string_view top_level_name, + absl::Span::InvariantChecker* const> + invariant_checkers) const override { + bool local_changed = true; + bool global_changed = false; + while (local_changed) { + XLS_ASSIGN_OR_RETURN( + local_changed, + (CompoundPassBase::RunInternal( + ir, options, results, top_level_name, invariant_checkers))); + global_changed = global_changed || local_changed; + } + return global_changed; + } +}; + +template +xabsl::StatusOr CompoundPassBase::RunInternal( + IrT* ir, const OptionsT& options, ResultsT* results, + absl::string_view top_level_name, + absl::Span invariant_checkers) const { + XLS_VLOG(1) << "Running " << this->short_name() + << " compound pass on package " << ir->name(); + XLS_VLOG(2) << "Start of compound pass " << this->long_name() << ":"; + XLS_VLOG_LINES(5, ir->DumpIr()); + + // Invariant checkers may be passed in from parent compound passes or + // contained by this pass itself. Merge them together. + std::vector checkers(invariant_checkers.begin(), + invariant_checkers.end()); + checkers.insert(checkers.end(), invariant_checker_ptrs_.begin(), + invariant_checker_ptrs_.end()); + auto run_invariant_checkers = + [&](absl::string_view str_context) -> absl::Status { + for (const auto& checker : checkers) { + absl::Status status = checker->Run(ir, options, results); + if (!status.ok()) { + return absl::Status(status.code(), absl::StrCat(status.message(), "; [", + str_context, "]")); + } + } + return absl::OkStatus(); + }; + XLS_RETURN_IF_ERROR(run_invariant_checkers( + absl::StrCat("start of compound pass '", this->long_name(), "'"))); + + bool changed = false; + for (const auto& pass : passes_) { + XLS_VLOG(1) << absl::StreamFormat("Running %s (%s) pass on package %s", + pass->long_name(), pass->short_name(), + ir->name()); + + if (!pass->IsCompound() && options.run_only_passes.has_value() && + std::find_if(options.run_only_passes->begin(), + options.run_only_passes->end(), + [&](const std::string& name) { + return pass->short_name() == name; + }) == options.run_only_passes->end()) { + XLS_VLOG(1) << "Skipping pass. Not contained in run_only_passes option."; + continue; + } + + if (std::find_if(options.skip_passes.begin(), options.skip_passes.end(), + [&](const std::string& name) { + return pass->short_name() == name; + }) != options.skip_passes.end()) { + XLS_VLOG(1) << "Skipping pass. Contained in skip_passes option."; + continue; + } + +#ifdef DEBUG + // Verify that the IR should change iff Run returns true. This is slow, so + // do not check it in optimized builds. + std::string ir_before = ir->DumpIr(); +#endif + absl::Time start = absl::Now(); + bool pass_changed; + if (pass->IsCompound()) { + XLS_ASSIGN_OR_RETURN( + pass_changed, + (down_cast*>(pass.get()) + ->RunInternal(ir, options, results, top_level_name, checkers))); + } else { + XLS_ASSIGN_OR_RETURN(pass_changed, pass->Run(ir, options, results)); + } + absl::Duration duration = absl::Now() - start; +#ifdef DEBUG + std::string ir_after = ir->DumpIr(); + if (pass_changed) { + if (ir_before == ir_after) { + return absl::InternalError(absl::StrFormat( + "Pass %s indicated IR changed, but IR is unchanged:\n\n%s", + pass->short_name(), ir_before)); + } + } else { + if (ir_before != ir_after) { + return absl::InternalError( + absl::StrFormat("Pass %s indicated IR unchanged, but IR is " + "changed:\n\n[Before]\n%s !=\n[after]\n%s", + pass->short_name(), ir_before, ir_after)); + } + } +#endif + changed |= pass_changed; + XLS_VLOG(1) << "Pass " << pass->short_name() + << (pass_changed ? " changed IR" : " did not change IR"); + if (!pass->IsCompound()) { + results->invocations.push_back( + {pass->short_name(), pass_changed, duration}); + } + if (!options.ir_dump_path.empty()) { + XLS_RETURN_IF_ERROR(DumpIr(options.ir_dump_path, ir, top_level_name, + absl::StrCat("after_", pass->short_name()), + /*ordinal=*/results->invocations.size(), + /*changed=*/pass_changed)); + } + XLS_RETURN_IF_ERROR(run_invariant_checkers( + absl::StrFormat("after '%s' pass, dynamic pass #%d", pass->long_name(), + results->invocations.size() - 1))); + + XLS_VLOG(5) << "After " << pass->long_name() << ":"; + XLS_VLOG_LINES(5, ir->DumpIr()); + } + return changed; +} + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_PASS_BASE_H_ diff --git a/xls/passes/passes.cc b/xls/passes/passes.cc new file mode 100644 index 0000000000..f194e23c27 --- /dev/null +++ b/xls/passes/passes.cc @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/passes.h" + +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "xls/common/status/status_macros.h" + +namespace xls { + +xabsl::StatusOr FunctionPass::Run(Package* p, const PassOptions& options, + PassResults* results) const { + bool changed = false; + for (auto& f : p->functions()) { + XLS_ASSIGN_OR_RETURN(bool function_changed, + RunOnFunction(f.get(), options, results)); + changed |= function_changed; + } + return changed; +} + +} // namespace xls diff --git a/xls/passes/passes.h b/xls/passes/passes.h new file mode 100644 index 0000000000..fdc05f9a1d --- /dev/null +++ b/xls/passes/passes.h @@ -0,0 +1,60 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_PASSES_H_ +#define THIRD_PARTY_XLS_PASSES_PASSES_H_ + +#include + +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_set.h" +#include "absl/time/time.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/ir/package.h" +#include "xls/passes/pass_base.h" + +namespace xls { + +// Defines the pass types for passes which operate strictly on XLS IR (i.e., +// xls::Package). +using Pass = PassBase; +using CompoundPass = CompoundPassBase; +using FixedPointCompoundPass = FixedPointCompoundPassBase; +using InvariantChecker = CompoundPass::InvariantChecker; + +// Abstract base class for passes operate at function scope. The derived class +// must define RunOnFunction. +class FunctionPass : public Pass { + public: + FunctionPass(absl::string_view short_name, absl::string_view long_name) + : Pass(short_name, long_name) {} + + virtual xabsl::StatusOr RunOnFunction(Function* f, + const PassOptions& options, + PassResults* results) const = 0; + + // Iterates over each function in the package calling RunOnFunction. + xabsl::StatusOr Run(Package* p, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_PASSES_H_ diff --git a/xls/passes/passes_test.cc b/xls/passes/passes_test.cc new file mode 100644 index 0000000000..104ba7d565 --- /dev/null +++ b/xls/passes/passes_test.cc @@ -0,0 +1,399 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/passes.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/str_format.h" +#include "xls/common/casts.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/examples/sample_packages.h" +#include "xls/ir/bits.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_parser.h" +#include "xls/ir/package.h" +#include "xls/ir/type.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; +using status_testing::StatusIs; +using ::testing::ElementsAre; +using ::testing::HasSubstr; + +class DummyPass : public Pass { + public: + DummyPass(std::string short_name, std::string long_name) + : Pass(short_name, long_name) {} + + xabsl::StatusOr Run(Package* p, const PassOptions& options, + PassResults* results) const override { + return false; + } +}; + +// BuildShift0 builds an IR that initially looks like this: +// sub +// shrl +// param(x) +// sub +// sub +// lit(3) +// lit(2) +// lit(1) +// lit(0) +// +// It also starts out with some dead code. +// Simplifications should reduce the whole tree to: +// param(x) +// +std::pair, Function*> BuildShift0() { + auto m = absl::make_unique("m"); + FunctionBuilder b("simple_arith", m.get()); + Type* bits_32 = m->GetBitsType(32); + auto x = b.Param("x", bits_32); + auto y = b.Param("y", bits_32); + auto imm_0 = b.Literal(UBits(0, /*bit_count=*/32)); + auto imm_1 = b.Literal(UBits(1, /*bit_count=*/32)); + auto imm_2 = b.Literal(UBits(2, /*bit_count=*/32)); + auto imm_3 = b.Literal(UBits(3, /*bit_count=*/32)); + auto deadcode = (y - imm_0); + (void)deadcode; + auto imm = imm_3 - imm_2 - imm_1; + auto result = ((x >> imm) - imm_0); + xabsl::StatusOr f_or_status = b.BuildWithReturnValue(result); + XLS_CHECK_OK(f_or_status.status()); + return {absl::move(m), *f_or_status}; +} + +TEST(PassesTest, AddPasses) { + std::unique_ptr p = BuildShift0().first; + + CompoundPass pass_mgr("TOP", "Top level pass manager"); + + pass_mgr.Add("d1", "Dummy Pass 1"); + pass_mgr.Add("d2", "Dummy Pass 2"); + pass_mgr.Add("d3", "Dummy Pass 3"); + + auto comp_pass = pass_mgr.Add("C1", "Some Compound Pass"); + comp_pass->Add("d4", "Dummy Pass 4"); + comp_pass->Add("d5", "Dummy Pass 5"); + + pass_mgr.Add("d6", "Dummy Pass 6"); + + PassResults results; + EXPECT_THAT(pass_mgr.Run(p.get(), PassOptions(), &results), + IsOkAndHolds(false)); + std::vector invocation_names; + for (const PassInvocation& invocation : results.invocations) { + invocation_names.push_back(invocation.pass_name); + } + EXPECT_THAT(invocation_names, + ElementsAre("d1", "d2", "d3", "d4", "d5", "d6")); +} + +// Invariant checker which returns an error if the package has function with a +// particular name. +class PackageNameChecker : public InvariantChecker { + public: + explicit PackageNameChecker(absl::string_view str) : str_(str) {} + + absl::Status Run(Package* package, const PassOptions& options, + PassResults* results) const override { + for (auto& function : package->functions()) { + if (function->name() == str_) { + return absl::InternalError( + absl::StrFormat("Function has name '%s'", str_)); + } + } + return absl::OkStatus(); + } + + private: + std::string str_; +}; + +// A trivial package used in the invariant tests. +const char kInvariantTesterPackage[] = R"( +package invariant_tester + +fn foo(x:bits[8]) -> bits[8] { + ret neg.2: bits[8] = neg(x) +} +)"; + +// Makes and returns a nesting of compound passes. +std::unique_ptr MakeNestedPasses() { + auto top = absl::make_unique("top", "Top level pass manager"); + auto comp_pass = top->Add("comp_pass", "compound pass"); + auto nested_comp_pass = + comp_pass->Add("nested_comp_pass", "nested pass"); + nested_comp_pass->Add("dummy", "blah"); + return top; +} + +TEST(PassesTest, InvariantChecker) { + // Verify the test invariant checker works as expected. + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(kInvariantTesterPackage)); + PassResults results; + EXPECT_THAT( + PackageNameChecker("foo").Run(p.get(), PassOptions(), &results).message(), + HasSubstr("Function has name 'foo'")); + XLS_EXPECT_OK( + PackageNameChecker("bar").Run(p.get(), PassOptions(), &results)); +} + +TEST(PassesTest, RunWithNoInvariantChecker) { + // Verify no error when running with no invariant checkers added. + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(kInvariantTesterPackage)); + std::unique_ptr top = MakeNestedPasses(); + PassResults results; + XLS_EXPECT_OK(top->Run(p.get(), PassOptions(), &results).status()); +} + +TEST(PassesTest, RunWithPassingInvariantChecker) { + // With an invariant checker that always passes, running should return ok. + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(kInvariantTesterPackage)); + std::unique_ptr top = MakeNestedPasses(); + top->AddInvariantChecker("bar"); + PassResults results; + XLS_EXPECT_OK(top->Run(p.get(), PassOptions(), &results).status()); +} + +TEST(PassesTest, RunWithFailingInvariantChecker) { + // With an invariant checker that fails, should return an error. + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(kInvariantTesterPackage)); + std::unique_ptr top = MakeNestedPasses(); + top->AddInvariantChecker("foo"); + PassResults results; + EXPECT_THAT(top->Run(p.get(), PassOptions(), &results), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Function has name 'foo'; [start of compound " + "pass 'Top level pass manager']"))); +} + +TEST(PassesTest, RunWithFailingNestedInvariantChecker) { + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(kInvariantTesterPackage)); + auto top = absl::make_unique("top", "Top level pass manager"); + top->AddInvariantChecker("bar"); + auto nested_pass = top->Add("comp_pass", "nested pass"); + nested_pass->AddInvariantChecker("foo"); + PassResults results; + EXPECT_THAT(top->Run(p.get(), PassOptions(), &results), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Function has name 'foo'; [start of compound " + "pass 'nested pass']"))); +} + +// Pass which adds a function of a particular name +class FunctionAdderPass : public Pass { + public: + explicit FunctionAdderPass(absl::string_view name) + : Pass("function_adder", absl::StrCat("Adds function named ", name)), + name_(name) {} + + // Adds a function named 'str_' to the package. + xabsl::StatusOr Run(Package* package, const PassOptions& options, + PassResults* results) const override { + const char format_string[] = + R"( +fn %s() -> bits[32] { + ret literal.3: bits[32] = literal(value=42) +} +)"; + XLS_RETURN_IF_ERROR( + Parser::ParseFunction(absl::StrFormat(format_string, name_), package) + .status()); + return true; + } + + private: + std::string name_; +}; + +TEST(PassesTest, InvariantCheckerFailsAfterPass) { + // Verify the error message when the invariant checker fails only after + // running a particular pass. + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr p, + Parser::ParsePackage(kInvariantTesterPackage)); + std::unique_ptr top = MakeNestedPasses(); + top->AddInvariantChecker("bar"); + PassResults results; + XLS_EXPECT_OK(top->Run(p.get(), PassOptions(), &results).status()); + down_cast(top->passes()[0])->Add("bar"); + auto result = top->Run(p.get(), PassOptions(), &results); + EXPECT_THAT(result, + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Function has name 'bar'; [after 'Adds " + "function named bar' pass"))) + << result.status(); +} + +// Pass which just records that it was run via a shared vector of strings. +class RecordingPass : public Pass { + public: + RecordingPass(std::string name, std::vector* record) + : Pass(name, name), record_(record) {} + + xabsl::StatusOr Run(Package* p, const PassOptions& options, + PassResults* results) const override { + record_->push_back(short_name()); + return false; + } + + private: + std::vector* record_; +}; + +TEST(PassesTest, RunOnlyPassesOption) { + std::unique_ptr p = BuildShift0().first; + + std::vector record; + CompoundPass compound_0("compound_0", "Compound pass 0"); + compound_0.Add("foo", &record); + compound_0.Add("bar", &record); + auto compound_1 = + compound_0.Add("compound_1", "Compound pass 1"); + compound_1->Add("qux", &record); + compound_1->Add("foo", &record); + + { + PassOptions options; + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre("foo", "bar", "qux", "foo")); + } + + { + PassOptions options; + options.run_only_passes = {"foo"}; + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre("foo", "foo")); + } + + { + PassOptions options; + options.run_only_passes = {"bar", "qux"}; + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre("bar", "qux")); + } + + { + PassOptions options; + options.run_only_passes = std::vector(); + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre()); + } + + { + PassOptions options; + options.run_only_passes = {"blah"}; + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre()); + } +} + +TEST(PassesTest, SkipPassesOption) { + std::unique_ptr p = BuildShift0().first; + + std::vector record; + CompoundPass compound_0("compound_0", "Compound pass 0"); + compound_0.Add("foo", &record); + compound_0.Add("bar", &record); + auto compound_1 = + compound_0.Add("compound_1", "Compound pass 1"); + compound_1->Add("qux", &record); + compound_1->Add("foo", &record); + + { + PassOptions options; + options.skip_passes = {"blah"}; + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre("foo", "bar", "qux", "foo")); + } + + { + PassOptions options; + options.skip_passes = {"foo"}; + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre("bar", "qux")); + } + + { + PassOptions options; + options.skip_passes = {"foo", "qux"}; + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre("bar")); + } +} + +TEST(PassesTest, RunOnlyAndSkipPassesOption) { + std::unique_ptr p = BuildShift0().first; + + std::vector record; + CompoundPass compound_0("compound_0", "Compound pass 0"); + compound_0.Add("foo", &record); + compound_0.Add("bar", &record); + auto compound_1 = + compound_0.Add("compound_1", "Compound pass 1"); + compound_1->Add("qux", &record); + compound_1->Add("foo", &record); + + { + PassOptions options; + options.run_only_passes = {"foo", "qux"}; + options.skip_passes = {"foo", "bar"}; + PassResults results; + record.clear(); + EXPECT_THAT(compound_0.Run(p.get(), options, &results), + IsOkAndHolds(false)); + EXPECT_THAT(record, ElementsAre("qux")); + } +} + +} // namespace +} // namespace xls diff --git a/xls/passes/python/BUILD b/xls/passes/python/BUILD new file mode 100644 index 0000000000..193d9041dd --- /dev/null +++ b/xls/passes/python/BUILD @@ -0,0 +1,48 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pytype tests are present in this file +load("//dependency_support/pybind11:pybind11.bzl", "xls_pybind_extension") + +package( + default_visibility = ["//xls:xls_internal"], + licenses = ["notice"], # Apache 2.0 +) + +xls_pybind_extension( + name = "standard_pipeline", + srcs = ["standard_pipeline.cc"], + py_deps = [ + "//xls/ir/python:package", # build_cleaner: keep + ], + deps = [ + "//xls/common/status:statusor_pybind_caster", + "//xls/ir/python:wrapper_types", + "//xls/passes:standard_pipeline", + ], +) + +py_test( + name = "standard_pipeline_test", + srcs = ["standard_pipeline_test.py"], + python_version = "PY3", + deps = [ + ":standard_pipeline", + "@com_google_absl_py//absl/testing:absltest", + "//xls/common/python:init_xls", + "//xls/ir/python:bits", + "//xls/ir/python:function_builder", + "//xls/ir/python:package", + ], +) diff --git a/xls/passes/python/standard_pipeline.cc b/xls/passes/python/standard_pipeline.cc new file mode 100644 index 0000000000..d6f7411b6c --- /dev/null +++ b/xls/passes/python/standard_pipeline.cc @@ -0,0 +1,32 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/standard_pipeline.h" + +#include "pybind11/pybind11.h" +#include "xls/common/status/statusor_pybind_caster.h" +#include "xls/ir/python/wrapper_types.h" + +namespace py = pybind11; + +namespace xls { + +PYBIND11_MODULE(standard_pipeline, m) { + py::module::import("xls.ir.python.package"); + + m.def("run_standard_pass_pipeline", PyWrap(&RunStandardPassPipeline), + py::arg("package")); +} + +} // namespace xls diff --git a/xls/passes/python/standard_pipeline_test.py b/xls/passes/python/standard_pipeline_test.py new file mode 100644 index 0000000000..2da04cbbce --- /dev/null +++ b/xls/passes/python/standard_pipeline_test.py @@ -0,0 +1,47 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Lint as: python3 + +"""Tests for xls.passes.python.standard_pipeline.""" + +import sys + +from xls.common.python import init_xls +from xls.ir.python import bits as bits_mod +from xls.ir.python import function_builder +from xls.ir.python import package +from xls.passes.python import standard_pipeline +from absl.testing import absltest + + +def setUpModule(): + # This is required so that module initializers are called including those + # which register delay models. + init_xls.init_xls(sys.argv) + + +class StandardPipelineTest(absltest.TestCase): + + def test_standard_pipeline(self): + pkg = package.Package('pname') + fb = function_builder.FunctionBuilder('main', pkg) + fb.add_literal_bits(bits_mod.UBits(value=2, bit_count=32)) + fb.build() + + self.assertFalse(standard_pipeline.run_standard_pass_pipeline(pkg)) + + +if __name__ == '__main__': + absltest.main() diff --git a/xls/passes/query_engine.cc b/xls/passes/query_engine.cc new file mode 100644 index 0000000000..3e28c208d9 --- /dev/null +++ b/xls/passes/query_engine.cc @@ -0,0 +1,116 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/query_engine.h" + +#include "xls/common/logging/logging.h" + +namespace xls { +namespace { + +// Converts the bits of the given node into a vector of BitLocations. +std::vector ToBitLocations(Node* node) { + XLS_CHECK(node->GetType()->IsBits()); + std::vector locations; + for (int64 i = 0; i < node->BitCountOrDie(); ++i) { + locations.push_back({node, i}); + } + return locations; +} + +// Converts the single-bit Nodes in preds into a vector of BitLocations. Each +// element in preds must be a single-bit bits-typed Node. +std::vector ToBitLocations(absl::Span preds) { + std::vector locations; + for (Node* pred : preds) { + XLS_CHECK(pred->GetType()->IsBits()); + XLS_CHECK_EQ(pred->BitCountOrDie(), 1); + locations.emplace_back(pred, 0); + } + return locations; +} + +} // namespace + +bool QueryEngine::AtMostOneNodeTrue(absl::Span preds) const { + return AtMostOneTrue(ToBitLocations(preds)); +} + +bool QueryEngine::AtMostOneBitTrue(Node* node) const { + return AtMostOneTrue(ToBitLocations(node)); +} + +bool QueryEngine::AtLeastOneNodeTrue(absl::Span preds) const { + return AtLeastOneTrue(ToBitLocations(preds)); +} + +bool QueryEngine::AtLeastOneBitTrue(Node* node) const { + return AtLeastOneTrue(ToBitLocations(node)); +} + +bool QueryEngine::IsKnown(const BitLocation& bit) const { + if (!IsTracked(bit.node)) { + return false; + } + return GetKnownBits(bit.node).Get(bit.bit_index); +} + +bool QueryEngine::IsMsbKnown(Node* node) const { + return IsTracked(node) && GetKnownBits(node).msb(); +} + +bool QueryEngine::IsOne(const BitLocation& bit) const { + return IsKnown(bit) && GetKnownBitsValues(bit.node).Get(bit.bit_index); +} + +bool QueryEngine::IsZero(const BitLocation& bit) const { + return IsKnown(bit) && !GetKnownBitsValues(bit.node).Get(bit.bit_index); +} + +bool QueryEngine::GetKnownMsb(Node* node) const { + XLS_CHECK(IsMsbKnown(node)); + return GetKnownBitsValues(node).msb(); +} + +bool QueryEngine::IsAllZeros(Node* node) const { + return IsTracked(node) && GetKnownBits(node).IsAllOnes() && + GetKnownBitsValues(node).IsAllZeros(); +} + +bool QueryEngine::IsAllOnes(Node* node) const { + return IsTracked(node) && GetKnownBits(node).IsAllOnes() && + GetKnownBitsValues(node).IsAllOnes(); +} + +bool QueryEngine::AllBitsKnown(Node* node) const { + return IsTracked(node) && GetKnownBits(node).IsAllOnes(); +} + +std::string QueryEngine::ToString(Node* node) const { + XLS_CHECK(IsTracked(node)); + std::string ret = "0b"; + for (int64 i = GetKnownBits(node).bit_count() - 1; i >= 0; --i) { + std::string c = "X"; + if (IsKnown(BitLocation(node, i))) { + c = IsOne(BitLocation(node, i)) ? "1" : "0"; + } + absl::StrAppend(&ret, c); + if ((i % 4) == 0 && i != 0) { + absl::StrAppend(&ret, "_"); + } + } + return ret; +} + +} // namespace xls diff --git a/xls/passes/query_engine.h b/xls/passes/query_engine.h new file mode 100644 index 0000000000..dc48aaa1c6 --- /dev/null +++ b/xls/passes/query_engine.h @@ -0,0 +1,123 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_QUERY_ENGINE_H_ +#define THIRD_PARTY_XLS_PASSES_QUERY_ENGINE_H_ + +#include "absl/types/variant.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/bits.h" +#include "xls/ir/node.h" + +namespace xls { + +// Abstraction representing a particular bit of a particular XLS Node. +struct BitLocation { + BitLocation() : node(nullptr), bit_index(0) {} + BitLocation(Node* n, int64 i) : node(n), bit_index(i) {} + + Node* node; + int64 bit_index; +}; + +// An abstract base class providing an interface for answering queries about the +// values of and relationship between bits in an XLS function. Information +// provided include statically known bit values and implications between bits in +// the graph. +// +// Generally query methods returning a boolean value return true if the +// condition is known to be true, and false if the condition cannot be +// determined. This means a false return value does *not* mean that the +// condition is necessarily false. For example, KnownEqual(a, b) returning false +// does not mean that 'a' is necessarily not equal 'b'. Rather, the false return +// value indicates that analysis could not determine whether 'a' and 'b' are +// necessarily equal. +// TODO(meheff): Support types other than bits type. +class QueryEngine { + public: + virtual ~QueryEngine() = default; + + // Returns whether any information is available for this node. + virtual bool IsTracked(Node* node) const = 0; + + // Returns a Bits object indicating which bits have known values for the given + // node. 'node' must be a Bits type. The Bits object matches the width of the + // respective Node. A one in a bit position means that the bit has a + // statically known value (0 or 1). + virtual const Bits& GetKnownBits(Node* node) const = 0; + + // Returns a Bits object indicating the values (0 or 1) of bits in the given + // node for bits with known values. If a value at a bit position is not known, + // the respective value is zero. + virtual const Bits& GetKnownBitsValues(Node* node) const = 0; + + // Returns true if at most one of the given bits can be true. + virtual bool AtMostOneTrue(absl::Span bits) const = 0; + + // Returns true if at least one of the given bits is true. + virtual bool AtLeastOneTrue(absl::Span bits) const = 0; + + // Returns true if 'a' implies 'b'. + virtual bool Implies(const BitLocation& a, const BitLocation& b) const = 0; + + // Returns true if 'a' equals 'b' + virtual bool KnownEquals(const BitLocation& a, + const BitLocation& b) const = 0; + + // Returns true if 'a' is the inverse of 'b' + virtual bool KnownNotEquals(const BitLocation& a, + const BitLocation& b) const = 0; + + // Returns true if at most/least one of the values in 'preds' is true. Each + // value in 'preds' must be a single-bit bits-typed value. + bool AtMostOneNodeTrue(absl::Span preds) const; + bool AtLeastOneNodeTrue(absl::Span preds) const; + + // Returns true if at most/least one of the bits in 'node' is true. 'node' + // must be bits-typed. + bool AtMostOneBitTrue(Node* node) const; + bool AtLeastOneBitTrue(Node* node) const; + + // Returns whether the value of the output bit of the given node at the given + // index is known (definitely zero or one). + bool IsKnown(const BitLocation& bit) const; + + // Returns if the most-significant bit is known of 'node'. + bool IsMsbKnown(Node* node) const; + + // Returns the value of the most-significant bit of 'node'. Precondition: the + // most-significan bit must be known (IsMsbKnown returns true), + bool GetKnownMsb(Node* node) const; + + // Returns whether the value of the output bit of the given node at the given + // index is definitely one (or zero). + bool IsOne(const BitLocation& bit) const; + bool IsZero(const BitLocation& bit) const; + + // Returns whether every bit in the output of the given node is definitely one + // (or zero). + bool IsAllZeros(Node* node) const; + bool IsAllOnes(Node* node) const; + + // Returns whether *all* the bits are known for "node". + bool AllBitsKnown(Node* node) const; + + // Returns the known bits information of the given node as a string of ternary + // symbols (0, 1, or X) with a '0b' prefix. For example: 0b1XX0. + std::string ToString(Node* node) const; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_QUERY_ENGINE_H_ diff --git a/xls/passes/query_engine_test.cc b/xls/passes/query_engine_test.cc new file mode 100644 index 0000000000..fd55f3ce39 --- /dev/null +++ b/xls/passes/query_engine_test.cc @@ -0,0 +1,557 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/query_engine.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/inlined_vector.h" +#include "absl/strings/string_view.h" +#include "xls/common/logging/logging.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/function.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_test_base.h" +#include "xls/ir/package.h" +#include "xls/passes/bdd_query_engine.h" +#include "xls/passes/ternary_logic.h" +#include "xls/passes/ternary_query_engine.h" + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +enum class QueryEngineType { kTernary, kBdd }; + +// A test of the query engine parameterized to test both ternary and BDD based +// engines. Tests basic functionality common to both engines. +class QueryEngineTest : public IrTestBase, + public testing::WithParamInterface { + protected: + xabsl::StatusOr> GetEngine(Function* f) { + if (GetParam() == QueryEngineType::kTernary) { + return TernaryQueryEngine::Run(f); + } + return BddQueryEngine::Run(f); + } + + // Create a BValue with known bits equal to the given ternary vector. Created + // using a param and AND/OR masks. + BValue MakeValueWithKnownBits(absl::string_view name, + TernaryVector known_bits, FunctionBuilder* fb) { + absl::InlinedVector known_zeros; + absl::InlinedVector known_ones; + for (TernaryValue value : known_bits) { + known_zeros.push_back(value == TernaryValue::kKnownZero); + known_ones.push_back(value == TernaryValue::kKnownOne); + } + BValue and_mask = fb->Literal(bits_ops::Not(Bits(known_zeros))); + BValue or_mask = fb->Literal(Bits(known_ones)); + return fb->Or(or_mask, + fb->And(and_mask, fb->Param(name, fb->package()->GetBitsType( + known_bits.size())))); + } + + // Runs QueryEngine on the op created with the passed in function. The + // input to the op is crafted to have known bits equal to the given + // TernaryVector. + xabsl::StatusOr RunOnUnaryOp( + absl::string_view operand_known_bits, + std::function make_op) { + Package p("test_package"); + FunctionBuilder fb("f", &p); + BValue operand = MakeValueWithKnownBits( + "input", StringToTernaryVector(operand_known_bits).value(), &fb); + make_op(operand, &fb); + XLS_ASSIGN_OR_RETURN(Function * f, fb.Build()); + XLS_VLOG(3) << f->DumpIr(); + XLS_ASSIGN_OR_RETURN(std::unique_ptr engine, GetEngine(f)); + return engine->ToString(f->return_value()); + } + + // Runs QueryEngine on the op created with the passed in function. The + // inputs to the op is crafted to have known bits equal to the given + // TernaryVectors. + xabsl::StatusOr RunOnBinaryOp( + absl::string_view lhs_known_bits, absl::string_view rhs_known_bits, + std::function make_op) { + Package p("test_package"); + FunctionBuilder fb("f", &p); + BValue lhs = MakeValueWithKnownBits( + "lhs", StringToTernaryVector(lhs_known_bits).value(), &fb); + BValue rhs = MakeValueWithKnownBits( + "rhs", StringToTernaryVector(rhs_known_bits).value(), &fb); + make_op(lhs, rhs, &fb); + XLS_ASSIGN_OR_RETURN(Function * f, fb.Build()); + XLS_VLOG(3) << f->DumpIr(); + XLS_ASSIGN_OR_RETURN(std::unique_ptr engine, GetEngine(f)); + return engine->ToString(f->return_value()); + } +}; + +TEST_P(QueryEngineTest, SimpleBinaryOp) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[32], y: bits[32]) -> bits[32] { + ret add.1: bits[32] = add(x, y) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + for (int64 i = 0; i < 32; ++i) { + for (Node* node : + {FindNode("x", f), FindNode("y", f), FindNode("add.1", f)}) { + EXPECT_FALSE(engine->IsKnown(BitLocation(node, i))); + EXPECT_FALSE(engine->IsOne(BitLocation(node, i))); + EXPECT_FALSE(engine->IsZero(BitLocation(node, i))); + } + } +} + +TEST_P(QueryEngineTest, OneLiteral) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f() -> bits[16] { + ret literal.1: bits[16] = literal(value=0x0ff0) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + ASSERT_TRUE(engine->IsTracked(f->return_value())); + EXPECT_EQ(engine->ToString(f->return_value()), "0b0000_1111_1111_0000"); + EXPECT_FALSE(engine->IsAllOnes(f->return_value())); + EXPECT_FALSE(engine->IsAllZeros(f->return_value())); + EXPECT_EQ(engine->ToString(f->return_value()), "0b0000_1111_1111_0000"); +} + +TEST_P(QueryEngineTest, BitSlice) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f() -> bits[9] { + literal.1: bits[16] = literal(value=0x0ff0) + ret bit_slice.2: bits[9] = bit_slice(literal.1, start=5, width=9) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + ASSERT_TRUE(engine->IsTracked(f->return_value())); + EXPECT_EQ(engine->ToString(f->return_value()), "0b0_0111_1111"); +} + +TEST_P(QueryEngineTest, OneHotLsbToMsb) { + auto make_one_hot = [](BValue operand, FunctionBuilder* fb) { + fb->OneHot(operand, LsbOrMsb::kLsb); + }; + EXPECT_THAT(RunOnUnaryOp("0bXX1", make_one_hot), IsOkAndHolds("0b0001")); + EXPECT_THAT(RunOnUnaryOp("0bX1X", make_one_hot), IsOkAndHolds("0b00XX")); + EXPECT_THAT(RunOnUnaryOp("0bX0X", make_one_hot), IsOkAndHolds("0bXX0X")); +} + +TEST_P(QueryEngineTest, OneHotMsbToLsb) { + auto make_one_hot = [](BValue operand, FunctionBuilder* fb) { + fb->OneHot(operand, LsbOrMsb::kMsb); + }; + EXPECT_THAT(RunOnUnaryOp("0bXX1", make_one_hot), IsOkAndHolds("0b0XXX")); + EXPECT_THAT(RunOnUnaryOp("0bX1X", make_one_hot), IsOkAndHolds("0b0XX0")); + EXPECT_THAT(RunOnUnaryOp("0bX0X", make_one_hot), IsOkAndHolds("0bXX0X")); +} + +TEST_P(QueryEngineTest, OneHotSelect) { + { + auto make_select = [](BValue operand, FunctionBuilder* fb) { + std::vector cases{fb->Param("foo", fb->package()->GetBitsType(4)), + fb->Literal(UBits(0b0011, 4)), + fb->Literal(UBits(0b0101, 4))}; + fb->OneHotSelect(operand, cases); + }; + EXPECT_THAT(RunOnUnaryOp("0bXX1", make_select), IsOkAndHolds("0bXXXX")); + EXPECT_THAT(RunOnUnaryOp("0bX1X", make_select), IsOkAndHolds("0bXX11")); + EXPECT_THAT(RunOnUnaryOp("0b1XX", make_select), IsOkAndHolds("0bX1X1")); + EXPECT_THAT(RunOnUnaryOp("0b0X0", make_select), IsOkAndHolds("0b00XX")); + } +} + +TEST_P(QueryEngineTest, OneHotSelectPrecededByOneHot) { + { + auto make_select = [](BValue operand, FunctionBuilder* fb) { + std::vector cases{fb->Param("foo", fb->package()->GetBitsType(4)), + fb->Literal(UBits(0b0011, 4)), + fb->Literal(UBits(0b0101, 4))}; + fb->OneHotSelect(fb->OneHot(operand, LsbOrMsb::kLsb), cases); + }; + EXPECT_THAT(RunOnUnaryOp("0bX1", make_select), IsOkAndHolds("0bXXXX")); + EXPECT_THAT(RunOnUnaryOp("0b1X", make_select), IsOkAndHolds("0bXXXX")); + EXPECT_THAT(RunOnUnaryOp("0bX0", make_select), IsOkAndHolds("0b0XX1")); + EXPECT_THAT(RunOnUnaryOp("0b0X", make_select), IsOkAndHolds("0bXXXX")); + } +} + +TEST_P(QueryEngineTest, Shll) { + // TODO(meheff): Enable test for BDD query engine when shifts are supported. + if (GetParam() == QueryEngineType::kBdd) { + return; + } + auto make_shll = [](BValue lhs, BValue rhs, FunctionBuilder* fb) { + fb->Shll(lhs, rhs); + }; + EXPECT_THAT(RunOnBinaryOp("0bXXX", "0b110", make_shll), + IsOkAndHolds("0b000")); + EXPECT_THAT(RunOnBinaryOp("0b1XX", "0b111", make_shll), + IsOkAndHolds("0b000")); + EXPECT_THAT(RunOnBinaryOp("0b0XX", "0bX1X", make_shll), + IsOkAndHolds("0bX00")); + EXPECT_THAT(RunOnBinaryOp("0b011", "0bXXX", make_shll), + IsOkAndHolds("0bXXX")); +} + +TEST_P(QueryEngineTest, Shra) { + // TODO(meheff): Enable test for BDD query engine when shifts are supported. + if (GetParam() == QueryEngineType::kBdd) { + return; + } + auto make_shra = [](BValue lhs, BValue rhs, FunctionBuilder* fb) { + fb->Shra(lhs, rhs); + }; + EXPECT_THAT(RunOnBinaryOp("0bXXX", "0b110", make_shra), + IsOkAndHolds("0bXXX")); + EXPECT_THAT(RunOnBinaryOp("0b1XX", "0b111", make_shra), + IsOkAndHolds("0b111")); + EXPECT_THAT(RunOnBinaryOp("0b0XX", "0bX1X", make_shra), + IsOkAndHolds("0b000")); + EXPECT_THAT(RunOnBinaryOp("0b011", "0bXXX", make_shra), + IsOkAndHolds("0b0XX")); +} + +TEST_P(QueryEngineTest, Shrl) { + // TODO(meheff): Enable test for BDD query engine when shifts are supported. + if (GetParam() == QueryEngineType::kBdd) { + return; + } + auto make_shrl = [](BValue lhs, BValue rhs, FunctionBuilder* fb) { + fb->Shrl(lhs, rhs); + }; + EXPECT_THAT(RunOnBinaryOp("0bXXX", "0b110", make_shrl), + IsOkAndHolds("0b000")); + EXPECT_THAT(RunOnBinaryOp("0b1XX", "0bX11", make_shrl), + IsOkAndHolds("0b000")); + EXPECT_THAT(RunOnBinaryOp("0b0XX", "0bX1X", make_shrl), + IsOkAndHolds("0b000")); + EXPECT_THAT(RunOnBinaryOp("0b011", "0bXXX", make_shrl), + IsOkAndHolds("0b0XX")); +} + +TEST_P(QueryEngineTest, Concat) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[3], y: bits[4]) -> bits[16] { + literal.1: bits[4] = literal(value=0b1100) + literal.2: bits[5] = literal(value=0b10101) + ret concat.3: bits[16] = concat(x, literal.1, y, literal.2) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->ToString(f->return_value()), "0bXXX1_100X_XXX1_0101"); +} + +TEST_P(QueryEngineTest, BitSliceOfConcat) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[3], y: bits[4]) -> bits[6] { + literal.1: bits[4] = literal(value=0b1100) + literal.2: bits[5] = literal(value=0b10101) + concat.3: bits[16] = concat(x, literal.1, y, literal.2) + ret bit_slice.4: bits[6] = bit_slice(concat.3, start=4, width=6) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->ToString(f->return_value()), "0b0X_XXX1"); +} + +TEST_P(QueryEngineTest, AllOnesOrZeros) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f() -> bits[16] { + literal.1: bits[16] = literal(value=0) + ret literal.2: bits[16] = literal(value=0xffff) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_FALSE(engine->IsAllOnes(FindNode("literal.1", f))); + EXPECT_TRUE(engine->IsAllZeros(FindNode("literal.1", f))); + EXPECT_TRUE(engine->IsAllOnes(FindNode("literal.2", f))); + EXPECT_FALSE(engine->IsAllZeros(FindNode("literal.2", f))); +} + +TEST_P(QueryEngineTest, BitwiseOps) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[8]) -> bits[24] { + literal.1: bits[16] = literal(value=0xff00) + literal.2: bits[16] = literal(value=0xf0f0) + concat.3: bits[24] = concat(x, literal.1) + concat.4: bits[24] = concat(y, literal.2) + and.5: bits[24] = and(concat.3, concat.4) + not.6: bits[24] = not(concat.3) + or.7: bits[24] = or(concat.3, concat.4) + ret xor.8: bits[24] = xor(concat.3, concat.4) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->GetKnownBits(FindNode("and.5", f)), UBits(0x00ffff, 24)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("and.5", f)), + UBits(0x00f000, 24)); + EXPECT_EQ(engine->ToString(FindNode("and.5", f)), + "0bXXXX_XXXX_1111_0000_0000_0000"); + + EXPECT_EQ(engine->GetKnownBits(FindNode("not.6", f)), UBits(0x00ffff, 24)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("not.6", f)), + UBits(0x0000ff, 24)); + EXPECT_EQ(engine->ToString(FindNode("not.6", f)), + "0bXXXX_XXXX_0000_0000_1111_1111"); + + EXPECT_EQ(engine->GetKnownBits(FindNode("or.7", f)), UBits(0x00ffff, 24)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("or.7", f)), + UBits(0x00fff0, 24)); + EXPECT_EQ(engine->ToString(FindNode("or.7", f)), + "0bXXXX_XXXX_1111_1111_1111_0000"); + + EXPECT_EQ(engine->GetKnownBits(FindNode("xor.8", f)), UBits(0x00ffff, 24)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("xor.8", f)), + UBits(0x000ff0, 24)); + EXPECT_EQ(engine->ToString(FindNode("xor.8", f)), + "0bXXXX_XXXX_0000_1111_1111_0000"); +} + +TEST_P(QueryEngineTest, SignExtend) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[4]) -> bits[24] { + literal.1: bits[4] = literal(value=0xa) + literal.2: bits[4] = literal(value=0x4) + concat.3: bits[8] = concat(literal.1, y) + concat.4: bits[8] = concat(literal.2, y) + + // Known sign. Sign bit is one. + sign_ext.5: bits[16] = sign_ext(concat.3, new_bit_count=16) + + // Known sign. Sign bit is zero. + sign_ext.6: bits[16] = sign_ext(concat.4, new_bit_count=16) + + // Identity sign-extension. + sign_ext.7: bits[8] = sign_ext(concat.3, new_bit_count=8) + + // Unknown sign. + concat.8: bits[16] = concat(x, concat.3) + ret sign_ext.9: bits[24] = sign_ext(concat.8, new_bit_count=24) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->GetKnownBits(FindNode("sign_ext.5", f)), UBits(0xfff0, 16)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("sign_ext.5", f)), + UBits(0xffa0, 16)); + + EXPECT_EQ(engine->GetKnownBits(FindNode("sign_ext.6", f)), UBits(0xfff0, 16)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("sign_ext.6", f)), + UBits(0x0040, 16)); + + EXPECT_EQ(engine->GetKnownBits(FindNode("sign_ext.7", f)), UBits(0xf0, 8)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("sign_ext.7", f)), + UBits(0xa0, 8)); + + EXPECT_EQ(engine->GetKnownBits(FindNode("sign_ext.9", f)), + UBits(0x0000f0, 24)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("sign_ext.9", f)), + UBits(0x0000a0, 24)); +} + +TEST_P(QueryEngineTest, BinarySelect) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(p: bits[1], y: bits[8]) -> bits[24] { + literal.1: bits[16] = literal(value=0xff00) + literal.2: bits[16] = literal(value=0xf0f0) + concat.3: bits[24] = concat(literal.1, y) + concat.4: bits[24] = concat(literal.2, y) + + ret sel.5: bits[24] = sel(p, cases=[concat.3, concat.4]) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->GetKnownBits(FindNode("sel.5", f)), UBits(0xf00f00, 24)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("sel.5", f)), + UBits(0xf00000, 24)); +} + +TEST_P(QueryEngineTest, TernarySelectWithDefault) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(p: bits[2]) -> bits[32] { + literal.1: bits[32] = literal(value=0xfffff000) + literal.2: bits[32] = literal(value=0xff00ff00) + literal.3: bits[32] = literal(value=0xf0f0f0f0) + literal.4: bits[32] = literal(value=0x0ffffff0) + ret sel.5: bits[32] = sel(p, cases=[literal.1, literal.2, literal.3], default=literal.4) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->GetKnownBits(FindNode("sel.5", f)), UBits(0x0000f00f, 32)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("sel.5", f)), + UBits(0x0000f000, 32)); +} + +TEST_P(QueryEngineTest, AndWithHighBitZeroes) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8], y: bits[16]) -> bits[16] { + literal.2: bits[8] = literal(value=0) + concat.3: bits[16] = concat(literal.2, x) + ret and.4: bits[16] = and(concat.3, y) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->GetKnownBits(FindNode("and.4", f)), UBits(0xff00, 16)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("and.4", f)), + UBits(0x0000, 16)); +} + +TEST_P(QueryEngineTest, NandTruthTable) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f() -> bits[4] { + literal.1: bits[4] = literal(value=0b0101) + literal.2: bits[4] = literal(value=0b0011) + nand.3: bits[4] = nand(literal.1, literal.2) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("nand.3", f)), + UBits(0b1110, 4)); +} + +TEST_P(QueryEngineTest, NorTruthTable) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f() -> bits[4] { + literal.1: bits[4] = literal(value=0b0101) + literal.2: bits[4] = literal(value=0b0011) + nor.3: bits[4] = nor(literal.1, literal.2) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("nor.3", f)), UBits(0b1000, 4)); +} + +TEST_P(QueryEngineTest, ShrlFullyKnownRhs) { + // TODO(meheff): Enable test for BDD query engine when shifts are supported. + if (GetParam() == QueryEngineType::kBdd) { + return; + } + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8]) -> bits[16] { + literal.2: bits[8] = literal(value=0xee) + concat.3: bits[16] = concat(literal.2, x) + literal.4: bits[16] = literal(value=8) + ret shrl.5: bits[16] = shrl(concat.3, literal.4) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + EXPECT_EQ(engine->GetKnownBits(FindNode("concat.3", f)), UBits(0xff00, 16)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("concat.3", f)), + UBits(0xee00, 16)); + EXPECT_EQ(engine->GetKnownBits(FindNode("shrl.5", f)), UBits(0xffff, 16)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("shrl.5", f)), + UBits(0x00ee, 16)); +} + +TEST_P(QueryEngineTest, ShrlPartiallyKnownRhs) { + // TODO(meheff): Enable test for BDD query engine when shifts are supported. + if (GetParam() == QueryEngineType::kBdd) { + return; + } + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[8]) -> bits[8] { + literal.2: bits[8] = literal(value=0xff) + literal.3: bits[8] = literal(value=1) + or.4: bits[8] = or(x, literal.3) + ret shrl.5: bits[8] = shrl(x, or.4) + } + )", + p.get())); + XLS_ASSERT_OK_AND_ASSIGN(std::unique_ptr engine, GetEngine(f)); + // The shift amount is at least 0b1 so we know the high bit must be zero. + EXPECT_EQ(engine->GetKnownBits(FindNode("shrl.5", f)), UBits(0x80, 8)); + EXPECT_EQ(engine->GetKnownBitsValues(FindNode("shrl.5", f)), UBits(0x00, 8)); +} + +TEST_P(QueryEngineTest, Decode) { + auto make_decode = [](BValue operand, FunctionBuilder* fb) { + fb->Decode(operand, /*width=*/8); + }; + EXPECT_THAT(RunOnUnaryOp("0b000", make_decode), IsOkAndHolds("0b0000_0001")); + EXPECT_THAT(RunOnUnaryOp("0b001", make_decode), IsOkAndHolds("0b0000_0010")); + EXPECT_THAT(RunOnUnaryOp("0b101", make_decode), IsOkAndHolds("0b0010_0000")); + EXPECT_THAT(RunOnUnaryOp("0bXX1", make_decode), IsOkAndHolds("0bX0X0_X0X0")); + EXPECT_THAT(RunOnUnaryOp("0bX1X", make_decode), IsOkAndHolds("0bXX00_XX00")); + EXPECT_THAT(RunOnUnaryOp("0bX0X", make_decode), IsOkAndHolds("0b00XX_00XX")); +} + +TEST_P(QueryEngineTest, Encode) { + auto make_encode = [](BValue operand, FunctionBuilder* fb) { + fb->Encode(operand); + }; + EXPECT_THAT(RunOnUnaryOp("0b0000", make_encode), IsOkAndHolds("0b00")); + EXPECT_THAT(RunOnUnaryOp("0b0010", make_encode), IsOkAndHolds("0b01")); + EXPECT_THAT(RunOnUnaryOp("0b1010", make_encode), IsOkAndHolds("0b11")); + EXPECT_THAT(RunOnUnaryOp("0bXX10", make_encode), IsOkAndHolds("0bX1")); + EXPECT_THAT(RunOnUnaryOp("0bX1X0", make_encode), IsOkAndHolds("0b1X")); + EXPECT_THAT(RunOnUnaryOp("0bX0X0", make_encode), IsOkAndHolds("0bXX")); +} + +TEST_P(QueryEngineTest, Reverse) { + auto make_reverse = [](BValue operand, FunctionBuilder* fb) { + fb->Reverse(operand); + }; + EXPECT_THAT(RunOnUnaryOp("0b0000", make_reverse), IsOkAndHolds("0b0000")); + EXPECT_THAT(RunOnUnaryOp("0b0010", make_reverse), IsOkAndHolds("0b0100")); + EXPECT_THAT(RunOnUnaryOp("0b1010", make_reverse), IsOkAndHolds("0b0101")); + EXPECT_THAT(RunOnUnaryOp("0bXX10", make_reverse), IsOkAndHolds("0b01XX")); + EXPECT_THAT(RunOnUnaryOp("0bX1X0", make_reverse), IsOkAndHolds("0b0X1X")); + EXPECT_THAT(RunOnUnaryOp("0bX0X0", make_reverse), IsOkAndHolds("0b0X0X")); +} + +INSTANTIATE_TEST_SUITE_P( + QueryEngineTestInstantiation, QueryEngineTest, + testing::Values(QueryEngineType::kTernary, QueryEngineType::kBdd), + [](const testing::TestParamInfo& info) { + return info.param == QueryEngineType::kTernary ? "ternary" : "bdd"; + }); + +} // namespace +} // namespace xls diff --git a/xls/passes/reassociation_pass.cc b/xls/passes/reassociation_pass.cc new file mode 100644 index 0000000000..e68cf92a6e --- /dev/null +++ b/xls/passes/reassociation_pass.cc @@ -0,0 +1,242 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/reassociation_pass.h" + +#include + +#include "absl/container/flat_hash_set.h" +#include "absl/strings/str_join.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/math_util.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/ir/node_iterator.h" +#include "xls/ir/node_util.h" +#include "xls/ir/op.h" + +namespace xls { + +// Walks an expression tree of operations with the given op and bit +// width. 'node' is the node currently being visited. The leaves of the +// expression tree are added to 'leaves', and the interior nodes of the tree are +// added to 'interior_nodes'. Returns the height of the tree. +// +// For example, if called at the root of the following expression: +// +// a b c d +// \ / \ / +// add.2 add.3 +// \ / +// add.1 +// +// Upon return the passed in vectors would be: +// +// leaves = {a, b, c, d} +// interior_nodes = {add.1, add.2, add.3} +// +// And the function would return 2, the depth of the tree (not counting the +// leaves). +int64 GatherExpressionLeaves(Op op, int64 bit_count, Node* node, + std::vector* leaves, + std::vector* interior_nodes) { + if (node->op() != op || node->BitCountOrDie() != bit_count || + node->operand(0)->BitCountOrDie() != bit_count || + node->operand(1)->BitCountOrDie() != bit_count) { + // 'node' does not match the other nodes of the tree and is thus a leaf. + leaves->push_back(node); + return 0; + } + // 'node' is an interior node in the tree of identical operations. Traverse + // into the operands if the operand has a single use, otherwise the operand is + // a leaf. + interior_nodes->push_back(node); + int64 max_depth = 1; + for (Node* operand : node->operands()) { + if (operand->users().size() == 1) { + max_depth = + std::max(max_depth, GatherExpressionLeaves(op, bit_count, operand, + leaves, interior_nodes) + + 1); + } else { + leaves->push_back(operand); + } + } + return max_depth; +} + +xabsl::StatusOr ReassociationPass::RunOnFunction( + Function* f, const PassOptions& options, PassResults* results) const { + XLS_VLOG(2) << "Running reassociation on function " << f->name(); + XLS_VLOG(3) << "Before:"; + XLS_VLOG_LINES(3, f->DumpIr()); + + bool changed = false; + + // Keep track of which nodes we've already considered for reassociation so we + // don't revisit subexpressions multiple times. + absl::flat_hash_set visited_nodes; + + // Traverse the nodes in reverse order because we construct expressions for + // reassociation starting from the roots. + for (Node* node : ReverseTopoSort(f)) { + if (visited_nodes.contains(node)) { + continue; + } + + // Only reassociate arithmetic operations. Logical operations can + // theoretically be reassociated, but they are better handled by collapsing + // into a single operation as logical operations are n-ary. + if (node->op() != Op::kAdd && node->op() != Op::kUMul && + node->op() != Op::kSMul) { + continue; + } + std::vector leaves; + std::vector interior_nodes; + int64 expression_depth = GatherExpressionLeaves( + node->op(), node->BitCountOrDie(), node, &leaves, &interior_nodes); + + // Interior nodes in the expression will be reassociated so add them + // to the set of associated nodes. This will prevent future + visited_nodes.insert(interior_nodes.begin(), interior_nodes.end()); + + // We want to reassociate under two conditions: + // + // (1) Reduce the height (delay) of the expression. An + // example is reassociating the following: + // + // c d + // \ / + // b + + // \ / + // a + + // \ / + // + + // + // into: + // + // a b c d + // \ / \ / + // + + + // \ / + // + + // + // (2) The expression includes more than one literal. This enables some + // folding and canonicalization by putting the literals on the right. + // For example, reassociating the following: + // + // a C_0 b C_1 + // \ / \ / + // + + + // \ / + // + + // + // into: + // + // a b C_0 C_1 + // \ / \ / + // + + + // \ / + // + + // + // Then C_0 + C_1 can be folded. + + // First, separate the leaves of the expression into literals and + // non-literals ('inputs'). + std::vector literals; + std::vector inputs; + for (Node* leaf : leaves) { + if (leaf->Is()) { + literals.push_back(leaf); + } else { + inputs.push_back(leaf); + } + } + + // We only want to transform for one of the two cases above. + if (interior_nodes.size() <= 1 || + (expression_depth == CeilOfLog2(leaves.size()) && + literals.size() <= 1)) { + continue; + } + + XLS_VLOG(4) << "Reassociated expression rooted at: " + << inputs[0]->GetName(); + XLS_VLOG(4) << " operations to reassociate: " + << absl::StrJoin(interior_nodes, ", ", NodeFormatter); + XLS_VLOG(4) << " leaves: " << absl::StrJoin(leaves, ", ", NodeFormatter); + + // Create a clone of 'node' for construcing a reassociated expression. + auto new_node = [&](Node* lhs, Node* rhs) -> xabsl::StatusOr { + return node->Clone({lhs, rhs}, node->function()); + }; + + if (literals.size() == 1) { + // Only one literal in the expression. Just add it to the other inputs. It + // will appear on the far right of the tree. + inputs.push_back(literals.front()); + } else if (literals.size() > 1) { + // More than one literal appears in the expression. Compute the result of + // the literals separately so it will be folded, then append the result to + // the other inputs. + XLS_ASSIGN_OR_RETURN(Node * literal_expr, + new_node(literals[0], literals[1])); + for (int64 i = 2; i < literals.size(); ++i) { + XLS_ASSIGN_OR_RETURN(literal_expr, new_node(literals[i], literal_expr)); + } + inputs.push_back(literal_expr); + } + + // Reassociate the expressions into a balanced tree. First, reduce the + // number of inputs to a power of two. Then build a balanced tree. + if (!IsPowerOfTwo(inputs.size())) { + // Number of operations to apply to the inputs to reduce operand count to + // a power of two. These ops will be the ragged top layer of the + // expression tree. + int64 op_count = inputs.size() - (1ULL << FloorOfLog2(inputs.size())); + std::vector next_inputs; + for (int64 i = 0; i < op_count; ++i) { + XLS_ASSIGN_OR_RETURN(Node * new_op, + new_node(inputs[2 * i], inputs[2 * i + 1])); + next_inputs.push_back(new_op); + } + for (int64 i = op_count * 2; i < inputs.size(); ++i) { + next_inputs.push_back(inputs[i]); + } + inputs = std::move(next_inputs); + } + + XLS_RET_CHECK(IsPowerOfTwo(inputs.size())); + while (inputs.size() != 1) { + // Inputs for the next layer in the tree. Will contain half as many + // elements as 'inputs'. + std::vector next_inputs; + for (int64 i = 0; i < inputs.size() / 2; ++i) { + XLS_ASSIGN_OR_RETURN(Node * new_op, + new_node(inputs[2 * i], inputs[2 * i + 1])); + next_inputs.push_back(new_op); + } + inputs = std::move(next_inputs); + } + XLS_RETURN_IF_ERROR(node->ReplaceUsesWith(inputs[0]).status()); + changed = true; + } + return changed; + + XLS_VLOG(3) << "After:"; + XLS_VLOG_LINES(3, f->DumpIr()); +} + +} // namespace xls diff --git a/xls/passes/reassociation_pass.h b/xls/passes/reassociation_pass.h new file mode 100644 index 0000000000..46f7721a74 --- /dev/null +++ b/xls/passes/reassociation_pass.h @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_XLS_PASSES_REASSOCIATION_PASS_H_ +#define THIRD_PARTY_XLS_PASSES_REASSOCIATION_PASS_H_ + +#include "xls/common/status/statusor.h" +#include "xls/ir/function.h" +#include "xls/passes/pass_base.h" +#include "xls/passes/passes.h" + +namespace xls { + +// Reassociates associative operations to reduce delay by transforming chains of +// operations to a balanced tree of operations, and gathering together constants +// in the expression for folding. +class ReassociationPass : public FunctionPass { + public: + ReassociationPass() : FunctionPass("reassociation", "Reassociation") {} + ~ReassociationPass() override {} + + xabsl::StatusOr RunOnFunction(Function* f, const PassOptions& options, + PassResults* results) const override; +}; + +} // namespace xls + +#endif // THIRD_PARTY_XLS_PASSES_REASSOCIATION_PASS_H_ diff --git a/xls/passes/reassociation_pass_test.cc b/xls/passes/reassociation_pass_test.cc new file mode 100644 index 0000000000..8605ef4adb --- /dev/null +++ b/xls/passes/reassociation_pass_test.cc @@ -0,0 +1,200 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/reassociation_pass.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "xls/common/status/matchers.h" +#include "xls/common/status/statusor.h" +#include "xls/ir/function_builder.h" +#include "xls/ir/ir_matcher.h" +#include "xls/ir/ir_test_base.h" + +namespace m = ::xls::op_matchers; + +namespace xls { +namespace { + +using status_testing::IsOkAndHolds; + +class ReassociationPassTest : public IrTestBase { + protected: + ReassociationPassTest() = default; + + xabsl::StatusOr Run(Package* p) { + PassResults results; + return ReassociationPass().Run(p, PassOptions(), &results); + } +}; + +TEST_F(ReassociationPassTest, SingleAdd) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Param("a", u32), fb.Param("b", u32)); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +TEST_F(ReassociationPassTest, TwoAdds) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Param("a", u32), fb.Add(fb.Param("b", u32), fb.Param("c", u32))); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +TEST_F(ReassociationPassTest, ChainOfThreeAddsRight) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Param("a", u32), + fb.Add(fb.Param("b", u32), + fb.Add(fb.Param("c", u32), fb.Param("d", u32)))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Add(m::Add(m::Param("a"), m::Param("b")), + m::Add(m::Param("c"), m::Param("d")))); +} + +TEST_F(ReassociationPassTest, ChainOfThreeUMulRight) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.UMul(fb.Param("a", u32), + fb.UMul(fb.Param("b", u32), + fb.UMul(fb.Param("c", u32), fb.Param("d", u32)))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::UMul(m::UMul(m::Param("a"), m::Param("b")), + m::UMul(m::Param("c"), m::Param("d")))); +} + +TEST_F(ReassociationPassTest, ChainOfFourUMulRight) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.UMul(fb.Param("a", u32), + fb.UMul(fb.Param("b", u32), + fb.UMul(fb.Param("c", u32), + fb.UMul(fb.Param("d", u32), fb.Param("e", u32))))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + m::UMul(m::UMul(m::UMul(m::Param("a"), m::Param("b")), m::Param("c")), + m::UMul(m::Param("d"), m::Param("e")))); +} + +TEST_F(ReassociationPassTest, ChainOfMixedOperations) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.UMul(fb.Param("a", u32), + fb.UMul(fb.Param("b", u32), + fb.Add(fb.Param("c", u32), + fb.Add(fb.Param("d", u32), fb.Param("e", u32))))); + XLS_ASSERT_OK(fb.Build().status()); +} + +TEST_F(ReassociationPassTest, ChainOfThreeAddsLeft) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Add(fb.Add(fb.Param("a", u32), fb.Param("b", u32)), + fb.Param("c", u32)), + fb.Param("d", u32)); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Add(m::Add(m::Param("a"), m::Param("b")), + m::Add(m::Param("c"), m::Param("d")))); +} + +TEST_F(ReassociationPassTest, DeepChain) { + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + BValue lhs = fb.Param("p0", u32); + for (int64 i = 1; i < 41; ++i) { + lhs = fb.Add(lhs, fb.Param(absl::StrFormat("p%d", i), u32)); + } + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); +} + +TEST_F(ReassociationPassTest, BalancedTreeOfThreeAdds) { + // An already balanced tree should not be transformed. + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Add(fb.Param("a", u32), fb.Param("b", u32)), fb.Param("c", u32)); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +TEST_F(ReassociationPassTest, BalancedTreeOfFourAdds) { + // An already balanced tree should not be transformed. + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Add(fb.Param("a", u32), fb.Param("b", u32)), + fb.Add(fb.Param("c", u32), fb.Param("d", u32))); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +TEST_F(ReassociationPassTest, BalancedTreeOfFiveAdds) { + // An already balanced tree should not be transformed. + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Add(fb.Param("a", u32), fb.Param("b", u32)), + fb.Add(fb.Param("c", u32), + fb.Add(fb.Param("d", u32), fb.Param("e", u32)))); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +TEST_F(ReassociationPassTest, ReassociateMultipleLiterals) { + // Multiple Literals should be reassociated to the right even if the tree is + // balanced. + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Add(fb.Param("a", u32), fb.Literal(UBits(42, 32))), + fb.Add(fb.Literal(UBits(123, 32)), fb.Param("b", u32))); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, fb.Build()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Add(m::Add(m::Param("a"), m::Param("b")), + m::Add(m::Literal(42), m::Literal(123)))); +} + +TEST_F(ReassociationPassTest, SingleLiteralNoReassociate) { + // If there is a single literal in the expression and the tree is balanced + // then no reassociation should happen. + auto p = CreatePackage(); + FunctionBuilder fb(TestName(), p.get()); + Type* u32 = p->GetBitsType(32); + fb.Add(fb.Add(fb.Param("a", u32), fb.Literal(UBits(42, 32))), + fb.Add(fb.Param("b", u32), fb.Param("c", u32))); + XLS_ASSERT_OK(fb.Build().status()); + ASSERT_THAT(Run(p.get()), IsOkAndHolds(false)); +} + +} // namespace +} // namespace xls diff --git a/xls/passes/select_simplification_pass.cc b/xls/passes/select_simplification_pass.cc new file mode 100644 index 0000000000..57e12f143d --- /dev/null +++ b/xls/passes/select_simplification_pass.cc @@ -0,0 +1,1017 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/passes/select_simplification_pass.h" + +#include + +#include "absl/algorithm/container.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/str_cat.h" +#include "xls/common/logging/log_lines.h" +#include "xls/common/logging/logging.h" +#include "xls/common/logging/vlog_is_on.h" +#include "xls/common/status/ret_check.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/visitor.h" +#include "xls/data_structures/algorithm.h" +#include "xls/ir/bits_ops.h" +#include "xls/ir/node.h" +#include "xls/ir/node_iterator.h" +#include "xls/ir/node_util.h" +#include "xls/ir/nodes.h" +#include "xls/passes/ternary_query_engine.h" + +namespace xls { +namespace { + +// Given a SelectT node (either OneHotSelect or Select), squeezes the const_msb +// and const_lsb values out of the output, and slices all the operands to +// correspond to the non-const run of bits in the center. +template +xabsl::StatusOr SqueezeSelect( + const Bits& const_msb, const Bits& const_lsb, + const std::function< + xabsl::StatusOr(SelectT*, std::vector)>& make_select, + SelectT* select) { + Function* f = select->function(); + int64 bit_count = select->BitCountOrDie(); + auto slice = [&](Node* n) -> xabsl::StatusOr { + int64 new_width = bit_count - const_msb.bit_count() - const_lsb.bit_count(); + return f->MakeNode(select->loc(), n, + /*start=*/const_lsb.bit_count(), + /*width=*/new_width); + }; + std::vector new_cases; + absl::Span cases = select->operands().subspan(1); + for (Node* old_case : cases) { + XLS_ASSIGN_OR_RETURN(Node * new_case, slice(old_case)); + new_cases.push_back(new_case); + } + XLS_ASSIGN_OR_RETURN(Node * msb_literal, + f->MakeNode(select->loc(), Value(const_msb))); + XLS_ASSIGN_OR_RETURN(Node * lsb_literal, + f->MakeNode(select->loc(), Value(const_lsb))); + XLS_ASSIGN_OR_RETURN(Node * new_select, make_select(select, new_cases)); + Node* select_node = select; + XLS_RETURN_IF_ERROR(select_node + ->ReplaceUsesWithNew(std::vector{ + msb_literal, new_select, lsb_literal}) + .status()); + return true; +} + +// The source of a bit. Can be either a literal 0/1 or a bit at a particular +// index of a Node. +using BitSource = absl::variant>; + +// Traces the bit at the given node and bit index through bit slices and concats +// and returns its source. +// TODO(meheff): Combine this into TernaryQueryEngine. +BitSource GetBitSource(Node* node, int64 bit_index, + const QueryEngine& query_engine) { + if (node->Is()) { + return GetBitSource(node->operand(0), + bit_index + node->As()->start(), + query_engine); + } else if (node->Is()) { + int64 offset = 0; + for (int64 i = node->operand_count() - 1; i >= 0; --i) { + Node* operand = node->operand(i); + if (bit_index - offset < operand->BitCountOrDie()) { + return GetBitSource(operand, bit_index - offset, query_engine); + } + offset += operand->BitCountOrDie(); + } + XLS_LOG(FATAL) << "Bit index " << bit_index << " too large for " + << node->ToString(); + } else if (node->Is()) { + return node->As()->value().bits().Get(bit_index); + } else if (query_engine.IsKnown(BitLocation(node, bit_index))) { + return query_engine.IsOne(BitLocation(node, bit_index)); + } + return std::make_pair(node, bit_index); +} + +std::string ToString(const BitSource& bit_source) { + return absl::visit(Visitor{[](bool value) { return absl::StrCat(value); }, + [](const std::pair& p) { + return absl::StrCat(p.first->GetName(), "[", + p.second, "]"); + }}, + bit_source); +} + +using MatchedPairs = std::vector>; + +// Returns the pairs of indices into 'nodes' for which the indexed Nodes have +// the same of bits sources at the given bit index. The returned indices are +// indices into the given 'nodes' span. For example, given the following: +// +// GetBitSource(a, 42) = BitSource{true} +// GetBitSource(b, 42) = BitSource{foo, 7} +// GetBitSource(c, 42) = BitSource{foo, 7} +// GetBitSource(d, 42) = BitSource{true} +// GetBitSource(e, 42) = BitSource{false} +// +// PairsOfBitsWithSameSource({a, b, c, d, e}, 42) would return [(0, 3), (1, 2)] +MatchedPairs PairsOfBitsWithSameSource(absl::Span nodes, + int64 bit_index, + const QueryEngine& query_engine) { + std::vector bit_sources; + for (Node* node : nodes) { + bit_sources.push_back(GetBitSource(node, bit_index, query_engine)); + } + MatchedPairs matching_pairs; + for (int64 i = 0; i < bit_sources.size(); ++i) { + for (int64 j = i + 1; j < bit_sources.size(); ++j) { + if (bit_sources[i] == bit_sources[j]) { + matching_pairs.push_back({i, j}); + } + } + } + return matching_pairs; +} + +std::string ToString(const MatchedPairs& pairs) { + std::string ret; + for (const auto& p : pairs) { + absl::StrAppend(&ret, "(", p.first, ", ", p.second, ") "); + } + return ret; +} + +// Returns a OneHotSelect instruction which selects a slice of the given +// OneHotSelect's cases. The cases are sliced with the given start and width and +// then selected with a new OnehotSelect which is returned. +xabsl::StatusOr SliceOneHotSelect(OneHotSelect* ohs, int64 start, + int64 width) { + std::vector case_slices; + for (Node* cas : ohs->cases()) { + XLS_ASSIGN_OR_RETURN( + Node * case_slice, + ohs->function()->MakeNode(ohs->loc(), cas, /*start=*/start, + /*width=*/width)); + case_slices.push_back(case_slice); + } + return ohs->function()->MakeNode(ohs->loc(), ohs->selector(), + case_slices); +} + +// Returns the length of the run of bit indices starting at 'start' for which +// there exists at least one pair of elements in 'cases' which have the same bit +// source at the respective bit indices in the entire run. For example, given +// the following +// +// a = Literal(value=0b110011) +// b = Literal(value=0b100010) +// c = Literal(value=0b101010) +// +// RunOfNonDistinctCaseBits({a, b, c}, 1) returns 3 because bits 1, 2, and 3 of +// 'a', and 'b' are the same (have the same BitSource). +int64 RunOfNonDistinctCaseBits(absl::Span cases, int64 start, + const QueryEngine& query_engine) { + XLS_VLOG(5) << "Finding runs of non-distinct bits starting at " << start; + // Do a reduction via intersection of the set of matching pairs within + // 'cases'. When the intersection is empty, the run is over. + MatchedPairs matches; + int64 i = start; + while (i < cases.front()->BitCountOrDie()) { + if (i == start) { + matches = PairsOfBitsWithSameSource(cases, i, query_engine); + } else { + MatchedPairs new_matches; + absl::c_set_intersection( + PairsOfBitsWithSameSource(cases, i, query_engine), matches, + std::back_inserter(new_matches)); + matches = std::move(new_matches); + } + + XLS_VLOG(5) << " " << i << ": " << ToString(matches); + if (matches.empty()) { + break; + } + ++i; + } + XLS_VLOG(5) << " run of " << i - start; + return i - start; +} + +// Returns the length of the run of bit indices starting at 'start' for which +// the indexed bits of the given cases are distinct at each +// bit index. For example: +int64 RunOfDistinctCaseBits(absl::Span cases, int64 start, + const QueryEngine& query_engine) { + XLS_VLOG(5) << "Finding runs of distinct case bit starting at " << start; + int64 i = start; + while (i < cases.front()->BitCountOrDie() && + PairsOfBitsWithSameSource(cases, i, query_engine).empty()) { + ++i; + } + XLS_VLOG(5) << " run of " << i - start << " bits"; + return i - start; +} + +// Try to split OneHotSelect instructions into separate OneHotSelect +// instructions which have common cases. For example, if some of the cases of a +// OneHotSelect have the same first three bits, then this transformation will +// slice off these three bits (and the remainder) into separate OneHotSelect +// operation and replace the original OneHotSelect with a concat of thes sharded +// OneHotSelects. +// +// Returns the newly created OneHotSelect instructions if the transformation +// succeeded. +xabsl::StatusOr> MaybeSplitOneHotSelect( + OneHotSelect* ohs, const QueryEngine& query_engine) { + if (!ohs->GetType()->IsBits()) { + return std::vector(); + } + + XLS_VLOG(4) << "Trying to split: " << ohs->ToString(); + if (XLS_VLOG_IS_ON(4)) { + for (int64 i = 0; i < ohs->cases().size(); ++i) { + Node* cas = ohs->cases()[i]; + XLS_VLOG(4) << " case (" << i << "): " << cas->ToString(); + for (int64 j = 0; j < cas->BitCountOrDie(); ++j) { + XLS_VLOG(4) << " bit " << j << ": " + << ToString(GetBitSource(cas, j, query_engine)); + } + } + } + + int64 start = 0; + std::vector ohs_slices; + std::vector new_ohses; + while (start < ohs->BitCountOrDie()) { + int64 run = RunOfDistinctCaseBits(ohs->cases(), start, query_engine); + if (run == 0) { + run = RunOfNonDistinctCaseBits(ohs->cases(), start, query_engine); + } + XLS_RET_CHECK_GT(run, 0); + if (run == ohs->BitCountOrDie()) { + // If all the cases are distinct (or have a matching pair) then just + // return as there is nothing to slice. + return std::vector(); + } + XLS_ASSIGN_OR_RETURN(OneHotSelect * ohs_slice, + SliceOneHotSelect(ohs, + /*start=*/start, + /*width=*/run)); + new_ohses.push_back(ohs_slice); + ohs_slices.push_back(ohs_slice); + start += run; + } + std::reverse(ohs_slices.begin(), ohs_slices.end()); + XLS_RETURN_IF_ERROR(ohs->ReplaceUsesWithNew(ohs_slices).status()); + return new_ohses; +} + +xabsl::StatusOr SimplifyNode(Node* node, const QueryEngine& query_engine, + bool split_ops) { + // Select with a constant selector can be replaced with the respective + // case. + if (node->Is()->selector()->Is()) { + Select* sel = node->As() && + node->As()->selector()->op() == Op::kNot && + node->As(); + XLS_RETURN_IF_ERROR( + sel->ReplaceUsesWithNew()) { + Select* sel = node->As( + is_zero, std::vector{sel->cases()[0], selected_zero}, + /*default_value=*/absl::nullopt) + .status()); + return true; + } + } + + // Replace a select among tuples to a tuple of selects. Handles both + // kOneHotSelect and kSelect. + if ((node->Is()) { + Select* sel = node->As(node->loc(), sel->selector(), + case_elements, default_element)); + selected_elements.push_back(selected_element); + } + XLS_RETURN_IF_ERROR( + node->ReplaceUsesWithNew(selected_elements).status()); + return true; + } + } + + // Common out equivalent cases in a one hot select. + if (node->Is()) { + Function* f = node->function(); + OneHotSelect* sel = node->As(); + if (!sel->cases().empty() && + absl::flat_hash_set(sel->cases().begin(), sel->cases().end()) + .size() != sel->cases().size()) { + // For any case that's equal to another case, we or together the one hot + // selectors and common out the value to squeeze the width of the one hot + // select. + std::vector new_selectors; + std::vector new_cases; + for (int64 i = 0; i < sel->cases().size(); ++i) { + Node* old_case = sel->cases()[i]; + XLS_ASSIGN_OR_RETURN(Node * old_selector, + f->MakeNode(node->loc(), sel->selector(), + /*start=*/i, 1)); + auto it = std::find_if( + new_cases.begin(), new_cases.end(), + [old_case](Node* new_case) { return old_case == new_case; }); + if (it == new_cases.end()) { + new_selectors.push_back(old_selector); + new_cases.push_back(old_case); + } else { + // Or together the selectors, no need to append the old case. + int64 index = std::distance(new_cases.begin(), it); + XLS_ASSIGN_OR_RETURN( + new_selectors[index], + f->MakeNode( + node->loc(), + std::vector{new_selectors[index], old_selector}, + Op::kOr)); + } + } + std::reverse(new_selectors.begin(), new_selectors.end()); + XLS_ASSIGN_OR_RETURN(Node * new_selector, + f->MakeNode(node->loc(), new_selectors)); + XLS_RETURN_IF_ERROR( + node->ReplaceUsesWithNew(new_selector, new_cases) + .status()); + return true; + } + } + + // Hoist bit slice above OneHotSelect. + if (node->Is() && SoleUserSatisfies(node, IsBitSlice)) { + std::vector new_cases; + auto ohs = node->As(); + auto bit_slice = node->users()[0]->As(); + for (Node* ohs_case : ohs->cases()) { + XLS_ASSIGN_OR_RETURN(Node * sliced, + bit_slice->Clone({ohs_case}, node->function())); + new_cases.push_back(sliced); + } + XLS_RETURN_IF_ERROR( + bit_slice->ReplaceUsesWithNew(ohs->selector(), new_cases) + .status()); + return true; + } + + // Replace a select with a default which only handles a single value of the + // selector with a select without a default. + if (node->Is(); + if (sel->default_value().has_value() && + (sel->cases().size() + 1) == + (1ULL << sel->selector()->BitCountOrDie())) { + std::vector new_cases(sel->cases().begin(), sel->cases().end()); + new_cases.push_back(*sel->default_value()); + XLS_RETURN_IF_ERROR( + node->ReplaceUsesWithNew() && node->GetType()->IsBits() && + node->BitCountOrDie() == 1 && node->operand(0)->BitCountOrDie() == 1; + }; + if (is_one_bit_mux() && + (node->operand(1)->Is() || node->operand(2)->Is() || + (node->operand(0) == node->operand(1) || + node->operand(0) == node->operand(2)))) { + Function* f = node->function(); + Select* select = node->As() && node->GetType()->IsBits() && + node->operand(0)->BitCountOrDie() == 1; + if (!ok) { + return false; + } + if (IsLiteralAllOnes(node->operand(2)) && + IsLiteralZero(node->operand(1))) { + *invert_selector = false; + return true; + } + if (IsLiteralAllOnes(node->operand(1)) && + IsLiteralZero(node->operand(2))) { + *invert_selector = true; + return true; + } + return false; + }; + + bool invert_selector; + if (is_signext_mux(&invert_selector)) { + Node* selector = node->operand(0); + if (invert_selector) { + XLS_ASSIGN_OR_RETURN(selector, node->function()->MakeNode( + node->loc(), selector, Op::kNot)); + } + XLS_RETURN_IF_ERROR(node->ReplaceUsesWithNew( + selector, node->BitCountOrDie(), Op::kSignExt) + .status()); + return true; + } + } + + // Merge consecutive one-hot-select instructions if the predecessor operation + // has only a single use. + if (node->Is()) { + OneHotSelect* select = node->As(); + absl::Span cases = select->cases(); + auto is_single_user_ohs = [](Node* n) { + return n->Is() && n->users().size() == 1; + }; + if (std::any_of(cases.begin(), cases.end(), is_single_user_ohs)) { + // Cases for the replacement one-hot-select. + std::vector new_cases; + // Pieces of the selector for the replacement one-hot-select. These are + // concatted together. + std::vector new_selector_parts; + // When iterating through the cases to perform this optimization, cases + // which are to remain unmodified (ie, not a single-use one-hot-select) + // are passed over. This lambda gathers the passed over cases and updates + // new_cases and new_selector_parts. + int64 unhandled_selector_bits = 0; + auto add_unhandled_selector_bits = [&](int64 index) -> absl::Status { + if (unhandled_selector_bits != 0) { + XLS_ASSIGN_OR_RETURN(Node * selector_part, + node->function()->MakeNode( + select->loc(), select->selector(), + /*start=*/index - unhandled_selector_bits, + /*width=*/ + unhandled_selector_bits)); + new_selector_parts.push_back(selector_part); + for (int64 i = index - unhandled_selector_bits; i < index; ++i) { + new_cases.push_back(select->cases()[i]); + } + } + unhandled_selector_bits = 0; + return absl::OkStatus(); + }; + // Iterate through the cases merging single-use one-hot-select cases. + for (int64 i = 0; i < cases.size(); ++i) { + if (is_single_user_ohs(cases[i])) { + OneHotSelect* ohs_operand = cases[i]->As(); + XLS_RETURN_IF_ERROR(add_unhandled_selector_bits(i)); + // The selector bits for the predecessor one-hot-select need to be + // ANDed with the original selector bit in the successor + // one-hot-select. Example: + // + // X = one_hot_select(selector={A, B, C}, + // cases=[x, y z]) + // Y = one_hot_select(selector={..., S, ...}, + // cases=[..., X, ...]) + // Becomes: + // + // Y = one_hot_select( + // selector={..., S & A, S & B, S & C, ...}, + // cases=[..., A, B, C, ...]) + // + XLS_ASSIGN_OR_RETURN(Node * selector_bit, + node->function()->MakeNode( + select->loc(), select->selector(), + /*start=*/i, /*width=*/1)); + XLS_ASSIGN_OR_RETURN( + Node * selector_bit_mask, + node->function()->MakeNode( + select->loc(), selector_bit, + /*new_bit_count=*/ohs_operand->cases().size(), Op::kSignExt)); + XLS_ASSIGN_OR_RETURN(Node * masked_selector, + node->function()->MakeNode( + select->loc(), + std::vector{selector_bit_mask, + ohs_operand->selector()}, + Op::kAnd)); + new_selector_parts.push_back(masked_selector); + for (Node* subcase : ohs_operand->cases()) { + new_cases.push_back(subcase); + } + } else { + unhandled_selector_bits++; + } + } + XLS_RETURN_IF_ERROR(add_unhandled_selector_bits(cases.size())); + // Reverse selector parts because concat operand zero is the msb. + std::reverse(new_selector_parts.begin(), new_selector_parts.end()); + XLS_ASSIGN_OR_RETURN(Node * new_selector, + node->function()->MakeNode( + select->loc(), new_selector_parts)); + XLS_RETURN_IF_ERROR( + node->ReplaceUsesWithNew(new_selector, new_cases) + .status()); + return true; + } + } + + // Literal zero cases can be removed from OneHotSelects. + if (split_ops && node->Is() && + std::any_of(node->As()->cases().begin(), + node->As()->cases().end(), + [](Node* n) { return IsLiteralZero(n); })) { + // Assemble the slices of the selector which correspond to non-zero cases. + OneHotSelect* select = node->As(); + std::vector nonzero_indices = + IndicesWhereNot(select->cases(), IsLiteralZero); + if (nonzero_indices.empty()) { + // If all cases were literal zeros, just replace with literal zero (chosen + // arbitrarily as the first case). + return node->ReplaceUsesWith(select->cases().front()); + } + XLS_ASSIGN_OR_RETURN(Node * new_selector, + GatherBits(select->selector(), nonzero_indices)); + std::vector new_cases = + GatherFromSequence(select->cases(), nonzero_indices); + XLS_RETURN_IF_ERROR( + node->ReplaceUsesWithNew(new_selector, new_cases) + .status()); + return true; + } + + // A two-way select with one arm which is literal zero can be replaced with an + // AND. + // + // sel(p cases=[x, 0]) => and(sign_ext(p), x) + auto is_select_with_zero = [&](Node* n) { + if (!n->Is(); + return sel->GetType()->IsBits() && sel->selector()->BitCountOrDie() == 1 && + sel->cases().size() == 2 && + (IsLiteralZero(sel->cases()[0]) || IsLiteralZero(sel->cases()[1])); + }; + if (is_select_with_zero(node)) { + Select* sel = node->As() || !node->GetType()->IsBits() || + !query_engine.IsTracked(node)) { + return false; + } + int64 leading_known = + bits_ops::CountLeadingOnes(query_engine.GetKnownBits(node)); + int64 trailing_known = + bits_ops::CountTrailingOnes(query_engine.GetKnownBits(node)); + if (leading_known == 0 && trailing_known == 0) { + return false; + } + int64 bit_count = node->BitCountOrDie(); + *msb = query_engine.GetKnownBitsValues(node).Slice( + /*start=*/bit_count - leading_known, /*width=*/leading_known); + if (leading_known == trailing_known && leading_known == bit_count) { + // This is just a constant value, just say we only have high constant + // bits, the replacement will be the same. + return true; + } + *lsb = query_engine.GetKnownBitsValues(node).Slice( + /*start=*/0, /*width=*/trailing_known); + return true; + }; + Bits const_msb, const_lsb; + if (is_squeezable_mux(&const_msb, &const_lsb)) { + std::function(Select*, std::vector)> + make_select = + [](Select* original, + std::vector new_cases) -> xabsl::StatusOr { + Function* f = original->function(); + absl::optional new_default; + if (original->default_value().has_value()) { + new_default = new_cases.back(); + new_cases.pop_back(); + } + return f->MakeNode()); + } + } + + // Collapse consecutivee two-ways selects which have share a common case. For + + // example: + // + // s1 = select(p1, [y, x]) + // s0 = select(p0, [s_1, x]) + // + // In this case, 'x' is a common case between the two selects and the above + // can be replaced with: + // + // p' = or(p0, p1) + // s0 = select(p', [x, y]) + // + // There are four different cases to consider depending upon whether the + // common case is on the LHS or RHS of the selects. + auto is_2way_select = [](Node* n) { + return n->Is()->selector()->BitCountOrDie() == 1 && + n->As(); + Node* p0 = sel0->selector(); + // The values below are by each matching cases below. + Node* x = nullptr; + Node* y = nullptr; + // The predicate to select the common case 'x' in the newly constructed + // select. + Node* p_x = nullptr; + if (is_2way_select(sel0->cases()[0])) { + Select* sel1 = sel0->cases()[0]->As(); + Node* p1 = sel1->selector(); + if (sel0->cases()[0] == sel1->cases()[0]) { + // x x y + // \ \ / + // \ sel1 <- p1 + // \ / + // sel0 <- p0 + // + // p_x = nand(p0, p1) + x = sel0->cases()[0]; + y = sel1->cases()[1]; + XLS_ASSIGN_OR_RETURN( + p_x, sel0->function()->MakeNode( + sel0->loc(), std::vector{p0, p1}, Op::kNand)); + } else if (sel0->cases()[0] == sel1->cases()[1]) { + // x y x + // \ \ / + // \ sel1 <- p1 + // \ / + // sel0 <- p0 + // + // p_x = !p0 | p1 + x = sel0->cases()[0]; + y = sel1->cases()[0]; + XLS_ASSIGN_OR_RETURN(Node * not_p0, sel0->function()->MakeNode( + sel0->loc(), p0, Op::kNot)); + XLS_ASSIGN_OR_RETURN( + p_x, sel0->function()->MakeNode( + sel0->loc(), std::vector{not_p0, p1}, Op::kOr)); + } + } + if (x != nullptr) { + XLS_ASSIGN_OR_RETURN(p_x, sel0->ReplaceUsesWithNew()); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), + m::Tuple(m::Select(m::Param("p"), + /*cases=*/{m::TupleIndex(m::Tuple(), 0), + m::TupleIndex(m::Tuple(), 0)}), + m::Select(m::Param("p"), + /*cases=*/{m::TupleIndex(m::Tuple(), 1), + m::TupleIndex(m::Tuple(), 1)}))); +} + +TEST_F(SelectSimplificationPassTest, BinaryTupleOneHotSelect) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(p: bits[2], x: bits[8], y: bits[8], z: bits[8]) -> (bits[8], bits[8]) { + tuple.1: (bits[8], bits[8]) = tuple(x, y) + tuple.2: (bits[8], bits[8]) = tuple(y, z) + ret sel.3: (bits[8], bits[8]) = one_hot_sel(p, cases=[tuple.2, tuple.1]) + } + )", + p.get())); + + EXPECT_TRUE(f->return_value()->Is()); + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT( + f->return_value(), + m::Tuple(m::OneHotSelect(m::Param("p"), + /*cases=*/{m::TupleIndex(m::Tuple(), 0), + m::TupleIndex(m::Tuple(), 0)}), + m::OneHotSelect(m::Param("p"), + /*cases=*/{m::TupleIndex(m::Tuple(), 1), + m::TupleIndex(m::Tuple(), 1)}))); +} + +TEST_F(SelectSimplificationPassTest, FourWayTupleSelect) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(p: bits[2], x: bits[8], y: bits[8], z: bits[8]) -> (bits[8], bits[8]) { + tuple.1: (bits[8], bits[8]) = tuple(x, y) + tuple.2: (bits[8], bits[8]) = tuple(y, z) + tuple.3: (bits[8], bits[8]) = tuple(x, z) + tuple.4: (bits[8], bits[8]) = tuple(z, z) + ret sel.5: (bits[8], bits[8]) = sel(p, cases=[tuple.1, tuple.2, tuple.3, tuple.4]) + } + )", + p.get())); + + EXPECT_TRUE(f->return_value()->Is()); + + EXPECT_THAT(Run(f), IsOkAndHolds(true)); + EXPECT_THAT(f->return_value(), m::Param("x")); +} + +TEST_F(SelectSimplificationPassTest, SelectWithConstantOneSelector) { + auto p = CreatePackage(); + XLS_ASSERT_OK_AND_ASSIGN(Function * f, ParseFunction(R"( + fn f(x: bits[42], y: bits[42]) -> bits[42] { + literal.1: bits[1] = literal(value=1) + ret sel.2: bits[42] = sel(literal.1, cases=[x, y]) + } + )", + p.get())); + EXPECT_TRUE(f->return_value()->Is() && node->GetType()->IsBits() && + node->BitCountOrDie() == 1 && node->operand(0)->BitCountOrDie() == 1; + }; + if (split_ops && is_one_bit_mux() && + (node->operand(1)->Is() || node->operand(2)->Is() || + (node->operand(0) == node->operand(1) || + node->operand(0) == node->operand(2)))) { + Function* f = node->function(); + Select* select = node->As