[libc-commits] [libc] 0f1507a - [libc] Add a JSON based config option system.

Siva Chandra Reddy via libc-commits libc-commits at lists.llvm.org
Tue Sep 5 07:19:29 PDT 2023


Author: Siva Chandra Reddy
Date: 2023-09-05T14:19:18Z
New Revision: 0f1507af411b08c66e9807684858893f2e921ee2

URL: https://github.com/llvm/llvm-project/commit/0f1507af411b08c66e9807684858893f2e921ee2
DIFF: https://github.com/llvm/llvm-project/commit/0f1507af411b08c66e9807684858893f2e921ee2.diff

LOG: [libc] Add a JSON based config option system.

Few printf config options have been setup using this new config system
along with their baremetal overrides. A follow up patch will add generation
of doc/config.rst, which will contain the full list of libc config options
and short description explaining how they affect the libc.

Reviewed By: gchatelet

Differential Revision: https://reviews.llvm.org/D159158

Added: 
    libc/cmake/modules/LibcConfig.cmake
    libc/config/baremetal/config.json
    libc/config/config.json

Modified: 
    libc/CMakeLists.txt
    libc/src/stdio/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index c885080cc33e9ad..549d1fbc3a55fe5 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -93,6 +93,46 @@ set(LIBC_ENABLE_HERMETIC_TESTS ${LLVM_LIBC_FULL_BUILD})
 # Defines LIBC_TARGET_ARCHITECTURE and associated macros.
 include(LLVMLibCArchitectures)
 
