pw_rust: Add Rust support to Bazel build

Change-Id: Ibf957372e9fdf763337ec561b3c269f61583d67c
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/127270
Pigweed-Auto-Submit: Erik Gilling <konkers@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Ted Pudlik <tpudlik@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index f991592..dc101b8 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -341,9 +341,7 @@
   ]
 
   if (pw_rust_ENABLE_EXPERIMENTAL_BUILD) {
-    deps += [
-      "$dir_pw_rust/example:hello($dir_pigweed/targets/host:host_clang_debug)",
-    ]
+    deps += [ "$dir_pw_rust/examples/host_executable:hello($dir_pigweed/targets/host:host_clang_debug)" ]
   }
 }
 
diff --git a/WORKSPACE b/WORKSPACE
index 948e95b..eab85b7 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -265,6 +265,72 @@
 
 register_gcc_arm_none_toolchain()
 
+# Rust Support
+#
+
+git_repository(
+    name = "rules_rust",
+    # Pulls in the main branch with https://github.com/bazelbuild/rules_rust/pull/1803
+    # merged.  Once a release is cut with that commit, we should switch to
+    # using a release tarbal.
+    commit = "a5853fd37053b65ee30ba4f8064b9db67c90d53f",
+    remote = "https://github.com/bazelbuild/rules_rust",
+    shallow_since = "1675302817 -0800",
+)
+
+load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_analyzer_toolchain_repository", "rust_repository_set")
+
+rules_rust_dependencies()
+
+# Here we pull in a specific toolchain.  Unfortunately `rust_repository_set`
+# does not provide a way to add `target_compatible_with` options which are
+# needed to be compatible with `@bazel_embedded` (specifically
+# `@bazel_embedded//constraints/fpu:none` which is specified in
+# `//platforms`)
+#
+# See `//toolchain:rust_linux_x86_64` for how this is used.
+#
+# Note: This statement creates name mangled remotes of the form:
+# `@{name}__{triplet}_tools`
+# (example: `@rust_linux_x86_64__thumbv7m-none-eabi_tools/`)
+rust_repository_set(
+    name = "rust_linux_x86_64",
+    edition = "2021",
+    exec_triple = "x86_64-unknown-linux-gnu",
+    extra_target_triples = [
+        "thumbv7m-none-eabi",
+        "thumbv6m-none-eabi",
+    ],
+    versions = ["1.67.0"],
+)
+
+# Registers our Rust toolchains that are compatable with `@bazel_embedded`.
+register_toolchains(
+    "//pw_toolchain:thumbv7m_rust_linux_x86_64",
+    "//pw_toolchain:thumbv6m_rust_linux_x86_64",
+)
+
+# Allows creation of a `rust-project.json` file to allow rust analyzer to work.
+load("@rules_rust//tools/rust_analyzer:deps.bzl", "rust_analyzer_dependencies")
+
+# Since we do not use rust_register_toolchains, we need to define a
+# rust_analyzer_toolchain.
+register_toolchains(rust_analyzer_toolchain_repository(
+    name = "rust_analyzer_toolchain",
+    # This should match the currently registered toolchain.
+    version = "1.67.0",
+))
+
+rust_analyzer_dependencies()
+
+# Vendored third party rust crates.
+git_repository(
+    name = "rust_crates",
+    commit = "c39c1d1d4e4bdf2d8145beb8882af6f6e4e6dbbc",
+    remote = "https://pigweed.googlesource.com/third_party/rust_crates",
+    shallow_since = "1675359057 +0000",
+)
+
 # Registers platforms for use with toolchain resolution
 register_execution_platforms("//pw_build/platforms:all")
 
diff --git a/pw_build/constraints/board/BUILD.bazel b/pw_build/constraints/board/BUILD.bazel
index 4696f87..58f637f 100644
--- a/pw_build/constraints/board/BUILD.bazel
+++ b/pw_build/constraints/board/BUILD.bazel
@@ -28,3 +28,8 @@
     name = "mimxrt595_evk",
     constraint_setting = ":board",
 )
+
+constraint_value(
+    name = "microbit",
+    constraint_setting = ":board",
+)
diff --git a/pw_build/constraints/chipset/BUILD.bazel b/pw_build/constraints/chipset/BUILD.bazel
index 3a2a2ea..e292906 100644
--- a/pw_build/constraints/chipset/BUILD.bazel
+++ b/pw_build/constraints/chipset/BUILD.bazel
@@ -28,3 +28,8 @@
     name = "lm3s6965evb",
     constraint_setting = ":chipset",
 )
