pw_toolchain: ARM clang toolchain

Initial work to get clang building ARM firmware.

Current state:
 - Does not work on Windows; no clang toolchain yet.
 - Almost all tests pass.
 - FreeListHeap tests that don't zero-initilize the buffer fail in qemu
   and crash on STM32F429I-DISC1. (pwbug/315)

Change-Id: I39559511f19571c26930a868406d6ee1b514c412
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/13144
Commit-Queue: Armando Montanez <amontanez@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 0c6414f..06e411d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -111,8 +111,14 @@
   }
 }
 
-_build_pigweed_default_at_all_optimization_levels("qemu") {
-  toolchain_prefix = "$dir_pigweed/targets/lm3s6965evb-qemu:lm3s6965evb_qemu_"
+_build_pigweed_default_at_all_optimization_levels("qemu_gcc") {
+  toolchain_prefix =
+      "$dir_pigweed/targets/lm3s6965evb-qemu:lm3s6965evb_qemu_gcc_"
+}
+
+_build_pigweed_default_at_all_optimization_levels("qemu_clang") {
+  toolchain_prefix =
+      "$dir_pigweed/targets/lm3s6965evb-qemu:lm3s6965evb_qemu_clang_"
 }
 
 group("docs") {
@@ -148,6 +154,7 @@
     "$dir_pw_rpc/py",
     "$dir_pw_status/py",
     "$dir_pw_tokenizer/py",
+    "$dir_pw_toolchain/py",
     "$dir_pw_trace/py",
     "$dir_pw_trace_tokenized/py",
     "$dir_pw_unit_test/py",
@@ -264,7 +271,6 @@
   # Targets for all module unit test groups.
   pw_test_group("pw_module_tests") {
     group_deps = [
-      "$dir_pw_allocator:tests",
       "$dir_pw_assert:tests",
       "$dir_pw_base64:tests",
       "$dir_pw_blob_store:tests",
@@ -306,6 +312,19 @@
       "$dir_pw_varint:tests",
     ]
 
+    # TODO(pwbug/315): Fix pw_allocator tests on ARM Clang build.
+    _qemu_toolchains = [
+      "lm3s6965evb_qemu_clang_debug",
+      "lm3s6965evb_qemu_clang_size_optimized",
+      "lm3s6965evb_qemu_clang_speed_optimized",
+    ]
+    _toolchain_is_qemu_clang =
+        _qemu_toolchains + [ get_label_info(current_toolchain, "name") ] -
+        [ get_label_info(current_toolchain, "name") ] != _qemu_toolchains
+    if (!_toolchain_is_qemu_clang) {
+      group_deps += [ "$dir_pw_allocator:tests" ]
+    }
+
     if (defined(pw_toolchain_SCOPE.is_host_toolchain) &&
         pw_toolchain_SCOPE.is_host_toolchain) {
       # TODO(pwbug/196): KVS tests are not compatible with device builds as they
diff --git a/pw_allocator/block_test.cc b/pw_allocator/block_test.cc
index fb918ba..f70c826 100644
--- a/pw_allocator/block_test.cc
+++ b/pw_allocator/block_test.cc
@@ -68,7 +68,7 @@
   alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   auto status = block->Split(kSplitN, &next_block);
@@ -101,7 +101,7 @@
   uintptr_t split_len = split_addr - (uintptr_t)&bytes;
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   auto status = block->Split(kSplitN, &next_block);
@@ -130,10 +130,10 @@
   constexpr size_t kN = 1024;
   constexpr size_t kSplit1 = 512;
   constexpr size_t kSplit2 = 256;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* block2 = nullptr;
   block->Split(kSplit1, &block2);
@@ -151,10 +151,10 @@
   constexpr size_t kN = 1024;
   constexpr size_t kSplitN =
       kN - sizeof(Block) - 2 * PW_ALLOCATOR_POISON_OFFSET - 1;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   auto status = block->Split(kSplitN, &next_block);
@@ -166,10 +166,10 @@
 TEST(Block, MustProvideNextBlockPointer) {
   constexpr size_t kN = 1024;
   constexpr size_t kSplitN = kN - sizeof(Block) - 1;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   auto status = block->Split(kSplitN, nullptr);
   EXPECT_EQ(status, Status::InvalidArgument());
@@ -178,10 +178,10 @@
 TEST(Block, CannotMakeBlockLargerInSplit) {
   // Ensure that we can't ask for more space than the block actually has...
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   auto status = block->Split(block->InnerSize() + 1, &next_block);
@@ -192,10 +192,10 @@
 TEST(Block, CannotMakeSecondBlockLargerInSplit) {
   // Ensure that the second block in split is at least of the size of header.
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   auto status = block->Split(
@@ -209,10 +209,10 @@
 TEST(Block, CanMakeZeroSizeFirstBlock) {
   // This block does support splitting with zero payload size.
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   auto status = block->Split(0, &next_block);
@@ -224,10 +224,10 @@
 TEST(Block, CanMakeZeroSizeSecondBlock) {
   // Likewise, the split block can be zero-width.
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   auto status = block->Split(
@@ -243,7 +243,7 @@
   alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   block->MarkUsed();
   EXPECT_EQ(block->Used(), true);
@@ -261,7 +261,7 @@
   alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   block->MarkUsed();
 
@@ -276,10 +276,10 @@
   constexpr size_t kN = 1024;
   constexpr size_t kSplit1 = 512;
   constexpr size_t kSplit2 = 256;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* block2 = nullptr;
   block->Split(kSplit1, &block2);
@@ -302,12 +302,12 @@
 
 TEST(Block, CannotMergeWithFirstOrLastBlock) {
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   // Do a split, just to sanity check that the checks on Next/Prev are
   // different...
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   block->Split(512, &next_block);
@@ -318,12 +318,12 @@
 
 TEST(Block, CannotMergeUsedBlock) {
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   // Do a split, just to sanity check that the checks on Next/Prev are
   // different...
   Block* block = nullptr;
-  Block::Init(std::span(bytes, kN), &block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
   block->Split(512, &next_block);
@@ -335,10 +335,10 @@
 
 TEST(Block, CanCheckValidBlock) {
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* first_block = nullptr;
-  Block::Init(std::span(bytes, kN), &first_block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &first_block), OkStatus());
 
   Block* second_block = nullptr;
   first_block->Split(512, &second_block);
@@ -353,10 +353,10 @@
 
 TEST(Block, CanCheckInalidBlock) {
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* first_block = nullptr;
-  Block::Init(std::span(bytes, kN), &first_block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &first_block), OkStatus());
 
   Block* second_block = nullptr;
   first_block->Split(512, &second_block);
@@ -391,10 +391,10 @@
 TEST(Block, CanPoisonBlock) {
 #if defined(PW_ALLOCATOR_POISON_ENABLE) && PW_ALLOCATOR_POISON_ENABLE
   constexpr size_t kN = 1024;
-  byte bytes[kN];
+  alignas(Block*) byte bytes[kN];
 
   Block* first_block = nullptr;
-  Block::Init(std::span(bytes, kN), &first_block);
+  EXPECT_EQ(Block::Init(std::span(bytes, kN), &first_block), OkStatus());
 
   Block* second_block = nullptr;
   first_block->Split(512, &second_block);
diff --git a/pw_boot_armv7m/basic_armv7m.ld b/pw_boot_armv7m/basic_armv7m.ld
index 7ca09de..e758aa2 100644
--- a/pw_boot_armv7m/basic_armv7m.ld
+++ b/pw_boot_armv7m/basic_armv7m.ld
@@ -179,6 +179,12 @@
     . = _stack_high;
     pw_boot_stack_high_addr = .;
   } >RAM
+
+  /* Discard unwind info. */
+  .ARM.extab 0x0 (INFO) :
+  {
+    KEEP(*(.ARM.extab*))
+  }
 }
 
 /* Symbols used by core_init.c: */
diff --git a/pw_boot_armv7m/public/pw_boot_armv7m/boot.h b/pw_boot_armv7m/public/pw_boot_armv7m/boot.h
index 9b683d5..88e11dc 100644
--- a/pw_boot_armv7m/public/pw_boot_armv7m/boot.h
+++ b/pw_boot_armv7m/public/pw_boot_armv7m/boot.h
@@ -85,7 +85,7 @@
 // (which usually points to Reset_Handler) must be set to point to this
 // function. This function is implemented by pw_boot_armv7m, and does early
 // memory initialization.
-PW_NO_PROLOGUE void pw_boot_Entry();
+PW_NO_RETURN void pw_boot_Entry();
 
 // pw_boot hook: Before static memory is initialized (user supplied)
 //
diff --git a/pw_cpu_exception_cortex_m/exception_entry_test.cc b/pw_cpu_exception_cortex_m/exception_entry_test.cc
index 842bcd4..f6c47c3 100644
--- a/pw_cpu_exception_cortex_m/exception_entry_test.cc
+++ b/pw_cpu_exception_cortex_m/exception_entry_test.cc
@@ -275,7 +275,7 @@
       // clang-format off
       : /*output=*/[local_msp]"=r"(local_msp), [local_psp]"=r"(local_psp)
       : /*input=*/[magic]"r"(magic)
-      : /*clobbers=*/"r4", "r5", "r11", "memory"
+      : /*clobbers=*/"r0", "r4", "r5", "r11", "memory"
       // clang-format on
   );
 
@@ -314,7 +314,7 @@
       // clang-format off
       : /*output=*/[local_msp]"=r"(local_msp), [local_psp]"=r"(local_psp)
       : /*input=*/[magic]"r"(magic)
-      : /*clobbers=*/"r4", "r5", "r11", "memory"
+      : /*clobbers=*/"r0", "r4", "r5", "r11", "memory"
       // clang-format on
   );
 
@@ -567,6 +567,14 @@
     trigger_nested_fault = false;
     BeginNestedFaultTest();
   }
+  // Logging may require FPU (fpu instructions in vsnprintf()), so re-enable
+  // asap.
+  EnableFpu();
+
+  // Disable traps. Must be disabled before EXPECT, as memcpy() can do unaligned
+  // operations.
+  cortex_m_ccr &= ~kUnalignedTrapEnableMask;
+  cortex_m_ccr &= ~kDivByZeroTrapEnableMask;
 
   // Clear HFSR forced (nested) hard fault mask if set. This will only be
   // set by the nested fault test.
@@ -582,7 +590,6 @@
                 sizeof(pw_cpu_exception_State));
 
     // Disable unaligned read/write trapping to "handle" exception.
-    cortex_m_ccr &= ~kUnalignedTrapEnableMask;
     cortex_m_cfsr = kUnalignedFaultMask;
     exceptions_handled++;
     return;
@@ -602,7 +609,6 @@
     }
 
     // Disable divide-by-zero trapping to "handle" exception.
-    cortex_m_ccr &= ~kDivByZeroTrapEnableMask;
     cortex_m_cfsr = kDivByZeroFaultMask;
     exceptions_handled++;
     return;
diff --git a/pw_kvs/size_report/base.cc b/pw_kvs/size_report/base.cc
index 9a675ba..be6d89c 100644
--- a/pw_kvs/size_report/base.cc
+++ b/pw_kvs/size_report/base.cc
@@ -31,7 +31,7 @@
   PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
 
   void* result =
-      std::memset((void*)working_buffer, sizeof(working_buffer), 0x55);
+      std::memset((void*)working_buffer, 0x55, sizeof(working_buffer));
   is_set = (result != nullptr);
   return 0;
 }
diff --git a/pw_kvs/size_report/base_with_only_flash.cc b/pw_kvs/size_report/base_with_only_flash.cc
index fe290ad..bb7c182 100644
--- a/pw_kvs/size_report/base_with_only_flash.cc
+++ b/pw_kvs/size_report/base_with_only_flash.cc
@@ -36,12 +36,12 @@
   PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
 
   void* result =
-      std::memset((void*)working_buffer, sizeof(working_buffer), 0x55);
+      std::memset((void*)working_buffer, 0x55, sizeof(working_buffer));
   is_set = (result != nullptr);
 
   test_partition.Erase();
 
-  std::memset((void*)working_buffer, sizeof(working_buffer), 0x55);
+  std::memset((void*)working_buffer, 0x55, sizeof(working_buffer));
 
   test_partition.Write(0, std::as_bytes(std::span(working_buffer)));
 
diff --git a/pw_kvs/size_report/with_kvs.cc b/pw_kvs/size_report/with_kvs.cc
index 209cfd3..fc99e55 100644
--- a/pw_kvs/size_report/with_kvs.cc
+++ b/pw_kvs/size_report/with_kvs.cc
@@ -46,7 +46,7 @@
   PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
 
   void* result =
-      std::memset((void*)working_buffer, sizeof(working_buffer), 0x55);
+      std::memset((void*)working_buffer, 0x55, sizeof(working_buffer));
   is_set = (result != nullptr);
 
   kvs.Init();
diff --git a/pw_malloc_freelist/freelist_malloc.cc b/pw_malloc_freelist/freelist_malloc.cc
index 8797295..6b5ecfe 100644
--- a/pw_malloc_freelist/freelist_malloc.cc
+++ b/pw_malloc_freelist/freelist_malloc.cc
@@ -17,13 +17,13 @@
 #include "pw_allocator/freelist_heap.h"
 #include "pw_boot_armv7m/boot.h"
 #include "pw_malloc/malloc.h"
+#include "pw_preprocessor/compiler.h"
 #include "pw_preprocessor/util.h"
 
 namespace {
 std::aligned_storage_t<sizeof(pw::allocator::FreeListHeapBuffer<>),
                        alignof(pw::allocator::FreeListHeapBuffer<>)>
     buf;
-std::span<std::byte> pw_allocator_freelist_raw_heap;
 }  // namespace
 pw::allocator::FreeListHeapBuffer<>* pw_freelist_heap;
 
diff --git a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/namespace.h b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/namespace.h
index abaab09..6c6bec5 100644
--- a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/namespace.h
+++ b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/namespace.h
@@ -13,9 +13,9 @@
 // the License.
 #pragma once
 
-// Clang uses a special namespace for standard library headers. Use this
+// libc++ uses a special namespace for standard library headers. Use this
 // namespace via the defines in <__config>.
-#if defined(__clang__) && __has_include(<__config>)
+#if defined(_LIBCPP_VERSION) && __has_include(<__config>)
 
 #include <__config>
 
@@ -27,11 +27,11 @@
 #define _PW_POLYFILL_BEGIN_NAMESPACE_STD namespace std {
 #define _PW_POLYFILL_END_NAMESPACE_STD }  // namespace std
 
-// Cannot compile with Clang / libc++ without the <__config> header.
-#ifdef __clang__
+// Cannot compile when using libc++ without the <__config> header.
+#ifdef _LIBCPP_VERSION
 static_assert(
     false,
-    "Compiling with Clang, but the <__config> header is not available. "
+    "Compiling against libc++, but the <__config> header is not available. "
     "The <__config> header provides various _LIBCPP defines used internally "
     "by libc++. pw_polyfill needs this header for the "
     "_LIBCPP_BEGIN_NAMESPACE_STD and _LIBCPP_END_NAMESPACE_STD macros, which "
@@ -43,6 +43,6 @@
     "<__config>, in which this file should be updated to properly "
     "set _PW_POLYFILL_BEGIN_NAMESPACE_STD and _PW_POLYFILL_END_NAMESPACE_STD.");
 
-#endif  // __clang__
+#endif  // _LIBCPP_VERSION
 
-#endif  // defined(__clang__) && __has_include(<__config>)
+#endif  // defined(_LIBCPP_VERSION) && __has_include(<__config>)
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index d1df037..f77d262 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -137,7 +137,13 @@
 @filter_paths(endswith=_BUILD_EXTENSIONS)
 def gn_qemu_build(ctx: PresubmitContext):
     build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, *_at_all_optimization_levels('qemu'))
+    build.ninja(ctx.output_dir, *_at_all_optimization_levels('qemu_gcc'))
+
+
+@filter_paths(endswith=_BUILD_EXTENSIONS)
+def gn_qemu_clang_build(ctx: PresubmitContext):
+    build.gn_gen(ctx.root, ctx.output_dir)
+    build.ninja(ctx.output_dir, *_at_all_optimization_levels('qemu_clang'))
 
 
 def gn_docs_build(ctx: PresubmitContext):
@@ -501,8 +507,6 @@
 BROKEN = (
     # TODO(pwbug/45): Remove clang-tidy from BROKEN when it passes.
     clang_tidy,
-    # QEMU build. Currently doesn't have test runners.
-    gn_qemu_build,
     # Build that attempts to duplicate the build OSS-Fuzz does. Currently
     # failing.
     oss_fuzz_build,
@@ -538,6 +542,9 @@
     # On Mac OS, system 'gcc' is a symlink to 'clang' by default, so skip GCC
     # host builds on Mac for now.
     gn_gcc_build if sys.platform != 'darwin' else (),
+    # Windows doesn't support QEMU yet.
+    gn_qemu_build if sys.platform != 'win32' else (),
+    gn_qemu_clang_build if sys.platform != 'win32' else (),
     source_is_in_build_files,
     python_checks,
     build_env_setup,
diff --git a/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc b/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc
index 1c1ccbc..ca70d97 100644
--- a/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc
+++ b/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc
@@ -25,10 +25,7 @@
 
 // UART status flags.
 constexpr uint32_t kTxFifoEmptyMask = 0b10000000;
-constexpr uint32_t kTxFifoFullMask = 0b1000000;
 constexpr uint32_t kRxFifoFullMask = 0b100000;
-constexpr uint32_t kRxFifoEmptyMask = 0b10000;
-constexpr uint32_t kTxBusyMask = 0b1000;
 
 // UART line control flags.
 // Default: 8n1
diff --git a/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc b/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc
index 915fd67..b5b1792 100644
--- a/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc
+++ b/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc
@@ -61,25 +61,21 @@
 };
 
 // Constants related to GPIO mode register masks.
-constexpr uint32_t kGpioPortModeMask = 0x3u;
 constexpr uint32_t kGpio9PortModePos = 18;
 constexpr uint32_t kGpio10PortModePos = 20;
 constexpr uint32_t kGpioPortModeAlternate = 2;
 
 // Constants related to GPIO port speed register masks.
-constexpr uint32_t kGpioPortSpeedMask = 0x3u;
 constexpr uint32_t kGpio9PortSpeedPos = 18;
 constexpr uint32_t kGpio10PortSpeedPos = 20;
 constexpr uint32_t kGpioSpeedVeryHigh = 3;
 
 // Constants related to GPIO pull up/down resistor type masks.
-constexpr uint32_t kGpioPullTypeMask = 0x3u;
 constexpr uint32_t kGpio9PullTypePos = 18;
 constexpr uint32_t kGpio10PullTypePos = 20;
 constexpr uint32_t kPullTypePullUp = 1;
 
 // Constants related to GPIO port speed register masks.
-constexpr uint32_t kGpioAltModeMask = 0x3u;
 constexpr uint32_t kGpio9AltModeHighPos = 4;
 constexpr uint32_t kGpio10AltModeHighPos = 8;
 
diff --git a/pw_toolchain/arm_clang/BUILD.gn b/pw_toolchain/arm_clang/BUILD.gn
new file mode 100644
index 0000000..d4b73a9
--- /dev/null
+++ b/pw_toolchain/arm_clang/BUILD.gn
@@ -0,0 +1,53 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+import("//build_overrides/pigweed.gni")
+
+import("clang_config.gni")
+
+cortex_m_common_flags = [
+  "-mabi=aapcs",
+  "-mthumb",
+]
+
+cortex_m_software_fpu_flags = [ "-mfloat-abi=soft" ]
+
+cortex_m_hardware_fpu_flags = [
+  "-mfloat-abi=hard",
+  "-mfpu=fpv4-sp-d16",
+
+  # Used by some pigweed tests/targets to correctly handle hardware FPU
+  # behavior.
+  "-DPW_ARMV7M_ENABLE_FPU=1",
+]
+
+config("enable_float_printf") {
+  ldflags = [ "-Wl,-u_printf_float" ]
+}
+
+pw_clang_arm_config("cortex_m3") {
+  cflags = [ "-mcpu=cortex-m3" ]
+  cflags += cortex_m_common_flags
+  cflags += cortex_m_software_fpu_flags
+  asmflags = cflags
+  ldflags = cflags
+}
+
+pw_clang_arm_config("cortex_m4f") {
+  cflags = [ "-mcpu=cortex-m4" ]
+  cflags += cortex_m_common_flags
+  cflags += cortex_m_hardware_fpu_flags
+  asmflags = cflags
+  ldflags = cflags
+}
diff --git a/pw_toolchain/arm_clang/clang_config.gni b/pw_toolchain/arm_clang/clang_config.gni
new file mode 100644
index 0000000..2c71a2f
--- /dev/null
+++ b/pw_toolchain/arm_clang/clang_config.gni
@@ -0,0 +1,82 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+_script_path = rebase_path("../py/pw_toolchain/clang_arm_toolchain.py")
+
+# This template generates a config that can be used to target ARM cores using
+# a clang compiler.
+#
+# Clang isn't a plug-and-play experience for Cortex-M baremetal targets; it's
+# missing C runtime libraries, C/C++ standard libraries, and a few other
+# things. This template uses the provided cflags, asmflags, ldflags, etc. to
+# generate a config that pulls the missing components from an arm-none-eabi-gcc
+# compiler on the system PATH. The end result is a clang-based compiler that
+# pulls in gcc-provided headers and libraries to complete the toolchain.
+#
+# Args:
+#   - asmflags, cflags, cflags_c, cflags_cc, ldflags: These flags are used to
+#         locate the correct architecture-specific libraries/headers. To
+#         properly drive the script, provide all architecture flags (e.g. -mcpu,
+#         -mabi, -mthumb, -mfloat-abi, -mfpu) in at least one of these
+#         variables.
+#
+# Generated targets:
+#   - $target_name: The final config to use with your clang toolchain.
+template("pw_clang_arm_config") {
+  config(target_name) {
+    # Pull all the compiler flags into a single list.
+    _compiler_flags = []
+    forward_variables_from(invoker, "*")
+    if (defined(asmflags)) {
+      _compiler_flags += asmflags
+    } else {
+      asmflags = []
+    }
+    if (defined(cflags)) {
+      _compiler_flags += cflags
+    } else {
+      cflags = []
+    }
+    if (defined(cflags_c)) {
+      _compiler_flags += cflags_c
+    } else {
+      cflags_c = []
+    }
+    if (defined(cflags_cc)) {
+      _compiler_flags += cflags_cc
+    } else {
+      cflags_cc = []
+    }
+    if (defined(ldflags)) {
+      _compiler_flags += ldflags
+    } else {
+      ldflags = []
+    }
+
+    # Invoke the script that will generate clang flags based on the current
+    # compiler version and desired arch.
+    _script_flags = [
+      "--gn-scope",
+      "--cflags",
+      "--ldflags",
+      "--",
+    ]
+    _script_flags += _compiler_flags
+    _arm_flags = exec_script(_script_path, _script_flags, "scope")
+
+    cflags += _arm_flags.cflags
+    ldflags += _arm_flags.cflags
+    ldflags += _arm_flags.ldflags
+  }
+}
diff --git a/pw_toolchain/arm_clang/toolchains.gni b/pw_toolchain/arm_clang/toolchains.gni
new file mode 100644
index 0000000..91aea25
--- /dev/null
+++ b/pw_toolchain/arm_clang/toolchains.gni
@@ -0,0 +1,115 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+import("//build_overrides/pigweed.gni")
+
+# Specifies the tools used by host Clang toolchains.
+_arm_clang_toolchain = {
+  # Note: On macOS, there is no "llvm-ar", only "ar", which happens to be LLVM
+  # ar. This should get updated for linux systems.
+  ar = "ar"
+  cc = "clang"
+  cxx = "clang++"
+
+  link_whole_archive = true
+}
+
+# Configs specific to different architectures.
+_cortex_m3 = [ "$dir_pw_toolchain/arm_clang:cortex_m3" ]
+
+_cortex_m4 = [ "$dir_pw_toolchain/arm_clang:cortex_m4" ]
+
+_cortex_m4f = [ "$dir_pw_toolchain/arm_clang:cortex_m4f" ]
+
+# Describes ARM clang toolchains for specific targets.
+pw_toolchain_arm_clang = {
+  cortex_m3_debug = {
+    name = "arm_clang_cortex_m3_debug"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m3 + [ "$dir_pw_build:optimize_debugging" ]
+    }
+  }
+  cortex_m3_speed_optimized = {
+    name = "arm_clang_cortex_m3_speed_optimized"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m3 + [ "$dir_pw_build:optimize_speed" ]
+    }
+  }
+  cortex_m3_size_optimized = {
+    name = "arm_clang_cortex_m3_size_optimized"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m3 + [ "$dir_pw_build:optimize_size" ]
+    }
+  }
+  cortex_m4_debug = {
+    name = "arm_clang_cortex_m4_debug"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m4 + [ "$dir_pw_build:optimize_debugging" ]
+    }
+  }
+  cortex_m4_speed_optimized = {
+    name = "arm_clang_cortex_m4_speed_optimized"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m4 + [ "$dir_pw_build:optimize_speed" ]
+    }
+  }
+  cortex_m4_size_optimized = {
+    name = "arm_clang_cortex_m4_size_optimized"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m4 + [ "$dir_pw_build:optimize_size" ]
+    }
+  }
+  cortex_m4f_debug = {
+    name = "arm_clang_cortex_m4f_debug"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m4f + [ "$dir_pw_build:optimize_debugging" ]
+    }
+  }
+  cortex_m4f_speed_optimized = {
+    name = "arm_clang_cortex_m4f_speed_optimized"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m4f + [ "$dir_pw_build:optimize_speed" ]
+    }
+  }
+  cortex_m4f_size_optimized = {
+    name = "arm_clang_cortex_m4f_size_optimized"
+    forward_variables_from(_arm_clang_toolchain, "*")
+    defaults = {
+      default_configs = _cortex_m4f + [ "$dir_pw_build:optimize_size" ]
+    }
+  }
+}
+
+# This list just contains the members of the above scope for convenience to make
+# it trivial to generate all the toolchains in this file via a
+# `generate_toolchains` target.
+pw_toolchain_arm_clang_list = [
+  pw_toolchain_arm_clang.cortex_m3_debug,
+  pw_toolchain_arm_clang.cortex_m3_speed_optimized,
+  pw_toolchain_arm_clang.cortex_m3_size_optimized,
+  pw_toolchain_arm_clang.cortex_m4_debug,
+  pw_toolchain_arm_clang.cortex_m4_speed_optimized,
+  pw_toolchain_arm_clang.cortex_m4_size_optimized,
+  pw_toolchain_arm_clang.cortex_m4f_debug,
+  pw_toolchain_arm_clang.cortex_m4f_speed_optimized,
+  pw_toolchain_arm_clang.cortex_m4f_size_optimized,
+]
diff --git a/pw_toolchain/py/BUILD.gn b/pw_toolchain/py/BUILD.gn
index 2bf0cd4..ee7df0a 100644
--- a/pw_toolchain/py/BUILD.gn
+++ b/pw_toolchain/py/BUILD.gn
@@ -21,6 +21,7 @@
   sources = [
     "pw_toolchain/__init__.py",
     "pw_toolchain/bad_toolchain.py",
+    "pw_toolchain/clang_arm_toolchain.py",
     "pw_toolchain/copy_with_metadata.py",
   ]
   pylintrc = "$dir_pigweed/.pylintrc"
