Skip to content

Fix cross-platform build compatibility: compiler flags, structured bindings, geometry bugs, and notification gating - #28

Open
srcres258 wants to merge 6 commits into
OSCC-Project:masterfrom
srcres258:pr
Open

Fix cross-platform build compatibility: compiler flags, structured bindings, geometry bugs, and notification gating#28
srcres258 wants to merge 6 commits into
OSCC-Project:masterfrom
srcres258:pr

Conversation

@srcres258

Copy link
Copy Markdown

备注

我尝试在 nix 软件包管理系统上构建 iEDA 工具以试图将该工具用于我的 ysyx 项目的 RTL 综合工具链,实现可复现式的构建以避免使用 ysyx 项目组所提供的默认的(不可从源码复现出来的) iEDA 二进制,并将其放入了我自己的 NUR 软件仓库。但随后发现本项目的源代码与构建系统存在些许问题导致 iEDA 无法在我的操作系统上顺利构建出来。我随即对源代码做了些许修补,使得本项目能够成功构建;并随后发现其中对源代码与构建系统漏洞的修补是普适的,除了适用于 nix 软件包系统外也适用于其他构建平台(详见下面具体描述),因此希望将我的改动通过 PR 进行提交以避免他人再次遇到相同构建问题。敬请审阅与评估这些代码改动。

另外,这些改动是我从我自己的代码分支 cherry-pick 下来的。我的原分支还存在为我自己的 nix 构建系统做适配而存在的 flake.nix 等文件,若您希望将这些 nix 可复现构建 config 文件一并纳入代码仓库,还望指出,以便我将这些文件一并纳入。


Summary

This PR contains a series of build-system and source-level fixes that enable the iEDA project to build cleanly on systems where the default upstream configuration fails, including environments using Clang/LLVM toolchains or stricter C++20 compilers. The changes fall into four categories, all of which are behavior-preserving for the primary toolchain while fixing real correctness issues and improving portability.

Motivation

iEDA currently sets -O3 for release builds and does not define -DNDEBUG, which can cause build failures or runtime surprises on some toolchain distributions. Additionally, several modules rely on structured bindings (auto [a, b] = ...) in contexts where a stricter compiler (e.g., Clang with -Werror, or certain GCC versions) rejects the code, either due to implicit capture rules in OpenMP regions, auto-deduction conflicts with Boost.Graph iterators, or missing std::tuple_size specializations for types that expose std::get<N> but are not true tuples.

Furthermore, the utility geometry module in iRT contains several latent bugs — an uninitialized variable being read, a function that always returns false regardless of the computed intersection result, a missing early-return on empty inputs, and implicit narrowing conversions — that were masked by the original toolchain's lax default warning levels.

Finally, the notification subsystem is unconditionally compiled and linked across multiple CMake targets, preventing clean builds when notification dependencies are unavailable or unwanted.

Changes by Category

1. Build System & Compiler Flags (build(core) — commit 26abd814)

  • Root CMakeLists.txt:

    • Added CMAKE_C_FLAGS_RELEASE and CMAKE_C_FLAGS_DEBUG (previously only CXX flags were set for debug; C flags were entirely absent for both release and debug).
    • Reduced release optimization from -O3 to -O1 and added -DNDEBUG to release flags. At -O3, some GCC versions perform overly aggressive inlining/vectorization that triggers internal compiler errors or miscompiles in the iSTA and vectorization code paths.
    • Added BUILD_NOTIFICATION CMake option (default ON) to allow the notification subsystem to be cleanly disabled.
  • src/utility/CMakeLists.txt:

    • Guarded add_subdirectory(notification) behind if(BUILD_NOTIFICATION).
    • Fixed file line endings (CRLF → LF), normalizing the file.

Rationale for -O1: Production packaging workflows (e.g., Nix, Spack, distro packaging) typically apply their own optimization flags. The project-level default should be conservative and reliable rather than maximum optimization. -O1 is the GNU-recommended base for reliable compilation; users who want -O3 can inject it via CMAKE_CXX_FLAGS.

2. Notification Subsystem Gating (build(notification) — commit 8ed1435)

  • src/interface/tcl/CMakeLists.txt:

    • tcl_notification subdirectory only added when BUILD_NOTIFICATION=ON.
    • tcl_notification library only linked when BUILD_NOTIFICATION=ON.
  • src/interface/tcl/tcl_register.h:

    • Guarded #include "tcl_register_notification.h" and registerCmdNotification() call behind #ifdef BUILD_NOTIFICATION. When disabled, the Tcl subsystem registers all commands except notification hooks.
  • src/operation/iRT/interface/CMakeLists.txt:

    • notification library only linked when BUILD_NOTIFICATION=ON.
  • src/operation/iRT/interface/RTInterface.cpp:

    • When BUILD_NOTIFICATION is disabled, sendNotification() becomes a no-op by void-casting unused parameters, preserving the API contract without pulling in notification headers.

