From 0d6a68f79e6dca830443900fee0bf51dae35bf20 Mon Sep 17 00:00:00 2001 From: Matthew Ballance Date: Sun, 7 Jun 2026 14:33:55 +0000 Subject: [PATCH] Enable macOS support for dynamic load Signed-off-by: Matthew Ballance --- src/V3EmitMk.cpp | 9 ++++++++- test_regress/t/t_flag_main_vpi.py | 18 ++++++++++++------ test_regress/t/t_flag_main_vpi_noexe.py | 7 ++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index 2b1eca99b..c6d6c629d 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -738,12 +738,19 @@ public: if (v3Global.opt.vpi()) { // Runtime VPI library loading (+verilator+vpi+) needs platform-specific - // link flags. Resolve them with $(UNAME_S) + // link flags so the dlopen'd library can resolve the executable's VPI symbols. + // Resolve them with $(UNAME_S). of.puts("# Runtime VPI library loading (+verilator+vpi+) link requirements\n"); + // -rdynamic exports the executable's VPI symbols to the dlopen'd library; + // -ldl provides dlopen/dlsym. of.puts("ifeq ($(UNAME_S),Linux)\n"); of.puts(/**/ "LDFLAGS += -rdynamic\n"); of.puts(/**/ "LDLIBS += -ldl\n"); of.puts("endif\n"); + // macOS analog of -rdynamic; dlopen/dlsym live in libSystem so no -ldl needed. + of.puts("ifeq ($(UNAME_S),Darwin)\n"); + of.puts(/**/ "LDFLAGS += -Wl,-export_dynamic\n"); + of.puts("endif\n"); of.puts("\n"); } } diff --git a/test_regress/t/t_flag_main_vpi.py b/test_regress/t/t_flag_main_vpi.py index dbb186f02..2ccadc844 100755 --- a/test_regress/t/t_flag_main_vpi.py +++ b/test_regress/t/t_flag_main_vpi.py @@ -7,6 +7,7 @@ # SPDX-FileCopyrightText: 2025 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +import platform import vltest_bootstrap test.scenarios('vlt') @@ -15,13 +16,18 @@ test.scenarios('vlt') # Also compile a VPI shared library to be loaded at runtime via +verilator+vpi+. test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"]) -# With --vpi and an executable (--binary implies --exe), the Makefile must, on Linux, -# export the executable's symbols (-rdynamic) and link libdl for the generated loader's -# dlopen/dlsym calls. These are gated on $(UNAME_S) so macOS/Windows are unaffected. +# With --vpi and an executable (--binary implies --exe), the Makefile must export the +# executable's symbols so the generated loader's dlopen'd library can resolve them. +# The flags are gated on $(UNAME_S): -rdynamic/-ldl on Linux, -Wl,-export_dynamic on +# macOS (Windows is unaffected, runtime loading is unsupported there). mk = test.obj_dir + "/V" + test.name + ".mk" -test.file_grep(mk, r'ifeq \(\$\(UNAME_S\),Linux\)') -test.file_grep(mk, r'LDFLAGS \+= -rdynamic') -test.file_grep(mk, r'LDLIBS \+= -ldl') +if platform.system() == 'Darwin': + test.file_grep(mk, r'ifeq \(\$\(UNAME_S\),Darwin\)') + test.file_grep(mk, r'LDFLAGS \+= -Wl,-export_dynamic') +else: + test.file_grep(mk, r'ifeq \(\$\(UNAME_S\),Linux\)') + test.file_grep(mk, r'LDFLAGS \+= -rdynamic') + test.file_grep(mk, r'LDLIBS \+= -ldl') # Run the generated binary; load the VPI library via the +verilator+vpi+ plusarg. # The VPI library's output (observed 'count' reaching MAX_TICKS, then end-of-sim) is diff --git a/test_regress/t/t_flag_main_vpi_noexe.py b/test_regress/t/t_flag_main_vpi_noexe.py index 8daa43531..c8177a0eb 100755 --- a/test_regress/t/t_flag_main_vpi_noexe.py +++ b/test_regress/t/t_flag_main_vpi_noexe.py @@ -21,9 +21,10 @@ test.compile(make_main=False, verilator_make_gmake=False, verilator_flags2=["--v test.file_grep(test.obj_dir + "/V" + test.name + "_classes.mk", r'VM_VPI = 1') # Without --exe there is no executable to export symbols from or to dlopen into, -# so the runtime-VPI link flags must not be emitted into the Makefile at all. +# so the runtime-VPI link flags must not be emitted into the Makefile on any platform. mk = test.obj_dir + "/V" + test.name + ".mk" -test.file_grep_not(mk, r'-rdynamic') -test.file_grep_not(mk, r'-ldl') +test.file_grep_not(mk, r'-rdynamic') # Linux +test.file_grep_not(mk, r'-ldl') # Linux +test.file_grep_not(mk, r'-export_dynamic') # macOS test.passes()