From 5588c8507361627c1b422d571b455946324b0f9b Mon Sep 17 00:00:00 2001 From: Jeffrey Song Date: Sun, 12 Jul 2026 20:28:03 -0700 Subject: [PATCH] Preserve the runtime C++14 floor after user flags User -CFLAGS may expand through GNU Make or the recipe shell after verilation. When the final expanded -std option or -ansi selects C++98 or C++11, generated runtime compilation can fall below the supported C++14 floor. Expose the configured minimum standard separately, keep the emitted user flags deferred, and append the minimum only for recognized pre-C++14 aliases. Evaluate the floor recursively for every Make target so automatic variables such as $@ cannot cache one target result into later targets. Newer and invalid standards remain untouched, and the last selector retains compiler-compatible precedence. Cover direct, Make-variable, environment-variable, -ansi, ordering, macro-text, invalid-option, automatic-build, simulation, and target-dependent expansion paths. The target regression builds a C++11 object followed by a C++17 object in one Make invocation. RED: the prior self-caching eval appended -std=gnu++14 to the C++17 target, producing __cplusplus=201402L and failing the 201703L assertion. Verification: make -j8; t_cflags_user_cxx_floor; t_flag_ldflags; make format with clang-format 18; changed-file clang-format and yapf checks; targeted pylint; distribution whitespace and C++ style tests; cppcheck-verilator; git diff --check. --- configure.ac | 10 +- include/verilated.mk.in | 2 + src/V3EmitMk.cpp | 26 ++++- test_regress/t/t_cflags_user_cxx_floor.cpp | 17 ++++ test_regress/t/t_cflags_user_cxx_floor.py | 106 +++++++++++++++++++++ test_regress/t/t_cflags_user_cxx_floor.v | 12 +++ 6 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 test_regress/t/t_cflags_user_cxx_floor.cpp create mode 100755 test_regress/t/t_cflags_user_cxx_floor.py create mode 100644 test_regress/t/t_cflags_user_cxx_floor.v diff --git a/configure.ac b/configure.ac index cc37e837b..6186a09a2 100644 --- a/configure.ac +++ b/configure.ac @@ -476,18 +476,22 @@ AC_SUBST(HAVE_DEV_GCOV) _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_PROFILE,-pg) AC_SUBST(CFG_CXXFLAGS_PROFILE) -# Flag to select newest language standard supported +# Flags to select minimum and newest language standards supported # Macros work such that first option that passes is the one we take # Currently enable c++17/c++14 due to packaged SystemC dependency # c++17 is the newest that Verilator is regularly tested to support # c++14 is the oldest that Verilator supports # gnu is required for Cygwin to compile verilated.h successfully +_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_MINIMUM,-std=gnu++14) +_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_MINIMUM,-std=c++14) +AC_SUBST(CFG_CXXFLAGS_STD_MINIMUM) #_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=gnu++20) #_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=c++20) _MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=gnu++17) _MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=c++17) -_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=gnu++14) -_MY_CXX_CHECK_SET(CFG_CXXFLAGS_STD_NEWEST,-std=c++14) +if test "$CFG_CXXFLAGS_STD_NEWEST" = "" ; then + CFG_CXXFLAGS_STD_NEWEST=$CFG_CXXFLAGS_STD_MINIMUM +fi AC_SUBST(CFG_CXXFLAGS_STD_NEWEST) # Flags for compiling Verilator internals including parser, and Verilated files diff --git a/include/verilated.mk.in b/include/verilated.mk.in index a90907755..4b806074f 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -34,6 +34,8 @@ CFG_CXX_VERSION = "@CFG_CXX_VERSION@" CFG_CXXFLAGS_PROFILE = @CFG_CXXFLAGS_PROFILE@ # Select language required to compile (often empty) CFG_CXXFLAGS_STD = @CFG_CXXFLAGS_STD@ +# Select the minimum supported language standard +CFG_CXXFLAGS_STD_MINIMUM = @CFG_CXXFLAGS_STD_MINIMUM@ # Select newest language (unused by this Makefile, for some test's Makefiles) CFG_CXXFLAGS_STD_NEWEST = @CFG_CXXFLAGS_STD_NEWEST@ # Compiler flags to use to turn off unused and generated code warnings, such as -Wno-div-by-zero diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index a63d328f3..761e0c34b 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -686,7 +686,7 @@ public: of.putSet("VM_MODPREFIX", v3Global.opt.modPrefix()); of.puts("# User CFLAGS (from -CFLAGS on Verilator command line)\n"); - of.puts("VM_USER_CFLAGS = \\\n"); + of.puts("VM_USER_CFLAGS_BASE = \\\n"); const std::string solver = V3Options::getenvVERILATOR_SOLVER(); if (v3Global.useRandomizeMethods() && solver != "") of.puts("\t-DVM_SOLVER_DEFAULT='\"" + V3OutFormatter::quoteNameControls(solver) @@ -695,6 +695,11 @@ public: const VStringList& cFlags = v3Global.opt.cFlags(); for (const string& i : cFlags) of.puts(" " + i + " \\\n"); of.puts("\n"); + if (!cFlags.empty()) { + of.puts("VM_USER_CFLAGS = $(VM_USER_CFLAGS_BASE) $(VM_CXXFLAGS_STD_FLOOR)\n\n"); + } else { + of.puts("VM_USER_CFLAGS = $(VM_USER_CFLAGS_BASE)\n\n"); + } of.puts("# User LDLIBS (from -LDFLAGS on Verilator command line)\n"); of.puts("VM_USER_LDLIBS = \\\n"); @@ -730,6 +735,25 @@ public: } of.puts("# Include global rules\n"); of.puts("include $(VERILATOR_ROOT)/include/verilated.mk\n"); + if (!cFlags.empty()) { + of.puts("\n# Keep expanded user CFLAGS at or above the runtime C++ minimum\n"); + of.puts("VM_CXXFLAGS_STD_FLOOR = $(shell \\\n"); + of.puts(" for flag in $(VM_USER_CFLAGS_BASE); do " + "printf '%s\\n' \"$$flag\"; done \\\n"); + of.puts(" | grep -e '^-ansi$$' -e '^-std=' -e '^--std=' \\\n"); + of.puts(" | tail -n 1 \\\n"); + of.puts(" | grep -F -x \\\n"); + of.puts(" -e '-ansi' \\\n"); + of.puts(" -e '-std=c++98' -e '--std=c++98' \\\n"); + of.puts(" -e '-std=gnu++98' -e '--std=gnu++98' \\\n"); + of.puts(" -e '-std=c++03' -e '--std=c++03' \\\n"); + of.puts(" -e '-std=gnu++03' -e '--std=gnu++03' \\\n"); + of.puts(" -e '-std=c++0x' -e '--std=c++0x' \\\n"); + of.puts(" -e '-std=gnu++0x' -e '--std=gnu++0x' \\\n"); + of.puts(" -e '-std=c++11' -e '--std=c++11' \\\n"); + of.puts(" -e '-std=gnu++11' -e '--std=gnu++11' >/dev/null \\\n"); + of.puts(" && printf '%s' '$(CFG_CXXFLAGS_STD_MINIMUM)')\n"); + } if (v3Global.opt.exe()) { of.puts("\n### Executable rules... (from --exe)\n"); diff --git a/test_regress/t/t_cflags_user_cxx_floor.cpp b/test_regress/t/t_cflags_user_cxx_floor.cpp new file mode 100644 index 000000000..c5d3b2fdb --- /dev/null +++ b/test_regress/t/t_cflags_user_cxx_floor.cpp @@ -0,0 +1,17 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +static_assert(__cplusplus >= EXPECT_CXX_MIN, "The runtime requires C++14 or newer"); + +int cxx14FloorProbe() { + const auto identity = [](const auto value) { return value; }; + return identity(14); +} diff --git a/test_regress/t/t_cflags_user_cxx_floor.py b/test_regress/t/t_cflags_user_cxx_floor.py new file mode 100755 index 000000000..f6d1f82f7 --- /dev/null +++ b/test_regress/t/t_cflags_user_cxx_floor.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: user CFLAGS preserve the runtime C++ standard floor +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os +import shlex + +import vltest_bootstrap + +test.scenarios("vlt") +cxx_source = os.path.abspath("t/t_cflags_user_cxx_floor.cpp") +test.setenv("EXPECT_CXX_MIN", "201402L") +test.setenv("TEST_ENV_CXXFLAGS", "") +test.setenv("TEST_MAKE_CXXFLAGS", "-std=c++11") +user_object = "t_cflags_user_cxx_floor.o" +per_target_old_object = "t_cflags_user_cxx_floor_target_old.o" +per_target_new_object = "t_cflags_user_cxx_floor_target_new.o" +per_target_makefile = f"{test.obj_dir}/t_cflags_user_cxx_floor_targets.mk" + +test.compile( + verilator_flags2=[ + "--binary", + "-CFLAGS", + "'-DEXPECT_CXX_MIN=$(EXPECT_CXX_MIN) " + "$(TEST_MAKE_CXXFLAGS) $${TEST_ENV_CXXFLAGS}'", + cxx_source, + ], + verilator_make_gmake=False, +) + +test.write_wholefile( + per_target_makefile, + f"""TEST_MAKE_CXXFLAGS = $(if $(filter {per_target_old_object},$@),-std=c++11,-std=c++17) +EXPECT_CXX_MIN = $(if $(filter {per_target_old_object},$@),201402L,201703L) +include {test.vm_prefix}.mk + +{per_target_old_object}: {cxx_source} +{per_target_new_object}: {cxx_source} {per_target_old_object} +{per_target_old_object} {per_target_new_object}: +\t$(OBJCACHE) $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(OPT_FAST) -c -o $@ {cxx_source} +""", +) + + +def compile_case(name, make_flags, env_flags="", min_cxx="201402L", fails=False): + logfile = f"{test.obj_dir}/vlt_gcc_{name}.log" + cmd = [ + "env", + "TEST_ENV_CXXFLAGS=" + shlex.quote(env_flags), + os.environ["MAKE"], + "-B", + "-C " + test.obj_dir, + "-f " + test.vm_prefix + ".mk", + "VM_PREFIX=" + test.vm_prefix, + "EXPECT_CXX_MIN=" + min_cxx, + "TEST_MAKE_CXXFLAGS=" + shlex.quote(make_flags), + user_object, + ] + test.run( + logfile=logfile, + tee=test.verbose, + fails="any" if fails else False, + cmd=cmd, + ) + + +compile_case("cxx98", "-std=c++98") +compile_case("ansi", "-ansi") +compile_case("ansi_then_new", "-ansi -std=c++17", min_cxx="201703L") +compile_case("new_then_ansi", "-std=c++17 -ansi") +compile_case("cxx11", "-std=c++11") +compile_case("cxx14", "-std=c++14") +compile_case("cxx17", "-std=c++17", min_cxx="201703L") +compile_case("last_old", "-std=c++17", env_flags="-std=c++11") +compile_case( + "last_new", + "-std=c++11", + env_flags="-std=c++17", + min_cxx="201703L", +) +compile_case( + "macro", + "-DKEEP=-std=c++11 -std=c++17", + min_cxx="201703L", +) +compile_case("invalid", "-std=c++11junk", fails=True) +test.run( + logfile=f"{test.obj_dir}/vlt_gcc_per_target.log", + tee=test.verbose, + cmd=[ + os.environ["MAKE"], + "-B", + "-C " + test.obj_dir, + "-f " + os.path.basename(per_target_makefile), + "VM_PREFIX=" + test.vm_prefix, + per_target_new_object, + ], +) +test.execute() + +test.passes() diff --git a/test_regress/t/t_cflags_user_cxx_floor.v b/test_regress/t/t_cflags_user_cxx_floor.v new file mode 100644 index 000000000..daf70aa63 --- /dev/null +++ b/test_regress/t/t_cflags_user_cxx_floor.v @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule