Write gen_defines.asm using a python script
authorCharles Giessen <charles@lunarg.com>
Fri, 9 Sep 2022 23:50:14 +0000 (17:50 -0600)
committerCharles Giessen <46324611+charles-lunarg@users.noreply.github.com>
Tue, 18 Oct 2022 21:58:28 +0000 (15:58 -0600)
This allows cross compilation to enable unkonwn function handling as
gen_defines.asm will be generated without needing to run code meant for the
target platform. Previously, asm_offset.c wrote the gen_defines.asm file
by being run. Now, compilers emit their intermediate assembly output that the
parse_asm_values.py script knows how to find the relevant information from.

Additionally set the test framework `framework_config` build option to always
copy, instead of copy_if_different. This is needed since cmake wouldn't update
this file when changing from/to debug & release mode.

loader/CMakeLists.txt
loader/asm_offset.c
scripts/parse_asm_values.py [new file with mode: 0644]
tests/framework/CMakeLists.txt

index bf4308702442b71eba42cf0ba010874e454563e4..0be1c5df4f4e81640095e461f03aac48e7aa7506 100644 (file)
@@ -157,13 +157,25 @@ if(WIN32)
         endif()
 
         add_executable(asm_offset asm_offset.c)
-        target_link_libraries(asm_offset loader_specific_options)
-        add_custom_command(OUTPUT gen_defines.asm DEPENDS asm_offset COMMAND asm_offset MASM)
+        target_link_libraries(asm_offset PRIVATE loader_specific_options)
+        # Forces compiler to write the intermediate asm file, needed so that we can get sizeof/offset of info out of it.
+        target_compile_options(asm_offset PRIVATE "/Fa$<TARGET_FILE_DIR:asm_offset>/asm_offset.asm" /FA)
+        # Force off optimization so that the output assembly includes all the necessary info - optimizer would get rid of it otherwise.
+        target_compile_options(asm_offset PRIVATE /Od)
+
+        find_package(PythonInterp REQUIRED)
+        # Run parse_asm_values.py on asm_offset's assembly file to generate the gen_defines.asm, which the asm code depends on
+        add_custom_command(TARGET asm_offset POST_BUILD
+            COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/parse_asm_values.py "${CMAKE_CURRENT_BINARY_DIR}/gen_defines.asm"
+                "$<TARGET_FILE_DIR:asm_offset>/asm_offset.asm" "MASM" "${CMAKE_CXX_COMPILER_ID}" "${CMAKE_SYSTEM_PROCESSOR}"
+            BYPRODUCTS gen_defines.asm
+            )
         add_custom_target(loader_asm_gen_files DEPENDS gen_defines.asm)
         set_target_properties(loader_asm_gen_files PROPERTIES FOLDER ${LOADER_HELPER_FOLDER})
+
         add_library(loader-unknown-chain OBJECT unknown_ext_chain_masm.asm)
         target_link_libraries(loader-unknown-chain Vulkan::Headers)
