diff --git a/xls/build_rules/xls_dslx_rules.bzl b/xls/build_rules/xls_dslx_rules.bzl index efd22093f6..058f3c3ecc 100644 --- a/xls/build_rules/xls_dslx_rules.bzl +++ b/xls/build_rules/xls_dslx_rules.bzl @@ -112,6 +112,8 @@ def _get_dslx_test_cmdline(ctx, src, all_srcs, append_cmd_line_args = True): "format_preference", "configured_values", "lower_to_proc_scoped_channels", + "lower_to_ir", + "convert_tests", ) dslx_test_args = dict(_dslx_test_args) diff --git a/xls/dslx/BUILD b/xls/dslx/BUILD index c5e5a34e2a..38ef9d5262 100644 --- a/xls/dslx/BUILD +++ b/xls/dslx/BUILD @@ -618,6 +618,7 @@ cc_binary( visibility = ["//xls:xls_users"], deps = [ ":command_line_utils", + ":create_import_data", ":default_dslx_stdlib_path", ":parse_and_typecheck", ":virtualizable_file_system", @@ -626,6 +627,7 @@ cc_binary( "//xls/common:init_xls", "//xls/common/file:filesystem", "//xls/common/status:status_macros", + "//xls/dslx/ir_convert:ir_converter", "//xls/dslx/run_routines", "//xls/dslx/run_routines:ir_test_runner", "//xls/dslx/run_routines:run_comparator", diff --git a/xls/dslx/frontend/module.h b/xls/dslx/frontend/module.h index a76acdadd7..21376b00f6 100644 --- a/xls/dslx/frontend/module.h +++ b/xls/dslx/frontend/module.h @@ -302,6 +302,10 @@ class Module : public AstNode { return GetTopWithT(); } + std::vector GetFunctions() const { + return GetTopWithT(); + } + std::vector GetImpls() const { return GetTopWithT(); } // Returns the identifiers for all functions within this module (in the order diff --git a/xls/dslx/interpreter_main.cc b/xls/dslx/interpreter_main.cc index f62e405ffe..78e51b3f7d 100644 --- a/xls/dslx/interpreter_main.cc +++ b/xls/dslx/interpreter_main.cc @@ -40,7 +40,9 @@ #include "xls/common/init_xls.h" #include "xls/common/status/status_macros.h" #include "xls/dslx/command_line_utils.h" +#include "xls/dslx/create_import_data.h" #include "xls/dslx/default_dslx_stdlib_path.h" +#include "xls/dslx/ir_convert/ir_converter.h" #include "xls/dslx/parse_and_typecheck.h" #include "xls/dslx/run_routines/ir_test_runner.h" #include "xls/dslx/run_routines/run_comparator.h" @@ -106,6 +108,12 @@ ABSL_FLAG( "this flag is off because the IR interpreter is too slow."); ABSL_FLAG(std::string, configured_values, "", "Configured values to use in DSLX parsing."); +ABSL_FLAG(std::optional, lower_to_ir, true, + "Enable checking if the code cannot be lowered to IR."); +ABSL_FLAG(std::optional, convert_tests, false, + "Include tests in the IR conversion test. Has effect only when " + "'lower_to_ir' flag is set."); + // LINT.ThenChange(//xls/build_rules/xls_dslx_rules.bzl) namespace xls::dslx { @@ -277,6 +285,73 @@ absl::StatusOr RealMain( SetFileContents(absl::GetFlag(FLAGS_output_results_proto), text)); } + // Early feeback if the code cannot be lowered to IR. + std::optional lower_to_ir_flag = absl::GetFlag(FLAGS_lower_to_ir); + if (lower_to_ir_flag.value_or(false)) { + LOG(INFO) << "Checking if code can be lowered to IR"; + std::optional convert_tests = absl::GetFlag(FLAGS_convert_tests); + bool is_convert_tests = convert_tests.value_or(false); + bool is_type_inference_v2 = type_inference_v2_flag.value_or(false); + bool printed_error = true; + + ConvertOptions ir_convert_options = { + .emit_positions = true, + .emit_assert = true, + .emit_cover = true, + .verify_ir = true, + .warnings_as_errors = false, + .warnings = kAllWarningsSet, + .convert_tests = is_convert_tests, + .type_inference_v2 = is_type_inference_v2, + .lower_to_proc_scoped_channels = true, + }; + std::array module_path{entry_module_path}; + + ImportData import_data(CreateImportData( + dslx_stdlib_path.string(), dslx_paths, ir_convert_options.warnings, + std::make_unique())); + + absl::StatusOr tm = ParseAndTypecheck( + program, entry_module_path, module_name, &import_data); + + // Module conversion cannot be used because it skips CheckAcceptableTopProc. + // Instead, we collect non-parametric processes and functions which are then + // passed separately as tops. + std::vector module_elements; + + std::vector module_procs = tm->module->GetProcs(); + for (Proc* elem : module_procs) { + if (!elem->IsParametric()) { + module_elements.push_back(elem->identifier()); + } + } + + std::vector module_funcs = tm->module->GetFunctions(); + for (Function* elem : module_funcs) { + if (!elem->IsParametric()) { + module_elements.push_back(elem->identifier()); + } + } + + std::vector failed_ir_conversion_entries; + for (std::string& elem : module_elements) { + // Convert to IR each element separately. + absl::StatusOr ir_conv_result = + ConvertFilesToPackage(module_path, dslx_stdlib_path.string(), + dslx_paths, ir_convert_options, elem, + module_name, &printed_error); + if (!ir_conv_result.ok()) { + failed_ir_conversion_entries.push_back(std::move(elem)); + } + } + + if (!failed_ir_conversion_entries.empty()) { + return absl::AbortedError(absl::StrFormat( + "IR conversion test failed for %s.", + absl::StrJoin(failed_ir_conversion_entries, ", "))); + } + } + return test_result.result(); } diff --git a/xls/dslx/interpreter_test.py b/xls/dslx/interpreter_test.py index b7742c2a23..ed7d4b0175 100644 --- a/xls/dslx/interpreter_test.py +++ b/xls/dslx/interpreter_test.py @@ -967,6 +967,57 @@ def test_out_of_tree_interpreter_invocation_with_tiv2_annotation(self): self.assertEqual(p.returncode, 0) self.assertIn('1 test(s) ran; 0 failed; 0 skipped', p.stderr) + def test_lower_to_ir_check(self): + """Tests performing an early feedback IR conversion test.""" + program = """ + proc Generator { + out_ch: chan out; + val: u32; + + init {()} + config(out_ch: chan out, val: u32, val2: u32) { + (out_ch, val + val2) + } + next(state: ()) { + send(join(), out_ch, val); + } + } + + #[test_proc] + proc Testing { + terminator: chan out; + response: chan in; + + init { } + + config(terminator: chan out){ + let (s, r) = chan("test_chan"); + spawn Generator(s, u32:66, u32:99); + (terminator, r) + } + + next(state: ()) { + let (tok, data) = recv(join(), response); + send(tok, terminator, true); + } + } + """ + temp_file = self.create_tempfile(content=program) + # Note: we have to supply `env` to avoid the Python testbridge setting + # seeping in. + p = subp.run( + [self.interpreter_path.full_path, temp_file.full_path], + stdout=subp.PIPE, + stderr=subp.PIPE, + encoding='utf-8', + env={}, + check=False, + ) + print('p:', p) + self.assertEqual(p.returncode, 1) + self.assertIn('1 test(s) ran; 0 failed; 0 skipped', p.stderr) + self.assertIn('IR conversion test failed for Generator.', p.stderr) + if __name__ == '__main__': test_base.main() diff --git a/xls/dslx/stdlib/tests/BUILD b/xls/dslx/stdlib/tests/BUILD index 7a385618c0..c1e836db19 100644 --- a/xls/dslx/stdlib/tests/BUILD +++ b/xls/dslx/stdlib/tests/BUILD @@ -260,6 +260,9 @@ xls_dslx_test( name = "round_dslx_test", srcs = ["round_tests.x"], deps = ["//xls/dslx/stdlib:round_dslx"], + dslx_test_args = { + "lower_to_ir": "false", + }, ) cc_test( diff --git a/xls/examples/BUILD b/xls/examples/BUILD index 2bd417435f..f50ea36852 100644 --- a/xls/examples/BUILD +++ b/xls/examples/BUILD @@ -1405,6 +1405,17 @@ xls_dslx_library( srcs = ["const_if.x"], ) +# This is an example that passes DSLX test but can't be converted to IR, +# so early IR conversion feedback is disabled ('lower_to_ir' flag). +xls_dslx_test( + name = "cannot_lower_to_ir_test", + srcs = ["cannot_lower_to_ir.x"], + dslx_test_args = { + "lower_to_ir": "false", + "convert_tests": "true", + }, +) + xls_dslx_ir( name = "const_if_ir", dslx_top = "Main", diff --git a/xls/examples/cannot_lower_to_ir.x b/xls/examples/cannot_lower_to_ir.x new file mode 100644 index 0000000000..8dcc4804d4 --- /dev/null +++ b/xls/examples/cannot_lower_to_ir.x @@ -0,0 +1,47 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// From `ProcWithUnconvertibleConfigGivesUsefulError` IR converter test +// as an example of proc that cannot be converted to IR. +proc Generator { + out_ch: chan out; + val: u32; + + init {()} + config(out_ch: chan out, val: u32, val2: u32) { + (out_ch, val + val2) + } + next(state: ()) { + send(join(), out_ch, val); + } +} + +#[test_proc] +proc Testing { + terminator: chan out; + response: chan in; + + init { } + + config(terminator: chan out){ + let (s, r) = chan("test_chan"); + spawn Generator(s, u32:66, u32:99); + (terminator, r) + } + + next(state: ()) { + let (tok, data) = recv(join(), response); + send(tok, terminator, true); + } +} diff --git a/xls/examples/dslx_intro/BUILD b/xls/examples/dslx_intro/BUILD index 443ade7e98..26b6ca19ea 100644 --- a/xls/examples/dslx_intro/BUILD +++ b/xls/examples/dslx_intro/BUILD @@ -56,12 +56,14 @@ xls_dslx_opt_ir_test( dep = ":crc32_one_byte_inferred", ) -# TODO(leary): 2019-07-24 Missing conversion of 'for/enumerate'. +# TODO: Remove `lower_to_ir` flag once the support for std::enumerate +# is merged: https://github.com/google/xls/pull/3816 xls_dslx_test( name = "prefix_scan_equality_dslx_test", srcs = ["prefix_scan_equality.x"], dslx_test_args = { "compare": "none", + "lower_to_ir": "false", }, )