diff --git a/pw_toolchain/py/pw_toolchain/clang_arm_toolchain.py b/pw_toolchain/py/pw_toolchain/clang_arm_toolchain.py
new file mode 100644
index 0000000..7494727
--- /dev/null
+++ b/pw_toolchain/py/pw_toolchain/clang_arm_toolchain.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+"""Generates flags needed for an ARM build using clang.
+
+Using clang on Cortex-M cores isn't intuitive as the end-to-end experience isn't
+quite completely in LLVM. LLVM doesn't yet provide compatible C runtime
+libraries or C/C++ standard libraries. To work around this, this script pulls
+the missing bits from an arm-none-eabi-gcc compiler on the system path. This
+lets clang do the heavy lifting while only relying on some headers provided by
+newlib/arm-none-eabi-gcc in addition to a small assortment of needed libraries.
+
+To use this script, specify what flags you want from the script, and run with
+the required architecture flags like you would with gcc:
+
+  python -m pw_toolchain.clang_arm_toolchain --cflags -- -mthumb -mcpu=cortex-m3
+
+The script will then print out the additional flags you need to pass to clang to
+get a working build.
+"""
+
+import argparse
+import sys
+import subprocess
+
+from pathlib import Path
+from typing import List, Dict, Tuple
+
+_ARM_COMPILER_PREFIX = 'arm-none-eabi'
+_ARM_COMPILER_NAME = _ARM_COMPILER_PREFIX + '-gcc'
+
+
+def _parse_args() -> argparse.Namespace:
+    """Parses arguments for this script, splitting out the command to run."""
+
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument(
+        '--gn-scope',
+        action='store_true',
+        help=("Formats the output like a GN scope so it can be ingested by "
+              "exec_script()"))
+    parser.add_argument('--cflags',
+                        action='store_true',
+                        help=('Include necessary C flags in the output'))
+    parser.add_argument('--ldflags',
+                        action='store_true',
+                        help=('Include necessary linker flags in the output'))
+    parser.add_argument(
+        'clang_flags',
+        nargs=argparse.REMAINDER,
+        help='Flags to pass to clang, which can affect library/include paths',
+    )
+    parsed_args = parser.parse_args()
+
+    assert parsed_args.clang_flags[0] == '--', 'arguments not correctly split'
+    parsed_args.clang_flags = parsed_args.clang_flags[1:]
+    return parsed_args
+
+
+def _compiler_info_command(print_command: str, cflags: List[str]) -> str:
+    command = [_ARM_COMPILER_NAME]
+    command.extend(cflags)
+    command.append(print_command)
+    result = subprocess.run(
+        command,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT,
+    )
+    result.check_returncode()
+    return result.stdout.decode().rstrip()
+
+
+def get_gcc_lib_dir(cflags: List[str]) -> Path:
+    return Path(_compiler_info_command('-print-libgcc-file-name',
+                                       cflags)).parent
+
+
+def get_compiler_info(cflags: List[str]) -> Dict[str, str]:
+    compiler_info: Dict[str, str] = {}
+    compiler_info['gcc_libs_dir'] = str(get_gcc_lib_dir(cflags))
+    compiler_info['sysroot'] = _compiler_info_command('-print-sysroot', cflags)
+    compiler_info['version'] = _compiler_info_command('-dumpversion', cflags)
+    compiler_info['multi_dir'] = _compiler_info_command(
+        '-print-multi-directory', cflags)
+    return compiler_info
+
+
+def get_cflags(compiler_info: Dict[str, str]):
+    # TODO(amontanez): Make newlib-nano optional.
+    cflags = [
+        # TODO(amontanez): For some reason, -stdlib++-isystem and
+        # -isystem-after work, but emit unused argument errors. This is the only
+        # way to let the build succeed.
+        '-Qunused-arguments',
+        # Disable all default libraries.
+        "-nodefaultlibs",
+        '--target=arm-none-eabi'
+    ]
+
+    # Add sysroot info.
+    cflags.extend((
+        '--sysroot=' + compiler_info['sysroot'],
+        '-isystem' +
+        str(Path(compiler_info['sysroot']) / 'include' / 'newlib-nano'),
+        # This must be included after Clang's builtin headers.
+        '-isystem-after' + str(Path(compiler_info['sysroot']) / 'include'),
+        '-stdlib++-isystem' + str(
+            Path(compiler_info['sysroot']) / 'include' / 'c++' /
+            compiler_info['version']),
+        '-isystem' + str(
+            Path(compiler_info['sysroot']) / 'include' / 'c++' /
+            compiler_info['version'] / _ARM_COMPILER_PREFIX /
+            compiler_info['multi_dir']),
+    ))
+
+    return cflags
+
+
+def get_crt_objs(compiler_info: Dict[str, str]) -> Tuple[str, ...]:
+    return (
+        str(Path(compiler_info['gcc_libs_dir']) / 'crtfastmath.o'),
+        str(Path(compiler_info['gcc_libs_dir']) / 'crti.o'),
+        str(Path(compiler_info['gcc_libs_dir']) / 'crtn.o'),
+        str(
+            Path(compiler_info['sysroot']) / 'lib' /
+            compiler_info['multi_dir'] / 'crt0.o'),
+    )
+
+
+def get_ldflags(compiler_info: Dict[str, str]) -> List[str]:
+    ldflags: List[str] = [
+        '-lnosys',
+        # Add library search paths.
+        '-L' + compiler_info['gcc_libs_dir'],
+        '-L' + str(
+            Path(compiler_info['sysroot']) / 'lib' /
+            compiler_info['multi_dir']),
+        # Add libraries to link.
+        '-lc_nano',
+        '-lm',
+        '-lgcc',
+        '-lstdc++_nano',
+    ]
+
+    # Add C runtime object files.
+    objs = get_crt_objs(compiler_info)
+    ldflags.extend(objs)
+
+    return ldflags
+
+
+def main(
+    cflags: bool,
+    ldflags: bool,
+    gn_scope: bool,
+    clang_flags: List[str],
+) -> int:
+    """Script entry point."""
+    compiler_info = get_compiler_info(clang_flags)
+    if ldflags:
+        ldflag_list = get_ldflags(compiler_info)
+
+    if cflags:
+        cflag_list = get_cflags(compiler_info)
+
+    if not gn_scope:
+        flags = []
+        if cflags:
+            flags.extend(cflag_list)
+        if ldflags:
+            flags.extend(ldflag_list)
+        print(' '.join(flags))
+        return 0
+
+    if cflags:
+        print('cflags = [')
+        for flag in cflag_list:
+            print(f'  "{flag}",')
+        print(']')
+
+    if ldflags:
+        print('ldflags = [')
+        for flag in ldflag_list:
+            print(f'  "{flag}",')
+        print(']')
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main(**vars(_parse_args())))
diff --git a/targets/lm3s6965evb-qemu/BUILD b/targets/lm3s6965evb-qemu/BUILD
index 307d555..bb15e06 100644
--- a/targets/lm3s6965evb-qemu/BUILD
+++ b/targets/lm3s6965evb-qemu/BUILD
@@ -25,7 +25,7 @@
     name = "pre_init",
     srcs = [
         "boot.cc",
-        "vector_table.cc"
+        "vector_table.c"
     ],
     deps = [
         "//pw_boot_armv7m",
diff --git a/targets/lm3s6965evb-qemu/BUILD.gn b/targets/lm3s6965evb-qemu/BUILD.gn
index beb52f3..ebfa30f 100644
--- a/targets/lm3s6965evb-qemu/BUILD.gn
+++ b/targets/lm3s6965evb-qemu/BUILD.gn
@@ -32,7 +32,7 @@
     deps = [ "$dir_pw_preprocessor" ]
     sources = [
       "boot.cc",
-      "vector_table.cc",
+      "vector_table.c",
     ]
   }
 }