+
+constraint_value(
+    name = "nrf52833",
+    constraint_setting = ":chipset",
+)
diff --git a/pw_build/platforms/BUILD.bazel b/pw_build/platforms/BUILD.bazel
index 7bbc6a3..bd87ad1 100644
--- a/pw_build/platforms/BUILD.bazel
+++ b/pw_build/platforms/BUILD.bazel
@@ -99,9 +99,21 @@
     parents = [":cortex_m3"],
 )
 
+platform(
+    name = "nrf52833",
+    constraint_values = ["//pw_build/constraints/chipset:nrf52833"],
+    parents = [":cortex_m0"],
+)
+
 # --- Boards ---
 platform(
     name = "stm32f429i-disc1",
     constraint_values = ["//pw_build/constraints/board:stm32f429i-disc1"],
     parents = [":stm32f429"],
 )
+
+platform(
+    name = "microbit",
+    constraint_values = ["//pw_build/constraints/board:microbit"],
+    parents = [":nrf52833"],
+)
diff --git a/pw_rust/docs.rst b/pw_rust/docs.rst
index 89e9efb..b917b54 100644
--- a/pw_rust/docs.rst
+++ b/pw_rust/docs.rst
@@ -3,13 +3,42 @@
 =======
 pw_rust
 =======
-Rust support in pigweed is ***highly*** experimental.  Currently only building
-a single host binary using the standard libraries is supported.  Only GN builds
-are supported and building on Windows is currently unsupported.
+Rust support in pigweed is **highly** experimental.  Currently functionality
+is split between Bazel and GN support.
 
---------
+-----
+Bazel
+-----
+Bazel support is based on `rules_rust <https://github.com/bazelbuild/rules_rust>`_
+and supports a rich set of targets for both host and target builds.
+
+Building and Running the Embedded Example
+=========================================
+The ``embedded_hello`` example can be built for both the ``lm3s6965evb``
+and ``microbit`` QEMU machines.  The example can be built and run using
+the following commands where ``PLATFORM`` is one of ``lm3s6965evb`` or
+``microbit``.
+
+.. code:: bash
+
+   $ bazel build //pw_rust/examples/embedded_hello:hello \
+     --platforms //pw_build/platforms:${PLATFORM} \
+
+   $ qemu-system-arm \
+     -machine ${PLATFORM} \
+     -nographic \
+     -semihosting-config enable=on,target=native \
+     -kernel ./bazel-bin/pw_rust/examples/embedded_hello/hello
+   Hello, Pigweed!
+
+--
+GN
+--
+In GN, currently only building a single host binary using the standard
+libraries is supported.  Windows builds are currently unsupported.
+
 Building
---------
+========
 To build the sample rust targets, you need to enable
 ``pw_rust_ENABLE_EXPERIMENTAL_BUILD``:
 
@@ -22,5 +51,13 @@
 .. code:: bash
 
    $ ninja -C out host_clang_debug/obj/pw_rust/example/bin/hello
-   $ ./out/host_clang_debug/obj/pw_rust/example/bin/hello
+   $ ./out/host_clang_debug/obj/pw_rust/examples/host_executable/bin/hello
    Hello, Pigweed!
