Merge changes Ibcefd6d9,I249c0815,If59cb6da

* changes:
  Remove some duplication in the makefile
  linker: add test for zero e_shentsize
  Add test for misaligned section header
diff --git a/libc/bionic/sysconf.cpp b/libc/bionic/sysconf.cpp
index 7be0ab7..4a23fec 100644
--- a/libc/bionic/sysconf.cpp
+++ b/libc/bionic/sysconf.cpp
@@ -29,10 +29,10 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <linux/uio.h>  // For UIO_MAXIOV.
 #include <pthread.h>
 #include <stdio.h>  // For FOPEN_MAX.
 #include <sys/auxv.h>
+#include <sys/param.h>
 #include <sys/resource.h>
 #include <sys/sysinfo.h>
 #include <time.h>
@@ -48,18 +48,39 @@
 
 long sysconf(int name) {
   switch (name) {
-    case _SC_ARG_MAX:           return ARG_MAX;
+    //
+    // Things we actually have to calculate...
+    //
+    case _SC_ARG_MAX:
+      // Not a constant since Linux 2.6.23; see fs/exec.c for details.
+      // At least 32 pages, otherwise a quarter of the stack limit.
+      return MAX(__sysconf_rlimit(RLIMIT_STACK) / 4, _KERNEL_ARG_MAX);
+
+    case _SC_AVPHYS_PAGES:      return get_avphys_pages();
+    case _SC_CHILD_MAX:         return __sysconf_rlimit(RLIMIT_NPROC);
+    case _SC_CLK_TCK:           return static_cast<long>(getauxval(AT_CLKTCK));
+    case _SC_NPROCESSORS_CONF:  return get_nprocs_conf();
+    case _SC_NPROCESSORS_ONLN:  return get_nprocs();
+    case _SC_OPEN_MAX:          return __sysconf_rlimit(RLIMIT_NOFILE);
+
+    case _SC_PAGESIZE:
+    case _SC_PAGE_SIZE:
+      // _SC_PAGESIZE and _SC_PAGE_SIZE are distinct, but return the same value.
+      return static_cast<long>(getauxval(AT_PAGESZ));
+
+    case _SC_PHYS_PAGES:        return get_phys_pages();
+
+    //
+    // Constants...
+    //
     case _SC_BC_BASE_MAX:       return _POSIX2_BC_BASE_MAX;   // Minimum requirement.
     case _SC_BC_DIM_MAX:        return _POSIX2_BC_DIM_MAX;    // Minimum requirement.
     case _SC_BC_SCALE_MAX:      return _POSIX2_BC_SCALE_MAX;  // Minimum requirement.
     case _SC_BC_STRING_MAX:     return _POSIX2_BC_STRING_MAX; // Minimum requirement.
-    case _SC_CHILD_MAX:         return __sysconf_rlimit(RLIMIT_NPROC);
-    case _SC_CLK_TCK:           return static_cast<long>(getauxval(AT_CLKTCK));
     case _SC_COLL_WEIGHTS_MAX:  return _POSIX2_COLL_WEIGHTS_MAX;  // Minimum requirement.
     case _SC_EXPR_NEST_MAX:     return _POSIX2_EXPR_NEST_MAX;     // Minimum requirement.
     case _SC_LINE_MAX:          return _POSIX2_LINE_MAX;          // Minimum requirement.
     case _SC_NGROUPS_MAX:       return NGROUPS_MAX;
-    case _SC_OPEN_MAX:          return __sysconf_rlimit(RLIMIT_NOFILE);
     case _SC_PASS_MAX:          return PASS_MAX;
     case _SC_2_C_BIND:          return _POSIX2_C_BIND;
     case _SC_2_C_DEV:           return _POSIX2_C_DEV;
@@ -84,12 +105,7 @@
     case _SC_XOPEN_REALTIME_THREADS: return _XOPEN_REALTIME_THREADS;
     case _SC_XOPEN_LEGACY:      return _XOPEN_LEGACY;
     case _SC_ATEXIT_MAX:        return LONG_MAX;    // Unlimited.
-    case _SC_IOV_MAX:           return UIO_MAXIOV;
-
-    // _SC_PAGESIZE and _SC_PAGE_SIZE are distinct, but return the same value.
-    case _SC_PAGESIZE:
-    case _SC_PAGE_SIZE:
-      return static_cast<long>(getauxval(AT_PAGESZ));
+    case _SC_IOV_MAX:           return IOV_MAX;
 
     case _SC_XOPEN_UNIX:        return _XOPEN_UNIX;
     case _SC_AIO_LISTIO_MAX:    return _POSIX_AIO_LISTIO_MAX;     // Minimum requirement.
@@ -132,10 +148,6 @@
     case _SC_THREAD_PRIO_INHERIT: return _POSIX_THREAD_PRIO_INHERIT;
     case _SC_THREAD_PRIO_PROTECT: return _POSIX_THREAD_PRIO_PROTECT;
     case _SC_THREAD_SAFE_FUNCTIONS:  return _POSIX_THREAD_SAFE_FUNCTIONS;
-    case _SC_NPROCESSORS_CONF:  return get_nprocs_conf();
-    case _SC_NPROCESSORS_ONLN:  return get_nprocs();
-    case _SC_PHYS_PAGES:        return get_phys_pages();
-    case _SC_AVPHYS_PAGES:      return get_avphys_pages();
     case _SC_MONOTONIC_CLOCK:   return _POSIX_VERSION;
 
     case _SC_2_PBS:             return -1;     // Obsolescent in POSIX.1-2008.
diff --git a/libc/include/android/versioning.h b/libc/include/android/versioning.h
new file mode 100644
index 0000000..3686261
--- /dev/null
+++ b/libc/include/android/versioning.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_VERSIONING_H
+#define ANDROID_VERSIONING_H
+
+#define __INTRODUCED_IN(api_level) __attribute__((annotate("introduced_in=" #api_level)))
+#define __INTRODUCED_IN_FUTURE __attribute__((annotate("introduced_in_future")))
+#define __DEPRECATED_IN(api_level) __attribute__((annotate("deprecated_in=" #api_level)))
+#define __REMOVED_IN(api_level) __attribute__((annotate("obsoleted_in=" #api_level)))
+#define __INTRODUCED_IN_32(api_level) __attribute__((annotate("introduced_in_32=" #api_level)))
+#define __INTRODUCED_IN_64(api_level) __attribute__((annotate("introduced_in_64=" #api_level)))
+#define __INTRODUCED_IN_ARM(api_level) __attribute__((annotate("introduced_in_arm=" #api_level)))
+#define __INTRODUCED_IN_X86(api_level) __attribute__((annotate("introduced_in_x86=" #api_level)))
+#define __INTRODUCED_IN_MIPS(api_level) __attribute__((annotate("introduced_in_mips=" #api_level)))
+
+#endif /* ANDROID_VERSIONING_H */
diff --git a/libc/include/bits/pthread_types.h b/libc/include/bits/pthread_types.h
index 194a49b..7fc379b 100644
--- a/libc/include/bits/pthread_types.h
+++ b/libc/include/bits/pthread_types.h
@@ -32,8 +32,6 @@
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
-typedef long pthread_t;
-
 typedef struct {
   uint32_t flags;
   void* stack_base;
@@ -46,4 +44,58 @@
 #endif
 } pthread_attr_t;
 
+typedef struct {
+#if defined(__LP64__)
+  int64_t __private[4];
+#else
+  int32_t __private[8];
+#endif
+} pthread_barrier_t;
+
+typedef int pthread_barrierattr_t;
+
+typedef struct {
+#if defined(__LP64__)
+  int32_t __private[12];
+#else
+  int32_t __private[1];
+#endif
+} pthread_cond_t;
+
+typedef long pthread_condattr_t;
+
+typedef int pthread_key_t;
+
+typedef struct {
+#if defined(__LP64__)
+  int32_t __private[10];
+#else
+  int32_t __private[1];
+#endif
+} pthread_mutex_t;
+
+typedef long pthread_mutexattr_t;
+
+typedef int pthread_once_t;
+
+typedef struct {
+#if defined(__LP64__)
+  int32_t __private[14];
+#else
+  int32_t __private[10];
+#endif
+} pthread_rwlock_t;
+
+typedef long pthread_rwlockattr_t;
+
+typedef struct {
+#if defined(__LP64__)
+  int64_t __private;
+#else
+  int32_t __private[2];
+#endif
+} pthread_spinlock_t;
+
+typedef long pthread_t;
+
 #endif
diff --git a/libc/include/locale.h b/libc/include/locale.h
index a8f03bc..a681a17 100644
--- a/libc/include/locale.h
+++ b/libc/include/locale.h
@@ -32,6 +32,9 @@
 #include <sys/cdefs.h>
 #include <xlocale.h>
 
+#define __need_NULL
+#include <stddef.h>
+
 __BEGIN_DECLS
 
 #define LC_CTYPE           0
diff --git a/libc/include/math.h b/libc/include/math.h
index 1afef4c..37aa2cc 100644
--- a/libc/include/math.h
+++ b/libc/include/math.h
@@ -81,6 +81,8 @@
 #define HUGE MAXFLOAT
 #endif
 
+extern int signgam;
+
 /*
  * Most of these functions depend on the rounding mode and have the side
  * effect of raising floating-point exceptions, so they are not declared
@@ -297,6 +299,13 @@
 long double tgammal(long double) __INTRODUCED_IN(21);
 long double truncl(long double);
 
+double j0(double);
+double j1(double);
+double jn(int, double);
+double y0(double);
+double y1(double);
+double yn(int, double);
+
 #define M_E		2.7182818284590452354	/* e */
 #define M_LOG2E		1.4426950408889634074	/* log 2e */
 #define M_LOG10E	0.43429448190325182765	/* log 10e */
@@ -314,13 +323,6 @@
 #define MAXFLOAT	((float)3.40282346638528860e+38)
 
 #if defined(__USE_BSD) || defined(__USE_GNU)
-extern int signgam;
-double j0(double);
-double j1(double);
-double jn(int, double);
-double y0(double);
-double y1(double);
-double yn(int, double);
 double gamma(double);
 double scalb(double, double);
 double drem(double, double);
diff --git a/libc/include/pthread.h b/libc/include/pthread.h
index 47dc153..85b8cd9 100644
--- a/libc/include/pthread.h
+++ b/libc/include/pthread.h
@@ -43,16 +43,6 @@
 #pragma clang diagnostic ignored "-Wnullability-completeness"
 #endif
 
-typedef struct {
-#if defined(__LP64__)
-  int32_t __private[10];
-#else
-  int32_t __private[1];
-#endif
-} pthread_mutex_t;
-
-typedef long pthread_mutexattr_t;
-
 enum {
     PTHREAD_MUTEX_NORMAL = 0,
     PTHREAD_MUTEX_RECURSIVE = 1,
@@ -68,28 +58,8 @@
 #define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP { { ((PTHREAD_MUTEX_RECURSIVE & 3) << 14) } }
 #define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP { { ((PTHREAD_MUTEX_ERRORCHECK & 3) << 14) } }
 
-typedef struct {
-#if defined(__LP64__)
-  int32_t __private[12];
-#else
-  int32_t __private[1];
-#endif
-} pthread_cond_t;
-
-typedef long pthread_condattr_t;
-
 #define PTHREAD_COND_INITIALIZER  { { 0 } }
 
-typedef struct {
-#if defined(__LP64__)
-  int32_t __private[14];
-#else
-  int32_t __private[10];
-#endif
-} pthread_rwlock_t;
-
-typedef long pthread_rwlockattr_t;
-
 #define PTHREAD_RWLOCK_INITIALIZER  { { 0 } }
 
 enum {
@@ -97,32 +67,10 @@
   PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP = 1,
 };
 
-typedef int pthread_key_t;
-
-typedef int pthread_once_t;
-
 #define PTHREAD_ONCE_INIT 0
 
-typedef struct {
-#if defined(__LP64__)
-  int64_t __private[4];
-#else
-  int32_t __private[8];
-#endif
-} pthread_barrier_t;
-
-typedef int pthread_barrierattr_t;
-
 #define PTHREAD_BARRIER_SERIAL_THREAD -1
 
-typedef struct {
-#if defined(__LP64__)
-  int64_t __private;
-#else
-  int32_t __private[2];
-#endif
-} pthread_spinlock_t;
-
 #if defined(__LP64__)
 #define PTHREAD_STACK_MIN (4 * PAGE_SIZE)
 #else
diff --git a/libc/include/sys/cdefs.h b/libc/include/sys/cdefs.h
index c54403b..13c3269 100644
--- a/libc/include/sys/cdefs.h
+++ b/libc/include/sys/cdefs.h
@@ -275,43 +275,7 @@
 /* Used to rename functions so that the compiler emits a call to 'x' rather than the function this was applied to. */
 #define __RENAME(x) __asm__(#x)
 
-#ifdef __clang__
-#define __AVAILABILITY(...) __attribute__((availability(android,__VA_ARGS__)))
-#else
-#define __AVAILABILITY(...)
-#endif
-
-#define __INTRODUCED_IN(api_level) __AVAILABILITY(introduced=api_level)
-#define __DEPRECATED_IN(api_level) __AVAILABILITY(deprecated=api_level)
-#define __REMOVED_IN(api_level) __AVAILABILITY(obsoleted=api_level)
-
-#define __INTRODUCED_IN_FUTURE __INTRODUCED_IN(10000)
-
-#if __LP64__
-#define __INTRODUCED_IN_32(api_level)
-#define __INTRODUCED_IN_64 __INTRODUCED_IN
-#else
-#define __INTRODUCED_IN_32 __INTRODUCED_IN
-#define __INTRODUCED_IN_64(api_level)
-#endif
-
-#if defined(__arm__)
-#define __INTRODUCED_IN_ARM __INTRODUCED_IN
-#else
-#define __INTRODUCED_IN_ARM(x)
-#endif
-
-#if defined(__i386__)
-#define __INTRODUCED_IN_X86 __INTRODUCED_IN
-#else
-#define __INTRODUCED_IN_X86(x)
-#endif
-
-#if defined(__mips__)
-#define __INTRODUCED_IN_MIPS __INTRODUCED_IN
-#else
-#define __INTRODUCED_IN_MIPS(x)
-#endif
+#include <android/versioning.h>
 
 #if __has_builtin(__builtin_umul_overflow) || __GNUC__ >= 5
 #if __LP64__
diff --git a/libc/include/sys/types.h b/libc/include/sys/types.h
index 2895057..8188f89 100644
--- a/libc/include/sys/types.h
+++ b/libc/include/sys/types.h
@@ -35,6 +35,8 @@
 #include <linux/types.h>
 #include <linux/posix_types.h>
 
+#include <bits/pthread_types.h>
+
 /* gids, uids, and pids are all 32-bit. */
 typedef __kernel_gid32_t __gid_t;
 typedef __gid_t gid_t;
diff --git a/libc/kernel/tools/defaults.py b/libc/kernel/tools/defaults.py
index 773d22f..340af13 100644
--- a/libc/kernel/tools/defaults.py
+++ b/libc/kernel/tools/defaults.py
@@ -53,9 +53,10 @@
     "x86": {},
     }
 
-# Replace tokens in the output according to this mapping
+# Replace tokens in the output according to this mapping.
 kernel_token_replacements = {
-    "asm": "__asm__",
+    # The kernel's ARG_MAX is actually the "minimum" maximum (see fs/exec.c).
+    "ARG_MAX": "_KERNEL_ARG_MAX",
     # The kernel usage of __unused for unused struct fields conflicts with the macro defined in <sys/cdefs.h>.
     "__unused": "__linux_unused",
     # The kernel's _NSIG/NSIG are one less than the userspace value, so we need to move them aside.
diff --git a/libc/kernel/uapi/linux/limits.h b/libc/kernel/uapi/linux/limits.h
index 406d175..4441592 100644
--- a/libc/kernel/uapi/linux/limits.h
+++ b/libc/kernel/uapi/linux/limits.h
@@ -21,7 +21,7 @@
 #define NR_OPEN 1024
 #define NGROUPS_MAX 65536
 /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ARG_MAX 131072
+#define _KERNEL_ARG_MAX 131072
 #define LINK_MAX 127
 #define MAX_CANON 255
 #define MAX_INPUT 255
diff --git a/tests/unistd_test.cpp b/tests/unistd_test.cpp
index 3a87e88..32b1626 100644
--- a/tests/unistd_test.cpp
+++ b/tests/unistd_test.cpp
@@ -857,6 +857,39 @@
   ASSERT_EQ(online_cpus, sysconf(_SC_NPROCESSORS_ONLN));
 }
 
+TEST(UNISTD_TEST, sysconf_SC_ARG_MAX) {
+  // Since Linux 2.6.23, ARG_MAX isn't a constant and depends on RLIMIT_STACK.
+
+  // Get our current limit, and set things up so we restore the limit.
+  rlimit rl;
+  ASSERT_EQ(0, getrlimit(RLIMIT_STACK, &rl));
+  uint64_t original_rlim_cur = rl.rlim_cur;
+  if (rl.rlim_cur == RLIM_INFINITY) {
+    rl.rlim_cur = 8 * 1024 * 1024; // Bionic reports unlimited stacks as 8MiB.
+  }
+  auto guard = make_scope_guard([&rl, original_rlim_cur]() {
+    rl.rlim_cur = original_rlim_cur;
+    ASSERT_EQ(0, setrlimit(RLIMIT_STACK, &rl));
+  });
+
+  // _SC_ARG_MAX should be 1/4 the stack size.
+  EXPECT_EQ(static_cast<long>(rl.rlim_cur / 4), sysconf(_SC_ARG_MAX));
+
+  // If you have a really small stack, the kernel still guarantees "32 pages" (fs/exec.c).
+  rl.rlim_cur = 1024;
+  rl.rlim_max = RLIM_INFINITY;
+  ASSERT_EQ(0, setrlimit(RLIMIT_STACK, &rl));
+
+  EXPECT_EQ(static_cast<long>(32 * sysconf(_SC_PAGE_SIZE)), sysconf(_SC_ARG_MAX));
+
+  // With a 128-page stack limit, we know exactly what _SC_ARG_MAX should be...
+  rl.rlim_cur = 128 * sysconf(_SC_PAGE_SIZE);
+  rl.rlim_max = RLIM_INFINITY;
+  ASSERT_EQ(0, setrlimit(RLIMIT_STACK, &rl));
+
+  EXPECT_EQ(static_cast<long>((128 * sysconf(_SC_PAGE_SIZE)) / 4), sysconf(_SC_ARG_MAX));
+}
+
 TEST(UNISTD_TEST, dup2_same) {
   // POSIX says of dup2:
   // If fildes2 is already a valid open file descriptor ...
diff --git a/tools/versioner/Android.mk b/tools/versioner/Android.mk
index 3036300..c455f97 100644
--- a/tools/versioner/Android.mk
+++ b/tools/versioner/Android.mk
@@ -1,25 +1,3 @@
-ifeq (true,$(FORCE_BUILD_LLVM_COMPONENTS))
-
 LOCAL_PATH := $(call my-dir)
 
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := versioner
-LOCAL_MODULE_HOST_OS := linux
-
-LOCAL_CLANG := true
-LOCAL_CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter
-LOCAL_CFLAGS += -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS
-LOCAL_CPPFLAGS := $(LOCAL_CFLAGS) -std=c++14 -fno-rtti
-
-LOCAL_SRC_FILES := \
-  src/versioner.cpp \
-  src/DeclarationDatabase.cpp \
-  src/SymbolDatabase.cpp \
-  src/Utils.cpp
-
-LOCAL_SHARED_LIBRARIES := libclang libLLVM
-
-include $(BUILD_HOST_EXECUTABLE)
-
-endif
+include $(call all-subdir-makefiles)
diff --git a/tools/versioner/run_tests.py b/tools/versioner/run_tests.py
index 18b2aa9..9bac3e2 100755
--- a/tools/versioner/run_tests.py
+++ b/tools/versioner/run_tests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python2
 
 import os
 import subprocess
@@ -13,6 +13,7 @@
 
 
 def indent(text, spaces=4):
+    text = text.decode("utf-8")
     prefix = "    "
     return "\n".join([prefix + line for line in text.split("\n")])
 
@@ -24,9 +25,17 @@
     (output, _) = process.communicate()
 
     if os.path.exists("expected_fail"):
-        with open("expected_fail") as f:
+        with open("expected_fail", "rb") as f:
             expected_output = f.read()
-            if not output.endswith(expected_output):
+            if process.returncode == 0:
+                print("{} {}: unexpected success:".format(prefix_fail, test_name))
+                print("")
+                print("  Expected:")
+                print(indent(expected_output))
+                print("  Actual:")
+                print(indent(output))
+                return False
+            elif not output.endswith(expected_output):
                 print("{} {}: expected output mismatch".format(
                     prefix_fail, test_name))
                 print("")
diff --git a/tools/versioner/src/Android.mk b/tools/versioner/src/Android.mk
new file mode 100644
index 0000000..c43a525
--- /dev/null
+++ b/tools/versioner/src/Android.mk
@@ -0,0 +1,48 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+include $(CLEAR_TBLGEN_VARS)
+
+# Only do this when Clang is available.
+CLANG_ROOT_PATH := external/clang
+ifneq ($(wildcard $(CLANG_ROOT_PATH)/clang.mk),)
+
+LLVM_ROOT_PATH := external/llvm
+include $(CLANG_ROOT_PATH)/clang.mk
+
+LOCAL_MODULE := versioner
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+TBLGEN_TABLES := \
+  AttrList.inc \
+  AttrVisitor.inc \
+  Attrs.inc \
+  CommentCommandList.inc \
+  DeclNodes.inc \
+  DiagnosticCommonKinds.inc \
+  StmtNodes.inc \
+
+LOCAL_SRC_FILES := \
+  versioner.cpp \
+  Arch.cpp \
+  DeclarationDatabase.cpp \
+  Preprocessor.cpp \
+  SymbolDatabase.cpp \
+  Utils.cpp
+
+LOCAL_SHARED_LIBRARIES := libclang libLLVM
+
+include $(CLANG_HOST_BUILD_MK)
+include $(CLANG_TBLGEN_RULES_MK)
+
+# Set these after including the clang makefiles, to avoid getting CFLAGS from them.
+LOCAL_CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter
+LOCAL_CFLAGS += -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS
+LOCAL_CPPFLAGS := -std=c++14 -fno-rtti
+
+LOCAL_MODULE_HOST_OS := linux
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif
diff --git a/tools/versioner/src/Arch.cpp b/tools/versioner/src/Arch.cpp
new file mode 100644
index 0000000..49dad8a
--- /dev/null
+++ b/tools/versioner/src/Arch.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Arch.h"
+
+#include <err.h>
+
+#include <string>
+
+std::string to_string(const Arch& arch) {
+  switch (arch) {
+    case Arch::arm:
+      return "arm";
+
+    case Arch::arm64:
+      return "arm64";
+
+    case Arch::mips:
+      return "mips";
+
+    case Arch::mips64:
+      return "mips64";
+
+    case Arch::x86:
+      return "x86";
+
+    case Arch::x86_64:
+      return "x86_64";
+  }
+
+  errx(1, "unknown arch '%zu'", size_t(arch));
+}
+
+Arch arch_from_string(const std::string& name) {
+  if (name == "arm") {
+    return Arch::arm;
+  } else if (name == "arm64") {
+    return Arch::arm64;
+  } else if (name == "mips") {
+    return Arch::mips;
+  } else if (name == "mips64") {
+    return Arch::mips64;
+  } else if (name == "x86") {
+    return Arch::x86;
+  } else if (name == "x86_64") {
+    return Arch::x86_64;
+  }
+
+  errx(1, "unknown architecture '%s'", name.c_str());
+}
diff --git a/tools/versioner/src/Arch.h b/tools/versioner/src/Arch.h
new file mode 100644
index 0000000..fbf7773
--- /dev/null
+++ b/tools/versioner/src/Arch.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdlib.h>
+
+#include <array>
+#include <initializer_list>
+#include <set>
+#include <string>
+
+enum class Arch : size_t {
+  arm = 0,
+  arm64,
+  mips,
+  mips64,
+  x86,
+  x86_64,
+};
+
+std::string to_string(const Arch& arch);
+Arch arch_from_string(const std::string& name);
+
+template <typename T>
+class ArchMapIterator;
+
+template <typename T>
+class ArchMap {
+ public:
+  ArchMap() {
+  }
+
+  ArchMap(std::initializer_list<std::pair<Arch, T>> initializer) {
+    for (auto& pair : initializer) {
+      this->operator[](pair.first) = pair.second;
+    }
+  }
+
+  T& operator[](Arch arch) {
+    return data_[size_t(arch)];
+  }
+
+  const T& operator[](Arch arch) const {
+    return data_[size_t(arch)];
+  }
+
+  bool operator==(const ArchMap& other) const {
+    for (size_t i = 0; i < data_.size(); ++i) {
+      if (data_[i] != other.data_[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  ArchMapIterator<T> begin() const {
+    return ArchMapIterator<T>(*this, Arch::arm);
+  }
+
+  ArchMapIterator<T> end() const {
+    return ArchMapIterator<T>(*this, Arch(size_t(Arch::x86_64) + 1));
+  }
+
+ private:
+  std::array<T, size_t(Arch::x86_64) + 1> data_ = {};
+};
+
+template <typename T>
+class ArchMapIterator {
+  const ArchMap<T>& map_;
+  Arch arch_ = Arch::arm;
+
+ public:
+  ArchMapIterator() = delete;
+
+  ArchMapIterator(const ArchMap<T>& map, Arch arch) : map_(map), arch_(arch) {
+  }
+
+  bool operator==(const ArchMapIterator<T>& rhs) const {
+    return map_ == rhs.map_ && arch_ == rhs.arch_;
+  }
+
+  bool operator!=(const ArchMapIterator<T>& rhs) const {
+    return !(*this == rhs);
+  }
+
+  ArchMapIterator& operator++() {
+    arch_ = Arch(size_t(arch_) + 1);
+    return *this;
+  }
+
+  ArchMapIterator operator++(int) {
+    ArchMapIterator result = *this;
+    ++*this;
+    return result;
+  }
+
+  std::pair<const Arch&, const T&> operator*() const {
+    return std::tie(arch_, map_[arch_]);
+  }
+
+  std::pair<const Arch&, const T&> operator->() const {
+    return std::tie(arch_, map_[arch_]);
+  }
+};
+
+static const std::set<Arch> supported_archs = {
+  Arch::arm,
+  Arch::arm64,
+  Arch::mips,
+  Arch::mips64,
+  Arch::x86,
+  Arch::x86_64,
+};
+
+static ArchMap<std::string> arch_targets = {
+  { Arch::arm, "arm-linux-androideabi" },
+  { Arch::arm64, "aarch64-linux-android" },
+  { Arch::mips, "mipsel-linux-android" },
+  { Arch::mips64, "mips64el-linux-android" },
+  { Arch::x86, "i686-linux-android" },
+  { Arch::x86_64, "x86_64-linux-android" },
+};
+
+static const std::set<int> supported_levels = { 9, 12, 13, 14, 15, 16, 17, 18, 19, 21, 23, 24 };
+
+static const ArchMap<int> arch_min_api = {
+  { Arch::arm, 9 },
+  { Arch::arm64, 21 },
+  { Arch::mips, 9 },
+  { Arch::mips64, 21 },
+  { Arch::x86, 9 },
+  { Arch::x86_64, 21 },
+};
diff --git a/tools/versioner/src/DeclarationDatabase.cpp b/tools/versioner/src/DeclarationDatabase.cpp
index 9f02588..8e8d84f 100644
--- a/tools/versioner/src/DeclarationDatabase.cpp
+++ b/tools/versioner/src/DeclarationDatabase.cpp
@@ -16,10 +16,15 @@
 
 #include "DeclarationDatabase.h"
 
+#include <err.h>
+
 #include <iostream>
 #include <map>
+#include <mutex>
 #include <set>
+#include <sstream>
 #include <string>
+#include <utility>
 
 #include <clang/AST/AST.h>
 #include <clang/AST/Attr.h>
@@ -32,12 +37,13 @@
 
 class Visitor : public RecursiveASTVisitor<Visitor> {
   HeaderDatabase& database;
+  CompilationType type;
   SourceManager& src_manager;
   std::unique_ptr<MangleContext> mangler;
 
  public:
-  Visitor(HeaderDatabase& database, ASTContext& ctx)
-      : database(database), src_manager(ctx.getSourceManager()) {
+  Visitor(HeaderDatabase& database, CompilationType type, ASTContext& ctx)
+      : database(database), type(type), src_manager(ctx.getSourceManager()) {
     mangler.reset(ItaniumMangleContext::create(ctx, ctx.getDiagnostics()));
   }
 
@@ -114,66 +120,238 @@
       return true;
     }
 
-    // Look for availability annotations.
+    auto start_loc = src_manager.getPresumedLoc(decl->getLocStart());
+    auto end_loc = src_manager.getPresumedLoc(decl->getLocEnd());
+
+    Location location = {
+      .filename = start_loc.getFilename(),
+      .start = {
+        .line = start_loc.getLine(),
+        .column = start_loc.getColumn(),
+      },
+      .end = {
+        .line = end_loc.getLine(),
+        .column = end_loc.getColumn(),
+      }
+    };
+
     DeclarationAvailability availability;
-    for (const AvailabilityAttr* attr : decl->specific_attrs<AvailabilityAttr>()) {
-      if (attr->getPlatform()->getName() != "android") {
-        fprintf(stderr, "skipping non-android platform %s\n",
-                attr->getPlatform()->getName().str().c_str());
-        continue;
+
+    // Find and parse __ANDROID_AVAILABILITY_DUMP__ annotations.
+    for (const AnnotateAttr* attr : decl->specific_attrs<AnnotateAttr>()) {
+      llvm::StringRef annotation = attr->getAnnotation();
+      if (annotation == "introduced_in_future") {
+        // Tag the compiled-for arch, since this can vary across archs.
+        availability.arch_availability[type.arch].future = true;
+      } else {
+        llvm::SmallVector<llvm::StringRef, 2> fragments;
+        annotation.split(fragments, "=");
+        if (fragments.size() != 2) {
+          continue;
+        }
+
+        auto& global_availability = availability.global_availability;
+        auto& arch_availability = availability.arch_availability;
+        std::map<std::string, std::vector<int*>> prefix_map = {
+          { "introduced_in", { &global_availability.introduced } },
+          { "deprecated_in", { &global_availability.deprecated } },
+          { "obsoleted_in", { &global_availability.obsoleted } },
+          { "introduced_in_arm", { &arch_availability[Arch::arm].introduced } },
+          { "introduced_in_mips", { &arch_availability[Arch::mips].introduced } },
+          { "introduced_in_x86", { &arch_availability[Arch::x86].introduced } },
+          { "introduced_in_32",
+            { &arch_availability[Arch::arm].introduced,
+              &arch_availability[Arch::mips].introduced,
+              &arch_availability[Arch::x86].introduced } },
+          { "introduced_in_64",
+            { &arch_availability[Arch::arm64].introduced,
+              &arch_availability[Arch::mips64].introduced,
+              &arch_availability[Arch::x86_64].introduced } },
+        };
+
+        auto it = prefix_map.find(fragments[0]);
+        if (it == prefix_map.end()) {
+          continue;
+        }
+        int value;
+        if (fragments[1].getAsInteger(10, value)) {
+          errx(1, "invalid __ANDROID_AVAILABILITY_DUMP__ annotation: '%s'",
+               annotation.str().c_str());
+        }
+
+        for (int* ptr : it->second) {
+          *ptr = value;
+        }
       }
-      if (attr->getIntroduced().getMajor() != 0) {
-        availability.introduced = attr->getIntroduced().getMajor();
-      }
-      if (attr->getDeprecated().getMajor() != 0) {
-        availability.deprecated = attr->getDeprecated().getMajor();
-      }
-      if (attr->getObsoleted().getMajor() != 0) {
-        availability.obsoleted = attr->getObsoleted().getMajor();
-      }
+    }
+
+    auto symbol_it = database.symbols.find(declaration_name);
+    if (symbol_it == database.symbols.end()) {
+      Symbol symbol = {.name = declaration_name };
+      bool dummy;
+      std::tie(symbol_it, dummy) = database.symbols.insert({ declaration_name, symbol });
     }
 
     // Find or insert an entry for the declaration.
-    auto declaration_it = database.declarations.find(declaration_name);
-    if (declaration_it == database.declarations.end()) {
-      Declaration declaration = {.name = declaration_name };
-      bool inserted;
-      std::tie(declaration_it, inserted) =
-          database.declarations.insert({ declaration_name, declaration });
-    }
-
-    auto& declaration_locations = declaration_it->second.locations;
-    auto presumed_loc = src_manager.getPresumedLoc(decl->getLocation());
-    DeclarationLocation location = {
-      .filename = presumed_loc.getFilename(),
-      .line_number = presumed_loc.getLine(),
-      .column = presumed_loc.getColumn(),
-      .type = declaration_type,
-      .is_extern = is_extern,
-      .is_definition = is_definition,
-      .availability = availability,
-    };
-
-    // It's fine if the location is already there, we'll get an iterator to the existing element.
-    auto location_it = declaration_locations.begin();
-    bool inserted = false;
-    std::tie(location_it, inserted) = declaration_locations.insert(location);
-
-    // If we didn't insert, check to see if the availability attributes are identical.
-    if (!inserted) {
-      if (location_it->availability != availability) {
-        fprintf(stderr, "ERROR: availability attribute mismatch\n");
-        decl->dump();
-        abort();
+    auto declaration_it = symbol_it->second.declarations.find(location);
+    if (declaration_it == symbol_it->second.declarations.end()) {
+      Declaration declaration;
+      declaration.name = declaration_name;
+      declaration.location = location;
+      declaration.is_extern = is_extern;
+      declaration.is_definition = is_definition;
+      declaration.availability.insert(std::make_pair(type, availability));
+      symbol_it->second.declarations.insert(std::make_pair(location, declaration));
+    } else {
+      if (declaration_it->second.is_extern != is_extern ||
+          declaration_it->second.is_definition != is_definition) {
+        errx(1, "varying declaration of '%s' at %s:%u:%u", declaration_name.c_str(),
+             location.filename.c_str(), location.start.line, location.start.column);
       }
+      declaration_it->second.availability.insert(std::make_pair(type, availability));
     }
 
     return true;
   }
 };
 
-void HeaderDatabase::parseAST(ASTUnit* ast) {
+bool DeclarationAvailability::merge(const DeclarationAvailability& other) {
+#define check_avail(expr) error |= (!this->expr.empty() && this->expr != other.expr);
+  bool error = false;
+
+  if (!other.global_availability.empty()) {
+    check_avail(global_availability);
+    this->global_availability = other.global_availability;
+  }
+
+  for (Arch arch : supported_archs) {
+    if (!other.arch_availability[arch].empty()) {
+      check_avail(arch_availability[arch]);
+      this->arch_availability[arch] = other.arch_availability[arch];
+    }
+  }
+#undef check_avail
+
+  return !error;
+}
+
+bool Declaration::calculateAvailability(DeclarationAvailability* output) const {
+  DeclarationAvailability avail;
+  for (const auto& it : this->availability) {
+    if (!avail.merge(it.second)) {
+      return false;
+    }
+  }
+  *output = avail;
+  return true;
+}
+
+bool Symbol::calculateAvailability(DeclarationAvailability* output) const {
+  DeclarationAvailability avail;
+  for (const auto& it : this->declarations) {
+    // Don't merge availability for inline functions (because they shouldn't have any).
+    if (it.second.is_definition) {
+      continue;
+    }
+
+    DeclarationAvailability decl_availability;
+    if (!it.second.calculateAvailability(&decl_availability)) {
+      return false;
+      abort();
+    }
+
+    if (!avail.merge(decl_availability)) {
+      return false;
+    }
+  }
+  *output = avail;
+  return true;
+}
+
+bool Symbol::hasDeclaration(const CompilationType& type) const {
+  for (const auto& decl_it : this->declarations) {
+    for (const auto& compilation_it : decl_it.second.availability) {
+      if (compilation_it.first == type) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void HeaderDatabase::parseAST(CompilationType type, ASTUnit* ast) {
+  std::unique_lock<std::mutex> lock(this->mutex);
   ASTContext& ctx = ast->getASTContext();
-  Visitor visitor(*this, ctx);
+  Visitor visitor(*this, type, ctx);
   visitor.TraverseDecl(ctx.getTranslationUnitDecl());
 }
+
+std::string to_string(const CompilationType& type) {
+  std::stringstream ss;
+  ss << to_string(type.arch) << "-" << type.api_level;
+  return ss.str();
+}
+
+std::string to_string(const AvailabilityValues& av) {
+  std::stringstream ss;
+
+  if (av.future) {
+    ss << "future, ";
+  }
+
+  if (av.introduced != 0) {
+    ss << "introduced = " << av.introduced << ", ";
+  }
+
+  if (av.deprecated != 0) {
+    ss << "deprecated = " << av.deprecated << ", ";
+  }
+
+  if (av.obsoleted != 0) {
+    ss << "obsoleted = " << av.obsoleted << ", ";
+  }
+
+  std::string result = ss.str();
+  if (!result.empty()) {
+    result = result.substr(0, result.length() - 2);
+  }
+  return result;
+}
+
+std::string to_string(const DeclarationType& type) {
+  switch (type) {
+    case DeclarationType::function:
+      return "function";
+    case DeclarationType::variable:
+      return "variable";
+    case DeclarationType::inconsistent:
+      return "inconsistent";
+  }
+  abort();
+}
+
+std::string to_string(const DeclarationAvailability& decl_av) {
+  std::stringstream ss;
+  if (!decl_av.global_availability.empty()) {
+    ss << to_string(decl_av.global_availability) << ", ";
+  }
+
+  for (const auto& it : decl_av.arch_availability) {
+    if (!it.second.empty()) {
+      ss << to_string(it.first) << ": " << to_string(it.second) << ", ";
+    }
+  }
+
+  std::string result = ss.str();
+  if (result.size() == 0) {
+    return "no availability";
+  }
+
+  return result.substr(0, result.length() - 2);
+}
+
+std::string to_string(const Location& loc) {
+  std::stringstream ss;
+  ss << loc.filename << ":" << loc.start.line << ":" << loc.start.column;
+  return ss.str();
+}
diff --git a/tools/versioner/src/DeclarationDatabase.h b/tools/versioner/src/DeclarationDatabase.h
index 2b462bd..8fe12ea 100644
--- a/tools/versioner/src/DeclarationDatabase.h
+++ b/tools/versioner/src/DeclarationDatabase.h
@@ -16,36 +16,32 @@
 
 #pragma once
 
-#include <iostream>
+#include <stdio.h>
+
 #include <map>
+#include <mutex>
 #include <set>
-#include <sstream>
 #include <string>
 #include <vector>
 
 #include <llvm/ADT/StringRef.h>
 
+#include "Arch.h"
 #include "Utils.h"
 
+namespace clang {
+class ASTUnit;
+class Decl;
+}
+
 enum class DeclarationType {
   function,
   variable,
   inconsistent,
 };
 
-static const char* declarationTypeName(DeclarationType type) {
-  switch (type) {
-    case DeclarationType::function:
-      return "function";
-    case DeclarationType::variable:
-      return "variable";
-    case DeclarationType::inconsistent:
-      return "inconsistent";
-  }
-}
-
 struct CompilationType {
-  std::string arch;
+  Arch arch;
   int api_level;
 
  private:
@@ -61,155 +57,169 @@
   bool operator==(const CompilationType& other) const {
     return tie() == other.tie();
   }
-
-  std::string describe() const {
-    return arch + "-" + std::to_string(api_level);
-  }
 };
 
-struct DeclarationAvailability {
+std::string to_string(const CompilationType& type);
+
+struct AvailabilityValues {
+  bool future = false;
   int introduced = 0;
   int deprecated = 0;
   int obsoleted = 0;
 
-  void dump(std::ostream& out = std::cout) const {
-    out << describe();
+  bool empty() const {
+    return !(future || introduced || deprecated || obsoleted);
   }
 
+  bool operator==(const AvailabilityValues& rhs) const {
+    return std::tie(introduced, deprecated, obsoleted) ==
+           std::tie(rhs.introduced, rhs.deprecated, rhs.obsoleted);
+  }
+
+  bool operator!=(const AvailabilityValues& rhs) const {
+    return !(*this == rhs);
+  }
+};
+
+std::string to_string(const AvailabilityValues& av);
+
+struct DeclarationAvailability {
+  AvailabilityValues global_availability;
+  ArchMap<AvailabilityValues> arch_availability;
+
   bool empty() const {
-    return !(introduced || deprecated || obsoleted);
-  }
+    if (!global_availability.empty()) {
+      return false;
+    }
 
-  auto tie() const {
-    return std::tie(introduced, deprecated, obsoleted);
+    for (const auto& it : arch_availability) {
+      if (!it.second.empty()) {
+        return false;
+      }
+    }
+
+    return true;
   }
 
   bool operator==(const DeclarationAvailability& rhs) const {
-    return this->tie() == rhs.tie();
+    return std::tie(global_availability, arch_availability) ==
+           std::tie(rhs.global_availability, rhs.arch_availability);
   }
 
   bool operator!=(const DeclarationAvailability& rhs) const {
     return !(*this == rhs);
   }
 
-  std::string describe() const {
-    if (!(introduced || deprecated || obsoleted)) {
-      return "no availability";
-    }
-
-    std::stringstream out;
-    bool need_comma = false;
-    auto comma = [&out, &need_comma]() {
-      if (!need_comma) {
-        need_comma = true;
-        return;
-      }
-      out << ", ";
-    };
-
-    if (introduced != 0) {
-      comma();
-      out << "introduced = " << introduced;
-    }
-    if (deprecated != 0) {
-      comma();
-      out << "deprecated = " << deprecated;
-    }
-    if (obsoleted != 0) {
-      comma();
-      out << "obsoleted = " << obsoleted;
-    }
-
-    return out.str();
-  }
+  // Returns false if the availability declarations conflict.
+  bool merge(const DeclarationAvailability& other);
 };
 
-struct DeclarationLocation {
-  std::string filename;
-  unsigned line_number;
+std::string to_string(const DeclarationAvailability& decl_av);
+
+struct FileLocation {
+  unsigned line;
   unsigned column;
-  DeclarationType type;
-  bool is_extern;
-  bool is_definition;
-  DeclarationAvailability availability;
 
-  auto tie() const {
-    return std::tie(filename, line_number, column, type, is_extern, is_definition);
+  bool operator<(const FileLocation& rhs) const {
+    return std::tie(line, column) < std::tie(rhs.line, rhs.column);
   }
 
-  bool operator<(const DeclarationLocation& other) const {
-    return tie() < other.tie();
-  }
-
-  bool operator==(const DeclarationLocation& other) const {
-    return tie() == other.tie();
-  }
-
-  void dump(const std::string& base_path = "", std::ostream& out = std::cout) const {
-    const char* var_type = declarationTypeName(type);
-    const char* declaration_type = is_definition ? "definition" : "declaration";
-    const char* linkage = is_extern ? "extern" : "static";
-
-    std::string stripped_path;
-    if (llvm::StringRef(filename).startswith(base_path)) {
-      stripped_path = filename.substr(base_path.size());
-    } else {
-      stripped_path = filename;
-    }
-
-    out << "        " << linkage << " " << var_type << " " << declaration_type << " @ "
-        << stripped_path << ":" << line_number << ":" << column;
-
-    out << "\t[";
-    availability.dump(out);
-    out << "]\n";
+  bool operator==(const FileLocation& rhs) const {
+    return std::tie(line, column) == std::tie(rhs.line, rhs.column);
   }
 };
 
+struct Location {
+  std::string filename;
+  FileLocation start;
+  FileLocation end;
+
+  bool operator<(const Location& rhs) const {
+    return std::tie(filename, start, end) < std::tie(rhs.filename, rhs.start, rhs.end);
+  }
+};
+
+std::string to_string(const Location& loc);
+
 struct Declaration {
   std::string name;
-  std::set<DeclarationLocation> locations;
+  Location location;
 
-  bool hasDefinition() const {
-    for (const auto& location : locations) {
-      if (location.is_definition) {
-        return true;
-      }
-    }
-    return false;
+  bool is_extern;
+  bool is_definition;
+  std::map<CompilationType, DeclarationAvailability> availability;
+
+  bool calculateAvailability(DeclarationAvailability* output) const;
+  bool operator<(const Declaration& rhs) const {
+    return location < rhs.location;
   }
 
-  DeclarationType type() const {
-    DeclarationType result = locations.begin()->type;
-    for (const DeclarationLocation& location : locations) {
-      if (location.type != result) {
-        result = DeclarationType::inconsistent;
-      }
-    }
-    return result;
-  }
+  void dump(const std::string& base_path = "", FILE* out = stdout, unsigned indent = 0) const {
+    std::string indent_str(indent, ' ');
+    fprintf(out, "%s", indent_str.c_str());
 
-  void dump(const std::string& base_path = "", std::ostream& out = std::cout) const {
-    out << "    " << name << " declared in " << locations.size() << " locations:\n";
-    for (const DeclarationLocation& location : locations) {
-      location.dump(base_path, out);
+    fprintf(out, "%s ", is_extern ? "extern" : "static");
+    fprintf(out, "%s ", is_definition ? "definition" : "declaration");
+    fprintf(out, "@ %s:%u:%u", StripPrefix(location.filename, base_path).str().c_str(),
+            location.start.line, location.start.column);
+
+    if (!availability.empty()) {
+      DeclarationAvailability avail;
+
+      fprintf(out, "\n%s  ", indent_str.c_str());
+      if (!calculateAvailability(&avail)) {
+        fprintf(out, "invalid availability");
+      } else {
+        fprintf(out, "%s", to_string(avail).c_str());
+      }
     }
   }
 };
 
-namespace clang {
-class ASTUnit;
-}
+struct Symbol {
+  std::string name;
+  std::map<Location, Declaration> declarations;
+
+  bool calculateAvailability(DeclarationAvailability* output) const;
+  bool hasDeclaration(const CompilationType& type) const;
+
+  bool operator<(const Symbol& rhs) const {
+    return name < rhs.name;
+  }
+
+  bool operator==(const Symbol& rhs) const {
+    return name == rhs.name;
+  }
+
+  void dump(const std::string& base_path = "", FILE* out = stdout) const {
+    DeclarationAvailability availability;
+    bool valid_availability = calculateAvailability(&availability);
+    fprintf(out, "  %s: ", name.c_str());
+
+    if (valid_availability) {
+      fprintf(out, "%s\n", to_string(availability).c_str());
+    } else {
+      fprintf(out, "invalid\n");
+    }
+
+    for (auto& it : declarations) {
+      it.second.dump(base_path, out, 4);
+      fprintf(out, "\n");
+    }
+  }
+};
 
 class HeaderDatabase {
+  std::mutex mutex;
+
  public:
-  std::map<std::string, Declaration> declarations;
+  std::map<std::string, Symbol> symbols;
 
-  void parseAST(clang::ASTUnit* ast);
+  void parseAST(CompilationType type, clang::ASTUnit* ast);
 
-  void dump(const std::string& base_path = "", std::ostream& out = std::cout) const {
-    out << "HeaderDatabase contains " << declarations.size() << " declarations:\n";
-    for (const auto& pair : declarations) {
+  void dump(const std::string& base_path = "", FILE* out = stdout) const {
+    fprintf(out, "HeaderDatabase contains %zu symbols:\n", symbols.size());
+    for (const auto& pair : symbols) {
       pair.second.dump(base_path, out);
     }
   }
diff --git a/tools/versioner/src/Preprocessor.cpp b/tools/versioner/src/Preprocessor.cpp
new file mode 100644
index 0000000..33531bd
--- /dev/null
+++ b/tools/versioner/src/Preprocessor.cpp
@@ -0,0 +1,525 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Preprocessor.h"
+
+#include <err.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <libgen.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <deque>
+#include <fstream>
+#include <string>
+#include <unordered_map>
+
+#include <llvm/ADT/StringRef.h>
+#include <llvm/ADT/Twine.h>
+#include <llvm/Support/FileSystem.h>
+#include <llvm/Support/Path.h>
+
+#include "Arch.h"
+#include "DeclarationDatabase.h"
+#include "versioner.h"
+
+using namespace std::string_literals;
+
+static DeclarationAvailability calculateRequiredGuard(const Declaration& declaration) {
+  // To avoid redundant macro guards, the availability calculated by this function is the set
+  // difference of 'targets marked-available' from 'targets the declaration is visible in'.
+  // For example, a declaration that is visible always and introduced in 9 would return introduced
+  // in 9, but the same declaration, except only visible in 9+ would return an empty
+  // DeclarationAvailability.
+
+  // This currently only handles __INTRODUCED_IN.
+  // TODO: Do the same for __REMOVED_IN.
+  int global_min_api_visible = 0;
+  ArchMap<int> arch_visibility;
+
+  for (const auto& it : declaration.availability) {
+    const CompilationType& type = it.first;
+
+    if (global_min_api_visible == 0 || global_min_api_visible > type.api_level) {
+      global_min_api_visible = type.api_level;
+    }
+
+    if (arch_visibility[type.arch] == 0 || arch_visibility[type.arch] > type.api_level) {
+      arch_visibility[type.arch] = type.api_level;
+    }
+  }
+
+  DeclarationAvailability decl_av;
+  if (!declaration.calculateAvailability(&decl_av)) {
+    fprintf(stderr, "versioner: failed to calculate availability while preprocessing:\n");
+    declaration.dump("", stderr, 2);
+    exit(1);
+  }
+
+  D("Calculating required guard for %s:\n", declaration.name.c_str());
+  D("  Declaration availability: %s\n", to_string(decl_av).c_str());
+
+  if (verbose) {
+    std::string arch_visibility_str;
+    for (Arch arch : supported_archs) {
+      if (arch_visibility[arch] != 0) {
+        arch_visibility_str += to_string(arch);
+        arch_visibility_str += ": ";
+        arch_visibility_str += std::to_string(arch_visibility[arch]);
+        arch_visibility_str += ", ";
+      }
+    }
+    if (!arch_visibility_str.empty()) {
+      arch_visibility_str.resize(arch_visibility_str.size() - 2);
+    }
+    D("  Declaration visibility: global = %d, arch = %s\n", global_min_api_visible,
+      arch_visibility_str.c_str());
+  }
+
+  DeclarationAvailability result = decl_av;
+  if (result.global_availability.introduced < global_min_api_visible) {
+    result.global_availability.introduced = 0;
+  }
+
+  for (Arch arch : supported_archs) {
+    if (result.arch_availability[arch].introduced < arch_visibility[arch]) {
+      result.arch_availability[arch].introduced = 0;
+    }
+  }
+
+  D("  Calculated result: %s\n", to_string(result).c_str());
+  D("\n");
+
+  return result;
+}
+
+static std::deque<std::string> readFileLines(const std::string& path) {
+  std::ifstream is(path.c_str());
+  std::deque<std::string> result;
+  std::string line;
+
+  while (std::getline(is, line)) {
+    result.push_back(std::move(line));
+  }
+
+  return result;
+}
+
+static std::string dirname(const std::string& path) {
+  std::unique_ptr<char, decltype(&free)> path_copy(strdup(path.c_str()), free);
+  return dirname(path_copy.get());
+}
+
+static bool mkdirs(const std::string& path) {
+  struct stat st;
+  if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
+    return true;
+  }
+
+  std::string parent = dirname(path);
+  if (parent == path) {
+    return false;
+  }
+
+  if (!mkdirs(parent)) {
+    return false;
+  }
+
+  if (mkdir(path.c_str(), 0700) != 0) {
+    return false;
+  }
+
+  return true;
+}
+
+static void writeFileLines(const std::string& path, const std::deque<std::string>& lines) {
+  if (!mkdirs(dirname(path))) {
+    err(1, "failed to create directory '%s'", dirname(path).c_str());
+  }
+
+  std::ofstream os(path.c_str(), std::ios_base::out | std::ios_base::trunc);
+
+  for (const std::string& line : lines) {
+    os << line << "\n";
+  }
+}
+
+using GuardMap = std::map<Location, DeclarationAvailability>;
+
+static std::string generateGuardCondition(const DeclarationAvailability& avail) {
+  // Logically orred expressions that constitute the macro guard.
+  std::vector<std::string> expressions;
+  static const std::vector<std::pair<std::string, std::set<Arch>>> arch_sets = {
+    { "", supported_archs },
+    { "!defined(__LP64__)", { Arch::arm, Arch::mips, Arch::x86 } },
+    { "defined(__LP64__)", { Arch::arm64, Arch::mips64, Arch::x86_64 } },
+    { "defined(__mips__)", { Arch::mips, Arch::mips64 } },
+  };
+  std::map<Arch, std::string> individual_archs = {
+    { Arch::arm, "defined(__arm__)" },
+    { Arch::arm64, "defined(__aarch64__)" },
+    { Arch::mips, "defined(__mips__) && !defined(__LP64__)" },
+    { Arch::mips64, "defined(__mips__) && defined(__LP64__)" },
+    { Arch::x86, "defined(__i386__)" },
+    { Arch::x86_64, "defined(__x86_64__)" },
+  };
+
+  auto generate_guard = [](const std::string& arch_expr, int min_version) {
+    if (min_version == 0) {
+      return arch_expr;
+    }
+    return arch_expr + " && __ANDROID_API__ >= " + std::to_string(min_version);
+  };
+
+  D("Generating guard for availability: %s\n", to_string(avail).c_str());
+  if (!avail.global_availability.empty()) {
+    for (Arch arch : supported_archs) {
+      if (!avail.arch_availability[arch].empty()) {
+        errx(1, "attempted to generate guard with global and per-arch values: %s",
+             to_string(avail).c_str());
+      }
+    }
+
+    if (avail.global_availability.introduced == 0) {
+      fprintf(stderr, "warning: attempted to generate guard with empty availability: %s\n",
+              to_string(avail).c_str());
+      return "";
+    }
+
+    if (avail.global_availability.introduced <= 9) {
+      return "";
+    }
+
+    return "__ANDROID_API__ >= "s + std::to_string(avail.global_availability.introduced);
+  }
+
+  for (const auto& it : arch_sets) {
+    const std::string& arch_expr = it.first;
+    const std::set<Arch>& archs = it.second;
+
+    D("  Checking arch set '%s'\n", arch_expr.c_str());
+
+    int version = avail.arch_availability[*it.second.begin()].introduced;
+
+    // Assume that the entire declaration is declared __INTRODUCED_IN_FUTURE if one arch is.
+    bool future = avail.arch_availability[*it.second.begin()].future;
+
+    if (future) {
+      return "0";
+    }
+
+    // The maximum min_version of the set.
+    int max_min_version = 0;
+    for (Arch arch : archs) {
+      if (arch_min_api[arch] > max_min_version) {
+        max_min_version = arch_min_api[arch];
+      }
+
+      if (avail.arch_availability[arch].introduced != version) {
+        D("    Skipping arch set, availability for %s doesn't match %s\n",
+          to_string(*it.second.begin()).c_str(), to_string(arch).c_str());
+        goto skip;
+      }
+    }
+
+    // If all of the archs in the set have a min_api that satifies version, elide the check.
+    if (max_min_version >= version) {
+      version = 0;
+    }
+
+    expressions.emplace_back(generate_guard(arch_expr, version));
+
+    D("    Generated expression '%s'\n", expressions.rbegin()->c_str());
+
+    for (Arch arch : archs) {
+      individual_archs.erase(arch);
+    }
+
+  skip:
+    continue;
+  }
+
+  for (const auto& it : individual_archs) {
+    const std::string& arch_expr = it.second;
+    int introduced = avail.arch_availability[it.first].introduced;
+    if (introduced == 0) {
+      expressions.emplace_back(arch_expr);
+    } else {
+      expressions.emplace_back(generate_guard(arch_expr, introduced));
+    }
+  }
+
+  if (expressions.size() == 0) {
+    errx(1, "generated empty guard for availability %s", to_string(avail).c_str());
+  } else if (expressions.size() == 1) {
+    return expressions[0];
+  }
+
+  return "("s + Join(expressions, ") || (") + ")";
+}
+
+// Assumes that nothing crazy is happening (e.g. having the semicolon be in a macro)
+static FileLocation findNextSemicolon(const std::deque<std::string>& lines, FileLocation start) {
+  unsigned current_line = start.line;
+  unsigned current_column = start.column;
+  while (current_line <= lines.size()) {
+    size_t result = lines[current_line - 1].find_first_of(';', current_column - 1);
+
+    if (result != std::string::npos) {
+      FileLocation loc = {
+        .line = current_line,
+        .column = unsigned(result) + 1,
+      };
+
+      return loc;
+    }
+
+    ++current_line;
+    current_column = 0;
+  }
+
+  errx(1, "failed to find semicolon starting from %u:%u", start.line, start.column);
+}
+
+// Merge adjacent blocks with identical guards.
+static void mergeGuards(std::deque<std::string>& file_lines, GuardMap& guard_map) {
+  if (guard_map.size() < 2) {
+    return;
+  }
+
+  auto current = guard_map.begin();
+  auto next = current;
+  ++next;
+
+  while (next != guard_map.end()) {
+    if (current->second != next->second) {
+      ++current;
+      ++next;
+      continue;
+    }
+
+    // Scan from the end of current to the beginning of next.
+    bool in_block_comment = false;
+    bool valid = true;
+
+    FileLocation current_location = current->first.end;
+    FileLocation end_location = next->first.start;
+
+    auto nextLine = [&current_location]() {
+      ++current_location.line;
+      current_location.column = 1;
+    };
+
+    auto nextCol = [&file_lines, &current_location, &nextLine]() {
+      if (current_location.column == file_lines[current_location.column - 1].length()) {
+        nextLine();
+      } else {
+        ++current_location.column;
+      }
+    };
+
+    // The end location will point to the semicolon, which we don't want to read, so skip it.
+    nextCol();
+
+    while (current_location < end_location) {
+      const std::string& line = file_lines[current_location.line - 1];
+      size_t line_index = current_location.column - 1;
+
+      if (in_block_comment) {
+        size_t pos = line.find("*/", line_index);
+        if (pos == std::string::npos) {
+          D("Didn't find block comment terminator, skipping line\n");
+          nextLine();
+          continue;
+        } else {
+          D("Found block comment terminator\n");
+          in_block_comment = false;
+          current_location.column = pos + 2;
+          nextCol();
+          continue;
+        }
+      } else {
+        size_t pos = line.find_first_not_of(" \t", line_index);
+        if (pos == std::string::npos) {
+          nextLine();
+          continue;
+        }
+
+        current_location.column = pos + 1;
+        if (line[pos] != '/') {
+          D("Trailing character '%c' is not a slash: %s\n", line[pos], line.substr(pos).c_str());
+          valid = false;
+          break;
+        }
+
+        nextCol();
+        if (line.length() <= pos + 1) {
+          // Trailing slash at the end of a line?
+          D("Trailing slash at end of line\n");
+          valid = false;
+          break;
+        }
+
+        if (line[pos + 1] == '/') {
+          // C++ style comment
+          nextLine();
+        } else if (line[pos + 1] == '*') {
+          // Block comment
+          nextCol();
+          in_block_comment = true;
+          D("In a block comment\n");
+        } else {
+          // Garbage?
+          D("Unexpected output after /: %s\n", line.substr(pos).c_str());
+          valid = false;
+          break;
+        }
+      }
+    }
+
+    if (!valid) {
+      D("Not merging blocks %s and %s\n", to_string(current->first).c_str(),
+        to_string(next->first).c_str());
+      ++current;
+      ++next;
+      continue;
+    }
+
+    D("Merging blocks %s and %s\n", to_string(current->first).c_str(),
+      to_string(next->first).c_str());
+
+    Location merged = current->first;
+    merged.end = next->first.end;
+
+    DeclarationAvailability avail = current->second;
+
+    guard_map.erase(current);
+    guard_map.erase(next);
+    bool dummy;
+    std::tie(current, dummy) = guard_map.insert(std::make_pair(merged, avail));
+    next = current;
+    ++next;
+  }
+}
+
+static void rewriteFile(const std::string& output_path, std::deque<std::string>& file_lines,
+                        const GuardMap& guard_map) {
+  for (auto it = guard_map.rbegin(); it != guard_map.rend(); ++it) {
+    const Location& loc = it->first;
+    const DeclarationAvailability& avail = it->second;
+
+    std::string condition = generateGuardCondition(avail);
+    if (condition.empty()) {
+      continue;
+    }
+
+    std::string prologue = "\n#if "s + condition + "\n";
+    std::string epilogue = "\n#endif /* " + condition + " */\n";
+
+    file_lines[loc.end.line - 1].insert(loc.end.column, epilogue);
+    file_lines[loc.start.line - 1].insert(loc.start.column - 1, prologue);
+  }
+
+  printf("Preprocessing %s...\n", output_path.c_str());
+  writeFileLines(output_path, file_lines);
+}
+
+bool preprocessHeaders(const std::string& dst_dir, const std::string& src_dir,
+                       HeaderDatabase* database) {
+  std::unordered_map<std::string, GuardMap> guards;
+  std::unordered_map<std::string, std::deque<std::string>> file_lines;
+
+  for (const auto& symbol_it : database->symbols) {
+    const Symbol& symbol = symbol_it.second;
+
+    for (const auto& decl_it : symbol.declarations) {
+      const Location& location = decl_it.first;
+      const Declaration& decl = decl_it.second;
+
+      DeclarationAvailability macro_guard = calculateRequiredGuard(decl);
+      if (!macro_guard.empty()) {
+        guards[location.filename][location] = macro_guard;
+      }
+    }
+  }
+
+  // Copy over any unchanged files directly.
+  char* fts_paths[2] = { const_cast<char*>(src_dir.c_str()), nullptr };
+  FTS* fts = fts_open(fts_paths, FTS_LOGICAL, nullptr);
+  while (FTSENT* ent = fts_read(fts)) {
+    llvm::StringRef path = ent->fts_path;
+    if (!path.startswith(src_dir)) {
+      err(1, "path '%s' doesn't start with source dir '%s'", ent->fts_path, src_dir.c_str());
+    }
+
+    if (ent->fts_info != FTS_F) {
+      continue;
+    }
+
+    std::string rel_path = path.substr(src_dir.length() + 1);
+    if (guards.count(rel_path) == 0) {
+      std::string dst_path = dst_dir + "/" + rel_path;
+      llvm::StringRef parent_path = llvm::sys::path::parent_path(dst_path);
+      if (llvm::sys::fs::create_directories(parent_path)) {
+        errx(1, "failed to ensure existence of directory '%s'", parent_path.str().c_str());
+      }
+      if (llvm::sys::fs::copy_file(path, dst_path)) {
+        errx(1, "failed to copy '%s/%s' to '%s'", src_dir.c_str(), path.str().c_str(),
+             dst_path.c_str());
+      }
+
+      printf("Copied unmodified header %s\n", dst_path.c_str());
+    }
+  }
+  fts_close(fts);
+
+  for (const auto& file_it : guards) {
+    file_lines[file_it.first] = readFileLines(file_it.first);
+  }
+
+  for (auto& file_it : guards) {
+    llvm::StringRef file_path = file_it.first;
+    GuardMap& orig_guard_map = file_it.second;
+
+    // The end positions given to us are the end of the declaration, which is some point before the
+    // semicolon. Fix up the end positions by scanning for the next semicolon.
+    GuardMap guard_map;
+    for (const auto& it : orig_guard_map) {
+      Location loc = it.first;
+      loc.end = findNextSemicolon(file_lines[file_path], loc.end);
+      guard_map[loc] = it.second;
+    }
+
+    // TODO: Make sure that the Locations don't overlap.
+    // TODO: Merge adjacent non-identical guards.
+    mergeGuards(file_lines[file_path], guard_map);
+
+    if (!file_path.startswith(src_dir)) {
+      errx(1, "input file %s is not in %s\n", file_path.str().c_str(), src_dir.c_str());
+    }
+
+    // rel_path has a leading slash.
+    llvm::StringRef rel_path = file_path.substr(src_dir.size(), file_path.size() - src_dir.size());
+    std::string output_path = (llvm::Twine(dst_dir) + rel_path).str();
+
+    rewriteFile(output_path, file_lines[file_path], guard_map);
+  }
+
+  return true;
+}
diff --git a/tools/versioner/src/Preprocessor.h b/tools/versioner/src/Preprocessor.h
new file mode 100644
index 0000000..7be018b
--- /dev/null
+++ b/tools/versioner/src/Preprocessor.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+class HeaderDatabase;
+
+bool preprocessHeaders(const std::string& preprocessor_output_path, const std::string& source_dir,
+                       HeaderDatabase* database);
diff --git a/tools/versioner/src/SymbolDatabase.cpp b/tools/versioner/src/SymbolDatabase.cpp
index a35f045..b5d2abb 100644
--- a/tools/versioner/src/SymbolDatabase.cpp
+++ b/tools/versioner/src/SymbolDatabase.cpp
@@ -75,7 +75,7 @@
     }
 
     std::string path = std::string(platform_dir) + "/android-" + std::to_string(api_level) +
-                       "/arch-" + type.arch + "/symbols/" + filename;
+                       "/arch-" + to_string(type.arch) + "/symbols/" + filename;
 
     stream = std::ifstream(path);
     if (stream) {
@@ -86,7 +86,7 @@
   }
 
   if (required) {
-    errx(1, "failed to find platform file '%s' for %s", filename.c_str(), type.describe().c_str());
+    errx(1, "failed to find platform file '%s' for %s", filename.c_str(), to_string(type).c_str());
   }
 
   return std::string();
diff --git a/tools/versioner/src/Utils.cpp b/tools/versioner/src/Utils.cpp
index 92cc9de..8dcadd1 100644
--- a/tools/versioner/src/Utils.cpp
+++ b/tools/versioner/src/Utils.cpp
@@ -21,9 +21,12 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <sstream>
 #include <string>
 #include <vector>
 
+#include "DeclarationDatabase.h"
+
 std::string getWorkingDir() {
   char buf[PATH_MAX];
   if (!getcwd(buf, sizeof(buf))) {
@@ -54,3 +57,10 @@
   fts_close(fts);
   return files;
 }
+
+llvm::StringRef StripPrefix(llvm::StringRef string, llvm::StringRef prefix) {
+  if (string.startswith(prefix)) {
+    return string.drop_front(prefix.size());
+  }
+  return string;
+}
diff --git a/tools/versioner/src/Utils.h b/tools/versioner/src/Utils.h
index f4845b8..8d8415f 100644
--- a/tools/versioner/src/Utils.h
+++ b/tools/versioner/src/Utils.h
@@ -19,10 +19,11 @@
 #include <string>
 #include <vector>
 
+#include <llvm/ADT/StringRef.h>
+
 std::string getWorkingDir();
 std::vector<std::string> collectFiles(const std::string& directory);
 
-namespace std {
 static __attribute__((unused)) std::string to_string(const char* c) {
   return c;
 }
@@ -30,13 +31,13 @@
 static __attribute__((unused)) const std::string& to_string(const std::string& str) {
   return str;
 }
-}
 
 template <typename Collection>
 static std::string Join(Collection c, const std::string& delimiter = ", ") {
   std::string result;
   for (const auto& item : c) {
-    result.append(std::to_string(item));
+    using namespace std;
+    result.append(to_string(item));
     result.append(delimiter);
   }
   if (!result.empty()) {
@@ -44,3 +45,5 @@
   }
   return result;
 }
+
+llvm::StringRef StripPrefix(llvm::StringRef string, llvm::StringRef prefix);
diff --git a/tools/versioner/src/versioner.cpp b/tools/versioner/src/versioner.cpp
index 7238a8c..b18c3c2 100644
--- a/tools/versioner/src/versioner.cpp
+++ b/tools/versioner/src/versioner.cpp
@@ -37,7 +37,9 @@
 #include <clang/Tooling/Tooling.h>
 #include <llvm/ADT/StringRef.h>
 
+#include "Arch.h"
 #include "DeclarationDatabase.h"
+#include "Preprocessor.h"
 #include "SymbolDatabase.h"
 #include "Utils.h"
 #include "versioner.h"
@@ -47,6 +49,7 @@
 using namespace clang::tooling;
 
 bool verbose;
+static bool add_include;
 
 class HeaderCompilationDatabase : public CompilationDatabase {
   CompilationType type;
@@ -75,9 +78,17 @@
     command.push_back("-D_FORTIFY_SOURCE=2");
     command.push_back("-D_GNU_SOURCE");
     command.push_back("-Wno-unknown-attributes");
+    command.push_back("-Wno-pragma-once-outside-header");
     command.push_back("-target");
     command.push_back(arch_targets[type.arch]);
 
+    if (add_include) {
+      const char* top = getenv("ANDROID_BUILD_TOP");
+      std::string header_path = to_string(top) + "/bionic/libc/include/android/versioning.h";
+      command.push_back("-include");
+      command.push_back(std::move(header_path));
+    }
+
     return CompileCommand(cwd, filename, command);
   }
 
@@ -105,8 +116,7 @@
   std::vector<std::string> dependencies;
 };
 
-static CompilationRequirements collectRequirements(const std::string& arch,
-                                                   const std::string& header_dir,
+static CompilationRequirements collectRequirements(const Arch& arch, const std::string& header_dir,
                                                    const std::string& dependency_dir) {
   std::vector<std::string> headers = collectFiles(header_dir);
 
@@ -143,7 +153,7 @@
     };
 
     collect_children(dependency_dir + "/common");
-    collect_children(dependency_dir + "/" + arch);
+    collect_children(dependency_dir + "/" + to_string(arch));
   }
 
   auto new_end = std::remove_if(headers.begin(), headers.end(), [&arch](llvm::StringRef header) {
@@ -165,10 +175,10 @@
   return result;
 }
 
-static std::set<CompilationType> generateCompilationTypes(
-    const std::set<std::string> selected_architectures, const std::set<int>& selected_levels) {
+static std::set<CompilationType> generateCompilationTypes(const std::set<Arch> selected_architectures,
+                                                          const std::set<int>& selected_levels) {
   std::set<CompilationType> result;
-  for (const std::string& arch : selected_architectures) {
+  for (const auto& arch : selected_architectures) {
     int min_api = arch_min_api[arch];
     for (int api_level : selected_levels) {
       if (api_level < min_api) {
@@ -181,31 +191,17 @@
   return result;
 }
 
-using DeclarationDatabase = std::map<std::string, std::map<CompilationType, Declaration>>;
-
-static DeclarationDatabase transposeHeaderDatabases(
-    const std::map<CompilationType, HeaderDatabase>& original) {
-  DeclarationDatabase result;
-  for (const auto& outer : original) {
-    const CompilationType& type = outer.first;
-    for (const auto& inner : outer.second.declarations) {
-      const std::string& symbol_name = inner.first;
-      result[symbol_name][type] = inner.second;
-    }
-  }
-  return result;
-}
-
-static DeclarationDatabase compileHeaders(const std::set<CompilationType>& types,
-                                          const std::string& header_dir,
-                                          const std::string& dependency_dir, bool* failed) {
+static std::unique_ptr<HeaderDatabase> compileHeaders(const std::set<CompilationType>& types,
+                                                      const std::string& header_dir,
+                                                      const std::string& dependency_dir,
+                                                      bool* failed) {
   constexpr size_t thread_count = 8;
   size_t threads_created = 0;
   std::mutex mutex;
   std::vector<std::thread> threads(thread_count);
 
   std::map<CompilationType, HeaderDatabase> header_databases;
-  std::unordered_map<std::string, CompilationRequirements> requirements;
+  std::unordered_map<Arch, CompilationRequirements> requirements;
 
   std::string cwd = getWorkingDir();
   bool errors = false;
@@ -216,6 +212,7 @@
     }
   }
 
+  auto result = std::make_unique<HeaderDatabase>();
   for (const auto& type : types) {
     size_t thread_id = threads_created++;
     if (thread_id >= thread_count) {
@@ -227,7 +224,6 @@
         [&](CompilationType type) {
           const auto& req = requirements[type.arch];
 
-          HeaderDatabase database;
           HeaderCompilationDatabase compilation_database(type, cwd, req.headers, req.dependencies);
 
           ClangTool tool(compilation_database, req.headers);
@@ -240,15 +236,12 @@
             if (diagnostics_engine.getNumWarnings() || diagnostics_engine.hasErrorOccurred()) {
               std::unique_lock<std::mutex> l(mutex);
               errors = true;
-              printf("versioner: compilation failure for %s in %s\n", type.describe().c_str(),
+              printf("versioner: compilation failure for %s in %s\n", to_string(type).c_str(),
                      ast->getOriginalSourceFileName().str().c_str());
             }
 
-            database.parseAST(ast.get());
+            result->parseAST(type, ast.get());
           }
-
-          std::unique_lock<std::mutex> l(mutex);
-          header_databases[type] = database;
         },
         type);
   }
@@ -266,83 +259,66 @@
     *failed = errors;
   }
 
-  return transposeHeaderDatabases(header_databases);
+  return result;
 }
 
-static bool sanityCheck(const std::set<CompilationType>& types,
-                        const DeclarationDatabase& database) {
+// Perform a sanity check on a symbol's declarations, enforcing the following invariants:
+//   1. At most one inline definition of the function exists.
+//   2. All of the availability declarations for a symbol are compatible.
+//      If a function is declared as an inline before a certain version, the inline definition
+//      should have no version tag.
+//   3. Each availability type must only be present globally or on a per-arch basis.
+//      (e.g. __INTRODUCED_IN_ARM(9) __INTRODUCED_IN_X86(10) __DEPRECATED_IN(11) is fine,
+//      but not __INTRODUCED_IN(9) __INTRODUCED_IN_X86(10))
+static bool checkSymbol(const Symbol& symbol) {
+  std::string cwd = getWorkingDir() + "/";
+
+  const Declaration* inline_definition = nullptr;
+  for (const auto& decl_it : symbol.declarations) {
+    const Declaration* decl = &decl_it.second;
+    if (decl->is_definition) {
+      if (inline_definition) {
+        fprintf(stderr, "versioner: multiple definitions of symbol %s\n", symbol.name.c_str());
+        symbol.dump(cwd);
+        inline_definition->dump(cwd);
+        return false;
+      }
+
+      inline_definition = decl;
+    }
+
+    DeclarationAvailability availability;
+    if (!decl->calculateAvailability(&availability)) {
+      fprintf(stderr, "versioner: failed to calculate availability for declaration:\n");
+      decl->dump(cwd, stderr, 2);
+      return false;
+    }
+
+    if (decl->is_definition && !availability.empty()) {
+      fprintf(stderr, "versioner: inline definition has non-empty versioning information:\n");
+      decl->dump(cwd, stderr, 2);
+      return false;
+    }
+  }
+
+  DeclarationAvailability availability;
+  if (!symbol.calculateAvailability(&availability)) {
+    fprintf(stderr, "versioner: inconsistent availability for symbol '%s'\n", symbol.name.c_str());
+    symbol.dump(cwd);
+    return false;
+  }
+
+  // TODO: Check invariant #3.
+  return true;
+}
+
+static bool sanityCheck(const HeaderDatabase* database) {
   bool error = false;
   std::string cwd = getWorkingDir() + "/";
 
-  for (auto outer : database) {
-    const std::string& symbol_name = outer.first;
-    CompilationType last_type;
-    DeclarationAvailability last_availability;
-
-    // Rely on std::set being sorted to loop through the types by architecture.
-    for (const CompilationType& type : types) {
-      auto inner = outer.second.find(type);
-      if (inner == outer.second.end()) {
-        // TODO: Check for holes.
-        continue;
-      }
-
-      const Declaration& declaration = inner->second;
-      bool found_availability = false;
-      bool availability_mismatch = false;
-      DeclarationAvailability current_availability;
-
-      // Ensure that all of the availability declarations for this symbol match.
-      for (const DeclarationLocation& location : declaration.locations) {
-        if (!found_availability) {
-          found_availability = true;
-          current_availability = location.availability;
-          continue;
-        }
-
-        if (current_availability != location.availability) {
-          availability_mismatch = true;
-          error = true;
-        }
-      }
-
-      if (availability_mismatch) {
-        printf("%s: availability mismatch for %s\n", symbol_name.c_str(), type.describe().c_str());
-        declaration.dump(cwd);
-      }
-
-      if (type.arch != last_type.arch) {
-        last_type = type;
-        last_availability = current_availability;
-        continue;
-      }
-
-      // Ensure that availability declarations are consistent across API levels for a given arch.
-      if (last_availability != current_availability) {
-        error = true;
-        printf("%s: availability mismatch between %s and %s: [%s] before, [%s] after\n",
-               symbol_name.c_str(), last_type.describe().c_str(), type.describe().c_str(),
-               last_availability.describe().c_str(), current_availability.describe().c_str());
-      }
-
-      // Ensure that at most one inline definition of a function exists.
-      std::set<DeclarationLocation> inline_definitions;
-
-      for (const DeclarationLocation& location : declaration.locations) {
-        if (location.is_definition) {
-          inline_definitions.insert(location);
-        }
-      }
-
-      if (inline_definitions.size() > 1) {
-        error = true;
-        printf("%s: multiple inline definitions found:\n", symbol_name.c_str());
-        for (const DeclarationLocation& location : declaration.locations) {
-          location.dump(cwd);
-        }
-      }
-
-      last_type = type;
+  for (const auto& symbol_it : database->symbols) {
+    if (!checkSymbol(symbol_it.second)) {
+      error = true;
     }
   }
   return !error;
@@ -351,172 +327,109 @@
 // Check that our symbol availability declarations match the actual NDK
 // platform symbol availability.
 static bool checkVersions(const std::set<CompilationType>& types,
-                          const DeclarationDatabase& declaration_database,
+                          const HeaderDatabase* header_database,
                           const NdkSymbolDatabase& symbol_database) {
+  std::string cwd = getWorkingDir() + "/";
   bool failed = false;
 
-  std::map<std::string, std::set<CompilationType>> arch_types;
+  std::map<Arch, std::set<CompilationType>> arch_types;
   for (const CompilationType& type : types) {
     arch_types[type.arch].insert(type);
   }
 
   std::set<std::string> completely_unavailable;
+  std::map<std::string, std::set<CompilationType>> missing_availability;
+  std::map<std::string, std::set<CompilationType>> extra_availability;
 
-  for (const auto& outer : declaration_database) {
-    const std::string& symbol_name = outer.first;
-    const auto& compilations = outer.second;
+  for (const auto& symbol_it : header_database->symbols) {
+    const auto& symbol_name = symbol_it.first;
+    DeclarationAvailability symbol_availability;
 
-    auto platform_availability_it = symbol_database.find(symbol_name);
+    if (!symbol_it.second.calculateAvailability(&symbol_availability)) {
+      errx(1, "failed to calculate symbol availability");
+    }
+
+    const auto platform_availability_it = symbol_database.find(symbol_name);
     if (platform_availability_it == symbol_database.end()) {
       completely_unavailable.insert(symbol_name);
       continue;
     }
 
     const auto& platform_availability = platform_availability_it->second;
-    std::set<CompilationType> missing_symbol;
-    std::set<CompilationType> missing_decl;
 
     for (const CompilationType& type : types) {
-      auto it = compilations.find(type);
-      if (it == compilations.end()) {
-        missing_decl.insert(type);
+      bool should_be_available = true;
+      const auto& global_availability = symbol_availability.global_availability;
+      const auto& arch_availability = symbol_availability.arch_availability[type.arch];
+      if (global_availability.introduced != 0 && global_availability.introduced > type.api_level) {
+        should_be_available = false;
+      }
+
+      if (arch_availability.introduced != 0 && arch_availability.introduced > type.api_level) {
+        should_be_available = false;
+      }
+
+      if (global_availability.obsoleted != 0 && global_availability.obsoleted <= type.api_level) {
+        should_be_available = false;
+      }
+
+      if (arch_availability.obsoleted != 0 && arch_availability.obsoleted <= type.api_level) {
+        should_be_available = false;
+      }
+
+      if (arch_availability.future) {
         continue;
       }
 
-      const Declaration& declaration = it->second;
+      // The function declaration might be (validly) missing for the given CompilationType.
+      if (!symbol_it.second.hasDeclaration(type)) {
+        should_be_available = false;
+      }
 
-      // sanityCheck ensured that the availability declarations for a given arch match.
-      DeclarationAvailability availability = declaration.locations.begin()->availability;
-      int api_level = type.api_level;
+      bool is_available = platform_availability.count(type);
 
-      int introduced = std::max(0, availability.introduced);
-      int obsoleted = availability.obsoleted == 0 ? INT_MAX : availability.obsoleted;
-      bool decl_available = api_level >= introduced && api_level < obsoleted;
-
-      auto symbol_availability_it = platform_availability.find(type);
-      bool symbol_available = symbol_availability_it != platform_availability.end();
-      if (decl_available) {
-        if (!symbol_available) {
-          // Ensure that either it exists in the platform, or an inline definition is visible.
-          if (!declaration.hasDefinition()) {
-            missing_symbol.insert(type);
-            continue;
-          }
+      if (should_be_available != is_available) {
+        if (is_available) {
+          extra_availability[symbol_name].insert(type);
         } else {
-          // Ensure that symbols declared as functions/variables actually are.
-          switch (declaration.type()) {
-            case DeclarationType::inconsistent:
-              printf("%s: inconsistent declaration type\n", symbol_name.c_str());
-              declaration.dump();
-              exit(1);
-
-            case DeclarationType::variable:
-              if (symbol_availability_it->second != NdkSymbolType::variable) {
-                printf("%s: declared as variable, exists in platform as function\n",
-                       symbol_name.c_str());
-                failed = true;
-              }
-              break;
-
-            case DeclarationType::function:
-              if (symbol_availability_it->second != NdkSymbolType::function) {
-                printf("%s: declared as function, exists in platform as variable\n",
-                       symbol_name.c_str());
-                failed = true;
-              }
-              break;
-          }
-        }
-      } else {
-        // Ensure that it's not available in the platform.
-        if (symbol_availability_it != platform_availability.end()) {
-          printf("%s: symbol should be unavailable in %s (declared with availability %s)\n",
-                 symbol_name.c_str(), type.describe().c_str(), availability.describe().c_str());
-          failed = true;
+          missing_availability[symbol_name].insert(type);
         }
       }
     }
+  }
 
-    // Allow declarations to be missing from an entire architecture.
-    for (const auto& arch_type : arch_types) {
-      const std::string& arch = arch_type.first;
-      bool found_all = true;
-      for (const auto& type : arch_type.second) {
-        if (missing_decl.find(type) == missing_decl.end()) {
-          found_all = false;
-          break;
-        }
-      }
+  for (const auto& it : symbol_database) {
+    const std::string& symbol_name = it.first;
 
-      if (!found_all) {
-        continue;
-      }
-
-      for (auto it = missing_decl.begin(); it != missing_decl.end();) {
-        if (it->arch == arch) {
-          it = missing_decl.erase(it);
-        } else {
-          ++it;
-        }
-      }
-    }
-
-    auto types_to_string = [](const std::set<CompilationType>& types) {
-      std::string result;
-      for (const CompilationType& type : types) {
-        result += type.describe();
-        result += ", ";
-      }
-      result.resize(result.length() - 2);
-      return result;
-    };
-
-    if (!missing_decl.empty()) {
-      printf("%s: declaration missing in %s\n", symbol_name.c_str(),
-             types_to_string(missing_decl).c_str());
-      failed = true;
-    }
-
-    if (!missing_symbol.empty()) {
+    bool symbol_error = false;
+    auto missing_it = missing_availability.find(symbol_name);
+    if (missing_it != missing_availability.end()) {
       printf("%s: declaration marked available but symbol missing in [%s]\n", symbol_name.c_str(),
-             types_to_string(missing_symbol).c_str());
+             Join(missing_it->second, ", ").c_str());
+      symbol_error = true;
       failed = true;
     }
-  }
 
-  for (const std::string& symbol_name : completely_unavailable) {
-    bool found_inline_definition = false;
-    bool future = false;
-
-    auto symbol_it = declaration_database.find(symbol_name);
-
-    // Ignore inline functions and functions that are tagged as __INTRODUCED_IN_FUTURE.
-    // Ensure that all of the declarations of that function satisfy that.
-    for (const auto& declaration_pair : symbol_it->second) {
-      const Declaration& declaration = declaration_pair.second;
-      DeclarationAvailability availability = declaration.locations.begin()->availability;
-
-      if (availability.introduced >= 10000) {
-        future = true;
-      }
-
-      if (declaration.hasDefinition()) {
-        found_inline_definition = true;
+    if (verbose) {
+      auto extra_it = extra_availability.find(symbol_name);
+      if (extra_it != extra_availability.end()) {
+        printf("%s: declaration marked unavailable but symbol available in [%s]\n",
+               symbol_name.c_str(), Join(extra_it->second, ", ").c_str());
+        symbol_error = true;
+        failed = true;
       }
     }
 
-    if (future || found_inline_definition) {
-      continue;
+    if (symbol_error) {
+      auto symbol_it = header_database->symbols.find(symbol_name);
+      if (symbol_it == header_database->symbols.end()) {
+        errx(1, "failed to find symbol in header database");
+      }
+      symbol_it->second.dump(cwd);
     }
-
-    if (missing_symbol_whitelist.count(symbol_name) != 0) {
-      continue;
-    }
-
-    printf("%s: not available in any platform\n", symbol_name.c_str());
-    failed = true;
   }
 
+  // TODO: Verify that function/variable declarations are actually function/variable symbols.
   return !failed;
 }
 
@@ -539,7 +452,12 @@
     fprintf(stderr, "  -p PATH\tcompare against NDK platform at PATH\n");
     fprintf(stderr, "  -v\t\tenable verbose warnings\n");
     fprintf(stderr, "\n");
+    fprintf(stderr, "Preprocessing:\n");
+    fprintf(stderr, "  -o PATH\tpreprocess header files and emit them at PATH\n");
+    fprintf(stderr, "  -f\tpreprocess header files even if validation fails\n");
+    fprintf(stderr, "\n");
     fprintf(stderr, "Miscellaneous:\n");
+    fprintf(stderr, "  -d\t\tdump function availability\n");
     fprintf(stderr, "  -h\t\tdisplay this message\n");
     exit(0);
   }
@@ -549,11 +467,14 @@
   std::string cwd = getWorkingDir() + "/";
   bool default_args = true;
   std::string platform_dir;
-  std::set<std::string> selected_architectures;
+  std::set<Arch> selected_architectures;
   std::set<int> selected_levels;
+  bool dump = false;
+  std::string preprocessor_output_path;
+  bool force = false;
 
   int c;
-  while ((c = getopt(argc, argv, "a:r:p:vh")) != -1) {
+  while ((c = getopt(argc, argv, "a:r:p:vo:fdhi")) != -1) {
     default_args = false;
     switch (c) {
       case 'a': {
@@ -572,10 +493,8 @@
       }
 
       case 'r': {
-        if (supported_archs.count(optarg) == 0) {
-          errx(1, "unsupported architecture: %s", optarg);
-        }
-        selected_architectures.insert(optarg);
+        Arch arch = arch_from_string(optarg);
+        selected_architectures.insert(arch);
         break;
       }
 
@@ -586,6 +505,10 @@
 
         platform_dir = optarg;
 
+        if (platform_dir.empty()) {
+          usage();
+        }
+
         struct stat st;
         if (stat(platform_dir.c_str(), &st) != 0) {
           err(1, "failed to stat platform directory '%s'", platform_dir.c_str());
@@ -600,10 +523,33 @@
         verbose = true;
         break;
 
+      case 'o':
+        if (!preprocessor_output_path.empty()) {
+          usage();
+        }
+        preprocessor_output_path = optarg;
+        if (preprocessor_output_path.empty()) {
+          usage();
+        }
+        break;
+
+      case 'f':
+        force = true;
+        break;
+
+      case 'd':
+        dump = true;
+        break;
+
       case 'h':
         usage(true);
         break;
 
+      case 'i':
+        // Secret option for tests to -include <android/versioning.h>.
+        add_include = true;
+        break;
+
       default:
         usage();
         break;
@@ -617,22 +563,23 @@
   std::string header_dir;
   std::string dependency_dir;
 
+  const char* top = getenv("ANDROID_BUILD_TOP");
+  if (!top && (optind == argc || add_include)) {
+    fprintf(stderr, "versioner: failed to autodetect bionic paths. Is ANDROID_BUILD_TOP set?\n");
+    usage();
+  }
+
   if (optind == argc) {
     // Neither HEADER_PATH nor DEPS_PATH were specified, so try to figure them out.
-    const char* top = getenv("ANDROID_BUILD_TOP");
-    if (!top) {
-      fprintf(stderr, "versioner: failed to autodetect bionic paths. Is ANDROID_BUILD_TOP set?\n");
-      usage();
-    }
-
-    std::string versioner_dir = std::to_string(top) + "/bionic/tools/versioner";
+    std::string versioner_dir = to_string(top) + "/bionic/tools/versioner";
     header_dir = versioner_dir + "/current";
     dependency_dir = versioner_dir + "/dependencies";
     if (platform_dir.empty()) {
       platform_dir = versioner_dir + "/platforms";
     }
   } else {
-    header_dir = argv[optind];
+    // Intentional leak.
+    header_dir = realpath(argv[optind], nullptr);
 
     if (argc - optind == 2) {
       dependency_dir = argv[optind + 1];
@@ -656,7 +603,6 @@
   }
 
   std::set<CompilationType> compilation_types;
-  DeclarationDatabase declaration_database;
   NdkSymbolDatabase symbol_database;
 
   compilation_types = generateCompilationTypes(selected_architectures, selected_levels);
@@ -668,19 +614,27 @@
   }
 
   bool failed = false;
-  declaration_database = compileHeaders(compilation_types, header_dir, dependency_dir, &failed);
+  std::unique_ptr<HeaderDatabase> declaration_database =
+      compileHeaders(compilation_types, header_dir, dependency_dir, &failed);
 
-  if (!sanityCheck(compilation_types, declaration_database)) {
-    printf("versioner: sanity check failed\n");
-    failed = true;
-  }
-
-  if (!platform_dir.empty()) {
-    if (!checkVersions(compilation_types, declaration_database, symbol_database)) {
-      printf("versioner: version check failed\n");
+  if (dump) {
+    declaration_database->dump(header_dir + "/");
+  } else {
+    if (!sanityCheck(declaration_database.get())) {
+      printf("versioner: sanity check failed\n");
       failed = true;
     }
+
+    if (!platform_dir.empty()) {
+      if (!checkVersions(compilation_types, declaration_database.get(), symbol_database)) {
+        printf("versioner: version check failed\n");
+        failed = true;
+      }
+    }
   }
 
+  if (!preprocessor_output_path.empty() && (force || !failed)) {
+    failed = !preprocessHeaders(preprocessor_output_path, header_dir, declaration_database.get());
+  }
   return failed;
 }
diff --git a/tools/versioner/src/versioner.h b/tools/versioner/src/versioner.h
index ced9b79..986c25f 100644
--- a/tools/versioner/src/versioner.h
+++ b/tools/versioner/src/versioner.h
@@ -24,37 +24,19 @@
 
 extern bool verbose;
 
-static const std::set<std::string> supported_archs = {
-  "arm", "arm64", "mips", "mips64", "x86", "x86_64",
-};
+#define D(...)             \
+  do {                     \
+    if (verbose) {         \
+      printf(__VA_ARGS__); \
+    }                      \
+  } while (0)
 
-static std::unordered_map<std::string, std::string> arch_targets = {
-  { "arm", "arm-linux-androideabi" },
-  { "arm64", "aarch64-linux-android" },
-  { "mips", "mipsel-linux-android" },
-  { "mips64", "mips64el-linux-android" },
-  { "x86", "i686-linux-android" },
-  { "x86_64", "x86_64-linux-android" },
-};
-
-static const std::set<int> supported_levels = { 9, 12, 13, 14, 15, 16, 17, 18, 19, 21, 23, 24 };
-
-// Non-const for the convenience of being able to index with operator[].
-static std::map<std::string, int> arch_min_api = {
-  { "arm", 9 },
-  { "arm64", 21 },
-  { "mips", 9 },
-  { "mips64", 21 },
-  { "x86", 9 },
-  { "x86_64", 21 },
-};
-
-static const std::unordered_map<std::string, std::set<std::string>> header_blacklist = {
+static const std::unordered_map<std::string, std::set<Arch>> header_blacklist = {
   // Internal header.
   { "sys/_system_properties.h", supported_archs },
 
   // time64.h #errors when included on LP64 archs.
-  { "time64.h", { "arm64", "mips64", "x86_64" } },
+  { "time64.h", { Arch::arm64, Arch::mips64, Arch::x86_64 } },
 };
 
 static const std::unordered_set<std::string> missing_symbol_whitelist = {
diff --git a/tools/versioner/tests/arch_specific/run.sh b/tools/versioner/tests/arch_specific/run.sh
index 6d97fb0..f0d95ae 100644
--- a/tools/versioner/tests/arch_specific/run.sh
+++ b/tools/versioner/tests/arch_specific/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -r x86 -a 9
+versioner headers -p platforms -r arm -r x86 -a 9 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/compilation_error/run.sh b/tools/versioner/tests/compilation_error/run.sh
index 8babb73..a34fda8 100644
--- a/tools/versioner/tests/compilation_error/run.sh
+++ b/tools/versioner/tests/compilation_error/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -a 9
+versioner headers -p platforms -r arm -a 9 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/dependencies/run.sh b/tools/versioner/tests/dependencies/run.sh
index 3a3dda8..0c17907 100644
--- a/tools/versioner/tests/dependencies/run.sh
+++ b/tools/versioner/tests/dependencies/run.sh
@@ -1 +1 @@
-versioner headers dependencies -p platforms -r arm -r x86 -a 9
+versioner headers dependencies -p platforms -r arm -r x86 -a 9
\ No newline at end of file
diff --git a/tools/versioner/tests/errordecl/headers/foo.h b/tools/versioner/tests/errordecl/headers/foo.h
deleted file mode 100644
index c466420..0000000
--- a/tools/versioner/tests/errordecl/headers/foo.h
+++ /dev/null
@@ -1 +0,0 @@
-int foo() __attribute__((unavailable));
diff --git a/tools/versioner/tests/errordecl/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/errordecl/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/versioner/tests/errordecl/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
diff --git a/tools/versioner/tests/errordecl/run.sh b/tools/versioner/tests/errordecl/run.sh
deleted file mode 100644
index 0dea98f..0000000
--- a/tools/versioner/tests/errordecl/run.sh
+++ /dev/null
@@ -1 +0,0 @@
-versioner -v headers -p platforms -r arm -a 9
diff --git a/tools/versioner/tests/future/headers/foo.h b/tools/versioner/tests/future/headers/foo.h
index b5113f4..54e8f0c 100644
--- a/tools/versioner/tests/future/headers/foo.h
+++ b/tools/versioner/tests/future/headers/foo.h
@@ -1 +1 @@
-int foo() __attribute__((availability(android, introduced = 10000)));
+int foo() __INTRODUCED_IN_FUTURE;
diff --git a/tools/versioner/tests/future/run.sh b/tools/versioner/tests/future/run.sh
index 0dea98f..041b047 100644
--- a/tools/versioner/tests/future/run.sh
+++ b/tools/versioner/tests/future/run.sh
@@ -1 +1 @@
-versioner -v headers -p platforms -r arm -a 9
+versioner -v headers -p platforms -r arm -a 9 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/future_arch/headers/foo.h b/tools/versioner/tests/future_arch/headers/foo.h
index 6740975..9dd976e 100644
--- a/tools/versioner/tests/future_arch/headers/foo.h
+++ b/tools/versioner/tests/future_arch/headers/foo.h
@@ -1,5 +1,5 @@
 #if defined(__arm__)
-int foo() __attribute__((availability(android, introduced = 9)));
+int foo() __INTRODUCED_IN(9);
 #else
-int foo() __attribute__((availability(android, introduced = 10000)));
+int foo() __INTRODUCED_IN_FUTURE;
 #endif
diff --git a/tools/versioner/tests/future_arch/run.sh b/tools/versioner/tests/future_arch/run.sh
index 36846da..ad8f430 100644
--- a/tools/versioner/tests/future_arch/run.sh
+++ b/tools/versioner/tests/future_arch/run.sh
@@ -1 +1 @@
-versioner -v headers -p platforms -r arm -r x86 -a 9
+versioner -v headers -p platforms -r arm -r x86 -a 9 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/inline/headers/foo.h b/tools/versioner/tests/inline/headers/foo.h
index 7a48a72..a61b386 100644
--- a/tools/versioner/tests/inline/headers/foo.h
+++ b/tools/versioner/tests/inline/headers/foo.h
@@ -1,7 +1,7 @@
-#if __ANDROID_API__ <= 9
-static int foo() __attribute__((availability(android, introduced = 9))) {
+#if __ANDROID_API__ < 12
+static int foo() {
   return 0;
 }
 #else
-int foo() __attribute__((availability(android, introduced = 9)));
+int foo() __INTRODUCED_IN(12);
 #endif
diff --git a/tools/versioner/tests/inline/run.sh b/tools/versioner/tests/inline/run.sh
index 914c55d..9bfbe6d 100644
--- a/tools/versioner/tests/inline/run.sh
+++ b/tools/versioner/tests/inline/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -a 9 -a 12
+versioner headers -p platforms -r arm -a 9 -a 12 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/inline_unavailable/headers/foo.h b/tools/versioner/tests/inline_unavailable/headers/foo.h
deleted file mode 100644
index 6800dd0..0000000
--- a/tools/versioner/tests/inline_unavailable/headers/foo.h
+++ /dev/null
@@ -1,3 +0,0 @@
-static int foo() __attribute__((availability(android, introduced = 9))) {
-  return 0;
-}
diff --git a/tools/versioner/tests/inline_unavailable/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/inline_unavailable/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/versioner/tests/inline_unavailable/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
diff --git a/tools/versioner/tests/inline_unavailable/run.sh b/tools/versioner/tests/inline_unavailable/run.sh
deleted file mode 100644
index 0dea98f..0000000
--- a/tools/versioner/tests/inline_unavailable/run.sh
+++ /dev/null
@@ -1 +0,0 @@
-versioner -v headers -p platforms -r arm -a 9
diff --git a/tools/versioner/tests/inline_version_mismatch/expected_fail b/tools/versioner/tests/inline_version_mismatch/expected_fail
deleted file mode 100644
index 7f0709c..0000000
--- a/tools/versioner/tests/inline_version_mismatch/expected_fail
+++ /dev/null
@@ -1,2 +0,0 @@
-foo: availability mismatch between arm-9 and arm-12: [introduced = 9] before, [introduced = 10] after
-versioner: sanity check failed
diff --git a/tools/versioner/tests/inline_version_mismatch/headers/foo.h b/tools/versioner/tests/inline_version_mismatch/headers/foo.h
deleted file mode 100644
index be7cc2c..0000000
--- a/tools/versioner/tests/inline_version_mismatch/headers/foo.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#if __ANDROID_API__ <= 9
-static int foo() __attribute__((availability(android, introduced = 9))) {
-  return 0;
-}
-#else
-int foo() __attribute__((availability(android, introduced = 10)));
-#endif
diff --git a/tools/versioner/tests/inline_version_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/inline_version_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
deleted file mode 100644
index e69de29..0000000
--- a/tools/versioner/tests/inline_version_mismatch/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
+++ /dev/null
diff --git a/tools/versioner/tests/inline_version_mismatch/run.sh b/tools/versioner/tests/inline_version_mismatch/run.sh
deleted file mode 100644
index 914c55d..0000000
--- a/tools/versioner/tests/inline_version_mismatch/run.sh
+++ /dev/null
@@ -1 +0,0 @@
-versioner headers -p platforms -r arm -a 9 -a 12
diff --git a/tools/versioner/tests/missing_api/expected_fail b/tools/versioner/tests/missing_api/expected_fail
index 85c07f5..65a25f2 100644
--- a/tools/versioner/tests/missing_api/expected_fail
+++ b/tools/versioner/tests/missing_api/expected_fail
@@ -1,2 +1,5 @@
 foo: declaration marked available but symbol missing in [arm-12]
+  foo: introduced = 9
+    extern declaration @ headers/foo.h:1:1
+      introduced = 9
 versioner: version check failed
diff --git a/tools/versioner/tests/missing_api/headers/foo.h b/tools/versioner/tests/missing_api/headers/foo.h
index 2998c8e..3ff3ff7 100644
--- a/tools/versioner/tests/missing_api/headers/foo.h
+++ b/tools/versioner/tests/missing_api/headers/foo.h
@@ -1 +1 @@
-int foo() __attribute__((availability(android, introduced = 9)));
+int foo() __INTRODUCED_IN(9);
\ No newline at end of file
diff --git a/tools/versioner/tests/missing_api/run.sh b/tools/versioner/tests/missing_api/run.sh
index 914c55d..9bfbe6d 100644
--- a/tools/versioner/tests/missing_api/run.sh
+++ b/tools/versioner/tests/missing_api/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -a 9 -a 12
+versioner headers -p platforms -r arm -a 9 -a 12 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/missing_arch/expected_fail b/tools/versioner/tests/missing_arch/expected_fail
index 56de9d9..ed8ab79 100644
--- a/tools/versioner/tests/missing_arch/expected_fail
+++ b/tools/versioner/tests/missing_arch/expected_fail
@@ -1,2 +1,5 @@
 foo: declaration marked available but symbol missing in [x86-9]
+  foo: no availability
+    extern declaration @ headers/foo.h:1:1
+      no availability
 versioner: version check failed
diff --git a/tools/versioner/tests/missing_arch/headers/foo.h b/tools/versioner/tests/missing_arch/headers/foo.h
index 5d5f8f0..176e7a3 100644
--- a/tools/versioner/tests/missing_arch/headers/foo.h
+++ b/tools/versioner/tests/missing_arch/headers/foo.h
@@ -1 +1 @@
-int foo();
+int foo();
\ No newline at end of file
diff --git a/tools/versioner/tests/missing_arch/run.sh b/tools/versioner/tests/missing_arch/run.sh
index 6d97fb0..f0d95ae 100644
--- a/tools/versioner/tests/missing_arch/run.sh
+++ b/tools/versioner/tests/missing_arch/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -r x86 -a 9
+versioner headers -p platforms -r arm -r x86 -a 9 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/multiple_decl/headers/bar.h b/tools/versioner/tests/multiple_decl/headers/bar.h
index 95f0174..1d3a28c 100644
--- a/tools/versioner/tests/multiple_decl/headers/bar.h
+++ b/tools/versioner/tests/multiple_decl/headers/bar.h
@@ -1 +1 @@
-int foo() __attribute__((availability(android, obsoleted = 12)));
+int foo() __REMOVED_IN(12);
\ No newline at end of file
diff --git a/tools/versioner/tests/multiple_decl/headers/foo.h b/tools/versioner/tests/multiple_decl/headers/foo.h
index 95f0174..1d3a28c 100644
--- a/tools/versioner/tests/multiple_decl/headers/foo.h
+++ b/tools/versioner/tests/multiple_decl/headers/foo.h
@@ -1 +1 @@
-int foo() __attribute__((availability(android, obsoleted = 12)));
+int foo() __REMOVED_IN(12);
\ No newline at end of file
diff --git a/tools/versioner/tests/multiple_decl/run.sh b/tools/versioner/tests/multiple_decl/run.sh
index 8babb73..a34fda8 100644
--- a/tools/versioner/tests/multiple_decl/run.sh
+++ b/tools/versioner/tests/multiple_decl/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -a 9
+versioner headers -p platforms -r arm -a 9 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/multiple_decl_mismatch/expected_fail b/tools/versioner/tests/multiple_decl_mismatch/expected_fail
index 6d0d209..8e8c846 100644
--- a/tools/versioner/tests/multiple_decl_mismatch/expected_fail
+++ b/tools/versioner/tests/multiple_decl_mismatch/expected_fail
@@ -1,5 +1,8 @@
-foo: availability mismatch for arm-9
-    foo declared in 2 locations:
-        extern function declaration @ headers/bar.h:1:5	[obsoleted = 12]
-        extern function declaration @ headers/foo.h:1:5	[obsoleted = 9]
+versioner: inconsistent availability for symbol 'foo'
+versioner: failed to calculate symbol availability
+  foo: invalid
+    extern declaration @ headers/bar.h:1:1
+      obsoleted = 12
+    extern declaration @ headers/foo.h:1:1
+      obsoleted = 9
 versioner: sanity check failed
diff --git a/tools/versioner/tests/multiple_decl_mismatch/headers/bar.h b/tools/versioner/tests/multiple_decl_mismatch/headers/bar.h
index 95f0174..1d3a28c 100644
--- a/tools/versioner/tests/multiple_decl_mismatch/headers/bar.h
+++ b/tools/versioner/tests/multiple_decl_mismatch/headers/bar.h
@@ -1 +1 @@
-int foo() __attribute__((availability(android, obsoleted = 12)));
+int foo() __REMOVED_IN(12);
\ No newline at end of file
diff --git a/tools/versioner/tests/multiple_decl_mismatch/headers/foo.h b/tools/versioner/tests/multiple_decl_mismatch/headers/foo.h
index 9c81a89..49a73ec 100644
--- a/tools/versioner/tests/multiple_decl_mismatch/headers/foo.h
+++ b/tools/versioner/tests/multiple_decl_mismatch/headers/foo.h
@@ -1 +1 @@
-int foo() __attribute__((availability(android, obsoleted = 9)));
+int foo() __REMOVED_IN(9);
\ No newline at end of file
diff --git a/tools/versioner/tests/multiple_decl_mismatch/run.sh b/tools/versioner/tests/multiple_decl_mismatch/run.sh
index 8babb73..a34fda8 100644
--- a/tools/versioner/tests/multiple_decl_mismatch/run.sh
+++ b/tools/versioner/tests/multiple_decl_mismatch/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -a 9
+versioner headers -p platforms -r arm -a 9 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/obsoleted/headers/foo.h b/tools/versioner/tests/obsoleted/headers/foo.h
index c190629..68f3d43 100644
--- a/tools/versioner/tests/obsoleted/headers/foo.h
+++ b/tools/versioner/tests/obsoleted/headers/foo.h
@@ -1 +1 @@
-int foo() __attribute__((availability(android, introduced = 9, obsoleted = 11)));
+int foo() __INTRODUCED_IN(9) __REMOVED_IN(11);
\ No newline at end of file
diff --git a/tools/versioner/tests/obsoleted/run.sh b/tools/versioner/tests/obsoleted/run.sh
index 914c55d..9bfbe6d 100644
--- a/tools/versioner/tests/obsoleted/run.sh
+++ b/tools/versioner/tests/obsoleted/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -a 9 -a 12
+versioner headers -p platforms -r arm -a 9 -a 12 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/preprocessor/expected/foo.h b/tools/versioner/tests/preprocessor/expected/foo.h
new file mode 100644
index 0000000..3e05a1e
--- /dev/null
+++ b/tools/versioner/tests/preprocessor/expected/foo.h
@@ -0,0 +1,50 @@
+int always_available();
+
+int also_always_available() __INTRODUCED_IN(9);
+
+
+#if __ANDROID_API__ >= 10
+int needs_guard() __INTRODUCED_IN(10);
+#endif /* __ANDROID_API__ >= 10 */
+
+
+#if __ANDROID_API__ >= 10
+int already_guarded() __INTRODUCED_IN(10);
+#endif
+
+#if defined(__arm__)
+
+#if __ANDROID_API__ >= 11
+int specific_arch() __INTRODUCED_IN(11);
+#endif /* __ANDROID_API__ >= 11 */
+
+#endif
+
+#if defined(__arm__) || defined(__i386__)
+
+#if __ANDROID_API__ >= 11
+int multiple_archs() __INTRODUCED_IN(11);
+#endif /* __ANDROID_API__ >= 11 */
+
+#endif
+
+// __INTRODUCED_IN_64(21) should be ignored.
+
+#if (defined(__LP64__)) || (defined(__arm__) && __ANDROID_API__ >= 10) || (defined(__mips__) && !defined(__LP64__) && __ANDROID_API__ >= 11) || (defined(__i386__) && __ANDROID_API__ >= 10)
+int multiple_introduced_1() __INTRODUCED_IN_ARM(10) __INTRODUCED_IN_MIPS(11) __INTRODUCED_IN_X86(10)
+    __INTRODUCED_IN_64(21);
+#endif /* (defined(__LP64__)) || (defined(__arm__) && __ANDROID_API__ >= 10) || (defined(__mips__) && !defined(__LP64__) && __ANDROID_API__ >= 11) || (defined(__i386__) && __ANDROID_API__ >= 10) */
+
+
+
+#if (defined(__LP64__) && __ANDROID_API__ >= 22) || (defined(__arm__) && __ANDROID_API__ >= 10) || (defined(__mips__) && !defined(__LP64__) && __ANDROID_API__ >= 11) || (defined(__i386__) && __ANDROID_API__ >= 10)
+int multiple_introduced_2() __INTRODUCED_IN_ARM(10) __INTRODUCED_IN_MIPS(11) __INTRODUCED_IN_X86(10)
+    __INTRODUCED_IN_64(22);
+#endif /* (defined(__LP64__) && __ANDROID_API__ >= 22) || (defined(__arm__) && __ANDROID_API__ >= 10) || (defined(__mips__) && !defined(__LP64__) && __ANDROID_API__ >= 11) || (defined(__i386__) && __ANDROID_API__ >= 10) */
+
+
+
+#if (!defined(__LP64__) && __ANDROID_API__ >= 12) || (defined(__LP64__))
+int group_lp32() __INTRODUCED_IN_ARM(12) __INTRODUCED_IN_X86(12) __INTRODUCED_IN_MIPS(12);
+#endif /* (!defined(__LP64__) && __ANDROID_API__ >= 12) || (defined(__LP64__)) */
+
diff --git a/tools/versioner/tests/preprocessor/headers/foo.h b/tools/versioner/tests/preprocessor/headers/foo.h
new file mode 100644
index 0000000..ae5b847
--- /dev/null
+++ b/tools/versioner/tests/preprocessor/headers/foo.h
@@ -0,0 +1,26 @@
+int always_available();
+
+int also_always_available() __INTRODUCED_IN(9);
+
+int needs_guard() __INTRODUCED_IN(10);
+
+#if __ANDROID_API__ >= 10
+int already_guarded() __INTRODUCED_IN(10);
+#endif
+
+#if defined(__arm__)
+int specific_arch() __INTRODUCED_IN(11);
+#endif
+
+#if defined(__arm__) || defined(__i386__)
+int multiple_archs() __INTRODUCED_IN(11);
+#endif
+
+// __INTRODUCED_IN_64(21) should be ignored.
+int multiple_introduced_1() __INTRODUCED_IN_ARM(10) __INTRODUCED_IN_MIPS(11) __INTRODUCED_IN_X86(10)
+    __INTRODUCED_IN_64(21);
+
+int multiple_introduced_2() __INTRODUCED_IN_ARM(10) __INTRODUCED_IN_MIPS(11) __INTRODUCED_IN_X86(10)
+    __INTRODUCED_IN_64(22);
+
+int group_lp32() __INTRODUCED_IN_ARM(12) __INTRODUCED_IN_X86(12) __INTRODUCED_IN_MIPS(12);
diff --git a/tools/versioner/tests/preprocessor/out/foo.h b/tools/versioner/tests/preprocessor/out/foo.h
new file mode 100644
index 0000000..3e05a1e
--- /dev/null
+++ b/tools/versioner/tests/preprocessor/out/foo.h
@@ -0,0 +1,50 @@
+int always_available();
+
+int also_always_available() __INTRODUCED_IN(9);
+
+
+#if __ANDROID_API__ >= 10
+int needs_guard() __INTRODUCED_IN(10);
+#endif /* __ANDROID_API__ >= 10 */
+
+
+#if __ANDROID_API__ >= 10
+int already_guarded() __INTRODUCED_IN(10);
+#endif
+
+#if defined(__arm__)
+
+#if __ANDROID_API__ >= 11
+int specific_arch() __INTRODUCED_IN(11);
+#endif /* __ANDROID_API__ >= 11 */
+
+#endif
+
+#if defined(__arm__) || defined(__i386__)
+
+#if __ANDROID_API__ >= 11
+int multiple_archs() __INTRODUCED_IN(11);
+#endif /* __ANDROID_API__ >= 11 */
+
+#endif
+
+// __INTRODUCED_IN_64(21) should be ignored.
+
+#if (defined(__LP64__)) || (defined(__arm__) && __ANDROID_API__ >= 10) || (defined(__mips__) && !defined(__LP64__) && __ANDROID_API__ >= 11) || (defined(__i386__) && __ANDROID_API__ >= 10)
+int multiple_introduced_1() __INTRODUCED_IN_ARM(10) __INTRODUCED_IN_MIPS(11) __INTRODUCED_IN_X86(10)
+    __INTRODUCED_IN_64(21);
+#endif /* (defined(__LP64__)) || (defined(__arm__) && __ANDROID_API__ >= 10) || (defined(__mips__) && !defined(__LP64__) && __ANDROID_API__ >= 11) || (defined(__i386__) && __ANDROID_API__ >= 10) */
+
+
+
+#if (defined(__LP64__) && __ANDROID_API__ >= 22) || (defined(__arm__) && __ANDROID_API__ >= 10) || (defined(__mips__) && !defined(__LP64__) && __ANDROID_API__ >= 11) || (defined(__i386__) && __ANDROID_API__ >= 10)
+int multiple_introduced_2() __INTRODUCED_IN_ARM(10) __INTRODUCED_IN_MIPS(11) __INTRODUCED_IN_X86(10)
+    __INTRODUCED_IN_64(22);
+#endif /* (defined(__LP64__) && __ANDROID_API__ >= 22) || (defined(__arm__) && __ANDROID_API__ >= 10) || (defined(__mips__) && !defined(__LP64__) && __ANDROID_API__ >= 11) || (defined(__i386__) && __ANDROID_API__ >= 10) */
+
+
+
+#if (!defined(__LP64__) && __ANDROID_API__ >= 12) || (defined(__LP64__))
+int group_lp32() __INTRODUCED_IN_ARM(12) __INTRODUCED_IN_X86(12) __INTRODUCED_IN_MIPS(12);
+#endif /* (!defined(__LP64__) && __ANDROID_API__ >= 12) || (defined(__LP64__)) */
+
diff --git a/tools/versioner/tests/inline_version_mismatch/platforms/android-12/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/preprocessor/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
similarity index 100%
rename from tools/versioner/tests/inline_version_mismatch/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
rename to tools/versioner/tests/preprocessor/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
diff --git a/tools/versioner/tests/preprocessor/run.sh b/tools/versioner/tests/preprocessor/run.sh
new file mode 100644
index 0000000..1b0aae2
--- /dev/null
+++ b/tools/versioner/tests/preprocessor/run.sh
@@ -0,0 +1,4 @@
+rm -rf out
+set -e
+versioner headers -i -o out
+diff -q -w -B out expected
diff --git a/tools/versioner/tests/preprocessor_merging/expected/foo.h b/tools/versioner/tests/preprocessor_merging/expected/foo.h
new file mode 100644
index 0000000..45eb32d
--- /dev/null
+++ b/tools/versioner/tests/preprocessor_merging/expected/foo.h
@@ -0,0 +1,9 @@
+
+#if __ANDROID_API__ >= 10
+int block_merging_1() __INTRODUCED_IN(10); // foo
+int block_merging_2() __INTRODUCED_IN(10); /* bar */
+int block_merging_3() __INTRODUCED_IN(10); /* baz
+//*/
+int block_merging_4() __INTRODUCED_IN(10);
+#endif /* __ANDROID_API__ >= 10 */
+
diff --git a/tools/versioner/tests/preprocessor_merging/headers/foo.h b/tools/versioner/tests/preprocessor_merging/headers/foo.h
new file mode 100644
index 0000000..ac9564b
--- /dev/null
+++ b/tools/versioner/tests/preprocessor_merging/headers/foo.h
@@ -0,0 +1,5 @@
+int block_merging_1() __INTRODUCED_IN(10); // foo
+int block_merging_2() __INTRODUCED_IN(10); /* bar */
+int block_merging_3() __INTRODUCED_IN(10); /* baz
+//*/
+int block_merging_4() __INTRODUCED_IN(10);
diff --git a/tools/versioner/tests/inline_version_mismatch/platforms/android-12/arch-arm/symbols/libc.so.functions.txt b/tools/versioner/tests/preprocessor_merging/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
similarity index 100%
copy from tools/versioner/tests/inline_version_mismatch/platforms/android-12/arch-arm/symbols/libc.so.functions.txt
copy to tools/versioner/tests/preprocessor_merging/platforms/android-9/arch-arm/symbols/libc.so.functions.txt
diff --git a/tools/versioner/tests/preprocessor_merging/run.sh b/tools/versioner/tests/preprocessor_merging/run.sh
new file mode 100644
index 0000000..1b0aae2
--- /dev/null
+++ b/tools/versioner/tests/preprocessor_merging/run.sh
@@ -0,0 +1,4 @@
+rm -rf out
+set -e
+versioner headers -i -o out
+diff -q -w -B out expected
diff --git a/tools/versioner/tests/smoke/headers/foo.h b/tools/versioner/tests/smoke/headers/foo.h
index 2998c8e..3ff3ff7 100644
--- a/tools/versioner/tests/smoke/headers/foo.h
+++ b/tools/versioner/tests/smoke/headers/foo.h
@@ -1 +1 @@
-int foo() __attribute__((availability(android, introduced = 9)));
+int foo() __INTRODUCED_IN(9);
\ No newline at end of file
diff --git a/tools/versioner/tests/smoke/run.sh b/tools/versioner/tests/smoke/run.sh
index 8babb73..a34fda8 100644
--- a/tools/versioner/tests/smoke/run.sh
+++ b/tools/versioner/tests/smoke/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -a 9
+versioner headers -p platforms -r arm -a 9 -i
\ No newline at end of file
diff --git a/tools/versioner/tests/version_mismatch/expected_fail b/tools/versioner/tests/version_mismatch/expected_fail
index 7f0709c..f83f71c 100644
--- a/tools/versioner/tests/version_mismatch/expected_fail
+++ b/tools/versioner/tests/version_mismatch/expected_fail
@@ -1,2 +1,8 @@
-foo: availability mismatch between arm-9 and arm-12: [introduced = 9] before, [introduced = 10] after
+versioner: inconsistent availability for symbol 'foo'
+versioner: failed to calculate symbol availability
+  foo: invalid
+    extern declaration @ headers/foo.h:2:1
+      introduced = 9
+    extern declaration @ headers/foo.h:4:1
+      introduced = 10
 versioner: sanity check failed
diff --git a/tools/versioner/tests/version_mismatch/headers/foo.h b/tools/versioner/tests/version_mismatch/headers/foo.h
index 4d23417..4604092 100644
--- a/tools/versioner/tests/version_mismatch/headers/foo.h
+++ b/tools/versioner/tests/version_mismatch/headers/foo.h
@@ -1,5 +1,5 @@
 #if __ANDROID_API__ <= 9
-int foo() __attribute__((availability(android, introduced = 9)));
+int foo() __INTRODUCED_IN(9);
 #else
-int foo() __attribute__((availability(android, introduced = 10)));
+int foo() __INTRODUCED_IN(10);
 #endif
diff --git a/tools/versioner/tests/version_mismatch/run.sh b/tools/versioner/tests/version_mismatch/run.sh
index 914c55d..9bfbe6d 100644
--- a/tools/versioner/tests/version_mismatch/run.sh
+++ b/tools/versioner/tests/version_mismatch/run.sh
@@ -1 +1 @@
-versioner headers -p platforms -r arm -a 9 -a 12
+versioner headers -p platforms -r arm -a 9 -a 12 -i
\ No newline at end of file