diff --git a/targets/lm3s6965evb-qemu/target_docs.rst b/targets/lm3s6965evb-qemu/target_docs.rst
index 6e8c12f..b5f5b84 100644
--- a/targets/lm3s6965evb-qemu/target_docs.rst
+++ b/targets/lm3s6965evb-qemu/target_docs.rst
@@ -12,12 +12,12 @@
 
 Building
 ========
-To build for this Pigweed target, simply build the top-level "qemu" Ninja
+To build for this Pigweed target, simply build the top-level "qemu_gcc" Ninja
 target.
 
 .. code:: sh
 
-  $ ninja -C out qemu
+  $ ninja -C out qemu_gcc
 
 Testing
 =======
diff --git a/targets/lm3s6965evb-qemu/target_toolchains.gni b/targets/lm3s6965evb-qemu/target_toolchains.gni
index b66ccd0..ce3c496 100644
--- a/targets/lm3s6965evb-qemu/target_toolchains.gni
+++ b/targets/lm3s6965evb-qemu/target_toolchains.gni
@@ -15,6 +15,7 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_sys_io/backend.gni")
+import("$dir_pw_toolchain/arm_clang/toolchains.gni")
 import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
 
 _test_runner_script = "py/lm3s6965evb_qemu_utils/unit_test_runner.py"