+include(LibcConfig)
+# Config loading happens in three steps:
+# 1. Load the config file config/config.json and set up config vars.
+# 2. Load config/${LIBC_TARGET_OS}/config.json if available and override
+#    vars as suitable.
+# 3. Load config/${LIBC_TARGET_OS}/${LIBC_TARGET_ARCH}/config.json is
+#    available and override vars as suitable.
+# All the three steps will not override options already set from the
+# CMake command line. That is, the CMake command line option values take
+# precedence over the values in config.json files.
+set(main_config_file ${LIBC_SOURCE_DIR}/config/config.json)
+read_libc_config(${main_config_file} global_config)
+foreach(opt IN LISTS global_config)
+  string(JSON opt_name ERROR_VARIABLE json_error MEMBER ${opt} 0)
+  if(json_error)
+    message(FATAL_ERROR ${json_error})
+  endif()
+  if(DEFINED ${opt_name})
+    # The option is already defined from the command line so we ignore it here.
+    # We still make note of it so that further config load can also ignore
+    # this option.
+    message(STATUS "${opt_name}: ${${opt_name}} (from command line)")
+    list(APPEND cmd_line_conf ${opt_name})
+    continue()
+  endif()
+
+  string(JSON opt_object ERROR_VARIABLE json_error GET ${opt} ${opt_name})
+  if(json_error)
+    message(FATAL_ERROR "Error reading info of option '${opt_name}': ${json_error}")
+  endif()
+  string(JSON opt_value ERROR_VARIABLE json_error GET ${opt_object} "value")
+  if(json_error)
+    message(FATAL_ERROR ${json_error})
+  endif()
+  message(STATUS "${opt_name}: ${opt_value}")
+  set(${opt_name} ${opt_value})
+endforeach()
+load_libc_config(${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/config.json ${cmd_line_conf})
+load_libc_config(${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/${LIBC_TARGET_ARCHITECTURE}/config.json ${cmd_line_conf})
+
 if(LIBC_TARGET_ARCHITECTURE_IS_GPU)
   set(LIBC_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/include)
   set(LIBC_INSTALL_INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR}/gpu-none-llvm)

diff  --git a/libc/cmake/modules/LibcConfig.cmake b/libc/cmake/modules/LibcConfig.cmake
new file mode 100644
index 000000000000000..a9c69de8a310a9b
--- /dev/null
+++ b/libc/cmake/modules/LibcConfig.cmake
@@ -0,0 +1,137 @@
+# This cmake module contains utilities to read and load libc config options
+# listed in config.json files.
+#
+# The JSON parsing commands that CMake provides are rather tedious to use.
+# Below is a quick reference which tries to map the CMake JSON parsing
+# commands to the Python dictionary API.
+#
+# * There is no way to iterate over the JSON items. One will first
+#   have to find the number of items using string(JSON ... LENGTH ...)
+#   command, and then iterate over the items using foreach(... RANGE ...).
+# * The way to get the key from the JSON dictionary is to use the index
+#   of the item and the string(JSON ... MEMBER ... $<index>) function.
+# * Once you have the key, you can use the string(JSON ... GET ... $<key>)
+#   function to get the value corresponding to the key.
+
+# Fill |opt_list| with all options listed in |config_file|. For each option,
+# the item added to |opt_list| is the dictionary of the form:
+#   {
+#     "<option name>": {
+#       "value: <option value>,
+#       "doc": "<option doc string>",
+#     }
+#   }
+# Each of the above items can be parsed again with the string(JSON ...)
+# command.
+# This function does nothing if |config_file| is missing.
+function(read_libc_config config_file opt_list)
+  if(NOT EXISTS ${config_file})
+    return()
+  endif()
+  # We will assume that a config file is loaded only once and that
+  # each config file loaded will affect config information. Since
+  # we want a change to config information to trigger reconfiguration,
+  # we add the |config_file| to the list of files the configure itself
+  # should depend on.
+  set_property(
+    DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+    PROPERTY CMAKE_CONFIGURE_DEPENDS ${config_file})
+
+  file(READ ${config_file} json_config)
+  string(JSON group_count ERROR_VARIABLE json_error LENGTH ${json_config})
+  if(json_error)
+    message(FATAL_ERROR "${config_file}: ${json_error}")
+  endif()
+  if(${group_count} EQUAL 0)
+    # This "if" conditions becomes active if there are no config options
+    # to load. If there are no config options, it is better to remove that
+    # config.json file instead of including an empty file.
+    message(FATAL_ERROR "${config_file}: Does not contain any config option groups")
+    return()
+  endif()
+  math(EXPR group_count_1 "${group_count} - 1")
+
+  set(optname_list)
+  foreach(group_num RANGE ${group_count_1})
+    # The group names are the keys of the global dictionary. So, we first
+    # lookup the group name or the key for each item in the dictionary.
+    string(JSON group_name ERROR_VARIABLE json_error MEMBER ${json_config} ${group_num})
+    if(json_error)
+      message(FATAL_ERROR "${config_file}: ${json_error}")
+    endif()
+
+    # Once we have the group name, we GET the option map for that group, which
+    # is the value corresponding to the group name key.
+    string(JSON option_map ERROR_VARIABLE json_error GET ${json_config} ${group_name})
+    if(json_error)
+      message(FATAL_ERROR ${json_error})
+    endif()
+    string(JSON option_count ERROR_VARIABLE jsor_error LENGTH ${option_map})
+    if(json_error)
+      message(FATAL_ERROR ${json_error})
+    endif()
+    if(${option_count} EQUAL 0)
+      message(FATAL_ERROR "${config_file}: No options listed against the config option group '${group_name}'")
+    endif()
+
+    math(EXPR option_count_1 "${option_count} - 1")
+    foreach(opt_num RANGE ${option_count_1})
+      string(JSON option_name ERROR_VARIABLE json_error MEMBER ${option_map} ${opt_num})
+      if(json_error)
+        message(FATAL_ERROR ${json_error})
+      endif()
+      list(FIND optname_list ${option_name} optname_exists)
+      if(${optname_exists} GREATER -1)
+        message(FATAL_ERROR "${config_file}: Found duplicate option name: ${option_name}")
+      endif()
+      list(APPEND optname_list ${option_name})
+
+      string(JSON optdata ERROR_VARIABLE json_error GET ${option_map} ${option_name})
+      if(json_error)
+        message(FATAL_ERROR ${json_error})
+      endif()
+      set(opt "{\"${option_name}\": ${optdata}}")
+      list(APPEND all_opts ${opt})
+    endforeach()
+  endforeach()
+  set(${opt_list} ${all_opts} PARENT_SCOPE)
+endfunction()
+
+# Loads the config options listed in |config_file| in the following way:
+# * For each option listed in the |config_file|, it looks for existence of a
+#   var with the same name. It is an error if the var is not already defined.
+#   If a var with the option name is found, then its value is overwritten
+#   with the value specified in |config_file|.
+# * If there are options which are not to be overriden, then the list of
+#   such options can be passed to this function after the |config_file|
+#   argument. Typically, these will be the options specified on the CMake
+#   command line.
+function(load_libc_config config_file)
+  read_libc_config(${config_file} file_opts)
+  foreach(opt IN LISTS file_opts)
+    string(JSON opt_name ERROR_VARIABLE json_error MEMBER ${opt} 0)
+    if(json_error)
+      message(FATAL_ERROR ${json_error})
+    endif()
+    if(NOT DEFINED ${opt_name})
+      message(FATAL_ERROR: " Option ${opt_name} defined in ${config_file} is invalid.")
+    endif()
+    if(ARGN)
+      list(FIND ARGN ${opt_name} optname_exists)
+      if(${optname_exists} GREATER -1)
+        # This option is not to be overridden so just skip further processing.
+        continue()
+      endif()
+    endif()
+    string(JSON opt_object ERROR_VARIABLE json_error GET ${opt} ${opt_name})
+    if(json_error)
+      message(FATAL_ERROR ${json_error})
+    endif()
+    string(JSON opt_value ERROR_VARIABLE jsor_error GET ${opt_object} "value")
+    if(json_error)
+      message(FATAL_ERROR ${json_error})
+    endif()
+    message(STATUS "Overriding - ${opt_name}: ${opt_value} (Previous value: ${${opt_name}})")
+    set(${opt_name} ${opt_value} PARENT_SCOPE)
+  endforeach()
+endfunction()

diff  --git a/libc/config/baremetal/config.json b/libc/config/baremetal/config.json
new file mode 100644
index 000000000000000..a65eaa8911e6c44
--- /dev/null
+++ b/libc/config/baremetal/config.json
@@ -0,0 +1,13 @@
+{
+  "printf": {
+    "LIBC_CONF_PRINTF_DISABLE_FLOAT": {
+      "value": true
+    },
+    "LIBC_CONF_PRINTF_DISABLE_INDEX_MODE": {
+      "value": true
+    },
+    "LIBC_CONF_PRINTF_DISABLE_WRITE_INT": {
+      "value": true
+    }
+  }
+}

diff  --git a/libc/config/config.json b/libc/config/config.json
new file mode 100644
index 000000000000000..cd68b81028bff7f
--- /dev/null
+++ b/libc/config/config.json
@@ -0,0 +1,16 @@
+{
+  "printf": {
+    "LIBC_CONF_PRINTF_DISABLE_FLOAT": {
+      "value": false,
+      "doc": "Disable printing floating point values in printf and friends."
+    },
+    "LIBC_CONF_PRINTF_DISABLE_INDEX_MODE": {
+      "value": false,
+      "doc": "Disable index mode in the printf format string."
+    },
+    "LIBC_CONF_PRINTF_DISABLE_WRITE_INT": {
+      "value": false,
+      "doc": "Disable handling of %n in printf format string."
+    }
+  }
+}

diff  --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index fc8588d75ad416b..79863f83c1e5e7d 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -26,14 +26,6 @@ if(NOT LIBC_TARGET_ARCHITECTURE_IS_GPU)
   add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/generic)
 endif()
 
