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.
This commit is contained in:
Jeffrey Song 2026-07-12 20:28:03 -07:00
parent dde8de0a0d
commit 5588c85073
6 changed files with 169 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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);
}

View File

@ -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()

View File

@ -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