@@ -63,11 +64,16 @@
   current_os = ""
 }
 
-_target_default_configs = [
+_gcc_target_default_configs = [
   "$dir_pw_build:extra_strict_warnings",
   "$dir_pw_toolchain/arm_gcc:enable_float_printf",
 ]
 
+_clang_target_default_configs = [
+  "$dir_pw_build:extra_strict_warnings",
+  "$dir_pw_toolchain/arm_clang:enable_float_printf",
+]
+
 pw_target_toolchain_lm3s6965evb_qemu = {
   _excluded_members = [
     "defaults",
@@ -75,35 +81,68 @@
   ]
 
   debug = {
-    name = "lm3s6965evb_qemu_debug"
+    name = "lm3s6965evb_qemu_gcc_debug"
     _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_debug
     forward_variables_from(_toolchain_base, "*", _excluded_members)
     defaults = {
       forward_variables_from(_toolchain_base.defaults, "*")
       forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
+      default_configs += _gcc_target_default_configs
     }
   }
 
   speed_optimized = {
-    name = "lm3s6965evb_qemu_speed_optimized"
+    name = "lm3s6965evb_qemu_gcc_speed_optimized"
     _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_speed_optimized
     forward_variables_from(_toolchain_base, "*", _excluded_members)
     defaults = {
       forward_variables_from(_toolchain_base.defaults, "*")
       forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
+      default_configs += _gcc_target_default_configs
     }
   }
 
   size_optimized = {
-    name = "lm3s6965evb_qemu_size_optimized"
+    name = "lm3s6965evb_qemu_gcc_size_optimized"
     _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_size_optimized
     forward_variables_from(_toolchain_base, "*", _excluded_members)
     defaults = {
       forward_variables_from(_toolchain_base.defaults, "*")
       forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
+      default_configs += _gcc_target_default_configs
+    }
+  }
+
+  debug_clang = {
+    name = "lm3s6965evb_qemu_clang_debug"
+    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_debug
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _clang_target_default_configs
+    }
+  }
+
+  speed_optimized_clang = {
+    name = "lm3s6965evb_qemu_clang_speed_optimized"
+    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_speed_optimized
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _clang_target_default_configs
+    }
+  }
+
+  size_optimized_clang = {
+    name = "lm3s6965evb_qemu_clang_size_optimized"
+    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_size_optimized
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _clang_target_default_configs
     }
   }
 }
