diff --git a/.gitignore b/.gitignore index 98298f9b..9f686a41 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ cmake_install.cmake !.vscode/tasks.json !.vscode/launch.json !.vscode/settings.json +!.vscode/c_cpp_properties.clang.example.json .history/ /.cache/ /.clangd diff --git a/.vscode/c_cpp_properties.clang.example.json b/.vscode/c_cpp_properties.clang.example.json new file mode 100644 index 00000000..d2a94c3d --- /dev/null +++ b/.vscode/c_cpp_properties.clang.example.json @@ -0,0 +1,74 @@ +{ + // Copy this file to c_cpp_properties.json and launch VS Code from a Visual Studio + // Developer Command Prompt so the selected toolset and SDK are available. + "env": { + // LLVM installed with its default Windows installer location. + "CLANG_CL_PATH": "${env:ProgramFiles}/LLVM/bin/clang-cl.exe", + "MSVC_TOOLS_DIR": "${env:VCToolsInstallDir}", + "WINDOWS_SDK_INCLUDE_DIR": "${env:WindowsSdkDir}/Include/${env:WindowsSDKVersion}", + // Set this to the CMake binary directory used for the clang-cl build. + "CLANG_BUILD_DIR": "${workspaceFolder}/build/clang-cl", + "CLANG_COMPILER_ARGS": [ + "--target=i686-pc-windows-msvc", + // Match the 14.44 MSVC toolset; update both together. + "/clang:-fms-compatibility-version=19.44" + ], + "CLANG_INCLUDE_PATH": [ + "${workspaceFolder}/code", + "${workspaceFolder}/code/vqalib", + "${workspaceFolder}/thirdparty/**", + "${CLANG_BUILD_DIR}/generated", + "${MSVC_TOOLS_DIR}/include", + "${MSVC_TOOLS_DIR}/atlmfc/include", + "${WINDOWS_SDK_INCLUDE_DIR}/ucrt", + "${WINDOWS_SDK_INCLUDE_DIR}/shared", + "${WINDOWS_SDK_INCLUDE_DIR}/um", + "${WINDOWS_SDK_INCLUDE_DIR}/winrt", + "${WINDOWS_SDK_INCLUDE_DIR}/cppwinrt" + ], + "CLANG_DEFINES": [ + "WIN32", + "_WIN32", + "_WINDOWS", + "_MBCS", + "NOMINMAX", + "NO_BLOWFISH_DLL" + ] + }, + "configurations": [ + { + "name": "Win32 clang-cl Debug", + // Do not let the workspace's Visual Studio CMake provider override this configuration. + "configurationProvider": "", + "compilerPath": "${CLANG_CL_PATH}", + "compilerArgs": ["${CLANG_COMPILER_ARGS}"], + "intelliSenseMode": "windows-clang-x86", + "cStandard": "c17", + "cppStandard": "c++20", + "includePath": ["${CLANG_INCLUDE_PATH}"], + "defines": ["${CLANG_DEFINES}", "_DEBUG", "BX_CONFIG_DEBUG=1"] + }, + { + "name": "Win32 clang-cl Release", + // Do not let the workspace's Visual Studio CMake provider override this configuration. + "configurationProvider": "", + "compilerPath": "${CLANG_CL_PATH}", + "compilerArgs": ["${CLANG_COMPILER_ARGS}"], + "intelliSenseMode": "windows-clang-x86", + "cStandard": "c17", + "cppStandard": "c++20", + "includePath": ["${CLANG_INCLUDE_PATH}"], + "defines": ["${CLANG_DEFINES}", "NDEBUG", "BX_CONFIG_DEBUG=0"] + }, + { + "name": "Win32 clang-cl compilation database", + // Configure the clang-cl Ninja build before selecting this configuration. + "configurationProvider": "", + "compileCommands": "${CLANG_BUILD_DIR}/compile_commands.json", + "intelliSenseMode": "windows-clang-x86", + "cStandard": "c17", + "cppStandard": "c++20" + } + ], + "version": 4 +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 33878dba..27386aa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,28 @@ set(OPENTS_VERSION_PRERELEASE "") # alone. Every other build names the commit it came from as well. option(OPENTS_OFFICIAL_BUILD "Build as an official release of the declared version" OFF) -if(NOT MSVC OR MSVC_VERSION LESS 1930) - message(FATAL_ERROR "OpenTS requires the Visual Studio 2022 MSVC toolchain.") +option(OPENTS_EXPERIMENTAL_CLANG_CL "Build with clang-cl using the MSVC ABI" OFF) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" + AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + if(NOT OPENTS_EXPERIMENTAL_CLANG_CL) + message(FATAL_ERROR + "The unsupported clang-cl experiment must be configured with " + "-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-cl-msvc.cmake.") + endif() +elseif(MSVC) + if(MSVC_VERSION LESS 1930) + message(FATAL_ERROR "OpenTS requires MSVC 19.30 or newer.") + endif() +else() + message(FATAL_ERROR + "OpenTS requires the Visual Studio 2022 MSVC toolchain. " + "For the unsupported clang-cl experiment, configure with " + "-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-cl-msvc.cmake.") endif() +enable_language(RC) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/cmake/toolchains/clang-cl-msvc.cmake b/cmake/toolchains/clang-cl-msvc.cmake new file mode 100644 index 00000000..ba3d7709 --- /dev/null +++ b/cmake/toolchains/clang-cl-msvc.cmake @@ -0,0 +1,114 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86) + +set(OPENTS_EXPERIMENTAL_CLANG_CL ON CACHE BOOL "" FORCE) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "" FORCE) +set(OPENTS_MSVC_ROOT "" CACHE PATH "Path to the MSVC and Windows SDK files") +list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES OPENTS_MSVC_ROOT) + +if(NOT OPENTS_MSVC_ROOT AND DEFINED ENV{OPENTS_MSVC_ROOT}) + set(OPENTS_MSVC_ROOT "$ENV{OPENTS_MSVC_ROOT}" CACHE PATH "" FORCE) +endif() + +if(NOT OPENTS_MSVC_ROOT) + message(FATAL_ERROR + "Set OPENTS_MSVC_ROOT to the directory containing MSVC and the Windows SDK.") +endif() + +set(_opents_msvc_version_file + "${OPENTS_MSVC_ROOT}/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt") +if(NOT EXISTS "${_opents_msvc_version_file}") + message(FATAL_ERROR + "Default MSVC toolset version not found: ${_opents_msvc_version_file}") +endif() + +file(STRINGS "${_opents_msvc_version_file}" _opents_msvc_version LIMIT_COUNT 1) +string(STRIP "${_opents_msvc_version}" _opents_msvc_version) +if(NOT _opents_msvc_version MATCHES "^14\\.([0-9]+)\\.") + message(FATAL_ERROR "Unsupported MSVC toolset version: ${_opents_msvc_version}") +endif() +set(_opents_msvc_compatibility_version "19.${CMAKE_MATCH_1}") + +set(_opents_msvc_dir "${OPENTS_MSVC_ROOT}/VC/Tools/MSVC/${_opents_msvc_version}") +set(_opents_sdk_dir "${OPENTS_MSVC_ROOT}/Windows Kits/10") + +file(GLOB _opents_sdk_candidates + LIST_DIRECTORIES TRUE + RELATIVE "${_opents_sdk_dir}/Include" + "${_opents_sdk_dir}/Include/*") +list(SORT _opents_sdk_candidates COMPARE NATURAL ORDER DESCENDING) + +unset(_opents_sdk_version) +foreach(_candidate IN LISTS _opents_sdk_candidates) + if(_candidate MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$" + AND IS_DIRECTORY "${_opents_sdk_dir}/Include/${_candidate}/shared" + AND IS_DIRECTORY "${_opents_sdk_dir}/Include/${_candidate}/ucrt" + AND IS_DIRECTORY "${_opents_sdk_dir}/Include/${_candidate}/um" + AND IS_DIRECTORY "${_opents_sdk_dir}/Include/${_candidate}/winrt" + AND IS_DIRECTORY "${_opents_sdk_dir}/Lib/${_candidate}/ucrt/x86" + AND IS_DIRECTORY "${_opents_sdk_dir}/Lib/${_candidate}/um/x86") + set(_opents_sdk_version "${_candidate}") + break() + endif() +endforeach() + +if(NOT _opents_sdk_version) + message(FATAL_ERROR "No complete Windows SDK found under: ${_opents_sdk_dir}") +endif() + +foreach(_required_path + "${_opents_msvc_dir}/include" + "${_opents_sdk_dir}/Include/${_opents_sdk_version}/um") + if(NOT EXISTS "${_required_path}") + message(FATAL_ERROR "Required MSVC component not found: ${_required_path}") + endif() +endforeach() + +find_program(_opents_clang_cl clang-cl REQUIRED) +find_program(_opents_lld_link lld-link REQUIRED) +find_program(_opents_llvm_lib llvm-lib REQUIRED) +find_program(_opents_llvm_mt llvm-mt REQUIRED) +find_program(_opents_llvm_rc llvm-rc REQUIRED) +find_program(_opents_uasm uasm REQUIRED) + +set(CMAKE_C_COMPILER "${_opents_clang_cl}") +set(CMAKE_CXX_COMPILER "${_opents_clang_cl}") +set(CMAKE_C_COMPILER_TARGET i686-pc-windows-msvc) +set(CMAKE_CXX_COMPILER_TARGET i686-pc-windows-msvc) +set(CMAKE_C_FLAGS_INIT + "/clang:-fms-compatibility-version=${_opents_msvc_compatibility_version}") +set(CMAKE_CXX_FLAGS_INIT + "/clang:-fms-compatibility-version=${_opents_msvc_compatibility_version}") +set(CMAKE_LINKER "${_opents_lld_link}") +set(CMAKE_AR "${_opents_llvm_lib}") +set(CMAKE_RC_COMPILER "${_opents_llvm_rc}" CACHE FILEPATH "" FORCE) +set(CMAKE_MT "${_opents_llvm_mt}") +set(CMAKE_ASM_MASM_COMPILER "${_opents_uasm}" CACHE FILEPATH "" FORCE) + +set(CMAKE_USER_MAKE_RULES_OVERRIDE + "${CMAKE_CURRENT_LIST_DIR}/clang-cl-rc-rules.cmake") + +set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES + "${_opents_msvc_dir}/atlmfc/include" + "${_opents_msvc_dir}/include" + "${_opents_sdk_dir}/Include/${_opents_sdk_version}/shared" + "${_opents_sdk_dir}/Include/${_opents_sdk_version}/ucrt" + "${_opents_sdk_dir}/Include/${_opents_sdk_version}/um" + "${_opents_sdk_dir}/Include/${_opents_sdk_version}/winrt") +set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_C_STANDARD_INCLUDE_DIRECTORIES}) + +set(_opents_rc_flags "") +foreach(_include_dir IN LISTS CMAKE_C_STANDARD_INCLUDE_DIRECTORIES) + string(APPEND _opents_rc_flags " /I\"${_include_dir}\"") +endforeach() +set(CMAKE_RC_FLAGS_INIT "${_opents_rc_flags}") + +set(_opents_linker_paths + "/libpath:\"${_opents_msvc_dir}/atlmfc/lib/x86\"" + "/libpath:\"${_opents_msvc_dir}/lib/x86\"" + "/libpath:\"${_opents_sdk_dir}/Lib/${_opents_sdk_version}/ucrt/x86\"" + "/libpath:\"${_opents_sdk_dir}/Lib/${_opents_sdk_version}/um/x86\"") +string(JOIN " " _opents_linker_flags ${_opents_linker_paths}) +set(CMAKE_EXE_LINKER_FLAGS_INIT "${_opents_linker_flags}") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "${_opents_linker_flags}") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "${_opents_linker_flags}") diff --git a/cmake/toolchains/clang-cl-rc-rules.cmake b/cmake/toolchains/clang-cl-rc-rules.cmake new file mode 100644 index 00000000..f2ebf475 --- /dev/null +++ b/cmake/toolchains/clang-cl-rc-rules.cmake @@ -0,0 +1,3 @@ +set(CMAKE_DEPFILE_FLAGS_RC "") +set(CMAKE_RC_COMPILE_OBJECT + " /C 1252 /fo ") diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 8e90291c..94876c52 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -101,17 +101,17 @@ set(OPENTS_COMPILE_OPTIONS # /arch:SSE2 -- IEEE-754 single precision, no x87 excess precision. # /fp:precise -- no reassociation of the engine's accumulations. $<$: - /Zi /Od /RTC1 /GR /MP /EHsc /Oy- /MTd /Zc:__cplusplus /arch:SSE2 /fp:precise + /Zi /Od /RTC1 /GR $<$:/MP> /EHsc /Oy- /MTd /Zc:__cplusplus /arch:SSE2 /fp:precise > # ---------- C ---------- $<$: - /Zi /Od /RTC1 /MP /Oy- /arch:SSE2 /fp:precise + /Zi /Od /RTC1 $<$:/MP> /Oy- /arch:SSE2 /fp:precise > # ---------- MASM ---------- $<$: - /Zi /c /coff /Cx /safeseh + -Zi -c -coff -Cx -safeseh > > @@ -122,17 +122,17 @@ set(OPENTS_COMPILE_OPTIONS # /arch:SSE2 -- IEEE-754 single precision, no x87 excess precision. # /fp:precise -- no reassociation of the engine's accumulations. $<$: - /Zi /O2 /GF /GR /MP /EHsc /MT /Zc:__cplusplus /arch:SSE2 /fp:precise + /Zi /O2 /GF /GR $<$:/MP> /EHsc /MT /Zc:__cplusplus /arch:SSE2 /fp:precise > # ---------- C ---------- $<$: - /Zi /O2 /GF /MP /arch:SSE2 /fp:precise + /Zi /O2 /GF $<$:/MP> /arch:SSE2 /fp:precise > # ---------- MASM ---------- $<$: - /Zi /c /coff /Cx /safeseh + -Zi -c -coff -Cx -safeseh > > ) @@ -168,6 +168,15 @@ set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/bgfxbackend.cpp" PROPER COMPILE_OPTIONS "/Zc:preprocessor" ) +# bx rewrites __stdcall while its headers are being parsed by clang-cl. Force the +# compatibility header into the renderer translation unit as well as bx/bgfx so the +# MSVC standard-library headers that follow still see the Win32 calling convention. +if(OPENTS_EXPERIMENTAL_CLANG_CL AND CMAKE_SIZEOF_VOID_P EQUAL 4) + set_property(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/bgfxbackend.cpp" APPEND PROPERTY + COMPILE_OPTIONS "/FI${CMAKE_SOURCE_DIR}/thirdparty/bx-clang-compat.h" + ) +endif() + message(STATUS "${PROJECT_NAME}: Adding compilier definitions...") target_compile_definitions(OpenTS PRIVATE WIN32 @@ -220,7 +229,7 @@ target_link_libraries(OpenTS PRIVATE ole32 oleaut32 uuid odbc32 odbccp32 ) -if (MSVC) +if(MSVC) target_link_options(OpenTS PRIVATE /SUBSYSTEM:WINDOWS /DEBUG @@ -245,7 +254,7 @@ message(STATUS "${PROJECT_NAME}: Adding MASM support...") enable_language(ASM_MASM) -# All .asm files compile automatically using ML.EXE with appropriate flags. +# Dash-form options assemble under both ML.EXE and the clang-cl toolchain's UASM. foreach(f ${OPENTS_SRC}) if(f MATCHES "\\.asm$") set_source_files_properties(${f} PROPERTIES diff --git a/code/language/CMakeLists.txt b/code/language/CMakeLists.txt index 4d1feabe..110a2ad1 100644 --- a/code/language/CMakeLists.txt +++ b/code/language/CMakeLists.txt @@ -12,7 +12,7 @@ target_compile_definitions(Language PRIVATE NOMINMAX) target_include_directories(Language PRIVATE "${OPENTS_GENERATED_DIR}") add_dependencies(Language OpenTSBuildStamp) -if (MSVC) +if(MSVC) target_link_options(Language PRIVATE "/NOENTRY" "/NODEFAULTLIB") endif() diff --git a/docs/BUILDING.md b/docs/BUILDING.md index f9078a61..631f9310 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -64,6 +64,33 @@ name and copy the runtime files into `TS_RUN_DIR`, which defaults to `Run/`: built configuration replaces the previous copy in `Run/`. Compiler and linker intermediates remain under the selected build directory. +## Experimental clang-cl cross-build + +An unsupported Linux cross-build is available for compiler-portability work. It +uses native `clang-cl`, LLD, LLVM library and resource tools, and UASM with the +MSVC headers and libraries. It does not expand the supported build matrix or +establish runtime behavior. + +The reconstructed codebase may still contain undefined behavior that the +supported MSVC build happens not to expose. A successful clang-cl build may +therefore run incorrectly or fail at runtime; validate any result separately. + +Provide a directory containing a Visual Studio layout and Windows SDK. The +cross-build uses the layout's default MSVC toolset and newest complete SDK. +Configure a single-configuration Ninja build: + +```bash +cmake -S . -B build/clang-cl -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-cl-msvc.cmake \ + -DOPENTS_MSVC_ROOT=/path/to/msvc +cmake --build build/clang-cl +``` + +The toolchain requires `clang-cl`, `lld-link`, `llvm-lib`, `llvm-mt`, `llvm-rc`, +and `uasm` on `PATH`. It exports `compile_commands.json`; one configuration in +`.vscode/c_cpp_properties.clang.example.json` reads that file for IntelliSense. + ## Build from Visual Studio Code Visual Studio Code (VSCode) support includes (assuming recommended extensions are installed): diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 603217a7..f16e34ec 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -19,3 +19,20 @@ set(BGFX_CONFIG_RENDERER_WEBGPU OFF CACHE BOOL "" FORCE) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") add_subdirectory(bgfx.cmake) + +# These libraries support the disabled texture tools and are not linked into OpenTS. +set_target_properties(bimg_decode bimg_encode PROPERTIES EXCLUDE_FROM_ALL TRUE) + +if(OPENTS_EXPERIMENTAL_CLANG_CL AND CMAKE_SIZEOF_VOID_P EQUAL 4) + # bx treats Clang with the MSVC CRT like a non-x86 compiler and erases + # __stdcall. Restore it before Windows declarations are parsed. + foreach(target bx bimg bgfx) + target_compile_options(${target} PRIVATE + "$<$:/FI${PROJECT_SOURCE_DIR}/thirdparty/bx-clang-compat.h>" + ) + endforeach() + + # The ASTC encoder selects a 64-bit popcount intrinsic when SSE4.2 is enabled, + # but that intrinsic is unavailable for the Win32 target. Its SSE2 path is portable. + target_compile_definitions(bimg PRIVATE ASTCENC_POPCNT=0 ASTCENC_SSE=20) +endif() diff --git a/thirdparty/bx-clang-compat.h b/thirdparty/bx-clang-compat.h new file mode 100644 index 00000000..35124010 --- /dev/null +++ b/thirdparty/bx-clang-compat.h @@ -0,0 +1,15 @@ +/******************************************************************************* + * O P E N T S + ****************************************************************************** + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + ******************************************************************************/ + +#pragma once + +#include + +#if defined(__clang__) && defined(_M_IX86) +#undef __stdcall +#define __stdcall __attribute__((stdcall)) +#endif