From c2b542b6c02bafbe7a83b2eeec6cb5a0bfa3ed0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 19 Mar 2016 15:40:22 +0000 Subject: Add support for Meson build system Tested on: - Linux/x86* with gcc - Android armv7 arm64 x86 x86_64 with clang - Windows x86 x86_64 with Visual Studio 2017 - Windows x86 x86_64 with MinGW - macOS x86_64 with clang - iOS arm64 x86_64 with clang Co-authored by: Nirbheek Chauhan https://gitlab.xiph.org/xiph/opus/-/merge_requests/13 --- .gitlab-ci.yml | 20 +- Makefile.am | 12 + celt/meson.build | 63 +++++ celt/tests/meson.build | 19 ++ doc/Doxyfile.in | 3 + doc/meson.build | 36 +++ include/meson.build | 13 + meson.build | 673 +++++++++++++++++++++++++++++++++++++++++++++ meson/get-version.py | 86 ++++++ meson/read-sources-list.py | 28 ++ meson_options.txt | 22 ++ silk/meson.build | 53 ++++ silk/tests/meson.build | 8 + src/meson.build | 45 +++ tests/meson.build | 34 +++ 15 files changed, 1114 insertions(+), 1 deletion(-) create mode 100644 celt/meson.build create mode 100644 celt/tests/meson.build create mode 100644 doc/meson.build create mode 100644 include/meson.build create mode 100644 meson.build create mode 100755 meson/get-version.py create mode 100755 meson/read-sources-list.py create mode 100644 meson_options.txt create mode 100644 silk/meson.build create mode 100644 silk/tests/meson.build create mode 100644 src/meson.build create mode 100644 tests/meson.build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 88a1342c..70c243b5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,7 +19,7 @@ autoconf: script: - ./autogen.sh - ./configure - - make + - make -j4 - make distcheck cache: paths: @@ -40,3 +40,21 @@ cmake: - cmake -S . -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DOPUS_BUILD_TESTING=ON -DOPUS_BUILD_PROGRAMS=ON - cmake --build build - cd build && ctest --output-on-failure + +meson: + stage: build + before_script: + - apt-get update && + apt-get install -y python3-pip ninja-build doxygen + - export XDG_CACHE_HOME=$PWD/pip-cache + - pip3 install --user meson + script: + - export PATH=$PATH:$HOME/.local/bin + - mkdir builddir + - meson setup --werror -Dtests=enabled -Ddocs=enabled -Dbuildtype=release builddir + - meson compile -C builddir + - meson test -C builddir + #- meson dist --no-tests -C builddir + cache: + paths: + - 'pip-cache/*' diff --git a/Makefile.am b/Makefile.am index e5213739..83beaa3f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -224,6 +224,18 @@ EXTRA_DIST = opus.pc.in \ cmake/OpusSources.cmake \ cmake/config.h.cmake.in \ cmake/vla.c \ + meson/get-version.py \ + meson/read-sources-list.py \ + meson.build \ + meson_options.txt \ + include/meson.build \ + celt/meson.build \ + celt/tests/meson.build \ + silk/meson.build \ + silk/tests/meson.build \ + src/meson.build \ + tests/meson.build \ + doc/meson.build \ tests/run_vectors.sh \ celt/arm/arm2gnu.pl \ celt/arm/celt_pitch_xcorr_arm.s \ diff --git a/celt/meson.build b/celt/meson.build new file mode 100644 index 00000000..370ea1fe --- /dev/null +++ b/celt/meson.build @@ -0,0 +1,63 @@ +celt_sources = sources['CELT_SOURCES'] + +celt_sse_sources = sources['CELT_SOURCES_SSE'] + +celt_sse2_sources = sources['CELT_SOURCES_SSE2'] + +celt_sse4_1_sources = sources['CELT_SOURCES_SSE4_1'] + +celt_neon_intr_sources = sources['CELT_SOURCES_ARM_NEON_INTR'] + +celt_static_libs = [] + +foreach intr_name : ['sse', 'sse2', 'sse4_1', 'neon_intr'] + have_intr = get_variable('have_' + intr_name) + if not have_intr + continue + endif + + intr_sources = get_variable('celt_@0@_sources'.format(intr_name)) + intr_args = get_variable('opus_@0@_args'.format(intr_name), []) + celt_static_libs += static_library('celt_' + intr_name, intr_sources, + c_args: intr_args, + include_directories: opus_includes, + install: false) +endforeach + +have_arm_intrinsics_or_asm = have_arm_ne10 +if (intrinsics_support.length() + asm_optimization.length() + inline_optimization.length()) > 0 + have_arm_intrinsics_or_asm = true +endif + +if host_cpu_family in ['arm', 'aarch64'] and have_arm_intrinsics_or_asm + celt_sources += sources['CELT_SOURCES_ARM'] + if have_arm_ne10 + celt_sources += sources['CELT_SOURCES_ARM_NE10'] + endif + if opus_arm_external_asm + arm2gnu = [find_program('arm/arm2gnu.pl')] + arm2gnu_args + celt_sources_arm_asm = configure_file(input: 'arm/celt_pitch_xcorr_arm.s', + output: '@BASENAME@-gnu.S', + command: arm2gnu + ['@INPUT@'], + capture: true) + celt_arm_armopts_s = configure_file(input: 'arm/armopts.s.in', + output: 'arm/armopts.s', + configuration: opus_conf) + celt_static_libs += static_library('celt-armasm', + celt_arm_armopts_s, celt_sources_arm_asm, + install: false) + endif +endif + +celt_c_args = [] +if host_system == 'windows' + celt_c_args += ['-DDLL_EXPORT'] +endif + +celt_lib = static_library('opus-celt', + celt_sources, + c_args: celt_c_args, + include_directories: opus_includes, + link_whole: celt_static_libs, + dependencies: libm, + install: false) diff --git a/celt/tests/meson.build b/celt/tests/meson.build new file mode 100644 index 00000000..0e6d2e62 --- /dev/null +++ b/celt/tests/meson.build @@ -0,0 +1,19 @@ +tests = [ + 'test_unit_types', + 'test_unit_mathops', + 'test_unit_entropy', + 'test_unit_laplace', + 'test_unit_dft', + 'test_unit_mdct', + 'test_unit_rotation', + 'test_unit_cwrs32', +] + +foreach test_name : tests + exe = executable(test_name, '@0@.c'.format(test_name), + include_directories : opus_includes, + link_with : [celt_lib, celt_static_libs], + dependencies : libm, + install : false) + test(test_name, exe) +endforeach diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 6d25f1fb..6eef650b 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -335,3 +335,6 @@ PREDEFINED = OPUS_EXPORT= \ # configure time. # HAVE_DOT = @HAVE_DOT@ + +# move docs to the correct place +OUTPUT_DIRECTORY = @top_builddir@/doc diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 00000000..8f967a51 --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,36 @@ +top_srcdir = meson.source_root() +top_builddir = meson.build_root() + +have_dot = find_program('dot', required: false).found() + +doxyfile_conf = configuration_data() +doxyfile_conf.set('VERSION', opus_version) +doxyfile_conf.set('HAVE_DOT', have_dot) +doxyfile_conf.set('top_srcdir', top_srcdir) +doxyfile_conf.set('top_builddir', top_builddir) + +doxyfile = configure_file(input: 'Doxyfile.in', + output: 'Doxyfile', + configuration: doxyfile_conf, + install: false) + +docdir = join_paths(get_option('datadir'), get_option('docdir')) + +doc_inputs = [ + 'customdoxygen.css', + 'footer.html', + 'header.html', + 'opus_logo.svg', + top_srcdir + '/include/opus.h', + top_srcdir + '/include/opus_multistream.h', + top_srcdir + '/include/opus_defines.h', + top_srcdir + '/include/opus_types.h', + top_srcdir + '/include/opus_custom.h', +] + +custom_target('doc', + input: [ doxyfile ] + doc_inputs, + output: [ 'html' ], + command: [ doxygen, doxyfile ], + install_dir: docdir, + install: true) diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 00000000..c1fb0b76 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,13 @@ +opus_headers = [ + 'opus.h', + 'opus_multistream.h', + 'opus_projection.h', + 'opus_types.h', + 'opus_defines.h', +] + +if opt_custom_modes + opus_headers += ['opus_custom.h'] +endif + +install_headers(opus_headers, subdir: 'opus') diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..45fff3f2 --- /dev/null +++ b/meson.build @@ -0,0 +1,673 @@ +project('opus', 'c', + version: run_command('meson/get-version.py', '--package-version', check: true).stdout().strip(), + meson_version: '>=0.54.0', + default_options: ['warning_level=2', + 'c_std=gnu99', + 'buildtype=debugoptimized']) + +libversion = run_command('meson/get-version.py', '--libtool-version', check: true).stdout().strip() +macosversion = run_command('meson/get-version.py', '--darwin-version', check: true).stdout().strip() + +cc = meson.get_compiler('c') +host_system = host_machine.system() +host_cpu_family = host_machine.cpu_family() + +opus_includes = include_directories('.', 'include', 'celt', 'silk') +opus_public_includes = include_directories('include') + +add_project_arguments('-DOPUS_BUILD', language: 'c') +add_project_arguments('-DHAVE_CONFIG_H', language: 'c') + +if host_system == 'windows' + if cc.get_argument_syntax() == 'msvc' + add_project_arguments('-D_CRT_SECURE_NO_WARNINGS', language: 'c') + endif +endif + +if cc.get_argument_syntax() == 'gnu' + add_project_arguments('-D_FORTIFY_SOURCE=2', language: 'c') +endif + +# Check for extra compiler args +additional_c_args = [] +if cc.get_argument_syntax() != 'msvc' + additional_c_args += [ + '-fvisibility=hidden', + '-Wcast-align', + '-Wnested-externs', + '-Wshadow', + '-Wstrict-prototypes', + ] + + # On Windows, -fstack-protector-strong adds a libssp-0.dll dependency and + # prevents static linking + if host_system != 'windows' + additional_c_args += ['-fstack-protector-strong'] + endif +endif + +foreach arg : additional_c_args + if cc.has_argument(arg) + add_project_arguments(arg, language: 'c') + endif +endforeach + +# Windows MSVC warnings +if cc.get_id() == 'msvc' + # Ignore several spurious warnings. + # If a warning is completely useless and spammy, use '/wdXXXX' to suppress it + # If a warning is harmless but hard to fix, use '/woXXXX' so it's shown once + # NOTE: Only add warnings here if you are sure they're spurious + add_project_arguments('/wd4035', '/wd4715', '/wd4116', '/wd4046', '/wd4068', + '/wd4820', '/wd4244', '/wd4255', '/wd4668', + language : 'c') +endif + +opus_version = meson.project_version() + +opus_conf = configuration_data() +opus_conf.set('PACKAGE_BUGREPORT', '"opus@xiph.org"') +opus_conf.set('PACKAGE_NAME', '"opus"') +opus_conf.set('PACKAGE_STRING', '"opus @0@"'.format(opus_version)) +opus_conf.set('PACKAGE_TARNAME', '"opus"') +opus_conf.set('PACKAGE_URL', '""') +opus_conf.set('PACKAGE_VERSION', '"@0@"'.format(opus_version)) + +# FIXME: optional Ne10 dependency +have_arm_ne10 = false + +libm = cc.find_library('m', required : false) + +opus_conf.set('HAVE_LRINTF', cc.has_function('lrintf', prefix: '#include ', dependencies: libm)) +opus_conf.set('HAVE_LRINT', cc.has_function('lrint', prefix: '#include ', dependencies: libm)) +opus_conf.set('HAVE___MALLOC_HOOK', cc.has_function('__malloc_hook', prefix: '#include ')) +opus_conf.set('HAVE_STDINT_H', cc.check_header('stdint.h')) + +# Check for restrict keyword +restrict_tmpl = ''' +typedef int * int_ptr; +int foo (int_ptr @0@ ip, int * @0@ baz[]) { + return ip[0]; +} +int main (int argc, char ** argv) { + int s[1]; + int * @0@ t = s; + t[0] = 0; + return foo(t, (void *)0); +}''' +# Define restrict to the equivalent of the C99 restrict keyword, or to +# nothing if this is not supported. Do not define if restrict is +# supported directly. +if not cc.compiles(restrict_tmpl.format('restrict'), name : 'restrict keyword') + if cc.compiles(restrict_tmpl.format('__restrict'), name : '__restrict') + opus_conf.set('restrict', '__restrict') + elif cc.compiles(restrict_tmpl.format('__restrict__'), name : '__restrict__') + opus_conf.set('restrict', '__restrict') + elif cc.compiles(restrict_tmpl.format('_Restrict'), name : '_Restrict') + opus_conf.set('restrict', '_Restrict') + else + opus_conf.set('restrict', '/**/') + endif +endif + +# Check for C99 variable-size arrays, or alloca() as fallback +msg_use_alloca = false +if cc.compiles('''static int x; + char some_func (void) { + char a[++x]; + a[sizeof a - 1] = 0; + int N; + return a[0]; + }''', name : 'C99 variable-size arrays') + opus_conf.set('VAR_ARRAYS', 1) + msg_use_alloca = 'NO (using C99 variable-size arrays instead)' +elif cc.compiles('''#include + void some_func (void) { + int foo=10; + int * array = alloca(foo); + }''', name : 'alloca (alloca.h)') + opus_conf.set('USE_ALLOCA', true) + opus_conf.set('HAVE_ALLOCA_H', true) + msg_use_alloca = true +elif cc.compiles('''#include + #include + void some_func (void) { + int foo=10; + int * array = alloca(foo); + }''', name : 'alloca (std)') + opus_conf.set('USE_ALLOCA', true) + msg_use_alloca = true +endif + +opts = [ + [ 'fixed-point', 'FIXED_POINT' ], + [ 'fixed-point-debug', 'FIXED_DEBUG' ], + [ 'custom-modes', 'CUSTOM_MODES' ], + [ 'float-approx', 'FLOAT_APPROX' ], + [ 'assertions', 'ENABLE_ASSERTIONS' ], + [ 'hardening', 'ENABLE_HARDENING' ], + [ 'fuzzing', 'FUZZING' ], + [ 'check-asm', 'OPUS_CHECK_ASM' ], +] + +foreach opt : opts + # we assume these are all boolean options + opt_foo = get_option(opt[0]) + if opt_foo + opus_conf.set(opt[1], 1) + endif + set_variable('opt_' + opt[0].underscorify(), opt_foo) +endforeach + +opt_asm = get_option('asm') +opt_rtcd = get_option('rtcd') +opt_intrinsics = get_option('intrinsics') +extra_programs = get_option('extra-programs') +opt_tests = get_option('tests') + +disable_float_api = not get_option('float-api') +if disable_float_api + opus_conf.set('DISABLE_FLOAT_API', 1) +endif + +# This is for the description in the pkg-config .pc file +if opt_fixed_point + pc_build = 'fixed-point' +else + pc_build = 'floating-point' +endif +if opt_custom_modes + pc_build = pc_build + ', custom modes' +endif + +rtcd_support = [] +# With GCC, Clang, ICC, etc, we differentiate between 'may support this SIMD' +# and 'presume we have this SIMD' by checking whether the SIMD / intrinsics can +# be compiled by the compiler as-is (presume) or with SIMD cflags (may have). +# With MSVC, the compiler will always build SIMD/intrinsics targeting all +# specific instruction sets supported by that version of the compiler. No +# special arguments are ever needed. If runtime CPU detection is not disabled, +# we must always assume that we only 'may have' it. +opus_can_presume_simd = true +if cc.get_argument_syntax() == 'msvc' + if opt_rtcd.disabled() + warning('Building with an MSVC-like compiler and runtime CPU detection is disabled. Outputs may not run on all @0@ CPUs.'.format(host_cpu_family)) + else + opus_can_presume_simd = false + endif +endif + +opus_arm_external_asm = false + +asm_tmpl = ''' +int main (int argc, char ** argv) { + __asm__("@0@"); + return 0; +}''' + +asm_optimization = [] +inline_optimization = [] +if not opt_asm.disabled() + # Currently we only have inline asm for fixed-point + if host_cpu_family == 'arm' and opt_fixed_point + opus_conf.set('OPUS_ARM_ASM', true) + + # Check if compiler supports gcc-style inline assembly + if cc.compiles('''#ifdef __GNUC_MINOR__ + #if (__GNUC__ * 1000 + __GNUC_MINOR__) < 3004 + #error GCC before 3.4 has critical bugs compiling inline assembly + #endif + #endif + __asm__ (""::)''', + name : 'compiler supports gcc-style inline assembly') + + opus_conf.set('OPUS_ARM_INLINE_ASM', 1) + + # AS_ASM_ARM_EDSP + if cc.compiles(asm_tmpl.format('qadd r3,r3,r3'), + name : 'assembler supports EDSP instructions on ARM') + opus_conf.set('OPUS_ARM_INLINE_EDSP', 1) + inline_optimization += ['ESDP'] + endif + + # AS_ASM_ARM_MEDIA + if cc.compiles(asm_tmpl.format('shadd8 r3,r3,r3'), + name : 'assembler supports ARMv6 media instructions on ARM') + opus_conf.set('OPUS_ARM_INLINE_MEDIA', 1) + inline_optimization += ['Media'] + endif + + # AS_ASM_ARM_NEON + if cc.compiles(asm_tmpl.format('vorr d0,d0,d0'), + name : 'assembler supports NEON instructions on ARM') + opus_conf.set('OPUS_ARM_INLINE_NEON', 1) + inline_optimization += ['NEON'] + endif + endif + + # We need Perl to translate RVCT-syntax asm to gas syntax + perl = find_program('perl', required: get_option('asm')) + if perl.found() + opus_arm_external_asm = true + # opus_arm_presume_* mean we can and will use those instructions + # directly without doing runtime CPU detection. + # opus_arm_may_have_* mean we can emit those instructions, but we can + # only use them after runtime detection. + # The same rules apply for x86 assembly and intrinsics. + + opus_arm_may_have_edsp = opus_conf.has('OPUS_ARM_INLINE_EDSP') + opus_arm_presume_edsp = opus_arm_may_have_edsp and opus_can_presume_simd + + opus_arm_may_have_media = opus_conf.has('OPUS_ARM_INLINE_MEDIA') + opus_arm_presume_media = opus_arm_may_have_media and opus_can_presume_simd + + opus_arm_may_have_neon = opus_conf.has('OPUS_ARM_INLINE_NEON') + opus_arm_presume_neon = opus_arm_may_have_neon and opus_can_presume_simd + + if not opt_rtcd.disabled() + if not opus_arm_may_have_edsp + message('Trying to force-enable armv5e EDSP instructions...') + # AS_ASM_ARM_EDSP_FORCE + opus_arm_may_have_edsp = cc.compiles(asm_tmpl.format('.arch armv5te\n.object_arch armv4t\nqadd r3,r3,r3'), + name : 'Assembler supports EDSP instructions on ARM (forced)') + endif + if not opus_arm_may_have_media + message('Trying to force-enable ARMv6 media instructions...') + opus_arm_may_have_media = cc.compiles(asm_tmpl.format('.arch armv6\n.object_arch armv4t\nshadd8 r3,r3,r3'), + name : 'Assembler supports ARMv6 media instructions on ARM (forced)') + endif + if not opus_arm_may_have_neon + message('Trying to force-enable NEON instructions...') + opus_arm_may_have_neon = cc.compiles(asm_tmpl.format('.arch armv7-a\n.fpu neon\n.object_arch armv4t\nvorr d0,d0,d0'), + name : 'Assembler supports NEON instructions on ARM (forced)') + endif + endif + + if opus_arm_may_have_edsp + opus_conf.set('OPUS_ARM_MAY_HAVE_EDSP', 1) + if opus_arm_presume_edsp + opus_conf.set('OPUS_ARM_PRESUME_EDSP', 1) + asm_optimization += ['EDSP'] + else + rtcd_support += ['EDSP'] + endif + endif + if opus_arm_may_have_media + opus_conf.set('OPUS_ARM_MAY_HAVE_MEDIA', 1) + if opus_arm_presume_media + opus_conf.set('OPUS_ARM_PRESUME_MEDIA', 1) + asm_optimization += ['Media'] + else + rtcd_support += ['Media'] + endif + endif + if opus_arm_may_have_neon + opus_conf.set('OPUS_ARM_MAY_HAVE_NEON', 1) + if opus_arm_presume_neon + opus_conf.set('OPUS_ARM_PRESUME_NEON', 1) + asm_optimization += ['NEON'] + else + rtcd_support += ['NEON'] + endif + endif + + if cc.get_define('__APPLE__') + arm2gnu_args = ['--apple'] + else + arm2gnu_args = [] + endif + endif # found perl + else # arm + enable fixed point + if opt_asm.enabled() + error('asm option is enabled, but no assembly support for ' + host_cpu_family) + endif + endif +endif # enable asm + +# Check whether we require assembly and we support assembly on this arch, +# but none were detected. Can happen because of incorrect compiler flags, such +# as missing -mfloat-abi=softfp on ARM32 softfp architectures. +if opt_asm.enabled() and (asm_optimization.length() + inline_optimization.length()) == 0 + error('asm option was enabled, but no assembly support was detected') +endif + +# XXX: NEON has hardfp vs softfp compiler configuration issues +# When targeting ARM32 softfp, we sometimes need to explicitly pass +# -mfloat-abi=softfp to enable NEON. F.ex., on Android. It should +# be set in the cross file. +arm_neon_intr_link_args = ['-mfpu=neon'] + +have_sse = false +have_sse2 = false +have_sse4_1 = false +have_avx = false # no avx opus code yet +have_neon_intr = false + +intrinsics_support = [] +if not opt_intrinsics.disabled() + if host_cpu_family in ['arm', 'aarch64'] + # Check for ARMv7/AArch64 neon intrinsics + intrin_check = ''' + #include + int main (void) { + static float32x4_t A0, A1, SUMM; + SUMM = vmlaq_f32(SUMM, A0, A1); + return (int)vgetq_lane_f32(SUMM, 0); + }''' + intrin_name = 'ARMv7/AArch64 NEON' + if cc.links(intrin_check, + name: 'compiler supports @0@ intrinsics'.format(intrin_name)) + opus_arm_presume_neon_intr = opus_can_presume_simd + opus_arm_may_have_neon_intr = true + else + opus_arm_presume_neon_intr = false + if cc.links(intrin_check, + args: arm_neon_intr_link_args, + name: 'compiler supports @0@ intrinsics with @1@'.format(intrin_name, ' '.join(arm_neon_intr_link_args))) + opus_arm_may_have_neon_intr = true + else + opus_arm_may_have_neon_intr = false + endif + endif + + if opus_arm_may_have_neon_intr + have_neon_intr = true + intrinsics_support += [intrin_name] + opus_conf.set('OPUS_ARM_MAY_HAVE_NEON_INTR', 1) + if opus_arm_presume_neon_intr + opus_conf.set('OPUS_ARM_PRESUME_NEON_INTR', 1) + else + rtcd_support += [intrin_name] + opus_neon_intr_args = arm_neon_intr_link_args + endif + else + message('Compiler does not support @0@ intrinsics'.format(intrin_name)) + endif + + # Check for aarch64 neon intrinsics + intrin_check = ''' + #include + int main (void) { + static int32_t IN; + static int16_t OUT; + OUT = vqmovns_s32(IN); + }''' + intrin_name = 'AArch64 NEON' + if cc.links(intrin_check, + name: 'compiler supports @0@ intrinsics'.format(intrin_name)) + opus_arm_presume_aarch64_neon_intr = opus_can_presume_simd + opus_arm_may_have_aarch64_neon_intr = true + else + opus_arm_presume_aarch64_neon_intr = false + if cc.links(intrin_check, + args: arm_neon_intr_link_args, + name: 'compiler supports @0@ intrinsics with @1@'.format(intrin_name, ' '.join(arm_neon_intr_link_args))) + opus_arm_may_have_aarch64_neon_intr = true + else + opus_arm_may_have_aarch64_neon_intr = false + endif + endif + + if opus_arm_may_have_aarch64_neon_intr + intrinsics_support += [intrin_name] + opus_conf.set('OPUS_X86_MAY_HAVE_AARCH64_NEON_INTR', 1) + if opus_arm_presume_aarch64_neon_intr + opus_conf.set('OPUS_X86_PRESUME_AARCH64_NEON_INTR', 1) + endif + else + message('Compiler does not support @0@ intrinsics'.format(intrin_name)) + endif + elif host_cpu_family in ['x86', 'x86_64'] + # XXX: allow external override/specification of the flags + x86_intrinsics = [ + [ 'SSE', 'xmmintrin.h', '__m128', '_mm_setzero_ps()', ['-msse'] ], + [ 'SSE2', 'emmintrin.h', '__m128i', '_mm_setzero_si128()', ['-msse2'] ], + [ 'SSE4.1', 'smmintrin.h', '__m128i', '_mm_setzero_si128(); mtest = _mm_cmpeq_epi64(mtest, mtest)', ['-msse4.1'] ], + [ 'AVX', 'immintrin.h', '__m256', '_mm256_setzero_ps()', ['-mavx'] ], + ] + + foreach intrin : x86_intrinsics + intrin_check = '''#include <@0@> + int main (int argc, char ** argv) { + static @1@ mtest; + mtest = @2@; + return *((unsigned char *) &mtest) != 0; + }'''.format(intrin[1],intrin[2],intrin[3]) + intrin_name = intrin[0] + # Intrinsics arguments are not available with MSVC-like compilers + intrin_args = cc.get_argument_syntax() == 'msvc' ? [] : intrin[4] + if cc.links(intrin_check, name : 'compiler supports @0@ intrinsics'.format(intrin_name)) + may_have_intrin = true + presume_intrin = opus_can_presume_simd + elif intrin_args.length() > 0 + presume_intrin = false + if cc.links(intrin_check, + args : intrin_args, + name : 'compiler supports @0@ intrinsics with @1@'.format(intrin_name, ' '.join(intrin_args))) + may_have_intrin = true + else + may_have_intrin = false + endif + endif + if may_have_intrin + intrinsics_support += [intrin_name] + intrin_lower_name = intrin_name.to_lower().underscorify() + set_variable('have_' + intrin_lower_name, true) + opus_conf.set('OPUS_X86_MAY_HAVE_' + intrin_name.underscorify(), 1) + if presume_intrin + opus_conf.set('OPUS_X86_PRESUME_' + intrin_name.underscorify(), 1) + else + rtcd_support += [intrin_name] + set_variable('opus_@0@_args'.format(intrin_lower_name), intrin_args) + endif + else + message('Compiler does not support @0@ intrinsics'.format(intrin_name)) + endif + endforeach + + if not opt_rtcd.disabled() + get_cpuid_by_asm = false + cpuid_asm_code = ''' + #include + int main (int argc, char ** argv) { + unsigned int CPUInfo0; + unsigned int CPUInfo1; + unsigned int CPUInfo2; + unsigned int CPUInfo3; + unsigned int InfoType; + #if defined(__i386__) && defined(__PIC__) + __asm__ __volatile__ ( + "xchg %%ebx, %1\n" + "cpuid\n" + "xchg %%ebx, %1\n": + "=a" (CPUInfo0), + "=r" (CPUInfo1), + "=c" (CPUInfo2), + "=d" (CPUInfo3) : + "a" (InfoType), "c" (0) + ); + #else + __asm__ __volatile__ ( + "cpuid": + "=a" (CPUInfo0), + "=b" (CPUInfo1), + "=c" (CPUInfo2), + "=d" (CPUInfo3) : + "a" (InfoType), "c" (0) + ); + #endif + return 0; + }''' + cpuid_c_code = ''' + #include + int main (int argc, char ** argv) { + unsigned int CPUInfo0; + unsigned int CPUInfo1; + unsigned int CPUInfo2; + unsigned int CPUInfo3; + unsigned int InfoType; + __get_cpuid(InfoType, &CPUInfo0, &CPUInfo1, &CPUInfo2, &CPUInfo3); + return 0; + }''' + cpuid_msvc_code = ''' + #include + int main (void) { + int CPUInfo, InfoType; + __cpuid(&CPUInfo, InfoType); + }''' + if cc.links(cpuid_asm_code, name : 'Get X86 CPU info via inline assembly') + opus_conf.set('CPU_INFO_BY_ASM', 1) + elif cc.links(cpuid_c_code, name : 'Get X86 CPU info via C method') + opus_conf.set('CPU_INFO_BY_C', 1) + elif cc.get_define('_MSC_VER') != '' and cc.links(cpuid_msvc_code) + message('Getting X86 CPU info via __cpuid') + else + if opt_intrinsics.enabled() and opt_rtcd.enabled() + error('intrinsics and rtcd options are enabled, but no Get CPU Info method detected') + endif + warning('Get CPU Info method not detected, no rtcd for intrinsics') + endif + endif # opt_rtcd + else + if opt_intrinsics.enabled() + error('intrinsics option enabled, but no intrinsics support for ' + host_machine.get_cpu()) + endif + warning('No intrinsics support for ' + host_machine.get_cpu()) + endif +endif + +# Check whether we require intrinsics and we support intrinsics on this arch, +# but none were detected. Can happen because of incorrect compiler flags, such +# as missing -mfloat-abi=softfp on ARM32 softfp architectures. +if opt_intrinsics.enabled() and intrinsics_support.length() == 0 + error('intrinsics option was enabled, but none were detected') +endif + +if opt_rtcd.disabled() + rtcd_support = 'disabled' +else + if rtcd_support.length() > 0 + opus_conf.set('OPUS_HAVE_RTCD', 1) + else + if intrinsics_support.length() == 0 + rtcd_support = 'none' + if opt_rtcd.enabled() + error('rtcd option is enabled, but no support for intrinsics or assembly is available') + endif + else + rtcd_support = 'not needed' + endif + endif +endif + +# extract source file lists from .mk files +mk_files = ['silk_sources.mk', 'opus_headers.mk', 'opus_sources.mk', 'silk_headers.mk', 'celt_sources.mk', 'celt_headers.mk'] +lines = run_command('meson/read-sources-list.py', mk_files, check: true).stdout().strip().split('\n') +sources = {} +foreach l : lines + a = l.split(' = ') + var_name = a[0] + file_list = a[1].split() + sources += {var_name: files(file_list)} +endforeach + +subdir('include') +subdir('silk') +subdir('celt') +subdir('src') + +configure_file(output: 'config.h', configuration: opus_conf) + +if not opt_tests.disabled() + subdir('celt/tests') + subdir('silk/tests') + subdir('tests') +endif + +# pkg-config files (not using pkg module so we can use the existing .pc.in file) +pkgconf = configuration_data() + +pkgconf.set('prefix', join_paths(get_option('prefix'))) +pkgconf.set('exec_prefix', '${prefix}') +pkgconf.set('libdir', '${prefix}/@0@'.format(get_option('libdir'))) +pkgconf.set('includedir', '${prefix}/@0@'.format(get_option('includedir'))) +pkgconf.set('VERSION', opus_version) +pkgconf.set('PC_BUILD', pc_build) +pkgconf.set('LIBM', libm.found() ? '-lm' : '') + +pkg_install_dir = '@0@/pkgconfig'.format(get_option('libdir')) + +configure_file(input : 'opus.pc.in', + output : 'opus.pc', + configuration : pkgconf, + install_dir : pkg_install_dir) + +# The uninstalled one has hardcoded libtool + static lib stuff, skip it for now +#configure_file(input : 'opus-uninstalled.pc.in', +# output : 'opus-uninstalled.pc', +# configuration : pkgconf, +# install : false) + +doxygen = find_program('doxygen', required: get_option('docs')) +if doxygen.found() + subdir('doc') +endif + +summary( + { + 'C99 var arrays': opus_conf.has('VAR_ARRAYS'), + 'C99 lrintf': opus_conf.has('HAVE_LRINTF'), + 'Use alloca': msg_use_alloca, + }, + section: 'Compiler support', + bool_yn: true, + list_sep: ', ', +) + +# Parse optimization status +foreach status : [['inline_optimization', opt_asm], + ['asm_optimization', opt_asm], + ['intrinsics_support', opt_intrinsics]] + res = status[0] + opt = status[1] + resval = get_variable(res) + if opt.disabled() + set_variable(res, 'disabled') + elif resval.length() == 0 + if host_cpu_family not in ['arm', 'aarch64', 'x86', 'x86_64'] + set_variable(res, 'No optimizations for your platform, please send patches') + else + set_variable(res, 'none') + endif + endif +endforeach + +summary( + { + 'Floating point support': not opt_fixed_point, + 'Fast float approximations': opt_float_approx, + 'Fixed point debugging': opt_fixed_point_debug, + 'Inline assembly optimizations': inline_optimization, + 'External assembly optimizations': asm_optimization, + 'Intrinsics optimizations': intrinsics_support, + 'Run-time CPU detection': rtcd_support, + }, + section: 'Optimizations', + bool_yn: true, + list_sep: ', ', +) +summary( + { + 'Custom modes': opt_custom_modes, + 'Assertions': opt_assertions, + 'Hardening': opt_hardening, + 'Fuzzing': opt_fuzzing, + 'Check ASM': opt_check_asm, + 'API documentation': doxygen.found(), + 'Extra programs': not extra_programs.disabled(), + 'Tests': not opt_tests.disabled(), + }, + section: 'General configuration', + bool_yn: true, + list_sep: ', ', +) diff --git a/meson/get-version.py b/meson/get-version.py new file mode 100755 index 00000000..0e8b8623 --- /dev/null +++ b/meson/get-version.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# +# Opus get-version.py +# +# Extracts versions for build: +# - Opus package version based on 'git describe' or $srcroot/package_version +# - libtool version based on configure.ac +# - macos lib version based on configure.ac +# +# Usage: +# get-version.py [--package-version | --libtool-version | --darwin-version] +import argparse +import subprocess +import os +import sys +import shutil + +if __name__ == '__main__': + arg_parser = argparse.ArgumentParser(description='Extract Opus package version or libtool version') + group = arg_parser.add_mutually_exclusive_group(required=True) + group.add_argument('--libtool-version', action='store_true') + group.add_argument('--package-version', action='store_true') + group.add_argument('--darwin-version', action='store_true') + args = arg_parser.parse_args() + + srcroot = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) + + # package version + if args.package_version: + package_version = None + + # check if git checkout + git_dir = os.path.join(srcroot, '.git') + is_git = os.path.isdir(git_dir) + have_git = shutil.which('git') is not None + + if is_git and have_git: + git_cmd = subprocess.run(['git', '--git-dir=' + git_dir, 'describe', 'HEAD'], stdout=subprocess.PIPE) + if git_cmd.returncode: + print('ERROR: Could not extract package version via `git describe` in', srcroot, file=sys.stderr) + sys.exit(-1) + package_version = git_cmd.stdout.decode('ascii').strip().lstrip('v') + else: + with open(os.path.join(srcroot, 'package_version'), 'r') as f: + for line in f: + if line.startswith('PACKAGE_VERSION="'): + package_version = line[17:].strip().lstrip('v').rstrip('"') + if package_version: + break + + if not package_version: + print('ERROR: Could not extract package version from package_version file in', srcroot, file=sys.stderr) + sys.exit(-1) + + print(package_version) + sys.exit(0) + + # libtool version + darwin version + elif args.libtool_version or args.darwin_version: + opus_lt_cur = None + opus_lt_rev = None + opus_lt_age = None + + with open(os.path.join(srcroot, 'configure.ac'), 'r') as f: + for line in f: + if line.strip().startswith('OPUS_LT_CURRENT='): + opus_lt_cur = line[16:].strip() + elif line.strip().startswith('OPUS_LT_REVISION='): + opus_lt_rev = line[17:].strip() + elif line.strip().startswith('OPUS_LT_AGE='): + opus_lt_age = line[12:].strip() + + if opus_lt_cur and opus_lt_rev and opus_lt_age: + opus_lt_cur = int(opus_lt_cur) + opus_lt_rev = int(opus_lt_rev) + opus_lt_age = int(opus_lt_age) + if args.libtool_version: + print('{}.{}.{}'.format(opus_lt_cur - opus_lt_age, opus_lt_age, opus_lt_rev)) + elif args.darwin_version: + print('{}.{}.{}'.format(opus_lt_cur + 1, 0, 0)) + sys.exit(0) + else: + print('ERROR: Could not extract libtool version from configure.ac file in', srcroot, file=sys.stderr) + sys.exit(-1) + else: + sys.exit(-1) diff --git a/meson/read-sources-list.py b/meson/read-sources-list.py new file mode 100755 index 00000000..fcbec501 --- /dev/null +++ b/meson/read-sources-list.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# +# opus/read-sources-list.py +# +# Parses .mk files and extracts list of source files. +# Prints one line per source file list, with filenames space-separated. + +import sys + +if len(sys.argv) < 2: + sys.exit('Usage: {} sources_foo.mk [sources_bar.mk...]'.format(sys.argv[0])) + +for input_fn in sys.argv[1:]: + with open(input_fn, 'r', encoding='utf8') as f: + text = f.read() + text = text.replace('\\\n', '') + + # Remove empty lines + lines = [line for line in text.split('\n') if line.strip()] + + # Print SOURCES_XYZ = file1.c file2.c + for line in lines: + values = line.strip().split('=', maxsplit=2) + if len(values) != 2: + raise RuntimeError('Unable to parse line "{}" from file "{}"'.format(line, input_fn)) + var, files = values + sources_list = [f for f in files.split(' ') if f] + print(var.strip(), '=', ' '.join(sources_list)) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 00000000..360ff263 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,22 @@ +# Optimizations +option('fixed-point', type : 'boolean', value : false, description : 'Compile without floating point (for machines without a fast enough FPU') +option('fixed-point-debug', type : 'boolean', value : false, description : 'Debug fixed-point implementation') +option('float-api', type : 'boolean', value : true, description : 'Compile with or without the floating point API (for machines with no float library') +option('float-approx', type : 'boolean', value : false, description : 'Enable fast approximations for floating point (not supported on all platforms)') +option('rtcd', type : 'feature', value : 'auto', description : 'Run-time CPU capabilities detection') +option('asm', type : 'feature', value : 'auto', description : 'Assembly optimizations for ARM (fixed-point)') +option('intrinsics', type : 'feature', value : 'auto', description : 'Intrinsics optimizations for ARM NEON or x86') + +option('custom-modes', type : 'boolean', value : false, description : 'Enable non-Opus modes, e.g. 44.1 kHz & 2^n frames') +option('extra-programs', type : 'feature', value : 'auto', description : 'Extra programs (demo and tests)') +option('assertions', type : 'boolean', value : false, description : 'Additional software error checking') +option('hardening', type : 'boolean', value : true, description : 'Run-time checks that are cheap and safe for use in production') +option('fuzzing', type : 'boolean', value : false, description : 'Causes the encoder to make random decisions') +option('check-asm', type : 'boolean', value : false, description : 'Run bit-exactness checks between optimized and c implementations') + +# common feature options +option('tests', type : 'feature', value : 'auto', description : 'Build tests') +option('docs', type: 'feature', value: 'auto', description: 'Build API documentation') + +# other options +option('docdir', type: 'string', value: 'doc/opus', description: 'Directory to install documentation into (default: DATADIR/doc/opus') diff --git a/silk/meson.build b/silk/meson.build new file mode 100644 index 00000000..70692372 --- /dev/null +++ b/silk/meson.build @@ -0,0 +1,53 @@ +silk_sources = sources['SILK_SOURCES'] + +silk_sources_sse4_1 = sources['SILK_SOURCES_SSE4_1'] + +silk_sources_neon_intr = sources['SILK_SOURCES_ARM_NEON_INTR'] + +silk_sources_fixed_neon_intr = sources['SILK_SOURCES_FIXED_ARM_NEON_INTR'] + +silk_sources_fixed = sources['SILK_SOURCES_FIXED'] + +silk_sources_fixed_sse4_1 = sources['SILK_SOURCES_FIXED_SSE4_1'] + +silk_sources_float = sources['SILK_SOURCES_FLOAT'] + +if opt_fixed_point + silk_sources += silk_sources_fixed +else + silk_sources += silk_sources_float +endif + +silk_includes = [opus_includes, include_directories('float', 'fixed')] +silk_static_libs = [] + +foreach intr_name : ['sse4_1', 'neon_intr'] + have_intr = get_variable('have_' + intr_name) + if not have_intr + continue + endif + + intr_sources = get_variable('silk_sources_' + intr_name) + if opt_fixed_point + intr_sources += get_variable('silk_sources_fixed_' + intr_name) + endif + + intr_args = get_variable('opus_@0@_args'.format(intr_name), []) + silk_static_libs += static_library('silk_' + intr_name, intr_sources, + c_args: intr_args, + include_directories: silk_includes, + install: false) +endforeach + +silk_c_args = [] +if host_machine.system() == 'windows' + silk_c_args += ['-DDLL_EXPORT'] +endif + +silk_lib = static_library('opus-silk', + silk_sources, + c_args: silk_c_args, + include_directories: silk_includes, + link_whole: silk_static_libs, + dependencies: libm, + install: false) diff --git a/silk/tests/meson.build b/silk/tests/meson.build new file mode 100644 index 00000000..b7c70f75 --- /dev/null +++ b/silk/tests/meson.build @@ -0,0 +1,8 @@ +exe = executable('test_unit_LPC_inv_pred_gain', + 'test_unit_LPC_inv_pred_gain.c', '../LPC_inv_pred_gain.c', + include_directories: opus_includes, + link_with: [celt_lib, celt_static_libs, silk_lib, silk_static_libs], + dependencies: libm, + install: false) + +test(test_name, exe) diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 00000000..cc07ff06 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,45 @@ +opus_sources = sources['OPUS_SOURCES'] + +opus_sources_float = sources['OPUS_SOURCES_FLOAT'] + +if not disable_float_api + opus_sources += opus_sources_float +endif + +opus_lib_c_args = [] +if host_machine.system() == 'windows' + opus_lib_c_args += ['-DDLL_EXPORT'] +endif + +opus_lib = library('opus', + opus_sources, + version: libversion, + darwin_versions: macosversion, + c_args: opus_lib_c_args, + include_directories: opus_includes, + link_with: [celt_lib, silk_lib], + dependencies: libm, + install: true) + +opus_dep = declare_dependency(link_with: opus_lib, + include_directories: opus_public_includes) + +# Extra uninstalled Opus programs +if not extra_programs.disabled() + foreach prog : ['opus_compare', 'opus_demo', 'repacketizer_demo'] + executable(prog, '@0@.c'.format(prog), + include_directories: opus_includes, + link_with: opus_lib, + dependencies: libm, + install: false) + endforeach + + if opt_custom_modes + executable('opus_custom_demo', '../celt/opus_custom_demo.c', + include_directories: opus_includes, + link_with: opus_lib, + dependencies: libm, + install: false) + endif + +endif diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 00000000..5f3ac9df --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,34 @@ +# Tests that link to libopus +opus_tests = [ + ['test_opus_api'], + ['test_opus_decode', [], 60], + ['test_opus_encode', 'opus_encode_regressions.c', 120], + ['test_opus_padding'], + ['test_opus_projection'], +] + +foreach t : opus_tests + test_name = t.get(0) + extra_srcs = t.get(1, []) + + test_kwargs = {} + if t.length() > 2 + test_kwargs += {'timeout': t[2]} + endif + + exe_kwargs = {} + # This test uses private symbols + if test_name == 'test_opus_projection' + exe_kwargs = { + 'link_with': [celt_lib, silk_lib], + 'objects': opus_lib.extract_all_objects(), + } + endif + + exe = executable(test_name, '@0@.c'.format(test_name), extra_srcs, + include_directories: opus_includes, + dependencies: [libm, opus_dep], + install: false, + kwargs: exe_kwargs) + test(test_name, exe, kwargs: test_kwargs) +endforeach -- cgit v1.2.1