@@ -115,4 +154,7 @@
   pw_target_toolchain_lm3s6965evb_qemu.debug,
   pw_target_toolchain_lm3s6965evb_qemu.speed_optimized,
   pw_target_toolchain_lm3s6965evb_qemu.size_optimized,
+  pw_target_toolchain_lm3s6965evb_qemu.debug_clang,
+  pw_target_toolchain_lm3s6965evb_qemu.speed_optimized_clang,
+  pw_target_toolchain_lm3s6965evb_qemu.size_optimized_clang,
 ]
diff --git a/targets/lm3s6965evb-qemu/vector_table.cc b/targets/lm3s6965evb-qemu/vector_table.c
similarity index 93%
rename from targets/lm3s6965evb-qemu/vector_table.cc
rename to targets/lm3s6965evb-qemu/vector_table.c
index 575a673..cbb5898 100644
--- a/targets/lm3s6965evb-qemu/vector_table.cc
+++ b/targets/lm3s6965evb-qemu/vector_table.c
@@ -12,14 +12,14 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_boot_armv7m/boot.h"
+#include <stdbool.h>
 
-namespace {
+#include "pw_boot_armv7m/boot.h"
 
 // Default handler to insert into the ARMv7-M vector table (below).
 // This function exists for convenience. If a device isn't doing what you
 // expect, it might have hit a fault and ended up here.
-void DefaultFaultHandler(void) {
+static void DefaultFaultHandler(void) {
   while (true) {
     // Wait for debugger to attach.
   }
@@ -44,7 +44,7 @@
     // This address is NOT an interrupt handler/function pointer, it is simply
     // the address that the main stack pointer should be initialized to. The
     // value is reinterpret casted because it needs to be in the vector table.
-    [0] = reinterpret_cast<InterruptHandler>(&pw_boot_stack_high_addr),
+    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
 
     // Reset handler, dictates how to handle reset interrupt. This is the
     // address that the Program Counter (PC) is initialized to at boot.
@@ -55,5 +55,3 @@
     // HardFault handler.
     [3] = DefaultFaultHandler,
 };
-
-}  // namespace
diff --git a/targets/stm32f429i-disc1/BUILD b/targets/stm32f429i-disc1/BUILD
index d88113b..b9cffcc 100644
--- a/targets/stm32f429i-disc1/BUILD
+++ b/targets/stm32f429i-disc1/BUILD
@@ -25,7 +25,7 @@
     name = "pre_init",
     srcs = [
         "boot.cc",
-        "vector_table.cc"
+        "vector_table.c"
     ],
     deps = [
         "//pw_boot_armv7m",
diff --git a/targets/stm32f429i-disc1/BUILD.gn b/targets/stm32f429i-disc1/BUILD.gn
index b148981..5527e65 100644
--- a/targets/stm32f429i-disc1/BUILD.gn
+++ b/targets/stm32f429i-disc1/BUILD.gn
@@ -43,7 +43,7 @@
     ]
     sources = [
       "boot.cc",
-      "vector_table.cc",
+      "vector_table.c",
     ]
   }
 
