What’s new in Pigweed: June 2026#
Highlights:
Rust detokenization support in pw_tokenizer - The initial support includes loading token databases from CSV and binary formats, configuring custom prefix characters, and detokenizing text. Error formatting behavior matches the C++ version of pw_tokenizer.
New pw_assert crate - The new Rust crate provides crash-safe assert and panic macros for embedded systems where standard library panics might not be suitable, or where specific logging/recovery behavior is needed.
New pw_time crate - The initial Rust crate supports time, instants, and durations.
In-memory protobuf serialization with BufferEncoder - The new
BufferEncoderC++ class provides zero-copy in-place message serialization with significant speedups.C++20 coroutine generator support in pw_async2 - pw_async2 now supports C++20 coroutine generators yielding asynchronous value streams via
co_yieldandNext().
Arch interfaces#
Target platforms for RP2350#
Added Zephyr, FreeRTOS, and pw_kernel support for pw_rp2350, Pigweed’s hardware testing board. CLs: 1, 2, 3
Example of building for the pw_kernel target within upstream Pigweed:
bazelisk build --platforms=@pigweed//targets/pw_rp2350/pigweed:pw_rp2350 \
//targets/pw_rp2350/pigweed:sleep_example_image
pw_boot entry attribute in Rust#
The new #[entry] attribute validates that the annotated function has the
required fn() -> ! signature and generates the standardized
pw_boot_rust_entry symbol expected by Pigweed bootloaders and platform
targets like pw_rp2350. CLs: 1
use pw_boot::entry;
#[entry]
fn main() -> ! {
pw_log::info!("Booting firmware...");
loop {}
}
Async and concurrency#
C++20 coroutine generator support in pw_async2#
Added Generator<T> and GeneratorPromise<T> to pw_async2 to support
C++20 coroutine generators that yield streams of values using co_yield. The
generator object implements a Next() method returning a future that resolves
to an std::optional<T>, allowing asynchronous streams to be consumed in
while loops with co_await. CLs: 1, 2
pw::async2::Generator<int> CountToFive(CoroContext cx) {
for (int i = 1; i <= 5; ++i) {
co_yield i;
}
}
pw::async2::Coro<int> SumGenerator(CoroContext cx, Generator<int>& gen) {
int sum = 0;
while (auto val = co_await gen.Next()) {
sum += *val;
}
co_return sum;
}
New pw_sync_spinlock crate#
The new pw_sync_spinlock Rust crate provides an InterruptSpinLock API for
protecting shared data across threads and interrupt handlers. While the lock is
held, local CPU interrupts are disabled on the calling core. The crate includes
standard library and FreeRTOS backends, with FreeRTOS critical section
management handled via taskENTER_CRITICAL and vTaskSuspendAll. CLs: 1
use pw_sync_spinlock::InterruptSpinLock;
static LOCK: InterruptSpinLock<u32> = InterruptSpinLock::new(0);
fn increment_in_isr() {
let mut data = LOCK.lock();
*data += 1;
}
Bluetooth#
Configurable HCI event filtering in pw_bluetooth_proxy#
Added runtime HCI event and LE Meta subevent filtering APIs to ProxyHost in
pw_bluetooth_proxy. Containers can now dynamically block or unblock specific
controller events from reaching the host using SetEventBlocked() and
SetLeSubeventBlocked(), query block statuses, and reset all filters via
ResetFilters(). CLs: 1, 2
proxy_host.SetEventBlocked(emboss::EventCode::LE_META_EVENT, true);
proxy_host.SetLeSubeventBlocked(emboss::LeSubEventCode::SUBRATE_CHANGE, true);
C++ data structures and utilities#
GenericDeque speedup#
Optimized GenericDeque::try_insert() and try_insert_shift_right() in
pw_containers by splitting non-contiguous underlying storage into at most
two contiguous segments during fill and move operations. For trivially copyable
types such as std::byte or integers, the compiler can now optimize
insertions into memcpy() or block fills rather than item-by-item copies,
resulting in up to a 30x speedup on 1000-item insertions. CLs: 1
Data encoding and serialization#
In-memory protobuf serialization with BufferEncoder#
The new BufferEncoder and BufferEncoderView C++ APIs in pw_protobuf
enable serializing protobuf messages directly to an in-memory buffer without
scratch buffers or copying nested submessages. It utilizes in-place length
prefix patching and provides both checked and unchecked write methods (such as
EnsureSpace() and UncheckedWrite*), achieving an 8x speedup for checked
writes and an 18x speedup for unchecked writes compared to StreamEncoder.
CLs: 1
pw::protobuf::BufferEncoder encoder(response_buffer);
auto view = encoder.view();
if (view.EnsureSpace(kMaxSizeBytes)) {
view.UncheckedWriteUint32(kMagicNumberField, 0x1a1a2b2b);
view.UncheckedWriteString(kFavoriteFoodField, "cookies");
}
Occurrence support in pw_protobuf Find APIs#
Updated pw_protobuf Find APIs (such as FindUint32 and
FindSubmessage) and pwpb code generation to accept an Occurrence
parameter (Occurrence::kFirst or Occurrence::kLast). When locating
scalar non-repeated fields, generated decoders now search for
Occurrence::kLast by default, complying with Protobuf specification
semantics where the last occurrence wins when fields are shadowed. CLs: 1
// Spec-compliant lookup returning the last occurrence of a field
pw::Result<uint32_t> age = pw::protobuf::FindUint32(
buffer, Field::kAge, pw::protobuf::Occurrence::kLast);
Base64 decoding in pw_base64 crate#
Added Base64 decoding support to the pw_base64 Rust crate. The new implementation includes functions for decoding Base64 slices in-place or to output buffers, validating Base64 strings, and calculating decoded buffer sizes. CLs: 1
use pw_base64::{decode, max_decoded_size};
let mut output = [0u8; max_decoded_size(4)];
let len = decode(b"Zm9v", &mut output).unwrap();
Read and write binary integer data from Python I/O streams#
Added the pw_bytes.io Python module, introducing BinaryReader and
BinaryWriter classes. These utilities simplify reading and writing of binary
integer data (such as u8, u16, u32, and u64 in little or big
endian) from and to Python I/O streams. CLs: 1
import io
from pw_bytes.io import BinaryReader, BinaryWriter
stream = io.BytesIO()
writer = BinaryWriter(stream, "little")
writer.write_u32(0x12345678)
stream.seek(0)
reader = BinaryReader(stream, "little")
val = reader.read_u32()
Developer tools#
C++ compile commands improvements in pw_ide#
Improved the Pigweed VS Code extension compile commands UI by replacing the
target selector dropdown with an interactive table showing targets, generation
times, and quick actions. In addition, atomic lockfile management (pw.lock)
was added to the compile commands merger to prevent concurrent compilation
database regenerations from conflicting. The compilation database Bazel aspect
in pw_ide (pw_cc_compile_commands_aspect.bzl) was also updated to
collect non-source generated headers (e.g. *.pb.h, *.nanopb.h, etc.) and
module maps (.cppmap) for clangd. Also added a
display_name attribute to the pw_compile_commands_generator Bazel rule
to make it easier to identify target platforms in the VS Code UI. CLs: 1, 2, 3
pw_compile_commands_generator(
name = "rp2040_compile_commands",
display_name = "RP2040",
platform = "//targets/rp2040",
target_patterns = ["//..."],
)
Rust IDE project generation support in pw_ide#
Added Rust IDE integration support to pw_ide, enabling rust-analyzer code
intelligence (such as autocomplete and navigation) for multi-platform Rust
targets. pw_ide now automatically generates the required
rust-project.json and configuration files for multi-platform Bazel projects.
CLs: 1
HALs#
New I3C driver layer in pw_i2c_mcuxpresso#
The new I3cDriver abstract interface, along with the
concrete implementations for interrupt-driven (I3cInterruptDriver) and
DMA-driven (I3cDmaDriver) transfers, allows I3C initiators to be
configured with different transfer mechanisms or custom driver implementations.
CLs: 1
Logging, debugging, and crash handling#
New pw_assert crate#
The new pw_assert crate provides crash-safe assert and panic macros that route to a central handler. This is designed for embedded systems where standard library panics might not be suitable, or where specific logging/recovery behavior is needed. CLs: 1
use pw_assert::assert;
assert!(stack_start as usize % 8 == 0, "Stack start is not aligned");
Tokenized event labels and data format plugins in pw_trace#
Added support for custom plugin data format strings in the pw_trace Python
decoder. Clients can register custom data format handlers using
register_plugin_data_format_handler() to specialize event decoding. In
addition, support for the built-in @pw_trace_tokenized_token_label format
was added to both the Python and C++ decoders (TokenizedDecoder) to resolve
event labels from 4-byte tokenized data fields. CLs: 1, 2
from pw_trace.trace import register_plugin_data_format_handler
def custom_handler(event, line):
line["args"] = {"custom": event.data.hex()}
register_plugin_data_format_handler("@my_custom_fmt", custom_handler)
Format string support in pw_format crate#
Added support for parsing and formatting format strings in the Rust
pw_format crate. The new implementation supports both printf-style and
core::fmt-style format string syntax. CLs: 1
use pw_format::{FormatString, FormatStyle, Arg};
let format_str = FormatString::parse_printf("Hello %s!").unwrap();
let args = [Arg::Str("world".to_string())];
let formatted = format_str.format(&args, FormatStyle::Printf);
assert_eq!(formatted, "Hello world!");
RPC#
Unencoded packet sending in pw_rpc and pw_grpc#
Added SupportsSendPacket() and SendPacket() C++ APIs to
pw::rpc::ChannelOutput to allow channel outputs to accept unencoded RPC
packets directly. This enables transports like gRPC (pw_grpc) to bypass
unnecessary buffer encoding and decoding overhead when transmitting packets.
CLs: 1
class MyChannelOutput : public pw::rpc::ChannelOutput {
bool SupportsSendPacket() const override { return true; }
pw::Status SendPacket(const Packet& packet) override;
};
Timing and clocks#
New pw_time crate#
The new pw_time Rust crate supports time, instants, and durations across multiple clock domains. CLs: 1, 2, 3
Example:
#![no_std]
#![no_main]
use boot as _;
use pw_boot::entry;
use pw_sync_spinlock::InterruptSpinLock;
use pw_time::{Clock, Duration, SystemClock};
// Put loop cycle count behind a spinlock to demonstrate them working.
static CYCLES: InterruptSpinLock<usize> = InterruptSpinLock::new(0);
#[entry]
fn entry() -> ! {
pw_log::info!("Rust system clock example");
loop {
let start = SystemClock::now();
{
let mut cycles = CYCLES.lock();
*cycles += 1;
pw_log::info!("Loop: {}", *cycles as usize);
}
while SystemClock::now() - start < Duration::from_millis(1000) {
core::hint::spin_loop();
}
}
}
Tokenization#
Rust detokenization support in pw_tokenizer#
Added initial Rust detokenization support to the pw_tokenizer crate. The
Rust Detokenizer supports loading token databases from both CSV and binary
formats, configuring custom prefix characters (from_csv_with_prefix() and
from_binary_with_prefix()), and detokenizing text. In addition, error
formatting behavior matches the C++ version using DetokenizeAttempt and
DecodedFormatString to retain original printf conversion specifiers or
render inline error descriptors when token arguments fail to decode. CLs: 1, 2, 3, 4, 5, 6
let detok = Detokenizer::from_binary_with_prefix(bin_data, '~')?;
let result = detok.detokenize_text("~#00000001");
Asterisk width and precision in C++ and Python detokenizers#
Updated the C++ and Python detokenizers in pw_tokenizer to support asterisk
(*) width and precision format modifiers (such as %*d, %.*s, and
%*.*f). When detokenizing, the decoders now dynamically decode varint
arguments for width and precision and handle negative precision values according
to the C99 printf standard. CLs: 1, 2
New pw_cc_enum_source_set template in GN and CMake#
Introduced the pw_cc_enum_source_set GN template and updated CMake enum
generation functions in pw_enum. The new build templates automatically
generate internal enum tokenization rules and expose generated headers cleanly
across public and private dependency boundaries.
CLs: 1
pw_cc_enum_source_set("my_enums") {
enum_headers = [ "public/my_enums.h" ]
include_dirs = [ "public" ]
}
Versioned enum domain tokenization in pw_tokenizer and pw_log#
Added support for versioned enum domain tokenization and logging across
pw_tokenizer, pw_enum, and pw_log. The new EnumDomainToken<T>()
template and PW_TOKENIZER_ENUM_DOMAIN_AND_VALUE macro resolve both an enum’s
32-bit domain token and its value token at compile time. In addition,
PW_LOG_ENUM_VERSIONED and PwEnumDomainName enable logging versioned
enums reliably across both tokenizing and non-tokenizing logging backends. CLs:
1, 2, 3, 4
#include "pw_log/tokenized_args.h"
PW_LOG_INFO("State " PW_LOG_ENUM_VERSIONED_FMT() ": received packet",
PW_LOG_ENUM_VERSIONED(state));
Toolchains and compilers#
New pw_elf Python package for building ELF files#
Added the pw_elf Python package for programmatically building ELF32 and
ELF64 binary files from scratch. The library introduces ElfBuilder for
constructing custom ELF binaries with memory regions and notes, as well as the
CoredumpBuilder subclass for generating crash coredumps with ARM32 register
dumps (PRSTATUS notes) and GNU build IDs. CLs: 1, 2
from pw_elf.builder import ElfBuilder, ElfClass, ElfMachine, ElfType
from pw_elf.coredump import CoredumpBuilder
builder = CoredumpBuilder(ElfMachine.ARM, ElfClass.CLASS32, "little")
builder.add_thread(thread_id=123, registers=regs)
Disabled -ffreestanding by default in arm_gcc Bazel builds#
Disabled -ffreestanding by default for the arm_gcc toolchain in
Pigweed’s Bazel builds. With the transition to GCC 15, the updated libstdc++
enforces stricter compliance checks that fail when freestanding mode is enabled
by default. Projects requiring freestanding mode can re-enable it in
.bazelrc via common --//pw_toolchain/arm_gcc:freestanding=True. CLs: 1
# To re-enable freestanding mode in your project's .bazelrc:
common --//pw_toolchain/arm_gcc:freestanding=True
Bazel compilation mode support for host_clang toolchain#
Added opt_feature, dbg_feature, and fastbuild_feature to the
host_clang toolchain in pw_toolchain. This enables Bazel’s standard
--compilation_mode=opt (or -c opt) flag to apply -O2 optimizations
during host builds while preserving default debugging symbols. CLs: 1
bazelisk build --compilation_mode=opt //...
Lifetime safety attributes in pw_preprocessor#
Added C++ lifetime safety checking attributes to pw_preprocessor:
PW_ATTRIBUTE_VIEWfor non-owning reference types like spans or string viewsPW_ATTRIBUTE_OWNERfor container or smart pointer types that own their dataPW_INTERNAL_ATTRIBUTE_CAPTURED_BYfor function parameters captured by another entity, such asthisor another parameter
These macros wrap Clang and GSL lifetime attributes to help compilers diagnose dangling references and returning addresses of stack memory. CLs: 1
struct PW_ATTRIBUTE_VIEW StringView {
template <class R>
StringView(const R&);
};
struct PW_ATTRIBUTE_OWNER String {};