+
+------------------
+Third Party Crates
+------------------
+Thrid party crates are vendored in the
+`third_party/rust_crates <https://pigweed.googlesource.com/third_party/rust_crates>`_
+respository.  Currently referencing these is only supported through the bazel
+build.
diff --git a/pw_rust/examples/embedded_hello/BUILD.bazel b/pw_rust/examples/embedded_hello/BUILD.bazel
new file mode 100644
index 0000000..e830965
--- /dev/null
+++ b/pw_rust/examples/embedded_hello/BUILD.bazel
@@ -0,0 +1,39 @@
+# Copyright 2023 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.
+
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+
+package(default_visibility = ["//visibility:public"])
+
+rust_binary(
+    name = "hello",
+    srcs = ["src/main.rs"],
+    edition = "2021",
+    linker_script = select({
+        "//pw_build/constraints/board:microbit": "qemu-rust-nrf51822.ld",
+        "//pw_build/constraints/chipset:lm3s6965evb": "qemu-rust-lm3s6965.ld",
+    }),
+    target_compatible_with = select({
+        "@platforms//os:linux": ["@platforms//:incompatible"],
+        "@platforms//os:macos": ["@platforms//:incompatible"],
+        "//pw_build/constraints/board:microbit": [],
+        "//pw_build/constraints/chipset:lm3s6965evb": [],
+    }),
+    deps = [
+        "@rust_crates//crates:cortex-m",
+        "@rust_crates//crates:cortex-m-rt",
+        "@rust_crates//crates:cortex-m-semihosting",
+        "@rust_crates//crates:panic-halt",
+    ],
+)
diff --git a/pw_rust/examples/embedded_hello/qemu-rust-lm3s6965.ld b/pw_rust/examples/embedded_hello/qemu-rust-lm3s6965.ld
new file mode 100644
index 0000000..7e65d25
--- /dev/null
+++ b/pw_rust/examples/embedded_hello/qemu-rust-lm3s6965.ld
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2023 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.
+ */
+
+/* This relatively simplified linker script will work with many ARMv7-M and
+ * ARMv8-M cores that have on-board memory-mapped RAM and FLASH. For more
+ * complex projects and devices, it's possible this linker script will not be
+ * sufficient as-is.
+ *
+ * This linker script is likely not suitable for a project with a bootloader.
+ */
+
+/* Note: This technically doesn't set the firmware's entry point. Setting the
+ *       firmware entry point is done by setting vector_table[1]
+ *       (Reset_Handler). However, this DOES tell the compiler how to optimize
+ *       when --gc-sections is enabled.
+ */
+ENTRY(Reset)
+
+MEMORY
+{
+  /* TODO(b/234892223): Make it possible for projects to freely customize
+   * memory regions.
+   */
+
+  /* Vector Table (typically in flash) */
+  VECTOR_TABLE(rx) : ORIGIN = 0x00000000, LENGTH = 1024
+  /* Internal Flash */
+  FLASH(rx) : ORIGIN = 0x00000400, LENGTH = 255K
+  /* Internal SRAM */
+  RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 64K
+
+  /* Each memory region above has an associated .*.unused_space section that
+   * overlays the unused space at the end of the memory segment. These segments
+   * are used by pw_bloat.bloaty_config to create the utilization data source
+   * for bloaty size reports.
+   *
+   * These sections MUST be located immediately after the last section that is
+   * placed in the respective memory region or lld will issue a warning like:
+   *
+   *   warning: ignoring memory region assignment for non-allocatable section
+   *      '.VECTOR_TABLE.unused_space'
+   *
+   * If this warning occurs, it's also likely that LLD will have created quite
+   * large padded regions in the ELF file due to bad cursor operations. This
+   * can cause ELF files to balloon from hundreds of kilobytes to hundreds of
+   * megabytes.
+   *
+   * Attempting to add sections to the memory region AFTER the unused_space
+   * section will cause the region to overflow.
+   */
+}
+
+SECTIONS
+{
+  /* This is the link-time vector table. If used, the VTOR (Vector Table Offset
+   * Register) MUST point to this memory location in order to be used. This can
+   * be done by ensuring this section exists at the default location of the VTOR
+   * so it's used on reset, or by explicitly setting the VTOR in a bootloader
+   * manually to point to &pw_boot_vector_table_addr before interrupts are
+   * enabled.
+   *
+   * The ARMv7-M architecture requires this is at least aligned to 128 bytes,
+   * and aligned to a power of two that is greater than 4 times the number of
+   * supported exceptions. 512 has been selected as it accommodates most
+   * devices' vector tables.
+   */
+  .vector_table : ALIGN(512)
+  {
+    LONG(pw_boot_stack_high_addr);
+    pw_boot_vector_table_addr = .;
+    KEEP(*(.vector_table))
+    KEEP(*(.vector_table.reset_vector))
+    KEEP(*(.vector_table.exceptions))
+    KEEP(*(.vector_table.interrupts))
+  } >VECTOR_TABLE
+
+  /* Represents unused space in the VECTOR_TABLE segment. This MUST be the last
+   * section assigned to the VECTOR_TABLE region.
+   */
+  .VECTOR_TABLE.unused_space (NOLOAD) : ALIGN(4)
+  {
+    . = ABSOLUTE(ORIGIN(VECTOR_TABLE) + LENGTH(VECTOR_TABLE));
+  } >VECTOR_TABLE
+
+  /* Main executable code. */
+  .code : ALIGN(4)
+  {
+    . = ALIGN(4);
+    /* cortex-m-rt expects these to be nearby each other because short branches
+     * are used.
+     */
+    *(.PreResetTrampoline);
+    *(.Reset);
+    *(.HardFaultTrampoline);
+    *(.HardFault.*);
+
+    /* Application code. */
+    *(.text)
+    *(.text*)
+    KEEP(*(.init))
+    KEEP(*(.fini))
+
+    . = ALIGN(4);
+    /* Constants.*/
+    *(.rodata)
+    *(.rodata*)
+
+    /* .preinit_array, .init_array, .fini_array are used by libc.
+     * Each section is a list of function pointers that are called pre-main and
+     * post-exit for object initialization and tear-down.
+     * Since the region isn't explicitly referenced, specify KEEP to prevent
+     * link-time garbage collection. SORT is used for sections that have strict
+     * init/de-init ordering requirements. */
+    . = ALIGN(4);
+    PROVIDE_HIDDEN(__preinit_array_start = .);
+    KEEP(*(.preinit_array*))
+    PROVIDE_HIDDEN(__preinit_array_end = .);
+
+    PROVIDE_HIDDEN(__init_array_start = .);
+    KEEP(*(SORT(.init_array.*)))
+    KEEP(*(.init_array*))
+    PROVIDE_HIDDEN(__init_array_end = .);
+
+    PROVIDE_HIDDEN(__fini_array_start = .);
+    KEEP(*(SORT(.fini_array.*)))
+    KEEP(*(.fini_array*))
+    PROVIDE_HIDDEN(__fini_array_end = .);
+  } >FLASH
+
+  /* Used by unwind-arm/ */
+  .ARM : ALIGN(4) {
+    __exidx_start = .;
+    *(.ARM.exidx*)
+    __exidx_end = .;
+  } >FLASH
+
+  /* Explicitly initialized global and static data. (.data)*/
+  .static_init_ram : ALIGN(4)
+  {
+    *(.data)
+    *(.data*)
+    . = ALIGN(4);
+  } >RAM AT> FLASH
+
+  /* Represents unused space in the FLASH segment. This MUST be the last section
+   * assigned to the FLASH region.
+   */
+  .FLASH.unused_space (NOLOAD) : ALIGN(4)
+  {
+    . = ABSOLUTE(ORIGIN(FLASH) + LENGTH(FLASH));
+  } >FLASH
+
+  /* The .zero_init_ram, .heap, and .stack sections below require (NOLOAD)
+   * annotations for LLVM lld, but not GNU ld, because LLVM's lld intentionally
+   * interprets the linker file differently from ld:
+   *
+   * https://discourse.llvm.org/t/lld-vs-ld-section-type-progbits-vs-nobits/5999/3
+   *
+   * Zero initialized global/static data (.bss) is initialized in
+   * pw_boot_Entry() via memset(), so the section doesn't need to be loaded from
+   * flash. The .heap and .stack sections don't require any initialization,
+   * as they only represent allocated memory regions, so they also do not need
+   * to be loaded.
+   */
+  .zero_init_ram (NOLOAD) : ALIGN(4)
+  {
+    *(.bss)
+    *(.bss*)
+    *(COMMON)
+    . = ALIGN(4);
+  } >RAM
+
+  .heap (NOLOAD) : ALIGN(4)
+  {
+    pw_boot_heap_low_addr = .;
+    . = . + 0;
+    . = ALIGN(4);
+    pw_boot_heap_high_addr = .;
+  } >RAM
+
+  /* Link-time check for stack overlaps.
+   *
+   * The ARMv7-M architecture may require 8-byte alignment of the stack pointer
+   * rather than 4 in some contexts and implementations, so this region is
+   * 8-byte aligned (see ARMv7-M Architecture Reference Manual DDI0403E
+   * section B1.5.7).
+   */
+  .stack (NOLOAD) : ALIGN(8)
+  {
+    /* Set the address that the main stack pointer should be initialized to. */
+    pw_boot_stack_low_addr = .;
+    HIDDEN(_stack_size = ORIGIN(RAM) + LENGTH(RAM) - .);
+    /* Align the stack to a lower address to ensure it isn't out of range. */
+    HIDDEN(_stack_high = (. + _stack_size) & ~0x7);
+    ASSERT(_stack_high - . >= 1K,
+           "Error: Not enough RAM for desired minimum stack size.");
+    . = _stack_high;
+    pw_boot_stack_high_addr = .;
+  } >RAM
+
+  /* Represents unused space in the RAM segment. This MUST be the last section
+   * assigned to the RAM region.
+   */
+  .RAM.unused_space (NOLOAD) : ALIGN(4)
+  {
+    . = ABSOLUTE(ORIGIN(RAM) + LENGTH(RAM));
+  } >RAM
+
+  /* Discard unwind info. */
+  .ARM.extab 0x0 (INFO) :
+  {
+    KEEP(*(.ARM.extab*))
+  }
+}
+
+/* Symbols used by core_init.c: */
+/* Start of .static_init_ram in FLASH. */
+_pw_static_init_flash_start = LOADADDR(.static_init_ram);
+
+/* Region of .static_init_ram in RAM. */
+_pw_static_init_ram_start = ADDR(.static_init_ram);
+_pw_static_init_ram_end = _pw_static_init_ram_start + SIZEOF(.static_init_ram);
+
+/* Region of .zero_init_ram. */
+_pw_zero_init_ram_start = ADDR(.zero_init_ram);
+_pw_zero_init_ram_end = _pw_zero_init_ram_start + SIZEOF(.zero_init_ram);
+
+/* Symbols needed for the Rust cortex-m-rt crate. */
+__sbss = _pw_zero_init_ram_start;
+__ebss = _pw_zero_init_ram_end;
+__sdata = _pw_static_init_ram_start;
+__edata = _pw_static_init_ram_end;
+__sidata = LOADADDR(.static_init_ram);
+__pre_init = DefaultPreInit;
+DefaultHandler = DefaultHandler_;
+NonMaskableInt = DefaultHandler;
+MemoryManagement = DefaultHandler;
+BusFault = DefaultHandler;
+UsageFault = DefaultHandler;
+SVCall = DefaultHandler;
+DebugMonitor = DefaultHandler;
+PendSV = DefaultHandler;
+SysTick = DefaultHandler;
+HardFault = HardFault_;
+
+/* arm-none-eabi expects `end` symbol to point to start of heap for sbrk. */
+PROVIDE(end = _pw_zero_init_ram_end);
+
+/* These symbols are used by pw_bloat.bloaty_config to create the memoryregions
+ * data source for bloaty in this format (where the optional _N defaults to 0):
+ * pw_bloat_config_memory_region_NAME_{start,end}{_N,} */
+pw_bloat_config_memory_region_VECTOR_TABLE_start = ORIGIN(VECTOR_TABLE);
+pw_bloat_config_memory_region_VECTOR_TABLE_end =
+    ORIGIN(VECTOR_TABLE) + LENGTH(VECTOR_TABLE);
+pw_bloat_config_memory_region_FLASH_start = ORIGIN(FLASH);
+pw_bloat_config_memory_region_FLASH_end = ORIGIN(FLASH) + LENGTH(FLASH);
+pw_bloat_config_memory_region_RAM_start = ORIGIN(RAM);
+pw_bloat_config_memory_region_RAM_end = ORIGIN(RAM) + LENGTH(RAM);
diff --git a/pw_rust/examples/embedded_hello/qemu-rust-nrf51822.ld b/pw_rust/examples/embedded_hello/qemu-rust-nrf51822.ld
new file mode 100644
index 0000000..f8366ac
--- /dev/null
+++ b/pw_rust/examples/embedded_hello/qemu-rust-nrf51822.ld
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2023 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.
+ */
+
+/* This relatively simplified linker script will work with many ARMv7-M and
+ * ARMv8-M cores that have on-board memory-mapped RAM and FLASH. For more
+ * complex projects and devices, it's possible this linker script will not be
+ * sufficient as-is.
+ *
+ * This linker script is likely not suitable for a project with a bootloader.
+ */
+
+/* Note: This technically doesn't set the firmware's entry point. Setting the
+ *       firmware entry point is done by setting vector_table[1]
+ *       (Reset_Handler). However, this DOES tell the compiler how to optimize
+ *       when --gc-sections is enabled.
+ */
+ENTRY(Reset)
+
+MEMORY
+{
+  /* TODO(b/234892223): Make it possible for projects to freely customize
+   * memory regions.
+   */
+
+  /* Vector Table (typically in flash) */
+  VECTOR_TABLE(rx) : ORIGIN = 0x00000000, LENGTH = 1024
+  /* Internal Flash */
+  FLASH(rx) : ORIGIN = 0x00000400, LENGTH = 255K
+  /* Internal SRAM */
+  RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 16K
+
+  /* Each memory region above has an associated .*.unused_space section that
+   * overlays the unused space at the end of the memory segment. These segments
+   * are used by pw_bloat.bloaty_config to create the utilization data source
+   * for bloaty size reports.
+   *
+   * These sections MUST be located immediately after the last section that is
+   * placed in the respective memory region or lld will issue a warning like:
+   *
+   *   warning: ignoring memory region assignment for non-allocatable section
+   *      '.VECTOR_TABLE.unused_space'
+   *
+   * If this warning occurs, it's also likely that LLD will have created quite
+   * large padded regions in the ELF file due to bad cursor operations. This
+   * can cause ELF files to balloon from hundreds of kilobytes to hundreds of
+   * megabytes.
+   *
+   * Attempting to add sections to the memory region AFTER the unused_space
+   * section will cause the region to overflow.
+   */
+}
+
+SECTIONS
+{
+  /* This is the link-time vector table. If used, the VTOR (Vector Table Offset
+   * Register) MUST point to this memory location in order to be used. This can
+   * be done by ensuring this section exists at the default location of the VTOR
+   * so it's used on reset, or by explicitly setting the VTOR in a bootloader
+   * manually to point to &pw_boot_vector_table_addr before interrupts are
+   * enabled.
+   *
+   * The ARMv7-M architecture requires this is at least aligned to 128 bytes,
+   * and aligned to a power of two that is greater than 4 times the number of
+   * supported exceptions. 512 has been selected as it accommodates most
+   * devices' vector tables.
+   */
+  .vector_table : ALIGN(512)
+  {
+    LONG(pw_boot_stack_high_addr);
+    pw_boot_vector_table_addr = .;
+    KEEP(*(.vector_table))
+    KEEP(*(.vector_table.reset_vector))
+    KEEP(*(.vector_table.exceptions))
+    KEEP(*(.vector_table.interrupts))
+  } >VECTOR_TABLE
+
+  /* Represents unused space in the VECTOR_TABLE segment. This MUST be the last
+   * section assigned to the VECTOR_TABLE region.
+   */
+  .VECTOR_TABLE.unused_space (NOLOAD) : ALIGN(4)
+  {
+    . = ABSOLUTE(ORIGIN(VECTOR_TABLE) + LENGTH(VECTOR_TABLE));
+  } >VECTOR_TABLE
+
+  /* Main executable code. */
+  .code : ALIGN(4)
+  {
+    . = ALIGN(4);
+    /* cortex-m-rt expects these to be nearby each other because short branches
+     * are used.
+     */
+    *(.PreResetTrampoline);
+    *(.Reset);
+    *(.HardFaultTrampoline);
+    *(.HardFault.*);
+
+    /* Application code. */
+    *(.text)
+    *(.text*)
+    KEEP(*(.init))
+    KEEP(*(.fini))
+
+    . = ALIGN(4);
+    /* Constants.*/
+    *(.rodata)
+    *(.rodata*)
+
+    /* .preinit_array, .init_array, .fini_array are used by libc.
+     * Each section is a list of function pointers that are called pre-main and
+     * post-exit for object initialization and tear-down.
+     * Since the region isn't explicitly referenced, specify KEEP to prevent
+     * link-time garbage collection. SORT is used for sections that have strict
+     * init/de-init ordering requirements. */
+    . = ALIGN(4);
+    PROVIDE_HIDDEN(__preinit_array_start = .);
+    KEEP(*(.preinit_array*))
+    PROVIDE_HIDDEN(__preinit_array_end = .);
+
+    PROVIDE_HIDDEN(__init_array_start = .);
+    KEEP(*(SORT(.init_array.*)))
+    KEEP(*(.init_array*))
+    PROVIDE_HIDDEN(__init_array_end = .);
+
+    PROVIDE_HIDDEN(__fini_array_start = .);
+    KEEP(*(SORT(.fini_array.*)))
+    KEEP(*(.fini_array*))
+    PROVIDE_HIDDEN(__fini_array_end = .);
+  } >FLASH
+
+  /* Used by unwind-arm/ */
+  .ARM : ALIGN(4) {
+    __exidx_start = .;
+    *(.ARM.exidx*)
+    __exidx_end = .;
+  } >FLASH
+
+  /* Explicitly initialized global and static data. (.data)*/
+  .static_init_ram : ALIGN(4)
+  {
+    *(.data)
+    *(.data*)
+    . = ALIGN(4);
+  } >RAM AT> FLASH
+
+  /* Represents unused space in the FLASH segment. This MUST be the last section
+   * assigned to the FLASH region.
+   */
+  .FLASH.unused_space (NOLOAD) : ALIGN(4)
+  {
+    . = ABSOLUTE(ORIGIN(FLASH) + LENGTH(FLASH));
+  } >FLASH
+
+  /* The .zero_init_ram, .heap, and .stack sections below require (NOLOAD)
+   * annotations for LLVM lld, but not GNU ld, because LLVM's lld intentionally
+   * interprets the linker file differently from ld:
+   *
+   * https://discourse.llvm.org/t/lld-vs-ld-section-type-progbits-vs-nobits/5999/3
+   *
+   * Zero initialized global/static data (.bss) is initialized in
+   * pw_boot_Entry() via memset(), so the section doesn't need to be loaded from
+   * flash. The .heap and .stack sections don't require any initialization,
+   * as they only represent allocated memory regions, so they also do not need
+   * to be loaded.
+   */
+  .zero_init_ram (NOLOAD) : ALIGN(4)
+  {
+    *(.bss)
+    *(.bss*)
+    *(COMMON)
+    . = ALIGN(4);
+  } >RAM
+
+  .heap (NOLOAD) : ALIGN(4)
+  {
+    pw_boot_heap_low_addr = .;
+    . = . + 0;
+    . = ALIGN(4);
+    pw_boot_heap_high_addr = .;
+  } >RAM
+
+  /* Link-time check for stack overlaps.
+   *
+   * The ARMv7-M architecture may require 8-byte alignment of the stack pointer
+   * rather than 4 in some contexts and implementations, so this region is
+   * 8-byte aligned (see ARMv7-M Architecture Reference Manual DDI0403E
+   * section B1.5.7).
+   */
+  .stack (NOLOAD) : ALIGN(8)
+  {
+    /* Set the address that the main stack pointer should be initialized to. */
+    pw_boot_stack_low_addr = .;
+    HIDDEN(_stack_size = ORIGIN(RAM) + LENGTH(RAM) - .);
+    /* Align the stack to a lower address to ensure it isn't out of range. */
+    HIDDEN(_stack_high = (. + _stack_size) & ~0x7);
+    ASSERT(_stack_high - . >= 1K,
+           "Error: Not enough RAM for desired minimum stack size.");
+    . = _stack_high;
+    pw_boot_stack_high_addr = .;
+  } >RAM
+
+  /* Represents unused space in the RAM segment. This MUST be the last section
+   * assigned to the RAM region.
+   */
+  .RAM.unused_space (NOLOAD) : ALIGN(4)
+  {
+    . = ABSOLUTE(ORIGIN(RAM) + LENGTH(RAM));
+  } >RAM
+
+  /* Discard unwind info. */
+  .ARM.extab 0x0 (INFO) :
+  {
+    KEEP(*(.ARM.extab*))
+  }
+}
+
+/* Symbols used by core_init.c: */
+/* Start of .static_init_ram in FLASH. */
+_pw_static_init_flash_start = LOADADDR(.static_init_ram);
+
+/* Region of .static_init_ram in RAM. */
+_pw_static_init_ram_start = ADDR(.static_init_ram);
+_pw_static_init_ram_end = _pw_static_init_ram_start + SIZEOF(.static_init_ram);
+
+/* Region of .zero_init_ram. */
+_pw_zero_init_ram_start = ADDR(.zero_init_ram);
+_pw_zero_init_ram_end = _pw_zero_init_ram_start + SIZEOF(.zero_init_ram);
+
+/* Symbols needed for the Rust cortex-m-rt crate. */
+__sbss = _pw_zero_init_ram_start;
+__ebss = _pw_zero_init_ram_end;
+__sdata = _pw_static_init_ram_start;
+__edata = _pw_static_init_ram_end;
+__sidata = LOADADDR(.static_init_ram);
+__pre_init = DefaultPreInit;
+DefaultHandler = DefaultHandler_;
+NonMaskableInt = DefaultHandler;
+MemoryManagement = DefaultHandler;
+BusFault = DefaultHandler;
+UsageFault = DefaultHandler;
+SVCall = DefaultHandler;
+DebugMonitor = DefaultHandler;
+PendSV = DefaultHandler;
+SysTick = DefaultHandler;
+HardFault = HardFault_;
+
+/* arm-none-eabi expects `end` symbol to point to start of heap for sbrk. */
+PROVIDE(end = _pw_zero_init_ram_end);
+
+/* These symbols are used by pw_bloat.bloaty_config to create the memoryregions
+ * data source for bloaty in this format (where the optional _N defaults to 0):
+ * pw_bloat_config_memory_region_NAME_{start,end}{_N,} */
+pw_bloat_config_memory_region_VECTOR_TABLE_start = ORIGIN(VECTOR_TABLE);
+pw_bloat_config_memory_region_VECTOR_TABLE_end =
+    ORIGIN(VECTOR_TABLE) + LENGTH(VECTOR_TABLE);
+pw_bloat_config_memory_region_FLASH_start = ORIGIN(FLASH);
+pw_bloat_config_memory_region_FLASH_end = ORIGIN(FLASH) + LENGTH(FLASH);
+pw_bloat_config_memory_region_RAM_start = ORIGIN(RAM);
+pw_bloat_config_memory_region_RAM_end = ORIGIN(RAM) + LENGTH(RAM);
diff --git a/pw_rust/examples/embedded_hello/src/main.rs b/pw_rust/examples/embedded_hello/src/main.rs
new file mode 100644
index 0000000..51028c4
--- /dev/null
+++ b/pw_rust/examples/embedded_hello/src/main.rs
@@ -0,0 +1,32 @@
+// Copyright 2023 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.
+
+#![no_main]
+#![no_std]
+
+// Panic handler that halts the CPU on panic.
+use panic_halt as _;
+
+// Cortex M runtime entry macro.
+use cortex_m_rt::entry;
+
+// Semihosting support which is well supported for QEMU targets.
+use cortex_m_semihosting::{debug, hprintln};
+
+#[entry]
+fn main() -> ! {
+    hprintln!("Hello, Pigweed!");
+    debug::exit(debug::EXIT_SUCCESS);
+    loop {}
+}
diff --git a/pw_rust/example/BUILD.gn b/pw_rust/examples/host_executable/BUILD.gn
similarity index 100%
rename from pw_rust/example/BUILD.gn
rename to pw_rust/examples/host_executable/BUILD.gn
diff --git a/pw_rust/example/main.rs b/pw_rust/examples/host_executable/main.rs
similarity index 100%
rename from pw_rust/example/main.rs
rename to pw_rust/examples/host_executable/main.rs
diff --git a/pw_toolchain/BUILD.bazel b/pw_toolchain/BUILD.bazel
index 15510fd..5f160c1 100644
--- a/pw_toolchain/BUILD.bazel
+++ b/pw_toolchain/BUILD.bazel
@@ -17,6 +17,7 @@
     "pw_cc_library",
     "pw_cc_test",
 )