diff --git a/targets/lm3s6965evb-qemu/vector_table.cc b/targets/stm32f429i-disc1/vector_table.c
similarity index 93%
copy from targets/lm3s6965evb-qemu/vector_table.cc
copy to targets/stm32f429i-disc1/vector_table.c
index 575a673..cbb5898 100644
--- a/targets/lm3s6965evb-qemu/vector_table.cc
+++ b/targets/stm32f429i-disc1/vector_table.c
@@ -12,14 +12,14 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_boot_armv7m/boot.h"
+#include <stdbool.h>
 
-namespace {
+#include "pw_boot_armv7m/boot.h"
 
 // Default handler to insert into the ARMv7-M vector table (below).
 // This function exists for convenience. If a device isn't doing what you
 // expect, it might have hit a fault and ended up here.
-void DefaultFaultHandler(void) {
+static void DefaultFaultHandler(void) {
   while (true) {
     // Wait for debugger to attach.
   }
@@ -44,7 +44,7 @@
     // This address is NOT an interrupt handler/function pointer, it is simply
     // the address that the main stack pointer should be initialized to. The
     // value is reinterpret casted because it needs to be in the vector table.
-    [0] = reinterpret_cast<InterruptHandler>(&pw_boot_stack_high_addr),
+    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
 
     // Reset handler, dictates how to handle reset interrupt. This is the
     // address that the Program Counter (PC) is initialized to at boot.
@@ -55,5 +55,3 @@
     // HardFault handler.
     [3] = DefaultFaultHandler,
 };
-
-}  // namespace
diff --git a/targets/stm32f429i-disc1/vector_table.cc b/targets/stm32f429i-disc1/vector_table.cc
deleted file mode 100644
index 575a673..0000000
--- a/targets/stm32f429i-disc1/vector_table.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_boot_armv7m/boot.h"
-
-namespace {
-
-// Default handler to insert into the ARMv7-M vector table (below).
-// This function exists for convenience. If a device isn't doing what you
-// expect, it might have hit a fault and ended up here.
-void DefaultFaultHandler(void) {
-  while (true) {
-    // Wait for debugger to attach.
-  }
-}
-
-// This is the device's interrupt vector table. It's not referenced in any
-// code because the platform (STM32F4xx) expects this table to be present at the
-// beginning of flash. The exact address is specified in the pw_boot_armv7m
-// configuration as part of the target config.
-//
-// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
-// section B1.5.3.
-
-// This typedef is for convenience when building the vector table. With the
-// exception of SP_main (0th entry in the vector table), all the entries of the
-// vector table are function pointers.
-typedef void (*InterruptHandler)();
-
-PW_KEEP_IN_SECTION(".vector_table")
-const InterruptHandler vector_table[] = {
-    // The starting location of the stack pointer.
-    // This address is NOT an interrupt handler/function pointer, it is simply
-    // the address that the main stack pointer should be initialized to. The
-    // value is reinterpret casted because it needs to be in the vector table.
-    [0] = reinterpret_cast<InterruptHandler>(&pw_boot_stack_high_addr),
-
-    // Reset handler, dictates how to handle reset interrupt. This is the
-    // address that the Program Counter (PC) is initialized to at boot.
-    [1] = pw_boot_Entry,
-
-    // NMI handler.
-    [2] = DefaultFaultHandler,
-    // HardFault handler.
-    [3] = DefaultFaultHandler,
-};
-
-}  // namespace