diff --git a/.gitignore b/.gitignore
index ce3195baac..ce3183f3d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,6 +63,10 @@ libtool
 flint.pc
 autom4te.cache/
 config.m4
+config.m4.in
 src/flint-mparam.h
 .gdb_history
 vgcore.*
+meson
+meson.build
+meson.options
diff --git a/_meson_build/Makefile b/_meson_build/Makefile
new file mode 100644
index 0000000000..88907fd785
--- /dev/null
+++ b/_meson_build/Makefile
@@ -0,0 +1,31 @@
+BUILDDIR = build.meson
+
+all: setup
+	meson compile -C $(BUILDDIR)
+
+setup: $(BUILDDIR)/.build-dir-created
+
+$(BUILDDIR)/.build-dir-created:
+	meson setup $(BUILDDIR)
+	touch $(BUILDDIR)/.build-dir-created
+
+clean:
+	rm -rf $(BUILDDIR)
+
+install: setup
+	meson install -C $(BUILDDIR)
+
+uninstall:
+	cd $(BUILDDIR) && ninja uninstall
+
+test: setup
+	meson configure $(BUILDDIR) -Dtests=enabled
+	meson test -C $(BUILDDIR) --timeout-multiplier 10
+
+coverage: setup
+	# Coverage tool e.g. gcovr needs to be installed *before* meson configure:
+	#    pip install gcovr
+	meson configure $(BUILDDIR) -Dtests=enabled -Db_coverage=true
+	meson test -C $(BUILDDIR) --timeout-multiplier 10
+	cd $(BUILDDIR) && ninja coverage-html
+	# open $(BUILDDIR)/meson-logs/coveragereport/index.html
diff --git a/_meson_build/bad_test.c b/_meson_build/bad_test.c
new file mode 100644
index 0000000000..3af1103e33
--- /dev/null
+++ b/_meson_build/bad_test.c
@@ -0,0 +1,10 @@
+/*
+ * This is used to fail the build tests of someone tries to run them without
+ * enabling them in the build first.
+ */
+#include <stdio.h>
+
+int main() {
+    printf("No tests run. Need to enable building tests with meson configure -Dtests=enabled\n");
+    return 1;
+}
diff --git a/_meson_build/config.m4.in b/_meson_build/config.m4.in
new file mode 100644
index 0000000000..2a36c4c373
--- /dev/null
+++ b/_meson_build/config.m4.in
@@ -0,0 +1,21 @@
+dnl config.m4.  Generated automatically by configure.
+changequote(<,>)
+ifdef(<__CONFIG_M4_INCLUDED__>,,<
+define(<TEXT>, <.text>)
+define(<DATA>, <.data>)
+define(<LABEL_SUFFIX>, <:>)
+define(<GLOBL>, <.globl>)
+define(<GLOBL_ATTR>, <>)
+define(<GSYM_PREFIX>, <_>)
+define(<RODATA>, <	.section	__TEXT,__const>)
+define(<TYPE>, <>)
+define(<SIZE>, <>)
+define(<LSYM_PREFIX>, @LSYM_PREFIX@)
+define(<ALIGN_LOGARITHMIC>,<yes>)
+>)
+changequote(`,')
+ifdef(`__CONFIG_M4_INCLUDED__',,`
+include(`../src/mpn_extras/asm-defs.m4')
+include(`../src/mpn_extras/arm64/darwin.m4')
+')
+define(`__CONFIG_M4_INCLUDED__')
diff --git a/_meson_build/configure b/_meson_build/configure
new file mode 100755
index 0000000000..2985c95bf3
--- /dev/null
+++ b/_meson_build/configure
@@ -0,0 +1,217 @@
+#!/usr/bin/env python
+
+from os.path import join, abspath, dirname
+import sys
+import shutil
+from subprocess import check_call
+from argparse import ArgumentParser
+
+
+def enable_disable_to_do(args, feature):
+    """Translate --enable-foo=no etc to foo=True/False/None"""
+    enable = getattr(args, 'enable_' + feature)
+    disable = getattr(args, 'disable_' + feature)
+    if enable is not None and disable is not None:
+        msg = 'Cannot use --enable-{} and --disable-{} together'
+        raise ValueError(msg.format(feature, feature))
+    if enable is not None:
+        return enable == 'yes'
+    if disable is not None:
+        return disable == 'no'
+    return None
+
+
+def run_command(cmd):
+    print('-' * 80)
+    print('$', ' '.join(cmd))
+    print('-' * 80)
+    check_call(cmd)
+
+
+def enabled_disabled(value):
+    if value is True:
+        return 'enabled'
+    elif value is False:
+        return 'disabled'
+    else:
+        raise ValueError('Invalid value: ' + value)
+
+
+def true_false(value):
+    if value is True:
+        return 'true'
+    elif value is False:
+        return 'false'
+    else:
+        raise ValueError('Invalid value: ' + str(value))
+
+
+def add_feature_option(parser, feature):
+    parser.add_argument('--enable-' + feature, help='Enable ' + feature,
+        choices=['yes', 'no', 'auto'], nargs='?', const='yes', action='store')
+    parser.add_argument('--disable-' + feature, help='Disable ' + feature,
+        choices=['yes', 'no', 'auto'], nargs='?', const='yes', action='store')
+
+
+def add_with_option(parser, libname, default):
+    parser.add_argument('--with-' + libname, help=f'Link to {libname}',
+        default=default, const=True, nargs='?')
+
+
+parser = ArgumentParser(description='Configure FLINT for building with meson')
+
+parser.add_argument('--build-dir', default='build.meson', help='Build directory')
+
+parser.add_argument('--prefix', default=None, help='Installation prefix')
+parser.add_argument('--bindir', default=None, help='Binary directory')
+parser.add_argument('--libdir', default=None, help='Library directory')
+parser.add_argument('--includedir', default=None, help='Include directory')
+
+parser.add_argument('--build', default=None, help='Not supported...')
+parser.add_argument('--host', default=None, help='Not supported...')
+
+add_feature_option(parser, 'shared')
+add_feature_option(parser, 'static')
+
+add_feature_option(parser, 'libtool-lock')
+add_feature_option(parser, 'pthread')
+add_feature_option(parser, 'reentrant')
+add_feature_option(parser, 'thread-safe')
+add_feature_option(parser, 'assert')
+add_feature_option(parser, 'coverage')
+add_feature_option(parser, 'debug')
+add_feature_option(parser, 'dependency-tracking')
+add_feature_option(parser, 'pretty-tests')
+
+add_feature_option(parser, 'gmp-internals')
+add_feature_option(parser, 'assembly')
+add_feature_option(parser, 'avx2')
+add_feature_option(parser, 'avx512')
+
+add_with_option(parser, 'gmp', default=True)
+add_with_option(parser, 'mpfr', default=True)
+add_with_option(parser, 'ntl', default=False)
+
+parser.add_argument('--with-blas', default=None, help='Not supported...')
+parser.add_argument('--with-gc', default=None, help='Not supported...')
+
+
+def get_meson_command_from_configure_args(args):
+    """
+    Translate e.g. 
+        ./configure --prefix=/opt/foo --enable-assembly=no
+    to
+        meson setup build --prefix=/opt/foo -Dassembly=disabled
+    Raise ValueError if modifying an option is not supported.
+    """
+    args = parser.parse_args(args)
+
+    setup_args = []
+
+    if args.bindir is not None:
+        setup_args.append('--bindir=' + args.bindir)
+    if args.libdir is not None:
+        setup_args.append('--libdir=' + args.libdir)
+    else:
+        setup_args.append('--libdir=lib')
+    if args.includedir is not None:
+        setup_args.append('--includedir=' + args.includedir)
+    if args.prefix is not None:
+        setup_args.append('--prefix=' + args.prefix)
+
+    do_shared = enable_disable_to_do(args, 'shared')
+    do_static = enable_disable_to_do(args, 'static')
+
+    do_libtool_lock = enable_disable_to_do(args, 'libtool_lock')
+    do_pthread = enable_disable_to_do(args, 'pthread')
+    do_reentrant = enable_disable_to_do(args, 'reentrant')
+    do_thread_safe = enable_disable_to_do(args, 'thread_safe')
+    do_assert = enable_disable_to_do(args, 'assert')
+    do_coverage = enable_disable_to_do(args, 'coverage')
+    do_debug = enable_disable_to_do(args, 'debug')
+    do_dependency_tracking = enable_disable_to_do(args, 'dependency_tracking')
+    do_pretty_tests = enable_disable_to_do(args, 'pretty_tests')
+
+    do_gmp_internals = enable_disable_to_do(args, 'gmp_internals')
+    do_assembly = enable_disable_to_do(args, 'assembly')
+    do_avx2 = enable_disable_to_do(args, 'avx2')
+    do_avx512 = enable_disable_to_do(args, 'avx512')
+
+    if do_shared is None:
+        do_shared = True
+    if do_static is None:
+        do_static = False
+
+    if do_shared and not do_static:
+        pass
+    elif not do_shared and do_static:
+        setup_args.append('--default-library=static')
+    elif do_shared and do_static:
+        setup_args.append('--default-library=both')
+    else:
+        raise ValueError('Cannot disable both shared and static libraries')
+
+    if do_debug is True:
+        setup_args.append('--buildtype=debugoptimized')
+
+    if do_libtool_lock is not None:
+        setup_args.append('-Dlibtool_lock=' + 'disabled')
+    if do_pthread is not None:
+        setup_args.append('-Dpthread=' + enabled_disabled(do_pthread))
+    if do_reentrant is not None:
+        setup_args.append('-Dreentrant=' + enabled_disabled(do_reentrant))
+    if do_thread_safe is not None:
+        setup_args.append('-Dthread_safe=' + enabled_disabled(do_thread_safe))
+    if do_assert is not None:
+        setup_args.append('-Dassert=' + enabled_disabled(do_assert))
+    if do_coverage is not None:
+        setup_args.append('-Db_coverage=' + true_false(do_coverage))
+    if do_dependency_tracking is not None:
+        pass # Meson always does dependency tracking with Ninja
+    if do_pretty_tests is not None:
+        setup_args.append('-Dpretty_tests=' + enabled_disabled(do_pretty_tests))
+
+    if do_gmp_internals is not None:
+        setup_args.append('-Dgmp_internals=' + enabled_disabled(do_gmp_internals))
+
+    if do_assembly is not None:
+        setup_args.append('-Dassembly=' + enabled_disabled(do_assembly))
+
+    if do_avx2 is False or do_avx512 is False:
+        setup_args.append('-Dfft_small=' + 'disabled')
+    elif do_avx2 is True or do_avx512 is True:
+        setup_args.append('-Dfft_small=' + 'enabled')
+
+    for lib in ['gmp', 'mpfr', 'gc', 'ntl', 'blas']:
+        val = getattr(args, 'with_' + lib)
+        if isinstance(val, str):
+            setup_args.append('--pkg-config-path=' + join(val, 'lib', 'pkgconfig'))
+
+    if args.with_ntl:
+        setup_args.append('-Dntl=enabled')
+
+    if args.with_blas is not None:
+        raise ValueError('--with-blas is not supported')
+    if args.with_gc is not None:
+        raise ValueError('--with-gc is not supported')
+
+    cmd = ['meson', 'setup', args.build_dir] + setup_args
+    return cmd, args
+
+
+def main(*args):
+    cmd, args = get_meson_command_from_configure_args(args)
+    # The default behaviour of ./configure is to basically wipe out the build.
+    # Everything is then rebuilt from scratch. We will do the same here to
+    # match the semantics of configure which does not preserve any settings
+    # from one run of configure to the next. When using meson directly, it is
+    # better not to do this because the files might not need to be rebuilt.
+    shutil.rmtree(args.build_dir, ignore_errors=True)
+    run_command(cmd)
+    # The Makefile uses this to check if project is configured
+    with open(join(args.build_dir, '.build-dir-created'), 'w') as f:
+        pass
+
+
+if __name__ == '__main__':
+    sys.exit(main(*sys.argv[1:]))
diff --git a/_meson_build/generate_meson_build.py b/_meson_build/generate_meson_build.py
new file mode 100644
index 0000000000..2c77ba07ed
--- /dev/null
+++ b/_meson_build/generate_meson_build.py
@@ -0,0 +1,408 @@
+import sys
+from os import listdir, makedirs
+from os.path import join, dirname, abspath, isdir
+from shutil import copyfile
+from argparse import ArgumentParser
+
+this_dir = dirname(abspath(__file__))
+
+files_to_copy = [
+    'configure',
+    'Makefile',
+    'meson.build',
+    'meson.options',
+    'include/meson.build',
+    'include/flint/meson.build',
+    'config.m4.in',
+]
+
+# Directories in src that are not modules or that need special handling
+exclude_mod_dirs = [
+    'test',
+    'profile',
+    'interfaces',
+    'python',
+    'include',
+]
+
+conditional_modules = [
+    'fft_small',
+]
+
+# Modules that do not have a corresponding header file
+mod_no_header = [
+    'generic_files',
+]
+
+# Headers that do not have a corresponding module
+head_no_dir = [
+    'NTL-interface',
+    'acb_types',
+    'acf_types',
+    'arb_types',
+    'arf_types',
+    'crt_helpers',
+    'fmpq_types',
+    'fmpz_mod_types',
+    'fmpz_types',
+    'fq_nmod_types',
+    'fq_types',
+    'fq_zech_types',
+    'gettimeofday',
+    'limb_types',
+    'longlong',
+    'longlong_asm_clang',
+    'longlong_asm_gcc',
+    'longlong_asm_gnu',
+    'longlong_div_gnu',
+    'longlong_msc_arm64',
+    'longlong_msc_x86',
+    'machine_vectors',
+    'mpf-impl',
+    'mpoly_types',
+    'n_poly_types',
+    'nmod_types',
+    'padic_types',
+    'profiler',
+    'templates',
+    'test_helpers',
+]
+
+headers_skip = [
+    'flint',
+    'config',
+    'flint-config',
+    'flint-mparam',
+    'gmpcompat',
+]
+
+mod_no_tests = [
+    'ca_vec',
+    'calcium',
+    'fexpr_builtin',
+    'fmpz_mod_vec',
+    'fq_zech_mpoly',
+    'fq_zech_mpoly_factor',
+    'generic_files',
+    'hypgeom',
+    # XXX: Templates...
+    'fq_templates',
+    'fq_embed_templates',
+    'fq_poly_templates',
+    'fq_poly_factor_templates',
+    'fq_mat_templates',
+    'fq_vec_templates',
+]
+
+src_meson_build = '''\
+#
+# This file is generated automatically.
+#
+# To regenerate it, run:
+#   python _meson_build/generate_meson_build.py
+#
+
+modules = [
+%s
+]
+
+modules_no_header = [
+%s
+]
+
+modules_no_tests = [
+%s
+]
+
+headers_no_dir = [
+%s
+]
+
+headers_all = []
+c_files_all = []
+mod_tests = []
+
+# Select the right version of fmpz.c (see configuration)
+c_files_all += files('fmpz/link' / fmpz_c_in)
+
+foreach mod : modules
+  subdir(mod)
+  if mod not in modules_no_tests
+    mod_tests += [mod / 'test']
+  endif
+endforeach
+
+foreach mod : modules + headers_no_dir
+  if mod not in modules_no_header
+    headers_all += [mod + '.h']
+  endif
+endforeach
+
+# Add conditional modules
+
+if have_fft_small
+  subdir('fft_small')
+  mod_tests += ['fft_small/test']
+  headers_all += ['fft_small.h']
+endif
+
+if ntl_opt.enabled()
+  mod_tests += ['interfaces/test']
+endif
+
+headers_all = files(headers_all)
+'''
+
+src_mod_meson_build = '''\
+#
+# This file is generated automatically.
+#
+# To regenerate it, run:
+#   python _meson_build/generate_meson_build.py
+#
+
+c_files_all += files(
+%s
+)
+'''
+
+test_mod_meson_build = '''\
+# This file is generated automatically.
+#
+# To regenerate it, run:
+#   python _meson_build/generate_meson_build.py
+#
+
+test_exe = executable('main',
+  'main.c',
+  dependencies: flint_deps,
+  link_with: libflint,
+  include_directories: [headers_built_nodir_inc, '../..'],
+  install: false,
+)
+
+test('%s', test_exe)
+'''
+
+
+test_mod_NTL_meson_build = '''\
+# This file is generated automatically.
+#
+# To regenerate it, run:
+#   python _meson_build/generate_meson_build.py
+#
+
+# NTL no found by pkgconfig
+cpp = meson.get_compiler('cpp')
+ntl_dep = cpp.find_library('ntl')
+
+test_exe = executable('main',
+  't-NTL-interface.cpp',
+  dependencies: flint_deps + [ntl_dep],
+  link_with: libflint,
+  include_directories: [headers_built_nodir_inc, '../..'],
+  install: false,
+)
+
+test('%s', test_exe)
+'''
+
+asm_submodule_arm64 = '''\
+
+asm_deps = [
+  '../asm-defs.m4',
+]
+
+if host_machine.system() == 'darwin'
+  asm_deps += ['darwin.m4']
+else
+  asm_deps += ['arm64-defs.m4']
+endif
+
+asm_files = [
+%s
+]
+
+src_dir_inc = include_directories('.')
+'''
+
+asm_submodule_x86_broadwell = '''\
+
+asm_deps = [
+    '../../asm-defs.m4',
+    '../x86_64-defs.m4',
+]
+
+if host_machine.system() == 'darwin'
+  asm_deps += ['darwin.m4']
+endif
+
+asm_files = [
+%s
+]
+'''
+
+asm_to_s_files = '''\
+
+m4_prog = find_program('m4', native: true)
+
+foreach asm_file: asm_files
+  s_filename = fs.stem(asm_file) + '.s'
+  s_file = custom_target(s_filename,
+    input: [asm_file, config_m4] + asm_deps,
+    output: s_filename,
+    command: [m4_prog, '@INPUT0@'],
+    capture: true,
+  )
+  s_files += [s_file]
+endforeach
+'''
+
+
+asm_modules = [
+    ('mpn_extras/arm64', asm_submodule_arm64),
+    ('mpn_extras/x86_64/broadwell', asm_submodule_x86_broadwell),
+]
+
+
+def get_flint_modules(flint_root):
+    """
+    Scan the src directory and return a list of all modules.
+
+    Also check for possible mismatches between subdirs and headers.
+    """
+    src_path = join(flint_root, 'src')
+    is_mod = lambda p: isdir(join(src_path, p)) and p not in exclude_mod_dirs
+    subdirs = [p for p in listdir(src_path) if is_mod(p)]
+    headers = [p[:-2] for p in listdir(src_path) if p.endswith('.h')]
+
+    # Check for mismatches between subdirs and headers apart from the
+    # known exceptions (exclude_mod_dirs, mod_no_header, head_no_dir)
+    extra_dirs = set(subdirs) - set(headers) - set(mod_no_header)
+    extra_headers = set(headers) - set(subdirs) - set(head_no_dir) - set(headers_skip)
+    if extra_dirs or extra_headers:
+        print('Mismatch between subdirs and headers in src')
+        print('Extra headers:\n', '\n'.join(sorted(extra_headers)))
+        print('Extra subdirs:\n', '\n'.join(sorted(extra_dirs)))
+        sys.exit(1)
+    missing_dirs = set(mod_no_header) - set(subdirs)
+    missing_headers = (set(head_no_dir) - set(headers))
+    if missing_dirs or missing_headers:
+        print('Missing subdirs:\n', '\n'.join(sorted(missing_dirs)))
+        print('Missing headers:\n', '\n'.join(sorted(missing_headers)))
+        sys.exit(1)
+
+    return subdirs
+
+
+parser = ArgumentParser(description='Generate Meson build files')
+parser.add_argument('-q', '--quiet', action='store_true',
+                    help='Do not print anything')
+parser.add_argument('-v', '--verbose', action='store_true',
+                    help='Show all steps')
+parser.add_argument('output_dir', default='.', help='Output directory')
+
+
+def main(args):
+    args = parser.parse_args(args)
+
+    for fname in files_to_copy:
+        src_path = join(this_dir, fname)
+        dst_path = join(args.output_dir, fname)
+        if not args.quiet:
+            print('Copying %s to %s' % (src_path, dst_path))
+        copy_file(src_path, dst_path)
+
+    modules = get_flint_modules(args.output_dir)
+
+    # We will generate the meson.build file for conditional modules but the
+    # main meson.build file will decide whether to include them or not.
+    modules_unconditional = set(modules) - set(conditional_modules)
+
+    # src/meson.build
+    src_meson_build_text = src_meson_build % (
+        format_lines(modules_unconditional),
+        format_lines(mod_no_header),
+        format_lines(mod_no_tests),
+        format_lines(head_no_dir),
+    )
+    dst_path = join(args.output_dir, 'src', 'meson.build')
+    if not args.quiet:
+        print('Writing %s' % dst_path)
+    write_file(dst_path, src_meson_build_text)
+
+    if not args.quiet:
+        print('Making meson.build files in all modules')
+    # src/mod/meson.build
+    for mod in modules + mod_no_header:
+        mod_dir = join(args.output_dir, 'src', mod)
+        c_files = [f for f in listdir(mod_dir) if f.endswith('.c')]
+        if mod == 'fmpz':
+            c_files = [f for f in c_files if f != 'fmpz.c']
+        src_mod_meson_build_text = src_mod_meson_build % format_lines(c_files)
+        dst_path = join(mod_dir, 'meson.build')
+        if args.verbose:
+            print('Writing %s' % dst_path)
+        write_file(dst_path, src_mod_meson_build_text)
+
+        # src/mod/test/meson.build
+        if mod not in mod_no_tests:
+            test_dir = join(mod_dir, 'test')
+            test_mod_meson_build_text = test_mod_meson_build % mod
+            dst_path = join(test_dir, 'meson.build')
+            if args.verbose:
+                print('Writing %s' % dst_path)
+            write_file(dst_path, test_mod_meson_build_text)
+
+    # Build for the NTL tests
+    dst_path = join(args.output_dir, 'src', 'interfaces', 'test', 'meson.build')
+    test_mod_meson_build_text = test_mod_NTL_meson_build % 'NTL-interface'
+    if args.verbose:
+        print('Writing %s' % dst_path)
+    write_file(dst_path, test_mod_meson_build_text)
+
+    # src/mpn_extras/*/meson.build
+    for path, asm_submodule in asm_modules:
+        asm_dir = join(args.output_dir, 'src', path)
+        asm_files = [f for f in listdir(asm_dir) if f.endswith('.asm')]
+        asm_submodule_text = asm_submodule % format_lines(asm_files)
+        asm_submodule_text += asm_to_s_files
+        dst_path = join(asm_dir, 'meson.build')
+        if args.verbose:
+            print('Writing %s' % dst_path)
+        write_file(dst_path, asm_submodule_text)
+
+
+def format_lines(lst):
+    return '\n'.join(f"  '{m}'," for m in sorted(lst))
+
+
+def write_file(dst_path, text):
+    makedirs(dirname(dst_path), exist_ok=True)
+    with open(dst_path, 'w') as fout:
+        fout.write(text)
+
+
+def copy_file(src_path, dst_path):
+    makedirs(dirname(dst_path), exist_ok=True)
+    copyfile(src_path, dst_path)
+
+
+def same_files(src_path, dst_path):
+    with open(src_path, 'rb') as f:
+        src_content = f.read()
+    return same_content(src_content, dst_path)
+
+
+def same_content(src_content, dst_path):
+    try:
+        with open(dst_path, 'rb') as f:
+            dst_content = f.read()
+    except FileNotFoundError:
+        return False
+    else:
+        return src_content == dst_content
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))
diff --git a/_meson_build/include/flint/meson.build b/_meson_build/include/flint/meson.build
new file mode 100644
index 0000000000..9848867438
--- /dev/null
+++ b/_meson_build/include/flint/meson.build
@@ -0,0 +1,62 @@
+header_install_kwargs = {
+  'install_dir': get_option('includedir') / 'flint',
+  'install': true,
+}
+
+flint_h = configure_file(
+  input: '../../src/flint.h.in',
+  output: 'flint.h',
+  configuration: {
+    'FLINT_MAJOR': FLINT_MAJOR,
+    'FLINT_MINOR': FLINT_MINOR,
+    'FLINT_PATCH': FLINT_PATCH,
+    'FLINT_VERSION_FULL': FLINT_VERSION_FULL,
+  },
+  kwargs: header_install_kwargs,
+)
+
+flint_config_h = configure_file(
+  output: 'flint-config.h',
+  configuration: cfg_data,
+  kwargs: header_install_kwargs,
+)
+
+config_h = configure_file(
+  output: 'config.h',
+  configuration: cfg_data_internal,
+  kwargs: header_install_kwargs,
+)
+
+if gmp_long_long_limb
+  gmpcompat_h_in = 'gmpcompat-longlong.h.in'
+else
+  gmpcompat_h_in = 'gmpcompat.h.in'
+endif
+
+gmpcompat_h = fs.copyfile(
+  '../../src' / gmpcompat_h_in,
+  'gmpcompat.h',
+  kwargs: header_install_kwargs,
+)
+
+flint_mparam_h_in = '../../src/mpn_extras' / flint_mparam / 'flint-mparam.h'
+
+flint_mparam_h = fs.copyfile(
+  flint_mparam_h_in,
+  'flint-mparam.h',
+  kwargs: header_install_kwargs,
+)
+
+# Copy all the other headers to the build directory so you can do
+#   gcc -Ibuild-dir/include ...
+foreach h: headers_all
+  fs.copyfile(h, kwargs: header_install_kwargs)
+endforeach
+
+headers_all += [
+  flint_h,
+  flint_config_h,
+  config_h,
+  gmpcompat_h,
+  flint_mparam_h,
+]
diff --git a/_meson_build/include/meson.build b/_meson_build/include/meson.build
new file mode 100644
index 0000000000..39b83d76e8
--- /dev/null
+++ b/_meson_build/include/meson.build
@@ -0,0 +1,4 @@
+subdir('flint')
+
+headers_built_inc = include_directories('.')
+headers_built_nodir_inc = include_directories('flint')
diff --git a/_meson_build/meson.build b/_meson_build/meson.build
new file mode 100644
index 0000000000..375a229e3c
--- /dev/null
+++ b/_meson_build/meson.build
@@ -0,0 +1,78 @@
+project('FLINT', 'c', 'cpp',
+  version: '3.2.0-dev',
+  license: 'LGPL3+',
+  default_options: [
+    'buildtype=release',
+    'c_std=c11',
+    'cpp_std=c++11',
+  ],
+)
+
+FLINT_MAJOR = meson.project_version().split('.')[0]
+FLINT_MINOR = meson.project_version().split('.')[1]
+FLINT_PATCH = meson.project_version().split('.')[2]
+FLINT_VERSION_FULL = meson.project_version()
+
+cc = meson.get_compiler('c')
+fs = import('fs')
+
+m_dep = cc.find_library('m', required: false)
+
+gmp_dep = dependency('gmp', version: '>= 6.2.1')
+mpfr_dep = dependency('mpfr', version: '>= 4.1.0')
+
+flint_deps = [gmp_dep, mpfr_dep, m_dep]
+
+add_project_arguments(
+  '-Werror=implicit-function-declaration',
+  language: 'c',
+)
+
+subdir('config')
+subdir('src')
+
+if have_assembly
+  # Generate config.m4 with assembly language configuration
+  config_m4 = configure_file(
+    input: 'config.m4.in',
+    output: 'config.m4',
+    configuration: {'LSYM_PREFIX': LSYM_PREFIX},
+  )
+
+  s_files = []
+  subdir('src' / assembly_dir)
+  c_files_all += s_files
+endif
+
+# Build include directory after list of all source files is complete
+subdir('include')
+
+libflint = library('flint', c_files_all,
+  dependencies: flint_deps,
+  include_directories: [headers_built_nodir_inc, 'src'],
+  install: true,
+  c_args: [
+    '-DBUILDING_FLINT',
+    '-DFLINT_NOSTDIO',
+    '-DFLINT_NOSTDARG',
+  ]
+)
+
+tests_opt = get_option('tests')
+if tests_opt.enabled()
+  foreach mod_test : mod_tests
+    subdir('src' / mod_test)
+  endforeach
+else
+  bad_test_exe = executable('bad_test', '_meson_build/bad_test.c')
+  test('Need to build tests with -Dtests=enabled', bad_test_exe)
+endif
+
+pkg_mod = import('pkgconfig')
+pkg_mod.generate(
+  libraries: libflint,
+  version : '3.1.0',
+  name: 'flint',
+  filebase: 'flint',
+  description: 'Fast Library for Number Theory'
+)
diff --git a/_meson_build/meson.options b/_meson_build/meson.options
new file mode 100644
index 0000000000..dbaa7eaaaf
--- /dev/null
+++ b/_meson_build/meson.options
@@ -0,0 +1,38 @@
+option('tests', type : 'feature', value : 'disabled',
+        description: 'Build the tests')
+option('libtool_lock', type : 'feature', value : 'enabled',
+        description: 'Use libtool lock')
+option('pthread', type : 'feature', value : 'enabled',
+        description: 'Use pthread')
+option('reentrant', type : 'feature', value : 'disabled',
+        description: 'Build the reentrant module')
+option('thread_safe', type : 'feature', value : 'enabled',
+        description: 'Build the threadsafe module')
+option('assert', type : 'feature', value : 'disabled',
+        description: 'Enable assertions')
+option('pretty_tests', type : 'feature', value : 'enabled',
+        description: 'Pretty print test output')
+option('gmp_internals', type : 'feature', value : 'auto',
+        description: 'Use GMP internals')
+option('assembly', type : 'feature', value : 'auto',
+        description: 'Use assembly code')
+option('avx2', type : 'feature', value : 'disabled',
+        description: 'Use AVX2 instructions')
+option('avx512', type : 'feature', value : 'disabled',
+        description: 'Use AVX512 instructions')
+option('fft_small', type : 'feature', value : 'auto',
+        description: 'Build the fft_small module')
+option('blas', type : 'feature', value : 'disabled',
+        description: 'Use BLAS')
+option('gc', type : 'feature', value : 'disabled',
+        description: 'Use the Boehm garbage collector')
+option('ntl', type : 'feature', value : 'disabled',
+        description: 'Use NTL (in tests)')
+# Not included from original configure:
+# coverage  -Db_coverage=true
+# debug
+# dependency_tracking
+# with-pic
+# with-aix-soname
+# with-gnu-ld
+# with-sysroot
diff --git a/config/meson.build b/config/meson.build
new file mode 100644
index 0000000000..7a2a644ea8
--- /dev/null
+++ b/config/meson.build
@@ -0,0 +1,783 @@
+# -----------------------------------------------------------------------------
+#   FLINT configuration
+# -----------------------------------------------------------------------------
+
+# Data set on cfg_data is used to generate flint-config.h
+# Data set on cfg_data_internal (below) is also used to generate config.h
+cfg_data = configuration_data()
+
+# -----------------------------------------------------------------------------
+#   Project options
+# -----------------------------------------------------------------------------
+
+libtool_lock_opt = get_option('libtool_lock')
+pthread_opt = get_option('pthread')
+reentrant_opt = get_option('reentrant')
+thread_safe_opt = get_option('thread_safe')
+assert_opt = get_option('assert')
+pretty_tests_opt = get_option('pretty_tests')
+gmp_internals_opt = get_option('gmp_internals')
+assembly_opt = get_option('assembly')
+avx2_opt = get_option('avx2')
+avx512_opt = get_option('avx512')
+fft_small_opt = get_option('fft_small')
+blas_opt = get_option('blas')
+gc_opt = get_option('gc')
+ntl_opt = get_option('ntl')
+coverage_opt = get_option('b_coverage')
+
+# -----------------------------------------------------------------------------
+#   Unsupported options
+# -----------------------------------------------------------------------------
+
+if avx2_opt.enabled()
+  error('AVX2 support is not yet implemented')
+elif avx512_opt.enabled()
+  error('AVX512 support is not yet implemented')
+elif blas_opt.enabled()
+  error('BLAS support is not yet implemented')
+elif gc_opt.enabled()
+  error('Garbage collection is not yet implemented')
+endif
+
+# -----------------------------------------------------------------------------
+#   fmpz memory management
+# -----------------------------------------------------------------------------
+
+if gc_opt.allowed()
+  if thread_safe_opt.allowed()
+    error('Garbage collection is not thread safe')
+  else
+    fmpz_c_in = 'fmpz_gc.c'
+  endif
+else
+  if reentrant_opt.enabled()
+    fmpz_c_in = 'fmpz_reentrant.c'
+  else
+    fmpz_c_in = 'fmpz_single.c'
+  endif
+endif
+
+# -----------------------------------------------------------------------------
+#   libm
+# -----------------------------------------------------------------------------
+
+if not cc.has_function('atan2', dependencies: m_dep)
+  error('libm is required')
+endif
+
+# -----------------------------------------------------------------------------
+#   GMP
+# -----------------------------------------------------------------------------
+
+gmp_long_long_limb = cc.compiles('''
+#include <gmp.h>
+#if !defined(_LONG_LONG_LIMB)
+# error mp_limb_t != unsigned long long int
+#endif
+''')
+
+# We need to check with cc.links rather than cc.has_function etc because we
+# are checking if we can link symbols not in the headers.
+
+gmp_test_code = '''
+#include <gmp.h>
+
+@0@
+
+int main() {
+  @1@
+  return 0;
+}
+'''
+
+gmp_base_tests = {
+  'mpz_init': ['', 'mpz_t x; __gmpz_init(x);'],
+  'mpn_mul_basecase': [
+    'void __gmpn_mul_basecase(mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t);',
+    'mp_limb_t a[2], b[2], p[4]; __gmpn_mul_basecase(p, a, (mp_size_t) 1, b, (mp_size_t) 1);',
+  ],
+}
+
+foreach name, snippets : gmp_base_tests
+  _code = gmp_test_code.format(snippets[0], snippets[1])
+  if not cc.links(_code, dependencies: gmp_dep, name: name + ' links')
+    message('This code did not link with -lgmp: ', _code)
+    error('GMP does not have ' + name)
+  endif
+endforeach
+
+if gmp_internals_opt.allowed()
+
+  gmp_internals_required = {
+    'mpn_gcd_11': ['', 'mp_limb_t res, a=0, b=0; res = __gmpn_gcd_11(a, b);'],
+    'mpn_div_q': [
+      'void __gmpn_div_q(mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t, mp_ptr);',
+      'mp_limb_t q[2], n[2], d[2], t[2]; __gmpn_div_q(q, t, (mp_size_t) 1, n, (mp_size_t) 1, d);'
+    ],
+  }
+
+  foreach name, snippets : gmp_internals_required
+    _code = gmp_test_code.format(snippets[0], snippets[1])
+    if not cc.links(_code, dependencies: gmp_dep, name: name + ' links')
+      message('This code did not link with -lgmp: ', _code)
+      # Maybe disable gmp_internals rather than error?
+      error('GMP does not have ' + name)
+    endif
+  endforeach
+
+  gmp_internals_optional = {
+    'mpn_add_n_sub_n': [
+      'void __gmpn_add_n_sub_n(mp_ptr, mp_ptr, mp_ptr, mp_size_t);',
+      'mp_limb_t a[2], b[2], c[2], d[2]; __gmpn_add_n_sub_n(a, b, c, (mp_size_t) 1);',
+    ],
+    'mpn_add_nc': [
+      'mp_limb_t __gmpn_add_nc(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);',
+      'mp_limb_t a[2], b[2]; __gmpn_add_nc(a, b, (mp_size_t) 1, (mp_limb_t) 0);',
+    ],
+    'mpn_addlsh1_n': [
+      'mp_limb_t __gmpn_addlsh1_n(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);',
+      'mp_limb_t a[2], b[2]; __gmpn_addlsh1_n(a, b, (mp_size_t) 1, (mp_limb_t) 0);',
+    ],
+    'mpn_addlsh1_n_ip1': [
+      'mp_limb_t __gmpn_addlsh1_n_ip1(mp_ptr, mp_srcptr, mp_size_t);',
+      'mp_limb_t a[2], b[2]; __gmpn_addlsh1_n_ip1(a, b, (mp_size_t) 1);',
+    ],
+    'mpn_addmul_2': [
+      'void __gmpn_addmul_2(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);',
+      'mp_limb_t a[2], b[2]; __gmpn_addmul_2(a, b, (mp_size_t) 1, (mp_limb_t) 0);',
+    ],
+    'mpn_modexact_1_odd': [
+      'mp_limb_t __gmpn_modexact_1_odd(mp_srcptr, mp_size_t, mp_limb_t);',
+      'mp_limb_t a[2]; __gmpn_modexact_1_odd(a, (mp_size_t) 1, (mp_limb_t) 0);',
+    ],
+    'mpn_rsh1add_n': [
+      'mp_limb_t __gmpn_rsh1add_n(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);',
+      'mp_limb_t a[2], b[2]; __gmpn_rsh1add_n(a, b, (mp_size_t) 1, (mp_limb_t) 0);',
+    ],
+    'mpn_rsh1sub_n': [
+      'mp_limb_t __gmpn_rsh1sub_n(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);',
+      'mp_limb_t a[2], b[2]; __gmpn_rsh1sub_n(a, b, (mp_size_t) 1, (mp_limb_t) 0);',
+    ],
+    'mpn_sub_nc': [
+      'mp_limb_t __gmpn_sub_nc(mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);',
+      'mp_limb_t a[2], b[2]; __gmpn_sub_nc(a, b, (mp_size_t) 1, (mp_limb_t) 0);',
+    ],
+  }
+
+  foreach name, snippets : gmp_internals_optional
+    _code = gmp_test_code.format(snippets[0], snippets[1])
+    if cc.links(_code, dependencies: gmp_dep, name: name + ' links')
+      cfg_data.set('FLINT_HAVE_NATIVE_' + name, 1,
+        description: 'Define if GMP has ' + name)
+    endif
+  endforeach
+
+endif
+
+# -----------------------------------------------------------------------------
+#    CPU detection
+# -----------------------------------------------------------------------------
+
+# We check here for gcc or clang because the config.guess script probably does
+# not work for other compilers. This check will also allow MinGW and cygwin on
+# Windows but not MSVC.
+
+# The config.guess script inspects the current CPU so should not be used for a
+# cross build. Maybe there should be a way to configure the exact CPU as a
+# project build option.
+
+if not meson.is_cross_build() and cc.get_id() in ['gcc', 'clang']
+  config_guess_result = run_command('./config.guess', check: true)
+  target_triple = config_guess_result.stdout().strip()
+  exact_cpu = target_triple.split('-')[0]
+else
+  # This could be 'aarch64' or 'x86_64'
+  # https://mesonbuild.com/Reference-tables.html#cpu-families
+  exact_cpu = host_machine.cpu_family()
+endif
+
+message('EXACT CPU:', exact_cpu)
+
+################################################################################
+# architecture specifics
+################################################################################
+
+gcc_cflags = '-O3 -pedantic -std=c11'
+gcc_warnings = '-Werror=implicit-function-declaration -Wall -Wno-stringop-overread -Wno-stringop-overflow'
+
+# We only try to provide specifics for those systems that currently supports
+# our assembly routines. If more combinations are wished for than what is
+# specified, please open up an issue at
+# <https://github.com/flintlib/flint/issues/> and we will consider it.
+#
+# For these systems we aim to provide:
+#
+#   gcc_cflags     flags for GCC-compatible compilers
+#
+#   asm_path       directory for assembly, relative to src/mpn_extras
+#   param_path     directory for flint-mparam.h, relative to src/mpn_extras
+#
+# For x86_64 systems, we can also set:
+#
+#   have_avx512    system has AVX512F and AVX512DQ (we assume this implies have_avx2)
+#   have_avx2      system has AVX2 and FMA
+
+# x86_64 CPUs that have AVX2:
+X86_64_ADX_CPU_NAMES = [
+  'zen1',
+  'zen2',
+  'zen3',
+  'zen4',
+  'coreibwl',
+  'broadwell',
+  'skylake',
+  'skylake_server',
+  'cannonlake',
+  'kabylake',
+  'icelake',
+  'icelake_server',
+  'rocketlake',
+  'tigerlake',
+  'alderlake',
+  'raptorlake',
+  'knightslanding',
+  'sapphirerapids',
+  'cometlake',
+]
+
+if host_machine.cpu_family() == 'aarch64'
+
+  gcc_cflags  =  gcc_cflags
+  gcc_cflags_optlist = 'arch tune'
+  asm_path = 'arm64'
+  param_path = 'arm64'
+  flint_cv_have_fft_small_arm_i = 'yes'
+  flint_know_strong_order = 'no'
+
+  # NOTE: Cortex values where taken from
+  # https://developer.arm.com/Processors/Cortex-XXX
+
+  if exact_cpu in ['armcortexa35', 'armcortexa35neon']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=cortex-a35'
+
+  elif exact_cpu in ['armcortexa53', 'armcortexa53neon']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=cortex-a53'
+
+  elif exact_cpu in ['armcortexa55', 'armcortexa55neon']
+    gcc_cflags_arch = '-march=armv8.2-a'
+    gcc_cflags_tune = '-mtune=cortex-a55'
+
+  elif exact_cpu in ['armcortexa57', 'armcortexa57neon']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=cortex-a57'
+
+  elif exact_cpu in ['armcortexa72', 'armcortexa72neon']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=cortex-a72'
+
+  elif exact_cpu in ['armcortexa73', 'armcortexa73neon']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=cortex-a73'
+
+  elif exact_cpu in ['armcortexa75', 'armcortexa75neon']
+    gcc_cflags_arch = '-march=armv8.2-a'
+    gcc_cflags_tune = '-mtune=cortex-a75'
+
+  elif exact_cpu in ['armcortexa76', 'armcortexa76neon']
+    gcc_cflags_arch = '-march=armv8.2-a'
+    gcc_cflags_tune = '-mtune=cortex-a76'
+
+  elif exact_cpu in ['armcortexa77', 'armcortexa77neon']
+    gcc_cflags_arch = '-march=armv8.2-a'
+    gcc_cflags_tune = '-mtune=cortex-a77'
+
+  elif exact_cpu in ['armcortexa65', 'armcortexa65neon']
+    gcc_cflags_arch = '-march=armv8.2-a'
+    gcc_cflags_tune = '-mtune=cortex-a65'
+
+  elif exact_cpu in ['armcortexa34', 'armcortexa34neon']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=cortex-a34'
+
+  elif exact_cpu in ['armcortexa78', 'armcortexa78neon']
+    gcc_cflags_arch = '-march=armv8.2-a'
+    gcc_cflags_tune = '-mtune=cortex-a78'
+
+  elif exact_cpu in ['armexynosm1']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=exynos-m1'
+
+  elif exact_cpu in ['armthunderx']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=thunderx'
+
+  elif exact_cpu in ['armxgene1']
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = '-mtune=xgene1'
+
+  elif exact_cpu.startswith('applem1')
+    gcc_cflags_arch = '-march=armv8.5-a'
+    gcc_cflags_tune = ''
+
+  elif exact_cpu.startswith('applem2')
+    gcc_cflags_arch = '-march=armv8.6-a'
+    gcc_cflags_tune = ''
+
+  elif exact_cpu.startswith('applem3')
+    gcc_cflags_arch = '-march=armv8.6-a'
+    gcc_cflags_tune = ''
+
+  elif exact_cpu.startswith('aarch64') or exact_cpu.startswith('armv8')
+    gcc_cflags_arch = '-march=armv8-a'
+    gcc_cflags_tune = ''
+
+  else
+    error_msg = '''
+    Error in deciding flags for @0@.
+    Please report at <https://github.com/flintlib/flint/issues/>
+    '''
+    error(error_msg.format(exact_cpu))
+
+  endif
+
+elif host_machine.cpu_family() == 'x86_64' and exact_cpu in X86_64_ADX_CPU_NAMES
+
+  gcc_cflags = gcc_cflags
+  gcc_cflags_optlist = 'arch'
+  asm_path = 'x86_64/broadwell'
+  param_path = 'x86_64/broadwell'
+
+  have_avx512 = 'no'
+  have_avx2 = 'yes'
+  flint_cv_have_fft_small_x86_i = 'yes'
+  flint_know_strong_order = 'yes'
+
+  if exact_cpu == 'zen'
+    gcc_cflags_arch = '-march=znver1'
+    param_path = 'x86_64/zen3'
+
+  elif exact_cpu == 'zen2'
+    gcc_cflags_arch = '-march=znver2'
+    param_path = 'x86_64/zen3'
+
+  elif exact_cpu == 'zen3'
+    gcc_cflags_arch = '-march=znver3'
+    param_path = 'x86_64/zen3'
+
+  elif exact_cpu == 'zen4'
+    gcc_cflags_arch = '-march=znver4'
+    param_path = 'x86_64/zen3'
+    have_avx512 = 'yes'
+
+  elif exact_cpu in ['coreibwl', 'broadwell']
+    gcc_cflags_arch = '-march=broadwell'
+    param_path = 'x86_64/skylake'
+
+  elif exact_cpu == 'skylake'
+    gcc_cflags_arch = '-march=skylake'
+    param_path = 'x86_64/skylake'
+
+  elif exact_cpu == 'skylake_server'
+    gcc_cflags_arch = '-march=skylake-avx512'
+    param_path = 'x86_64/skylake'
+    have_avx512 = 'yes'
+
+  elif exact_cpu == 'cannonlake'
+    gcc_cflags_arch = '-march=cannonlake'
+    param_path = 'x86_64/skylake'
+    have_avx512 = 'yes'
+
+  elif exact_cpu == 'kabylake'
+    gcc_cflags_arch = '-march=skylake'
+    param_path = 'x86_64/skylake'
+
+  elif exact_cpu == 'icelake'
+    gcc_cflags_arch = '-march=icelake-client'
+    param_path = 'x86_64/skylake'
+    have_avx512 = 'yes'
+
+  elif exact_cpu == 'icelake_server'
+    gcc_cflags_arch = '-march=icelake-server'
+    param_path = 'x86_64/skylake'
+    have_avx512 = 'yes'
+
+  elif exact_cpu == 'rocketlake'
+    gcc_cflags_arch = '-march=rocketlake'
+    param_path = 'x86_64/skylake'
+    have_avx512 = 'yes'
+
+  elif exact_cpu == 'tigerlake'
+    gcc_cflags_arch = '-march=tigerlake'
+    param_path = 'x86_64/skylake'
+    have_avx512 = 'yes'
+
+  elif exact_cpu == 'alderlake'
+    gcc_cflags_arch = '-march=alderlake'
+    param_path = 'x86_64/skylake'
+
+  elif exact_cpu == 'raptorlake'
+    gcc_cflags_arch = '-march=alderlake'
+    param_path = 'x86_64/skylake'
+
+  elif exact_cpu == 'knightslanding'
+    gcc_cflags_arch = '-march=knl'
+    param_path = 'x86_64/skylake'
+
+  elif exact_cpu == 'sapphirerapids'
+    gcc_cflags_arch = '-march=sapphirerapids'
+    param_path = 'x86_64/skylake'
+    have_avx512 = 'yes'
+
+  elif exact_cpu == 'cometlake'
+    gcc_cflags_arch = '-march=kabylake'
+    param_path = 'x86_64/skylake'
+
+  else
+    error_msg = '''
+    Error in deciding flags for @0@.
+    Please report at <https://github.com/flintlib/flint/issues/>
+    '''
+    error(error_msg.format(exact_cpu))
+
+  endif
+
+  # Disable assembly on Windows
+  if host_machine.system() == 'windows'
+    flint_nonstd_abi = 'yes'
+    asm_path = ''
+    param_path = 'x86_64'
+  endif
+
+elif host_machine.cpu_family() == 'x86_64'
+
+  # NOTE: We do not care if the user specifies noavx!
+  gcc_cflags = '$gcc_cflags'
+  gcc_cflags_optlist = 'arch'
+  asm_path = ''
+  param_path = 'x86_64'
+
+  have_avx512 = 'no'
+  have_avx2 = 'no'
+  flint_know_strong_order = 'yes'
+
+  if exact_cpu in ['coreiibr', 'ivybridge']
+    gcc_cflags_arch = '-march=ivybridge'
+
+  elif exact_cpu in ['coreihwl', 'haswell']
+    gcc_cflags_arch = '-march=haswell'
+
+  elif exact_cpu in ['piledriver', 'bd2']
+    gcc_cflags_arch = '-march=bdver2'
+
+  elif exact_cpu in ['steamroller', 'bd3']
+    gcc_cflags_arch = '-march=bdver3'
+
+  elif exact_cpu in ['excavator', 'bd4']
+    gcc_cflags_arch = '-march=bdver4'
+    have_avx2 = 'yes'
+
+  elif exact_cpu in ['x86_64v3']
+    gcc_cflags_arch = '-march=x86-64-v3'
+    have_avx2 = 'yes'
+
+  elif exact_cpu in ['x86_64v4']
+    gcc_cflags_arch = '-march=x86-64-v4'
+    have_avx512 = 'yes'
+
+  else
+    flint_know_strong_order = 'no'
+
+  endif
+
+  if have_avx512 == 'yes'
+    have_avx2 = 'yes'
+  endif
+
+  if have_avx2 == 'yes'
+    flint_cv_have_fft_small_x86_i = 'yes'
+  else
+    flint_cv_have_fft_small_x86_i = 'no'
+  endif
+
+else
+  # Generic case for unrecognised CPU
+  asm_path = ''
+  param_path = 'generic'
+  gcc_cflags = gcc_cflags
+
+  have_avx512 = 'no'
+  have_avx2 = 'no'
+  flint_cv_have_fft_small_x86_i = 'no'
+  flint_know_strong_order = 'no'
+
+endif
+
+# -----------------------------------------------------------------------------
+#   mparam
+# -----------------------------------------------------------------------------
+
+flint_mparam = param_path
+
+# -----------------------------------------------------------------------------
+#    fft_small
+# -----------------------------------------------------------------------------
+
+fft_small_opt = get_option('fft_small')
+
+fft_small_arm_code = '''
+#include <arm_neon.h>
+#if !(defined(__GNUC__) && defined(__ARM_NEON))
+# if !(defined(_MSC_VER) && defined(_M_ARM64))
+#  error
+error
+# endif
+#endif
+int main(){return 0;}
+'''
+
+fft_small_x86_code = '''
+#if defined(__GNUC__)
+# include <x86intrin.h>
+#elif defined(_MSC_VER)
+# include <intrin.h>
+#else
+# error
+error
+#endif
+
+#if !defined(__AVX2__)
+# error
+error
+#endif
+int main(){return 0;}
+'''
+
+if host_machine.cpu_family() == 'aarch64'
+  fft_small_supported = (
+    flint_cv_have_fft_small_arm_i == 'yes'
+    and cc.compiles(fft_small_arm_code, dependencies: gmp_dep)
+  )
+else
+  fft_small_supported = (
+    flint_cv_have_fft_small_x86_i == 'yes'
+    and cc.compiles(fft_small_x86_code, dependencies: gmp_dep)
+  )
+endif
+
+# Error if fft_small enabled but not possible, otherwise disable if auto
+fft_small_opt = fft_small_opt.require(fft_small_supported,
+    error_message: 'host CPU does not support AVX/NEON. Set fft_small to disabled or auto')
+
+# If fft_small was auto we enable it if possible
+fft_small_opt = fft_small_opt.enable_auto_if(fft_small_supported)
+have_fft_small = fft_small_opt.enabled()
+
+message('FFT_SMALL: ', have_fft_small ? 'enabled' : 'disabled')
+
+# -----------------------------------------------------------------------------
+#   Assembly
+# -----------------------------------------------------------------------------
+
+if asm_path != ''
+  assembly_cpu_supported = true
+  assembly_dir = 'mpn_extras/' + asm_path
+  if host_machine.cpu_family() == 'aarch64'
+    FLINT_HAVE_ASSEMBLY = 'armv8'
+  elif host_machine.cpu_family() == 'x86_64'
+    FLINT_HAVE_ASSEMBLY = 'x86_64_adx'
+  else
+    error('Assembly only supported for x86_64 or aarch64')
+  endif
+else
+  assembly_cpu_supported = false
+  FLINT_HAVE_ASSEMBLY = ''
+endif
+
+assembly_opt = assembly_opt.require(host_machine.system() != 'windows',
+    error_message: 'No assembly support for Windows')
+
+assembly_opt = assembly_opt.require(assembly_cpu_supported,
+    error_message: 'No assembly support for host CPU')
+
+assembly_opt = assembly_opt.enable_auto_if(true)
+have_assembly = assembly_opt.enabled()
+
+message('ASSEMBLY: ', have_assembly ? 'enabled' : 'disabled')
+
+# XXX: Need better detection of ASM language support
+if have_assembly
+  LSYM_PREFIX = 'L'
+endif
+
+# -----------------------------------------------------------------------------
+#   cpu_set_t support
+# -----------------------------------------------------------------------------
+
+have_cpu_set_t = cc.compiles('''
+#define _GNU_SOURCE
+#include <sched.h>
+#include <pthread.h>
+
+int main() {
+  cpu_set_t s;
+  CPU_ZERO(&s);
+  pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &s);
+  return 0;
+}
+''')
+
+# -----------------------------------------------------------------------------
+#  external configuration for flint_config.h
+# -----------------------------------------------------------------------------
+
+if host_machine.endian() == 'big'
+  cfg_data.set('FLINT_BIG_ENDIAN', 1,
+    description: 'Define if system is big endian.')
+endif
+
+if have_assembly and FLINT_HAVE_ASSEMBLY == 'armv8'
+  cfg_data.set('FLINT_HAVE_ASSEMBLY_armv8', 1,
+    description: 'Define if Arm v8 assembly is available')
+elif have_assembly and FLINT_HAVE_ASSEMBLY == 'x86_64_adx'
+  cfg_data.set('FLINT_HAVE_ASSEMBLY_x86_64_adx', 1,
+    description: 'Define if x86_64 ADX assembly is available')
+endif
+
+if have_fft_small
+  cfg_data.set('FLINT_HAVE_FFT_SMALL', 1,
+    description: 'Define to use the fft_small module')
+endif
+
+if flint_know_strong_order == 'yes'
+  cfg_data.set('FLINT_KNOW_STRONG_ORDER', 1,
+    description: 'Define if system is strongly ordered')
+endif
+
+if reentrant_opt.enabled()
+  cfg_data.set('FLINT_REENTRANT', 1,
+    description: 'Define to enable reentrant.')
+endif
+
+cfg_data.set('FLINT_UNROLL_LOOPS', 1,
+  description: 'Define to locally unroll some loops')
+
+if blas_opt.enabled()
+  cfg_data.set('FLINT_USES_BLAS', 1,
+    description: 'Define to enable BLAS.')
+endif
+
+if have_cpu_set_t
+  cfg_data.set('FLINT_USES_CPUSET', 1,
+    description: 'Define if system has cpu_set_t')
+endif
+
+if gc_opt.enabled()
+  cfg_data.set('FLINT_USES_GC', 1,
+    description: 'Define to enable the Boehm-Demers-Weise garbage collector.')
+endif
+
+if pthread_opt.enabled()
+  cfg_data.set('FLINT_USES_PTHREAD', 1,
+    description: 'Define to enable the use of pthread.')
+endif
+
+if thread_safe_opt.enabled()
+  cfg_data.set('FLINT_USES_TLS', 1,
+    description: 'Define to enable thread-local storage.')
+endif
+
+if assert_opt.enabled()
+  cfg_data.set('FLINT_WANT_ASSERT', 1,
+    description: 'Define to enable use of asserts.')
+endif
+
+if gmp_internals_opt.allowed()
+  cfg_data.set('FLINT_WANT_GMP_INTERNALS', 1,
+    description: 'Define to enable use of GMP internals.')
+endif
+
+if pretty_tests_opt.enabled()
+  cfg_data.set('FLINT_WANT_PRETTY_TESTS', 1,
+    description: 'Define to enable pretty printing for tests.')
+endif
+
+# -----------------------------------------------------------------------------
+#  internal configuration for config.h
+# -----------------------------------------------------------------------------
+
+cfg_data_internal = cfg_data
+
+#cfg_data_internal.set('AC_APPLE_UNIVERSAL_BUILD', 1)
+
+if coverage_opt
+  cfg_data_internal.set('FLINT_COVERAGE', 1)
+endif
+
+if cc.has_header_symbol('stdlib.h', 'aligned_alloc')
+  cfg_data_internal.set('HAVE_ALIGNED_ALLOC', 1,
+    description: 'Define to 1 if you have the \'aligned_alloc\' function.')
+endif
+
+ac_headers_check = [
+  'alloca.h',
+  'arm_neon.h',
+  'dlfcn.h',
+  'errno.h',
+  'fenv.h',
+  'float.h',
+  'immintrin.h', # XXX: has_header is not equivalent to autotools check...
+  'inttypes.h',
+  'malloc.h',
+  'math.h',
+  'pthread_np.h',
+  'stdarg.h',
+  'stdint.h',
+  'stdio.h',
+  'stdlib.h',
+  'string.h',
+  'strings.h',
+  'sys/param.h',
+  'sys/stat.h',
+  'sys/types.h',
+  'unistd.h',
+  'windows.h',
+]
+
+foreach h : ac_headers_check
+  if cc.has_header(h)
+    cfg_data_internal.set('HAVE_' + h.underscorify().to_upper(), 1,
+      description: f'Define to 1 if you have the <@h@> header file.')
+  endif
+endforeach
+
+# XXX: Do we need to check for _alligned_malloc?
+
+# XXX: Need better checking for LSYM_PREFIX and related assembly options
+cfg_data_internal.set_quoted('LSYM_PREFIX', 'L',
+  description: 'Assembler local label prefix')
+
+cfg_data_internal.set_quoted('LT_OBJDIR', '.libs/',
+  description: 'Define to the sub-directory where libtool stores uninstalled libraries.')
+cfg_data_internal.set_quoted('PACKAGE_BUGREPORT',
+  'https://github.com/flintlib/flint/issues/',
+  description: 'Define to the address where bug reports for this package should be sent.')
+cfg_data_internal.set_quoted('PACKAGE_NAME', 'FLINT',
+  description: 'Define to the full name of this package.')
+cfg_data_internal.set_quoted('PACKAGE_STRING', 'FLINT ' + FLINT_VERSION_FULL,
+  description: 'Define to the full name and version of this package.')
+cfg_data_internal.set_quoted('PACKAGE_TARNAME', 'flint',
+  description: 'Define to the one symbol short name of this package.')
+cfg_data_internal.set_quoted('PACKAGE_URL', 'https://flintlib.org/',
+  description: 'Define to the home page for this package.')
+cfg_data_internal.set_quoted('PACKAGE_VERSION', FLINT_VERSION_FULL,
+  description: 'Define to the version of this package.')
+
+cfg_data_internal.set('STDC_HEADERS', 1,
+  description: '''Define to 1 if all of the C89 standard headers exist (not just the ones
+   required in a freestanding environment). This macro is provided for
+   backward compatibility; new code need not use it.''')
+
+# XXX: set WORDS_BIGENDIAN or undef inline here?
diff --git a/meson_bootstrap.py b/meson_bootstrap.py
new file mode 100755
index 0000000000..e610fdc18a
--- /dev/null
+++ b/meson_bootstrap.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+
+from os.path import join, dirname, abspath
+from sys import executable as python, argv
+from subprocess import check_call
+
+this_dir = dirname(abspath(__file__))
+meson_build_dir = join(this_dir, '_meson_build')
+generate_script = join(meson_build_dir, 'generate_meson_build.py')
+check_call([python, generate_script, this_dir] + argv[1:])