1# Copyright © 2018-2022, VideoLAN and dav1d authors 2# All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are met: 6# 7# 1. Redistributions of source code must retain the above copyright notice, this 8# list of conditions and the following disclaimer. 9# 10# 2. Redistributions in binary form must reproduce the above copyright notice, 11# this list of conditions and the following disclaimer in the documentation 12# and/or other materials provided with the distribution. 13# 14# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 25project('dav1d', ['c'], 26 version: '1.4.2', 27 default_options: ['c_std=c99', 28 'warning_level=2', 29 'buildtype=release', 30 'b_ndebug=if-release'], 31 meson_version: '>= 0.49.0') 32 33dav1d_soname_version = '7.0.0' 34dav1d_api_version_array = dav1d_soname_version.split('.') 35dav1d_api_version_major = dav1d_api_version_array[0] 36dav1d_api_version_minor = dav1d_api_version_array[1] 37dav1d_api_version_revision = dav1d_api_version_array[2] 38 39dav1d_src_root = meson.current_source_dir() 40cc = meson.get_compiler('c') 41 42# Configuratin data for config.h 43cdata = configuration_data() 44 45# Configuration data for config.asm 46cdata_asm = configuration_data() 47 48# Include directories 49dav1d_inc_dirs = include_directories(['.', 'include/dav1d', 'include']) 50 51 52 53# 54# Option handling 55# 56 57# Bitdepth option 58dav1d_bitdepths = get_option('bitdepths') 59foreach bitdepth : ['8', '16'] 60 cdata.set10('CONFIG_@0@BPC'.format(bitdepth), dav1d_bitdepths.contains(bitdepth)) 61endforeach 62 63# ASM option 64is_asm_enabled = (get_option('enable_asm') == true and 65 (host_machine.cpu_family() == 'aarch64' or 66 host_machine.cpu_family().startswith('arm') or 67 host_machine.cpu() == 'ppc64le' or 68 host_machine.cpu_family().startswith('riscv') or 69 host_machine.cpu_family().startswith('loongarch') or 70 host_machine.cpu_family() == 'x86' or 71 (host_machine.cpu_family() == 'x86_64' and cc.get_define('__ILP32__').strip() == ''))) 72cdata.set10('HAVE_ASM', is_asm_enabled) 73 74if is_asm_enabled and get_option('b_sanitize') == 'memory' 75 error('asm causes false positive with memory sanitizer. Use \'-Denable_asm=false\'.') 76endif 77 78cdata.set10('TRIM_DSP_FUNCTIONS', get_option('trim_dsp') == 'true' or 79 (get_option('trim_dsp') == 'if-release' and get_option('buildtype') == 'release')) 80 81# Logging option 82cdata.set10('CONFIG_LOG', get_option('logging')) 83 84cdata.set10('CONFIG_MACOS_KPERF', get_option('macos_kperf')) 85 86# 87# OS/Compiler checks and defines 88# 89 90# Arguments in test_args will be used even on feature tests 91test_args = [] 92 93optional_arguments = [] 94optional_link_arguments = [] 95 96if host_machine.system() in ['linux', 'gnu', 'emscripten'] 97 test_args += '-D_GNU_SOURCE' 98 add_project_arguments('-D_GNU_SOURCE', language: 'c') 99endif 100 101if host_machine.system() == 'windows' 102 cdata.set('_WIN32_WINNT', '0x0601') 103 cdata.set('UNICODE', 1) # Define to 1 for Unicode (Wide Chars) APIs 104 cdata.set('_UNICODE', 1) # Define to 1 for Unicode (Wide Chars) APIs 105 cdata.set('__USE_MINGW_ANSI_STDIO', 1) # Define to force use of MinGW printf 106 cdata.set('_CRT_DECLARE_NONSTDC_NAMES', 1) # Define to get off_t from sys/types.h on MSVC 107 if cc.has_function('fseeko', prefix : '#include <stdio.h>', args : test_args) 108 cdata.set('_FILE_OFFSET_BITS', 64) # Not set by default by Meson on Windows 109 else 110 cdata.set('fseeko', '_fseeki64') 111 cdata.set('ftello', '_ftelli64') 112 endif 113 114 if host_machine.cpu_family() == 'x86_64' 115 if cc.get_argument_syntax() != 'msvc' 116 optional_link_arguments += '-Wl,--dynamicbase,--nxcompat,--tsaware,--high-entropy-va' 117 endif 118 elif host_machine.cpu_family() == 'x86' or host_machine.cpu_family() == 'arm' 119 if cc.get_argument_syntax() == 'msvc' 120 optional_link_arguments += '/largeaddressaware' 121 else 122 optional_link_arguments += '-Wl,--dynamicbase,--nxcompat,--tsaware,--large-address-aware' 123 endif 124 endif 125 126 # On Windows, we use a compatibility layer to emulate pthread 127 thread_dependency = [] 128 thread_compat_dep = declare_dependency(sources : files('src/win32/thread.c')) 129 130 rt_dependency = [] 131 132 rc_version_array = meson.project_version().split('.') 133 winmod = import('windows') 134 rc_data = configuration_data() 135 rc_data.set('PROJECT_VERSION_MAJOR', rc_version_array[0]) 136 rc_data.set('PROJECT_VERSION_MINOR', rc_version_array[1]) 137 rc_data.set('PROJECT_VERSION_REVISION', rc_version_array[2]) 138 rc_data.set('API_VERSION_MAJOR', dav1d_api_version_major) 139 rc_data.set('API_VERSION_MINOR', dav1d_api_version_minor) 140 rc_data.set('API_VERSION_REVISION', dav1d_api_version_revision) 141 rc_data.set('COPYRIGHT_YEARS', '2018-2024') 142else 143 thread_dependency = dependency('threads') 144 thread_compat_dep = [] 145 146 rt_dependency = [] 147 if cc.has_function('clock_gettime', prefix : '#include <time.h>', args : test_args) 148 cdata.set('HAVE_CLOCK_GETTIME', 1) 149 elif host_machine.system() not in ['darwin', 'ios', 'tvos'] 150 rt_dependency = cc.find_library('rt', required: false) 151 if not cc.has_function('clock_gettime', prefix : '#include <time.h>', args : test_args, dependencies : rt_dependency) 152 error('clock_gettime not found') 153 endif 154 cdata.set('HAVE_CLOCK_GETTIME', 1) 155 endif 156 157 if cc.has_function('posix_memalign', prefix : '#include <stdlib.h>', args : test_args) 158 cdata.set('HAVE_POSIX_MEMALIGN', 1) 159 endif 160endif 161 162# check for fseeko on android. It is not always available if _FILE_OFFSET_BITS is defined to 64 163have_fseeko = true 164if host_machine.system() == 'android' 165 if not cc.has_function('fseeko', prefix : '#include <stdio.h>', args : test_args) 166 if cc.has_function('fseeko', prefix : '#include <stdio.h>', args : test_args + ['-U_FILE_OFFSET_BITS']) 167 warning('Files larger than 2 gigabytes might not be supported in the dav1d CLI tool.') 168 add_project_arguments('-U_FILE_OFFSET_BITS', language: 'c') 169 elif get_option('enable_tools') 170 error('dav1d CLI tool needs fseeko()') 171 else 172 have_fseeko = false 173 endif 174 endif 175endif 176 177libdl_dependency = [] 178if host_machine.system() == 'linux' 179 libdl_dependency = cc.find_library('dl', required : false) 180 if cc.has_function('dlsym', prefix : '#include <dlfcn.h>', args : test_args, dependencies : libdl_dependency) 181 cdata.set('HAVE_DLSYM', 1) 182 endif 183endif 184 185libm_dependency = cc.find_library('m', required: false) 186 187 188# Header checks 189 190stdatomic_dependencies = [] 191if not cc.check_header('stdatomic.h') 192 if cc.get_id() == 'msvc' 193 # we have a custom replacement for MSVC 194 stdatomic_dependencies += declare_dependency( 195 include_directories : include_directories('include/compat/msvc'), 196 ) 197 elif cc.compiles('''int main() { int v = 0; return __atomic_fetch_add(&v, 1, __ATOMIC_SEQ_CST); }''', 198 name : 'GCC-style atomics', args : test_args) 199 stdatomic_dependencies += declare_dependency( 200 include_directories : include_directories('include/compat/gcc'), 201 ) 202 else 203 error('Atomics not supported') 204 endif 205endif 206 207if host_machine.cpu_family().startswith('wasm') 208 # enable atomics + bulk-memory features 209 stdatomic_dependencies += thread_dependency.partial_dependency(compile_args: true) 210endif 211 212if cc.check_header('unistd.h') 213 cdata.set('HAVE_UNISTD_H', 1) 214endif 215 216if cc.check_header('io.h') 217 cdata.set('HAVE_IO_H', 1) 218endif 219 220if cc.check_header('pthread_np.h') 221 cdata.set('HAVE_PTHREAD_NP_H', 1) 222 test_args += '-DHAVE_PTHREAD_NP_H' 223endif 224 225 226# Function checks 227 228if not cc.has_function('getopt_long', prefix : '#include <getopt.h>', args : test_args) 229 getopt_dependency = declare_dependency( 230 sources: files('tools/compat/getopt.c'), 231 include_directories : include_directories('include/compat'), 232 ) 233else 234 getopt_dependency = [] 235endif 236 237if (host_machine.cpu_family() == 'aarch64' or 238 host_machine.cpu_family().startswith('arm') or 239 host_machine.cpu_family().startswith('loongarch') or 240 host_machine.cpu() == 'ppc64le' or 241 host_machine.cpu_family().startswith('riscv')) 242 if cc.has_function('getauxval', prefix : '#include <sys/auxv.h>', args : test_args) 243 cdata.set('HAVE_GETAUXVAL', 1) 244 endif 245 if cc.has_function('elf_aux_info', prefix : '#include <sys/auxv.h>', args : test_args) 246 cdata.set('HAVE_ELF_AUX_INFO', 1) 247 endif 248endif 249 250pthread_np_prefix = ''' 251#include <pthread.h> 252#ifdef HAVE_PTHREAD_NP_H 253#include <pthread_np.h> 254#endif 255''' 256if cc.has_function('pthread_getaffinity_np', prefix : pthread_np_prefix, args : test_args, dependencies : thread_dependency) 257 cdata.set('HAVE_PTHREAD_GETAFFINITY_NP', 1) 258endif 259if cc.has_function('pthread_setaffinity_np', prefix : pthread_np_prefix, args : test_args, dependencies : thread_dependency) 260 cdata.set('HAVE_PTHREAD_SETAFFINITY_NP', 1) 261endif 262 263if cc.compiles('int x = _Generic(0, default: 0);', name: '_Generic', args: test_args) 264 cdata.set('HAVE_C11_GENERIC', 1) 265endif 266 267# Compiler flag tests 268 269if cc.has_argument('-fvisibility=hidden') 270 add_project_arguments('-fvisibility=hidden', language: 'c') 271else 272 warning('Compiler does not support -fvisibility=hidden, all symbols will be public!') 273endif 274 275# Compiler flags that should be set 276# But when the compiler does not supports them 277# it is not an error and silently tolerated 278if cc.get_argument_syntax() != 'msvc' 279 optional_arguments += [ 280 '-Wundef', 281 '-Werror=vla', 282 '-Wno-maybe-uninitialized', 283 '-Wno-missing-field-initializers', 284 '-Wno-unused-parameter', 285 '-Wstrict-prototypes', 286 '-Werror=missing-prototypes', 287 '-Wshorten-64-to-32', 288 ] 289 if host_machine.cpu_family() == 'x86' 290 optional_arguments += [ 291 '-msse2', 292 '-mfpmath=sse', 293 ] 294 endif 295else 296 optional_arguments += [ 297 '-wd4028', # parameter different from declaration 298 '-wd4090', # broken with arrays of pointers 299 '-wd4996' # use of POSIX functions 300 ] 301endif 302 303if (get_option('buildtype') != 'debug' and get_option('buildtype') != 'plain') 304 optional_arguments += '-fomit-frame-pointer' 305 optional_arguments += '-ffast-math' 306endif 307 308if (host_machine.system() in ['darwin', 'ios', 'tvos'] and cc.get_id() == 'clang' and 309 cc.version().startswith('11')) 310 # Workaround for Xcode 11 -fstack-check bug, see #301 311 optional_arguments += '-fno-stack-check' 312endif 313 314if (host_machine.cpu_family() == 'aarch64' or host_machine.cpu_family().startswith('arm')) 315 optional_arguments += '-fno-align-functions' 316endif 317 318add_project_arguments(cc.get_supported_arguments(optional_arguments), language : 'c') 319add_project_link_arguments(cc.get_supported_link_arguments(optional_link_arguments), language : 'c') 320 321# libFuzzer related things 322fuzzing_engine = get_option('fuzzing_engine') 323if fuzzing_engine == 'libfuzzer' 324 if not cc.has_argument('-fsanitize=fuzzer') 325 error('fuzzing_engine libfuzzer requires "-fsanitize=fuzzer"') 326 endif 327 fuzzer_args = ['-fsanitize=fuzzer-no-link', '-fsanitize=fuzzer'] 328 add_project_arguments(cc.first_supported_argument(fuzzer_args), language : 'c') 329endif 330 331cdata.set10('ENDIANNESS_BIG', host_machine.endian() == 'big') 332 333if host_machine.cpu_family().startswith('x86') 334 if get_option('stack_alignment') > 0 335 stack_alignment = get_option('stack_alignment') 336 elif host_machine.cpu_family() == 'x86_64' or host_machine.system() in ['linux', 'darwin', 'ios', 'tvos'] 337 stack_alignment = 16 338 else 339 stack_alignment = 4 340 endif 341 cdata_asm.set('STACK_ALIGNMENT', stack_alignment) 342endif 343 344cdata.set10('ARCH_AARCH64', host_machine.cpu_family() == 'aarch64' or host_machine.cpu() == 'arm64') 345cdata.set10('ARCH_ARM', host_machine.cpu_family().startswith('arm') and host_machine.cpu() != 'arm64') 346if (is_asm_enabled and 347 (host_machine.cpu_family() == 'aarch64' or 348 host_machine.cpu_family().startswith('arm'))) 349 350 as_func_code = '''__asm__ ( 351".func meson_test" 352".endfunc" 353); 354''' 355 have_as_func = cc.compiles(as_func_code) 356 cdata.set10('HAVE_AS_FUNC', have_as_func) 357 358 # fedora package build infrastructure uses a gcc specs file to enable 359 # '-fPIE' by default. The chosen way only adds '-fPIE' to the C compiler 360 # with integrated preprocessor. It is not added to the standalone 361 # preprocessor or the preprocessing stage of '.S' files. So we have to 362 # compile code to check if we have to define PIC for the arm asm to 363 # avoid absolute relocations when building for example checkasm. 364 check_pic_code = ''' 365#if defined(PIC) 366#error "PIC already defined" 367#elif !(defined(__PIC__) || defined(__pic__)) 368#error "no pic" 369#endif 370''' 371 if cc.compiles(check_pic_code) 372 cdata.set('PIC', '3') 373 endif 374 375 if host_machine.cpu_family() == 'aarch64' 376 have_as_arch = cc.compiles('''__asm__ (".arch armv8-a");''') 377 cdata.set10('HAVE_AS_ARCH_DIRECTIVE', have_as_arch) 378 as_arch_str = '' 379 if have_as_arch 380 as_arch_level = 'armv8-a' 381 # Check what .arch levels are supported. In principle, we only 382 # want to detect up to armv8.2-a here (binutils requires that 383 # in order to enable i8mm). However, older Clang versions 384 # (before Clang 17, and Xcode versions up to and including 15.0) 385 # didn't support controlling dotprod/i8mm extensions via 386 # .arch_extension, therefore try to enable a high enough .arch 387 # level as well, to implicitly make them available via that. 388 foreach arch : ['armv8.2-a', 'armv8.4-a', 'armv8.6-a'] 389 if cc.compiles('__asm__ (".arch ' + arch + '\\n");') 390 as_arch_level = arch 391 endif 392 endforeach 393 # Clang versions before 17 also had a bug 394 # (https://github.com/llvm/llvm-project/issues/32220) 395 # causing a plain ".arch <level>" to not have any effect unless it 396 # had an extra "+<feature>" included - but it was activated on the 397 # next ".arch_extension" directive instead. Check if we can include 398 # "+crc" as dummy feature to make the .arch directive behave as 399 # expected and take effect right away. 400 if cc.compiles('__asm__ (".arch ' + as_arch_level + '+crc\\n");') 401 as_arch_level = as_arch_level + '+crc' 402 endif 403 cdata.set('AS_ARCH_LEVEL', as_arch_level) 404 as_arch_str = '".arch ' + as_arch_level + '\\n"' 405 endif 406 extensions = { 407 'dotprod': 'udot v0.4s, v0.16b, v0.16b', 408 'i8mm': 'usdot v0.4s, v0.16b, v0.16b', 409 'sve': 'whilelt p0.s, x0, x1', 410 'sve2': 'sqrdmulh z0.s, z0.s, z0.s', 411 } 412 foreach name, instr : extensions 413 # Test for support for the various extensions. First test if 414 # the assembler supports the .arch_extension directive for 415 # enabling/disabling the extension, then separately check whether 416 # the instructions themselves are supported. Even if .arch_extension 417 # isn't supported, we may be able to assemble the instructions 418 # if the .arch level includes support for them. 419 code = '__asm__ (' + as_arch_str 420 code += '".arch_extension ' + name + '\\n"' 421 code += ');' 422 supports_archext = cc.compiles(code) 423 cdata.set10('HAVE_AS_ARCHEXT_' + name.to_upper() + '_DIRECTIVE', supports_archext) 424 code = '__asm__ (' + as_arch_str 425 if supports_archext 426 code += '".arch_extension ' + name + '\\n"' 427 endif 428 code += '"' + instr + '\\n"' 429 code += ');' 430 supports_instr = cc.compiles(code, name: name.to_upper()) 431 cdata.set10('HAVE_' + name.to_upper(), supports_instr) 432 endforeach 433 endif 434endif 435 436cdata.set10('ARCH_X86', host_machine.cpu_family().startswith('x86')) 437cdata.set10('ARCH_X86_64', host_machine.cpu_family() == 'x86_64') 438cdata.set10('ARCH_X86_32', host_machine.cpu_family() == 'x86') 439 440if host_machine.cpu_family().startswith('x86') 441 cdata_asm.set('private_prefix', 'dav1d') 442 cdata_asm.set10('ARCH_X86_64', host_machine.cpu_family() == 'x86_64') 443 cdata_asm.set10('ARCH_X86_32', host_machine.cpu_family() == 'x86') 444 cdata_asm.set10('PIC', true) 445 446 # Convert SSE asm into (128-bit) AVX when compiler flags are set to use AVX instructions 447 cdata_asm.set10('FORCE_VEX_ENCODING', cc.get_define('__AVX__').strip() != '') 448endif 449 450cdata.set10('ARCH_PPC64LE', host_machine.cpu() == 'ppc64le') 451 452cdata.set10('ARCH_RISCV', host_machine.cpu_family().startswith('riscv')) 453cdata.set10('ARCH_RV32', host_machine.cpu_family() == 'riscv32') 454cdata.set10('ARCH_RV64', host_machine.cpu_family() == 'riscv64') 455 456cdata.set10('ARCH_LOONGARCH', host_machine.cpu_family().startswith('loongarch')) 457cdata.set10('ARCH_LOONGARCH32', host_machine.cpu_family() == 'loongarch32') 458cdata.set10('ARCH_LOONGARCH64', host_machine.cpu_family() == 'loongarch64') 459 460# meson's cc.symbols_have_underscore_prefix() is unfortunately unrelieably 461# when additional flags like '-fprofile-instr-generate' are passed via CFLAGS 462# see following meson issue https://github.com/mesonbuild/meson/issues/5482 463if (host_machine.system() in ['darwin', 'ios', 'tvos'] or 464 (host_machine.system() == 'windows' and host_machine.cpu_family() == 'x86')) 465 cdata.set10('PREFIX', true) 466 cdata_asm.set10('PREFIX', true) 467endif 468 469# 470# ASM specific stuff 471# 472if is_asm_enabled and host_machine.cpu_family().startswith('x86') 473 474 # NASM compiler support 475 476 nasm = find_program('nasm') 477 478 # check NASM version 479 if nasm.found() 480 nasm_r = run_command(nasm, '-v', check: true) 481 482 out = nasm_r.stdout().strip().split() 483 if out[1].to_lower() == 'version' 484 if out[2].version_compare('<2.14') 485 error('nasm 2.14 or later is required, found nasm @0@'.format(out[2])) 486 endif 487 else 488 error('unexpected nasm version string: @0@'.format(nasm_r.stdout())) 489 endif 490 endif 491 492 # Generate config.asm 493 config_asm_target = configure_file(output: 'config.asm', output_format: 'nasm', configuration: cdata_asm) 494 495 if host_machine.system() == 'windows' 496 nasm_format = 'win' 497 elif host_machine.system() in ['darwin', 'ios', 'tvos'] 498 nasm_format = 'macho' 499 else 500 nasm_format = 'elf' 501 endif 502 if host_machine.cpu_family() == 'x86_64' 503 nasm_format += '64' 504 else 505 nasm_format += '32' 506 endif 507 508 nasm_gen = generator(nasm, 509 output: '@BASENAME@.obj', 510 depfile: '@BASENAME@.obj.ndep', 511 arguments: [ 512 '-f', nasm_format, 513 '-I', '@0@/src/'.format(dav1d_src_root), 514 '-I', '@0@/'.format(meson.current_build_dir()), 515 '-MQ', '@OUTPUT@', '-MF', '@DEPFILE@', 516 '@EXTRA_ARGS@', 517 '@INPUT@', 518 '-o', '@OUTPUT@' 519 ]) 520endif 521 522use_gaspp = false 523if (is_asm_enabled and 524 (host_machine.cpu_family() == 'aarch64' or 525 host_machine.cpu_family().startswith('arm')) and 526 cc.get_argument_syntax() == 'msvc' and 527 (cc.get_id() != 'clang-cl' or meson.version().version_compare('<0.58.0'))) 528 gaspp = find_program('gas-preprocessor.pl') 529 use_gaspp = true 530 gaspp_gen = generator(gaspp, 531 output: '@BASENAME@.obj', 532 arguments: [ 533 '-as-type', 'armasm', 534 '-arch', host_machine.cpu_family(), 535 '--', 536 host_machine.cpu_family() == 'aarch64' ? 'armasm64' : 'armasm', 537 '-nologo', 538 '-I@0@'.format(dav1d_src_root), 539 '-I@0@/'.format(meson.current_build_dir()), 540 '@INPUT@', 541 '-c', 542 '-o', '@OUTPUT@' 543 ]) 544endif 545 546if is_asm_enabled and host_machine.cpu_family().startswith('riscv') 547 as_option_code = '''__asm__ ( 548".option arch, +v\n" 549"vsetivli zero, 0, e8, m1, ta, ma" 550); 551''' 552 if not cc.compiles(as_option_code, name : 'RISC-V Vector') 553 error('Compiler doesn\'t support \'.option arch\' asm directive. Update to binutils>=2.38 or clang>=17 or use \'-Denable_asm=false\'.') 554 endif 555endif 556 557# Generate config.h 558config_h_target = configure_file(output: 'config.h', configuration: cdata) 559 560 561 562# 563# Include subdir meson.build files 564# The order is important! 565 566subdir('include') 567 568subdir('doc') 569 570subdir('src') 571 572subdir('tools') 573 574subdir('examples') 575 576subdir('tests') 577