-if(${LIBC_TARGET_OS} STREQUAL "baremetal")
-  list(APPEND printf_copts
-      "-DLIBC_COPT_PRINTF_DISABLE_FLOAT"
-      "-DLIBC_COPT_PRINTF_DISABLE_INDEX_MODE"
-      "-DLIBC_COPT_PRINTF_DISABLE_WRITE_INT"
-    )
-endif()
-
 add_subdirectory(printf_core)
 add_subdirectory(scanf_core)
 
@@ -426,6 +418,17 @@ list(APPEND printf_deps
       libc.src.__support.arg_list
       libc.src.stdio.printf_core.vfprintf_internal
 )
+
+if(LIBC_CONF_PRINTF_DISABLE_FLOAT)
+  list(APPEND printf_copts "-DLIBC_COPT_PRINTF_DISABLE_FLOAT")
+endif()
+if(LIBC_CONF_PRINTF_DISABLE_INDEX_MODE)
+  list(APPEND printf_copts "-DLIBC_COPT_PRINTF_DISABLE_INDEX_MODE")
+endif()
+if(LIBC_CONF_PRINTF_DISABLE_WRITE_INT)
+  list(APPEND printf_copts "-DLIBC_COPT_PRINTF_DISABLE_WRITE_INT")
+endif()
+
 if(LLVM_LIBC_FULL_BUILD)
   list(APPEND printf_deps
       libc.src.__support.File.file


        


More information about the libc-commits mailing list