+load(":rust_toolchain.bzl", "pw_rust_toolchain")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -43,3 +44,22 @@
     linkopts = ["-Wl,--wrap=abort"],
     deps = ["//pw_assert"],
 )
+
+# Define rust toolchains that are compatable with @bazel_embedded.
+pw_rust_toolchain(
+    name = "thumbv7m_rust_linux_x86_64",
+    exec_cpu = "x86_64",
+    exec_os = "linux",
+    exec_triple = "x86_64-unknown-linux-gnu",
+    rust_target_triple = "thumbv7m-none-eabi",
+    target_cpu = "armv7-m",
+)
+
+pw_rust_toolchain(
+    name = "thumbv6m_rust_linux_x86_64",
+    exec_cpu = "x86_64",
+    exec_os = "linux",
+    exec_triple = "x86_64-unknown-linux-gnu",
+    rust_target_triple = "thumbv6m-none-eabi",
+    target_cpu = "armv6-m",
+)
diff --git a/pw_toolchain/rust_toolchain.bzl b/pw_toolchain/rust_toolchain.bzl
new file mode 100644
index 0000000..32b5ce4
--- /dev/null
+++ b/pw_toolchain/rust_toolchain.bzl
@@ -0,0 +1,48 @@
+# Copyright 2023 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.
+"""Utilities for declaring Rust toolchains that are compatible with @bazel_embedded"""
+
+load("@rules_rust//rust:toolchain.bzl", "rust_toolchain")
+
+def pw_rust_toolchain(name, exec_cpu, exec_os, target_cpu, rust_target_triple, exec_triple):
+    proxy_toolchain = "@rust_{}_{}__{}__stable_tools".format(exec_os, exec_cpu, rust_target_triple)
+
+    rust_toolchain(
+        name = "{}_impl".format(name),
+        binary_ext = "",
+        dylib_ext = ".so",
+        exec_triple = exec_triple,
+        os = "none",
+        rust_doc = "{}//:rustdoc".format(proxy_toolchain),
+        rust_std = "{}//:rust_std-{}".format(proxy_toolchain, rust_target_triple),
+        rustc = "{}//:rustc".format(proxy_toolchain),
+        rustc_lib = "{}//:rustc_lib".format(proxy_toolchain),
+        staticlib_ext = ".a",
+        stdlib_linkflags = [],
+        target_triple = rust_target_triple,
+    )
+
+    native.toolchain(
+        name = name,
+        exec_compatible_with = [
+            "@platforms//cpu:{}".format(exec_cpu),
+            "@platforms//os:{}".format(exec_os),
+        ],
+        target_compatible_with = [
+            "@platforms//cpu:{}".format(target_cpu),
+            "@bazel_embedded//constraints/fpu:none",
+        ],
+        toolchain = ":{}_impl".format(name),
+        toolchain_type = "@rules_rust//rust:toolchain",
+    )