-        target_include_directories(loader-unknown-chain PUBLIC $<TARGET_PROPERTY:loader_asm_gen_files,BINARY_DIR>)
+        target_include_directories(loader-unknown-chain PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
         add_dependencies(loader-unknown-chain loader_asm_gen_files)
     else()
         message(WARNING "Could not find working MASM assembler\n${ASM_FAILURE_MSG}")
@@ -204,9 +216,23 @@ else() # i.e.: Linux
     endif()
 
     if(ASSEMBLER_WORKS)
-        add_executable(asm_offset asm_offset.c)
+        add_library(asm_offset STATIC asm_offset.c)
         target_link_libraries(asm_offset loader_specific_options)
-        add_custom_command(OUTPUT gen_defines.asm DEPENDS asm_offset COMMAND asm_offset GAS)
+        # Forces compiler to write the intermediate asm file, needed so that we can get sizeof/offset of info out of it.
+        target_compile_options(asm_offset PRIVATE -save-temps=obj)
+        if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+            set(ASM_OFFSET_INTERMEDIATE_LOCATION "$<TARGET_FILE_DIR:asm_offset>/CMakeFiles/asm_offset.dir/asm_offset.c.s")
+        elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+            set(ASM_OFFSET_INTERMEDIATE_LOCATION "$<TARGET_FILE_DIR:asm_offset>/CMakeFiles/asm_offset.dir/asm_offset.s")
+        endif()
+
+        find_package(PythonInterp REQUIRED)
+        # Run parse_asm_values.py on asm_offset's assembly file to generate the gen_defines.asm, which the asm code depends on
+        add_custom_command(TARGET asm_offset POST_BUILD
+            COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/parse_asm_values.py "$<TARGET_FILE_DIR:asm_offset>/gen_defines.asm"
+                "${ASM_OFFSET_INTERMEDIATE_LOCATION}" "GAS" "${CMAKE_CXX_COMPILER_ID}" "${CMAKE_SYSTEM_PROCESSOR}"
+            BYPRODUCTS gen_defines.asm
+            )
         add_custom_target(loader_asm_gen_files DEPENDS gen_defines.asm)
     else()
         if(USE_GAS)
index 80b71065cd7e1bdca0e3aa8ed5dcc1253124b342..82230b7d5558eaea9dec437a61183437169da8ec 100644 (file)
 #include "loader_common.h"
 #include "log.h"
 
+#if defined(__GNUC__) || defined(__clang__)
+void produce_asm_define() {
+    // GCC and clang make it easy to print easy to regex for values
+    __asm__("# VULKAN_LOADER_ERROR_BIT = %c0" : : "i"(VULKAN_LOADER_ERROR_BIT));
+    __asm__("# PTR_SIZE = %c0" : : "i"(sizeof(void *)));
+    __asm__("# CHAR_PTR_SIZE = %c0" : : "i"(sizeof(char *)));
+    __asm__("# FUNCTION_OFFSET_INSTANCE = %c0" : : "i"(offsetof(struct loader_instance, phys_dev_ext_disp_functions)));
+    __asm__("# PHYS_DEV_OFFSET_INST_DISPATCH = %c0" : : "i"(offsetof(struct loader_instance_dispatch_table, phys_dev_ext)));
+    __asm__("# PHYS_DEV_OFFSET_PHYS_DEV_TRAMP = %c0" : : "i"(offsetof(struct loader_physical_device_tramp, phys_dev)));
+    __asm__("# ICD_TERM_OFFSET_PHYS_DEV_TERM = %c0" : : "i"(offsetof(struct loader_physical_device_term, this_icd_term)));
+    __asm__("# PHYS_DEV_OFFSET_PHYS_DEV_TERM = %c0" : : "i"(offsetof(struct loader_physical_device_term, phys_dev)));
+    __asm__("# INSTANCE_OFFSET_ICD_TERM = %c0" : : "i"(offsetof(struct loader_icd_term, this_instance)));
+    __asm__("# DISPATCH_OFFSET_ICD_TERM = %c0" : : "i"(offsetof(struct loader_icd_term, phys_dev_ext)));
+    __asm__("# EXT_OFFSET_DEVICE_DISPATCH = %c0" : : "i"(offsetof(struct loader_dev_dispatch_table, ext_dispatch)));
+}
+#elif defined(_WIN32)
+// MSVC will print the name of the value and the value in hex
+// Must disable optimization for this translation unit, otherwise the compiler strips out the variables
+static const uint32_t PTR_SIZE = sizeof(void *);
+static const uint32_t CHAR_PTR_SIZE = sizeof(char *);
+static const uint32_t FUNCTION_OFFSET_INSTANCE = offsetof(struct loader_instance, phys_dev_ext_disp_functions);
+static const uint32_t PHYS_DEV_OFFSET_INST_DISPATCH = offsetof(struct loader_instance_dispatch_table, phys_dev_ext);
+static const uint32_t PHYS_DEV_OFFSET_PHYS_DEV_TRAMP = offsetof(struct loader_physical_device_tramp, phys_dev);
+static const uint32_t ICD_TERM_OFFSET_PHYS_DEV_TERM = offsetof(struct loader_physical_device_term, this_icd_term);
+static const uint32_t PHYS_DEV_OFFSET_PHYS_DEV_TERM = offsetof(struct loader_physical_device_term, phys_dev);
+static const uint32_t INSTANCE_OFFSET_ICD_TERM = offsetof(struct loader_icd_term, this_instance);
+static const uint32_t DISPATCH_OFFSET_ICD_TERM = offsetof(struct loader_icd_term, phys_dev_ext);
+static const uint32_t EXT_OFFSET_DEVICE_DISPATCH = offsetof(struct loader_dev_dispatch_table, ext_dispatch);
+#endif
+
 #if !defined(_MSC_VER) || (_MSC_VER >= 1900)
 #define SIZE_T_FMT "%-8zu"
 #else
@@ -38,69 +68,6 @@ struct ValueInfo {
     const char *comment;
 };
 
-int main(int argc, char **argv) {
-    const char *assembler = NULL;
-    for (int i = 0; i < argc; ++i) {
-        if (!strcmp(argv[i], "MASM")) {
-            assembler = "MASM";
-        } else if (!strcmp(argv[i], "GAS")) {
-            assembler = "GAS";
-        }
-    }
-    if (assembler == NULL) {
-        return 1;
-    }
-
-    struct ValueInfo values[] = {
-        // clang-format off
-        { .name = "VULKAN_LOADER_ERROR_BIT", .value = (size_t) VULKAN_LOADER_ERROR_BIT,
-            .comment = "The numerical value of the enum value 'VULKAN_LOADER_ERROR_BIT'" },
-        { .name = "PTR_SIZE", .value = sizeof(void*),
-            .comment = "The size of a pointer" },
-        { .name = "CHAR_PTR_SIZE", .value = sizeof(char *),
-            .comment = "The size of a 'const char *' struct" },
-        { .name = "FUNCTION_OFFSET_INSTANCE", .value = offsetof(struct loader_instance, phys_dev_ext_disp_functions),
-            .comment = "The offset of 'phys_dev_ext_disp_functions' within a 'loader_instance' struct" },
-        { .name = "PHYS_DEV_OFFSET_INST_DISPATCH", .value = offsetof(struct loader_instance_dispatch_table, phys_dev_ext),
-            .comment = "The offset of 'phys_dev_ext' within in 'loader_instance_dispatch_table' struct" },
-        { .name = "PHYS_DEV_OFFSET_PHYS_DEV_TRAMP", .value = offsetof(struct loader_physical_device_tramp, phys_dev),
-            .comment = "The offset of 'phys_dev' within a 'loader_physical_device_tramp' struct" },
-        { .name = "ICD_TERM_OFFSET_PHYS_DEV_TERM", .value = offsetof(struct loader_physical_device_term, this_icd_term),
-            .comment = "The offset of 'this_icd_term' within a 'loader_physical_device_term' struct" },
-        { .name = "PHYS_DEV_OFFSET_PHYS_DEV_TERM", .value = offsetof(struct loader_physical_device_term, phys_dev),
-            .comment = "The offset of 'phys_dev' within a 'loader_physical_device_term' struct" },
-        { .name = "INSTANCE_OFFSET_ICD_TERM", .value = offsetof(struct loader_icd_term, this_instance),
-            .comment = "The offset of 'this_instance' within a 'loader_icd_term' struct" },
-        { .name = "DISPATCH_OFFSET_ICD_TERM", .value = offsetof(struct loader_icd_term, phys_dev_ext),
-            .comment = "The offset of 'phys_dev_ext' within a 'loader_icd_term' struct" },
-        { .name = "EXT_OFFSET_DEVICE_DISPATCH", .value = offsetof(struct loader_dev_dispatch_table, ext_dispatch),
-            .comment = "The offset of 'ext_dispatch' within a 'loader_dev_dispatch_table' struct" },
-        // clang-format on
-    };
-
-    FILE *file = fopen("gen_defines.asm", "w");
-    fprintf(file, "\n");
-    if (!strcmp(assembler, "MASM")) {
-        for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) {
-            fprintf(file, "%-32s equ " SIZE_T_FMT "; %s\n", values[i].name, values[i].value, values[i].comment);
-        }
-    } else if (!strcmp(assembler, "GAS")) {
-#if defined(__x86_64__) || defined(__i386__)
-        const char *comment_delimiter = "#";
-#if defined(__x86_64__)
-        fprintf(file, ".set X86_64, 1\n");
-#endif  // defined(__x86_64__)
-#elif defined(__aarch64__)
-        const char *comment_delimiter = "//";
-        fprintf(file, ".set AARCH_64, 1\n");
-#else
-        // Default comment delimiter
-        const char *comment_delimiter = "#";
-#endif
-        for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) {
-            fprintf(file, ".set %-32s, " SIZE_T_FMT "%s %s\n", values[i].name, values[i].value, comment_delimiter,
-                    values[i].comment);
-        }
-    }
-    return fclose(file);
-}
+// This file is not intended to be executed, as the generated asm contains all the relevant data which
+// the parse_asm_values.py script needs to write gen_defines.asm
+int main(int argc, char **argv) { return 0; }
diff --git a/scripts/parse_asm_values.py b/scripts/parse_asm_values.py
new file mode 100644 (file)
index 0000000..bff263d
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/python3 -i
+#
+# Copyright (c) 2022 The Khronos Group Inc.
+# Copyright (c) 2022 LunarG, Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Author: Charles Giessen <charles@lunarg.com>
+
+# This script reads in the 'intermediate output' of a compiler to look for sizeof/offsetof information
+# necessary for the assembler portions of the loader. This is achieved by forcing the compiler to output
+# the intermediate assembly output and looking for specific patterns which contain the relevant information
+
+import sys
+import os.path
+from os.path import exists
+import re
+
+
+# Where to write the "gen_defines.asm" file
+destination_file = sys.argv[1]
+# The location the build system puts the intermediate asm file which depends on the compiler
+source_asm_file = sys.argv[2]
+# Whether we are using "MASM" or "GAS" for the assembler
+assembler_type = sys.argv[3]
+# Whether we are using gcc, clang, or msvc
+compiler = sys.argv[4]
+# taken from CMAKE_SYSTEM_PROCESSOR - x86_64 or aarch64
+arch = sys.argv[5]
+
+if destination_file is None or source_asm_file is None or assembler_type is None or compiler is None or arch is None:
+    print("Required command line arguments were not provided")
+    sys.exit(1)
+
+defines = ["VULKAN_LOADER_ERROR_BIT",
+            "PTR_SIZE",
+            "CHAR_PTR_SIZE",
+            "FUNCTION_OFFSET_INSTANCE",
+            "PHYS_DEV_OFFSET_INST_DISPATCH",
+            "PHYS_DEV_OFFSET_PHYS_DEV_TRAMP",
+            "ICD_TERM_OFFSET_PHYS_DEV_TERM",
+            "PHYS_DEV_OFFSET_PHYS_DEV_TERM",
+            "INSTANCE_OFFSET_ICD_TERM",
+            "DISPATCH_OFFSET_ICD_TERM",
+            "EXT_OFFSET_DEVICE_DISPATCH" ]
+
+try:
+    with open(source_asm_file, 'r') as f:
+        asm_intermediate_file = f.read()
+except IOError:
+    print("Could not open assembler file:", source_asm_file)
+    sys.exit(1)
+
+with open(destination_file, "w", encoding="utf-8") as dest:
+    if assembler_type == "MASM":
+        # special case vulkan error bit due to it not appearing in the asm - its defined in the header as 8 so it shouldn't change
+        dest.write("VULKAN_LOADER_ERROR_BIT equ 8;\n")
+    elif assembler_type == "GAS":
+        # let the assembler know which platform to use
+        if arch == "x86_64":
+            dest.write(".set X86_64, 1\n")
+        elif arch == "aarch64":
+            dest.write(".set AARCH_64, 1\n")
+
+    for d in defines:
+        match = None
+        if compiler == "MSVC":
+            if d == "VULKAN_LOADER_ERROR_BIT":
+                continue # skip due to special case
+            match = re.search(d + " DD [ ]*([0-9a-f]+)H", asm_intermediate_file)
+        elif compiler == "Clang" or compiler == "GNU":
+            match = re.search(d + " = ([0-9]+)", asm_intermediate_file)
+
+        if match:
+            if compiler == "MSVC":
+                value = str(int(match.group(1), 16))
+            elif compiler == "Clang" or compiler == "GNU":
+                value = match.group(1)
+            if assembler_type == "MASM":
+            # MASM uses hex values, decode them here
+                dest.write(d + " equ " + value +";\n")
+            elif assembler_type == "GAS":
+                dest.write(".set " + d + ", " + value + "\n")
+        else:
+            print("Couldn't find ", d)
+            sys.exit(1)
+
index 1ecc82cfdbbac149f011347a38cf4e5e34f01b24..3aedff55a56582d660c5548aeb4a55933058d268 100644 (file)
@@ -85,12 +85,12 @@ add_custom_command(
     PRE_BUILD
     COMMAND ${CMAKE_COMMAND} "-E" "copy_if_different" "${CMAKE_CURRENT_BINARY_DIR}/framework_config_$<CONFIG>.h" "${CMAKE_CURRENT_BINARY_DIR}/framework_config.h"
     VERBATIM
-    PRE_BUILD
     DEPENDS  "${CMAKE_CURRENT_BINARY_DIR}/framework_config_$<CONFIG>.h"
     OUTPUT   "${CMAKE_CURRENT_BINARY_DIR}/framework_config.h"
     COMMENT  "creating framework_config.h file ({event: PRE_BUILD}, {filename: framework_config.h })"
     )
 add_custom_target (generate_framework_config DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/framework_config.h")
+add_dependencies (generate_framework_config vulkan)
 add_dependencies (testing_framework_util generate_framework_config)
 
 add_library(testing_dependencies STATIC test_environment.cpp test_environment.h)