4.5 KiB
Using libnd4j.preprocess for Macro Debugging
Overview
The libnd4j.preprocess flag is a specialized debugging tool that runs only the C++ preprocessor, outputting the preprocessed source files before actual compilation. This is invaluable for:
- Debugging complex macro expansions
- Verifying include paths and header dependencies
- Understanding template instantiations
- Investigating conditional compilation issues
Usage
Basic Command
mvn clean install -Dlibnd4j.preprocess=ON
Output Location
Preprocessed files are created in the preprocessed directory with the following naming convention:
- Directory structure is flattened with underscores
- Extension changes to
.i - Example:
include/ops/declarable/generic/transforms/reverse.cppbecomespreprocessed/ops_declarable_generic_transforms_reverse.i
What the Preprocessor Does
The preprocessor performs several transformations:
- Macro expansion
- Header file inclusion
- Conditional compilation evaluation (#ifdef, #ifndef, etc.)
- Line number and file name tracking
- Comment removal
- Macro constant substitution
Implementation Details
From the CMakeLists.txt configuration:
- Directory Setup:
set(PREPROCESSED_DIR "${CMAKE_SOURCE_DIR}/preprocessed")
file(MAKE_DIRECTORY ${PREPROCESSED_DIR})
- File Processing:
# Command format used internally
${compiler} -E ${include_dirs} "${src}" -o "${preprocessed_file}"
- Language Detection:
if(src MATCHES "\\.c$")
set(language "C")
elseif(src MATCHES "\\.cpp$|\\.cxx$|\\.cc$")
set(language "CXX")
else()
set(language "CXX")
endif()
Common Use Cases
1. Debugging Complex Macros
When dealing with complex macro definitions like BUILD_DOUBLE_TEMPLATE:
#if defined(SD_COMMON_TYPES_GEN) && defined(SD_COMMON_TYPES_@FL_TYPE_INDEX@)
BUILD_DOUBLE_TEMPLATE(template void someFunc, (arg_list,..),
SD_COMMON_TYPES_@FL_TYPE_INDEX@, SD_INDEXING_TYPES);
#endif
Using preprocessor output helps verify:
- Macro expansion correctness
- Template instantiation
- Type substitutions
2. Include Path Issues
When headers aren't found or wrong versions are included:
# Check full include path resolution
grep "#include" preprocessed/your_file.i
3. Conditional Compilation
For code with multiple #ifdef paths:
#ifdef SD_CUDA
// CUDA-specific code
#else
// CPU code
#endif
The preprocessed output shows exactly which path was taken.
Best Practices
-
Organized Investigation:
# Create a directory for the specific issue mkdir debug_issue_123 cd debug_issue_123 # Run preprocessor and copy relevant files mvn clean install -Dlibnd4j.preprocess=ON cp ../preprocessed/relevant_file.i . -
Diff Comparison:
# Compare different configurations mv preprocessed/file.i file_config1.i # Change configuration mvn clean install -Dlibnd4j.preprocess=ON diff file_config1.i preprocessed/file.i -
Macro Tracing:
- Keep the original source file open
- Search for specific macros in the preprocessed output
- Use line directives (#line) to map back to source
Troubleshooting Common Issues
-
Missing Definitions
- Problem: Macros not expanding as expected
- Solution: Check preprocessed output for undefined macros
grep -n "undefined" preprocessed/*.i -
Include Order Issues
- Problem: Header dependencies not resolving correctly
- Solution: Examine the order of #include directives in preprocessed output
grep -n "#include" preprocessed/your_file.i -
Template Instantiation Problems
- Problem: Templates not generating expected code
- Solution: Search for template instantiations in preprocessed output
grep -n "template" preprocessed/your_file.i
Tips for Large Codebases
-
Filtering Output:
# Remove empty lines and comments grep -v '^$' preprocessed/file.i | grep -v '^//' # Focus on specific sections sed -n '/START_SECTION/,/END_SECTION/p' preprocessed/file.i -
Finding Macro Expansions:
# Search for specific macro expansions grep -A 5 -B 5 "MACRO_NAME" preprocessed/file.i -
Managing Output Size:
# Split large files for easier analysis split -l 1000 preprocessed/large_file.i split_ # Create focused preprocessor outputs mv preprocessed better_name_preprocessed_$(date +%Y%m%d)