From 4e14c2d330e8e3db2aaf0f5ae0a2c218fa9e8e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Venegas=20Arrab=C3=A9?= Date: Thu, 30 Jul 2026 22:10:32 +0200 Subject: [PATCH 1/6] xilinx: declare the Configuration explicit specializations in the header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit configuration.cc explicitly specializes Configuration::createType2ConfigurationPacketData and Configuration<...>::createConfigurationPackage (Spartan6, Series7, UltraScale, UltraScalePlus), but none of those specializations were declared in configuration.h. The standard requires an explicit specialization to be declared in every translation unit that uses it ([temp.expl.spec]); without the declaration, a TU calling createType2ConfigurationPacketData for Spartan6 instantiates the primary template — which is defined in this header — and emits its own COMDAT copy of the symbol. That copy collides with the strong definition from configuration.cc when linking xc7frames2bit/xc7patch with mingw-w64 ld ("multiple definition of ...createType2ConfigurationPacketData..."), which is why Windows builds needed -Wl,--allow-multiple-definition. ELF linkers happen to resolve the collision silently in favour of the strong symbol, so Linux builds never noticed. Declare all five specializations in the header so every user references the single definition in configuration.cc. Verified with a mingw-w64 cross build: the tools now link without the workaround linker flag. No behaviour change on ELF. --- lib/include/prjxray/xilinx/configuration.h | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/include/prjxray/xilinx/configuration.h b/lib/include/prjxray/xilinx/configuration.h index 0a883547..4721f8db 100644 --- a/lib/include/prjxray/xilinx/configuration.h +++ b/lib/include/prjxray/xilinx/configuration.h @@ -75,6 +75,44 @@ class Configuration { FrameMap frames_; }; +// The explicit specializations below are defined in configuration.cc. +// They must be declared before use in every translation unit +// ([temp.expl.spec]): without these declarations, a TU that calls +// createType2ConfigurationPacketData instantiates the primary +// template defined further down in this header, and that COMDAT copy +// collides with the strong definition from configuration.cc when linking +// with mingw-w64 ld ("multiple definition"); ELF linkers silently +// resolve the collision in favour of the strong symbol. +template <> +Configuration::PacketData +Configuration::createType2ConfigurationPacketData( + const Frames::Frames2Data& frames, + absl::optional& part); + +template <> +void Configuration::createConfigurationPackage( + Spartan6::ConfigurationPackage& out_packets, + const PacketData& packet_data, + absl::optional& part); + +template <> +void Configuration::createConfigurationPackage( + Series7::ConfigurationPackage& out_packets, + const PacketData& packet_data, + absl::optional& part); + +template <> +void Configuration::createConfigurationPackage( + UltraScale::ConfigurationPackage& out_packets, + const PacketData& packet_data, + absl::optional& part); + +template <> +void Configuration::createConfigurationPackage( + UltraScalePlus::ConfigurationPackage& out_packets, + const PacketData& packet_data, + absl::optional& part); + template typename Configuration::PacketData Configuration::createType2ConfigurationPacketData( From 6731005c1e898affd6be59571708bf7bae0b9934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Venegas=20Arrab=C3=A9?= Date: Thu, 30 Jul 2026 16:19:02 +0200 Subject: [PATCH 2/6] fasm2frames: default to sys.stdout instead of /dev/stdout The fn_out argument defaulted to the literal path '/dev/stdout', which does not exist on Windows, so running fasm2frames without an output file (the common shell-redirect invocation) failed there. Default to None and fall back to sys.stdout. --- utils/fasm2frames.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/fasm2frames.py b/utils/fasm2frames.py index 82ddc355..101744de 100755 --- a/utils/fasm2frames.py +++ b/utils/fasm2frames.py @@ -296,16 +296,16 @@ def main(): parser.add_argument('fn_in', help='Input FPGA assembly (.fasm) file') parser.add_argument( 'fn_out', - default='/dev/stdout', + default=None, nargs='?', - help='Output FPGA frame (.frm) file') + help='Output FPGA frame (.frm) file (default: stdout)') args = parser.parse_args() run( db_root=args.db_root, part=args.part, filename_in=args.fn_in, - f_out=open(args.fn_out, 'w'), + f_out=(open(args.fn_out, 'w') if args.fn_out else sys.stdout), sparse=args.sparse, roi=args.roi, debug=args.debug, From 19d29f9c841403575cc9a649121244c8d6d62c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Venegas=20Arrab=C3=A9?= Date: Thu, 30 Jul 2026 16:19:15 +0200 Subject: [PATCH 3/6] bit2fasm: close the temporary bits file before bitread writes it The temporary bits file was created with NamedTemporaryFile() and kept open while the bitread subprocess wrote to it by name. On Windows that is a sharing violation (the open handle blocks the child's write), so bit2fasm always failed there. Create the file with delete=False, close it immediately, and unlink it via the ExitStack instead. No behaviour change on POSIX beyond the file being closed while bitread runs. --- utils/bit2fasm.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/utils/bit2fasm.py b/utils/bit2fasm.py index 859ec109..464c3de1 100755 --- a/utils/bit2fasm.py +++ b/utils/bit2fasm.py @@ -108,7 +108,12 @@ def main(): if args.bits_file: bits_file = stack.enter_context(open(args.bits_file, 'wb')) else: - bits_file = stack.enter_context(tempfile.NamedTemporaryFile()) + # On Windows an open NamedTemporaryFile cannot be re-opened by + # the bitread subprocess (sharing violation): create it closed + # and clean it up ourselves. + bits_file = tempfile.NamedTemporaryFile(delete=False) + bits_file.close() + stack.callback(os.unlink, bits_file.name) bit_to_bits( bitread=args.bitread, From 10381074b26da703a4330272166c81984e66dd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Venegas=20Arrab=C3=A9?= Date: Thu, 30 Jul 2026 16:19:34 +0200 Subject: [PATCH 4/6] util: make OpenSafeFile work without fcntl (Windows) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prjxray/util.py imported fcntl at module level, so merely importing prjxray (e.g. from fasm2frames) failed on Windows, where fcntl does not exist. The only user is OpenSafeFile's advisory flock, whose timeout also relies on SIGALRM — equally POSIX-only. Guard the import and skip the locking when fcntl is unavailable: OpenSafeFile degrades to a plain open on Windows, while POSIX behaviour is unchanged. --- prjxray/util.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/prjxray/util.py b/prjxray/util.py index 615bf726..1fc1ac04 100644 --- a/prjxray/util.py +++ b/prjxray/util.py @@ -8,7 +8,12 @@ # https://opensource.org/licenses/ISC # # SPDX-License-Identifier: ISC -import fcntl +try: + import fcntl +except ImportError: + # Windows: no fcntl (and no SIGALRM); OpenSafeFile degrades to a plain + # open without inter-process locking. + fcntl = None import math import os import random @@ -47,6 +52,8 @@ class OpenSafeFile: def lock_file(self): assert self.fd is not None + if fcntl is None: + return try: signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(self.timeout) @@ -58,6 +65,8 @@ class OpenSafeFile: def unlock_file(self): assert self.fd is not None + if fcntl is None: + return fcntl.flock(self.fd.fileno(), fcntl.LOCK_UN) From a2c712e5887f4cdb6dd4c6a27aa2b3a6a0fd3858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Venegas=20Arrab=C3=A9?= Date: Thu, 30 Jul 2026 16:18:36 +0200 Subject: [PATCH 5/6] lib: Win32 port of MemoryMappedFile lib/memory_mapped_file.cc used POSIX open/fstat/mmap, which does not exist under mingw-w64, blocking a native Windows build of the prjxray tools. Add an #ifdef _WIN32 branch using CreateFileA/CreateFileMappingA/MapViewOfFile; the POSIX path is byte-for-byte unchanged. One behavioural note: a zero-length file cannot be mapped on Windows, so that case returns an object with nullptr data and zero size to preserve the "file exists" contract of InitWithFile. --- lib/memory_mapped_file.cc | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/memory_mapped_file.cc b/lib/memory_mapped_file.cc index 0600c9b2..a5be18be 100644 --- a/lib/memory_mapped_file.cc +++ b/lib/memory_mapped_file.cc @@ -9,16 +9,56 @@ */ #include +#ifdef _WIN32 +#include +#else #include #include #include #include #include +#endif namespace prjxray { std::unique_ptr MemoryMappedFile::InitWithFile( const std::string& path) { +#ifdef _WIN32 + HANDLE file = CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + NULL); + if (file == INVALID_HANDLE_VALUE) + return nullptr; + + LARGE_INTEGER file_size; + if (!GetFileSizeEx(file, &file_size)) { + CloseHandle(file); + return nullptr; + } + + // A zero-length file cannot be mapped; return an object (to indicate + // the file exists) with a nullptr and zero length. + if (file_size.QuadPart == 0) { + CloseHandle(file); + return std::unique_ptr( + new MemoryMappedFile(nullptr, 0)); + } + + HANDLE mapping = + CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL); + // The view keeps the file/mapping alive, so the handles can be closed. + CloseHandle(file); + if (mapping == NULL) + return nullptr; + + void* file_map = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); + CloseHandle(mapping); + if (file_map == NULL) + return nullptr; + + return std::unique_ptr(new MemoryMappedFile( + file_map, static_cast(file_size.QuadPart))); +#else int fd = open(path.c_str(), O_RDONLY, 0); if (fd == -1) return nullptr; @@ -51,10 +91,16 @@ std::unique_ptr MemoryMappedFile::InitWithFile( return std::unique_ptr( new MemoryMappedFile(file_map, statbuf.st_size)); +#endif } MemoryMappedFile::~MemoryMappedFile() { +#ifdef _WIN32 + if (data_) + UnmapViewOfFile(data_); +#else munmap(data_, size_); +#endif } } // namespace prjxray From 78019690be3a0792fd185ad7a1ec5feed43772ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Venegas=20Arrab=C3=A9?= Date: Thu, 30 Jul 2026 16:18:36 +0200 Subject: [PATCH 6/6] lib: Win32 port of the Database segbits enumeration lib/database.cc used glob(3) to enumerate segbits files, which is unavailable on Windows. Add an #ifdef _WIN32 branch using FindFirstFileA/FindNextFileA; the POSIX glob path is unchanged. --- lib/database.cc | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/database.cc b/lib/database.cc index e9d767bf..defa3325 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -9,7 +9,11 @@ */ #include +#ifdef _WIN32 +#include +#else #include +#endif #include @@ -23,6 +27,25 @@ std::vector> Database::segbits() const { std::vector> segbits; +#ifdef _WIN32 + const std::string pattern = + absl::StrCat(db_path_, "/", kSegbitsGlobPattern); + WIN32_FIND_DATAA find_data; + HANDLE handle = FindFirstFileA(pattern.c_str(), &find_data); + if (handle == INVALID_HANDLE_VALUE) { + return {}; + } + + do { + auto this_segbit = SegbitsFileReader::InitWithFile( + absl::StrCat(db_path_, "/", find_data.cFileName)); + if (this_segbit) { + segbits.emplace_back(std::move(this_segbit)); + } + } while (FindNextFileA(handle, &find_data)); + + FindClose(handle); +#else glob_t segbits_glob_results; int ret = glob(absl::StrCat(db_path_, "/", kSegbitsGlobPattern).c_str(), GLOB_NOSORT | GLOB_TILDE, NULL, &segbits_glob_results); @@ -39,6 +62,7 @@ std::vector> Database::segbits() } globfree(&segbits_glob_results); +#endif return segbits; }