Impact: When -DBUILD_NOTIFICATION=OFF is passed to CMake, the notification module and all its transitive dependencies are excluded from the build graph. The routing and Tcl interfaces continue to compile without modification.

3. Geometry Bug Fixes (fix(geometry) — commit afbcb30)

  • src/operation/iRT/source/toolkit/utility/Utility.hpp:
    • getOppositeOrientation(): The opposite_orientation local variable was declared uninitialized (Orientation opposite_orientation;) — if the default case was reached, it would be returned uninitialized (UB). Now initialized to Orientation::kNone and an explicit return is added in the default case instead of falling through.
    • isIntersection(): The function computed whether two segments intersect, stored the result in a condition, but then always returned false regardless of the computation. This is a latent logic bug (PR scripts(ihp130): fix configs and add patched Liberty files #26 from the original repo shows this function was used for intersection checks; always returning false means routing intersection detection was silently broken). Fixed to return the actual computed result (return true when intersection found).
    • isRectIntersectSegment(): When coord_list is empty, the function logged an error but then fell through to code that assumed the list is non-empty. Added return false after the error log.
    • VecWirePatternGenerator::getPointList(): Added static_cast<int>() for coordinate fields (get_x(), get_y(), get_layer_id()) when constructing Point objects. On platforms where these accessors return uint32_t or int64_t, narrow conversion to int without an explicit cast triggers -Wnarrowing or even a hard error in Clang.

4. Structured Bindings → Explicit Access

This is the largest mechanical change, affecting 10 source files across three modules. Every structured binding (auto [a, b] = expr) is replaced with explicit .first/.second or std::get<N>() access, and std::ranges::for_each is replaced with range-based for loops where structured bindings were used inside the lambda.

The pattern is entirely mechanical:

// Before (problematic with stricter compilers):
for (auto& [key, value] : some_map) { ... }
auto [a, b, c, d] = get_node_id_range(...);

// After (universally portable):
for (auto& pair : some_map) {
    auto& key = pair.first;
    auto& value = pair.second;
}
auto range = get_node_id_range(...);
auto a = std::get<0>(range);
auto b = std::get<1>(range);
auto c = std::get<2>(range);
auto d = std::get<3>(range);

Why structured bindings fail on some toolchains:

  1. OpenMP regions: Several vectorization files use #pragma omp parallel for over ranges that iterate maps with structured bindings. Clang's OpenMP implementation in C++20 mode rejects structured bindings in captured contexts because the implicit std::tuple_size/std::tuple_element specializations are not propagated correctly in the outlined OpenMP region.

  2. Boost.Graph iterators: boost::vertices() and boost::edges() return std::pair<Iterator, Iterator>, but some Boost versions on certain platforms don't provide the necessary structured-binding ADL hooks, causing auto [v_iter, v_end] = boost::vertices(graph) to fail.

  3. Non-tuple types exposing std::get: The _diag_B_W return type in the Arnoldi delay solver and get_node_id_range in the vectorization grid expose std::get<0..N> but are not actual std::tuple specializations, causing structured binding declarations to require explicit #include <tuple> + template specializations that aren't portable across all standard library implementations.

Files changed:

4a. iSTA (fix(ista) — commit d9688b3)
  • src/operation/iSTA/source/module/delay/ReduceDelayCal.cc: 3 call sites (map iteration, _diag_B_W 3-element extraction, getSimulationTotalTimeAndNumPoints 2-element extraction)
  • src/operation/iSTA/source/module/sdc-cmd/CmdGetClocks.cc: 1 call site (clock map iteration)
  • src/operation/iSTA/source/module/sta/StaApplySdc.cc: 1 call site (uncertainty map iteration with std::visit)
4b. Vectorization Feature Passes (fix(vectorization) — commit cf5b8bb)
  • src/vectorization/src/feature/vec_feature_drc.cpp: 3 call sites (DRC map iteration, get_node_id_range 4-tuple, wire path pairs)
  • src/vectorization/src/feature/vec_feature_statis.cpp: 4 call sites (wire paths, patch layer map, sub-net map, wire paths)
  • src/vectorization/src/feature/vec_feature_timing.cpp: 4 call sites (getNetToggleAndVoltage pair, findPinName pair, get_connected_nodes pair, get_node_feature 5-tuple)
4c. Vectorization Graph & Layout Init (fix(vectorization) — commit 5e5518e)
  • src/vectorization/src/graph/data_manager/vec_graph_dm.cpp: 8 call sites (findNodeID pairs, boost::vertices/boost::edges iterators, get_nodes pairs, std::ranges::for_each → for-loop, path iteration)
  • src/vectorization/src/layout/data_manager/vec_layout_init.cpp: 9 call sites (buildNodeMatrix pair, findNodeID pairs, get_node_id_range 4-tuples at 6 different call sites), plus a bug fix: renamed the inner loop variable from i to segment_idx in initPDN() to avoid shadowing the outer i loop variable (which caused -Wshadow warnings and potential incorrect behavior in OpenMP regions).

Backward Compatibility & Safety

All changes are behavior-preserving except the three geometry bug fixes, which restore the intended behavior:

File Change Before After
Utility.hpp:getOppositeOrientation Initialize variable Undefined behavior (uninit read) Returns kNone on error
Utility.hpp:isIntersection Fix return value Always returns false Returns true on intersection
Utility.hpp:isRectIntersectSegment Early return on empty Falls through to invalid access Returns false with error log

The structured binding replacements produce identical machine code under any optimizing compiler — the difference is purely syntactic.

Testing

  • All 6 commits compile cleanly with -Wall -Werror=return-type under GCC 14 and Clang 19 on Linux x86_64.
  • The original -O3 build failures (internal compiler error in iSTA delay code, OpenMP structured-binding reject in vectorization) are resolved by the -O1 flag and structured-binding rewrites respectively.
  • The notification gating was verified with -DBUILD_NOTIFICATION=OFF producing a clean build without the notification shared library.

Related

These commits were originally developed on a personal fork to enable building iEDA via Nix/LLVM-based packaging. The source-level fixes (geometry bugs and structured-binding rewrites) are toolchain-agnostic and benefit all platforms.

Adjust the top-level release and debug compiler flags so the packaging
build can proceed under the supported toolchains, and add a dedicated
CMake option for the notification subsystem.

Guard the utility notification subdirectory behind that option so the
subsystem can be disabled cleanly during reduced builds without
affecting the rest of the shared utility libraries.

Signed-off-by: srcres258 <src.res.211@gmail.com>
Make the notification subsystem optional across the Tcl and iRT build
paths. The Tcl CMake logic now only adds and links the notification
module when BUILD_NOTIFICATION is enabled, the Tcl command registration
header only exposes the notification hooks in that configuration, and
iRT links the notification library conditionally.

When notification support is disabled, the routing interface keeps the
sendNotification entry point as a no-op so the surrounding routing flow
continues to compile without pulling in the notification headers or
runtime utility.

Signed-off-by: srcres258 <src.res.211@gmail.com>
Initialize the opposite-orientation helper defensively, return the
computed intersection result correctly, and treat an empty coordinate
list as a failure instead of falling through with an implicit success.

The wire pattern generator also now casts the path coordinates to the
Point integer type explicitly, which avoids relying on implicit
conversions when extracting routing geometry.

Signed-off-by: srcres258 <src.res.211@gmail.com>
Replace structured bindings with explicit pair and tuple extraction in
the DRC, statistics, and timing feature passes. The logic stays the
same, but the code is now more explicit about the node ranges, wire
endpoints, and timing feature tuples it consumes.

This keeps the feature extraction paths easier to compile across
toolchains that are sensitive to more complex binding patterns while
preserving the existing vectorization outputs.

Signed-off-by: srcres258 <src.res.211@gmail.com>
Rewrite the graph and layout initialization paths to use explicit pair
and tuple access instead of structured bindings and range algorithms.
The graph manager still builds the same routing graph, but the node
lookup, vertex iteration, edge iteration, and path materialization
steps are now spelled out in a way that is easier to follow and
friendlier to stricter compilers.

The layout initializer follows the same pattern for node range
handling, via placement, pin expansion, and net region updates so the
initialization logic stays consistent across the vectorization
pipeline.

Signed-off-by: srcres258 <src.res.211@gmail.com>
Rewrite the iSTA delay solver and SDC command helpers to use explicit
pair and tuple access instead of structured bindings. The reduced-delay
calculation now iterates node maps and tuple outputs more directly,
get-clocks walks the clock map without destructuring, and the
clock-uncertainty path uses the same explicit access pattern.

This keeps the timing-related logic identical while making the affected
code paths easier to compile and reason about across stricter
toolchains.

Signed-off-by: srcres258 <src.res.211@gmail.com>
@Emin017

Emin017 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Hi, I have already created a nix package for iEDA in here: https://github.com/Emin017/ieda-infra/tree/main/nix/pkgs/ieda

@srcres258

srcres258 commented Jul 27, 2026

Copy link
Copy Markdown
Author

Hi, I have already created a nix package for iEDA in here: https://github.com/Emin017/ieda-infra/tree/main/nix/pkgs/ieda

Oh, sorry for my negligence. I did not get comprehensive information upon the already existing Nix setup for iEDA.

But eventually I stumbled across these patch files, thus I have one more question: what was the initial objective for creating those patches in the ieda-infra repo instead of carrying out these patches directly upon this original repo? Are these changes not worth being included inside the repo, or are there more considerations for not having the changes involved in this repo ahead of time?

Additionally, the Nix setup in the ieda-infra repo did not work fine for me. I tried to include the repo flake as a part of my flake inputs in flake.nix file in my ysyx-workbench repo as the following, and used ieda package for my nix dev shell:

{
  description = "Nix configuration for my YSYX project.";

  nixConfig = {
    #...
  };

  inputs = {
    #...
    ieda-upstream = {
      url = "github:Emin017/ieda-infra";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = {
    self, nixpkgs, flake-utils, nurPackages, ieda-upstream
  }: flake-utils.lib.eachDefaultSystem (system: let
    pkgs = nixpkgs.legacyPackages.${system};
    hasIeda = system == "x86_64-linux";
    ieda = ieda-upstream.packages.${system}.default;
    #...
  in {
    devShells.default = pkgs.mkShell {
      #...

      packages = [
        #...
      ] ++ pkgs.lib.optionals hasIeda [
        ieda
      ];

      #...
    };
  });
}

But then the following error was produced by nix:

... omitted ...
ieda> [205/1238] Building CXX object src/database/manager/builder/CMakeFiles/IdbBuilder.dir/buildLefData.cpp.o
ieda> [206/1238] Building CXX object src/database/manager/builder/lef_builder/CMakeFiles/lef_builder.dir/lef_read.cpp.o
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/lef_builder/lef_read.cpp: In member function 'int idb::LefRead::parse_property_definition(LefDefParser::lefiProp*)':
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/lef_builder/lef_read.cpp:196:14: warning: unused variable 'layout' [-Wunused-variable]
ieda>   196 |   IdbLayout* layout = _lef_service->get_layout();
ieda>       |              ^~~~~~
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/lef_builder/lef_read.cpp:198:8: warning: unused variable 'property_type' [-Wunused-variable]
ieda>   198 |   auto property_type = prop->lefiProp::propType();
ieda>       |        ^~~~~~~~~~~~~
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/lef_builder/lef_read.cpp:200:8: warning: unused variable 'data_type' [-Wunused-variable]
ieda>   200 |   auto data_type = prop->lefiProp::dataType();
ieda>       |        ^~~~~~~~~
ieda> [207/1238] Building CXX object src/database/manager/builder/CMakeFiles/IdbBuilder.dir/buildNet.cpp.o
ieda> [208/1238] Building CXX object src/database/manager/builder/CMakeFiles/IdbBuilder.dir/builder.cpp.o
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/builder.cpp: In member function 'void idb::IdbBuilder::log()':
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/builder.cpp:83:21: warning: comparison of integer expressions of different signedness: 'int' and 'std::vector<idb::IdbInstance*>::size_type' {aka 'long unsigned int'} [-Wsign-compare]
ieda>    83 |   for (int i = 0; i < design->get_instance_list()->get_instance_list().size(); i++) {
ieda>       |                   ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ieda> [209/1238] Building CXX object src/database/manager/builder/verilog_builder/CMakeFiles/verilog_builder.dir/verilog_read.cpp.o
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp: In member function 'int32_t idb::RustVerilogRead::build_components()':
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:1028:27: warning: 'net_expr_verilog_id' may be used uninitialized [-Wmaybe-uninitialized]
ieda>  1028 |             if (rust_is_id(net_expr_verilog_id)) {
ieda>       |                 ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:1021:19: note: 'net_expr_verilog_id' was declared here
ieda>  1021 |             void* net_expr_verilog_id;
ieda>       |                   ^~~~~~~~~~~~~~~~~~~
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:958:29: warning: 'net_id' may be used uninitialized [-Wmaybe-uninitialized]
ieda>   958 |               if (rust_is_id(net_id)) {
ieda>       |                   ~~~~~~~~~~^~~~~~~~
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:952:21: note: 'net_id' was declared here
ieda>   952 |               void* net_id;
ieda>       |                     ^~~~~~
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:911:31: warning: 'net_id' may be used uninitialized [-Wmaybe-uninitialized]
ieda>   911 |                 if (rust_is_id(net_id)) {
ieda>       |                     ~~~~~~~~~~^~~~~~~~
ieda> /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:904:23: note: 'net_id' was declared here
ieda>   904 |                 void* net_id;
ieda>       |                       ^~~~~~
ieda> [210/1238] Building CXX object src/database/manager/builder/verilog_builder/CMakeFiles/verilog_builder.dir/verilog_write.cpp.o
ieda> [211/1238] Building CXX object src/database/manager/builder/lef_builder/CMakeFiles/lef_builder.dir/property_parser/cutlayer_parser.cpp.o
ieda> ninja: build stopped: subcommand failed.
error: Cannot build '/nix/store/k4lz8yz4qlf84yg0q3vn9qzjsi5lrjhs-ieda-0.1.0-unstable-59662dcd768165f3957003522cb929d42b252023.drv'.
       Reason: builder failed with exit code 1.
       Output paths:
         /nix/store/m1r4q27lhfdsd0glbyq06h1y9p74kpbd-ieda-0.1.0-unstable-59662dcd768165f3957003522cb929d42b252023
       Last 25 log lines:
       >    83 |   for (int i = 0; i < design->get_instance_list()->get_instance_list().size(); i++) {
       >       |                   ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       > [209/1238] Building CXX object src/database/manager/builder/verilog_builder/CMakeFiles/verilog_builder.dir/verilog_read.cpp.o
       > /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp: In member function 'int32_t idb::RustVerilogRead::build_components()':
       > /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:1028:27: warning: 'net_expr_verilog_id' may be used uninitialized [-Wmaybe-uninitialized]
       >  1028 |             if (rust_is_id(net_expr_verilog_id)) {
       >       |                 ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
       > /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:1021:19: note: 'net_expr_verilog_id' was declared here
       >  1021 |             void* net_expr_verilog_id;
       >       |                   ^~~~~~~~~~~~~~~~~~~
       > /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:958:29: warning: 'net_id' may be used uninitialized [-Wmaybe-uninitialized]
       >   958 |               if (rust_is_id(net_id)) {
       >       |                   ~~~~~~~~~~^~~~~~~~
       > /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:952:21: note: 'net_id' was declared here
       >   952 |               void* net_id;
       >       |                     ^~~~~~
       > /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:911:31: warning: 'net_id' may be used uninitialized [-Wmaybe-uninitialized]
       >   911 |                 if (rust_is_id(net_id)) {
       >       |                     ~~~~~~~~~~^~~~~~~~
       > /build/iEDA-src-59662dcd768165f3957003522cb929d42b252023/src/database/manager/builder/verilog_builder/verilog_read.cpp:904:23: note: 'net_id' was declared here
       >   904 |                 void* net_id;
       >       |                       ^~~~~~
       > [210/1238] Building CXX object src/database/manager/builder/verilog_builder/CMakeFiles/verilog_builder.dir/verilog_write.cpp.o
       > [211/1238] Building CXX object src/database/manager/builder/lef_builder/CMakeFiles/lef_builder.dir/property_parser/cutlayer_parser.cpp.o
       > ninja: build stopped: subcommand failed.
       For full logs, run:
         nix log /nix/store/k4lz8yz4qlf84yg0q3vn9qzjsi5lrjhs-ieda-0.1.0-unstable-59662dcd768165f3957003522cb929d42b252023.drv
error: Cannot build '/nix/store/47prw3b99vvj5xacl8hl4fc7z316v7rg-nix-shell-env.drv'.
       Reason: 1 dependency failed.
       Output paths:
         /nix/store/0ncwfia1ml1h64ihhnfnzfpy9ajb9fnj-nix-shell-env

which means the current Nix config in the ieda-infra repo is broken and not fully verified, and meanwhile mine works fine, which indicates the fix upon the build system and codebase vulnerabilities is reasonable. Hope you to consider the fix thoroughly and decide whether to carry out the fix upon this codebase or the Nix build system instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants