Merge patch series "zerocopy support"

Introduce support for `zerocopy` [1][2]:

    Fast, safe, compile error. Pick two.

    Zerocopy makes zero-cost memory manipulation effortless. We write
    `unsafe` so you don't have to.

It essentially provides derivable traits (e.g. `FromBytes`) and macros
(e.g. `transmute!`) for safely converting between byte sequences and
other types. Having such support allows us to remove some `unsafe` code.

It is among the most downloaded Rust crates (top #50 recent, top #100
all-time downloads; according to crates.io), and it is also used by the
Rust compiler itself.

The series starts with a few preparation commits, then the `zerocopy`
and `zerocopy-derive` crates are added. Finally, an example patch using
it is on top, removing one `unsafe impl`.

I had to adapt the crates slightly (just +2/-3 lines), but both patches
could potentially be provided upstream eventually. Please see the
commits for details.

In total, it is about ~39k lines added, ~32k without counting `benches/`
which are just for documentation purposes.

See the cover letter for `syn` for some more details about depending on
third-party crates in commit 54e3eae855 ("Merge patch series "`syn`
support"").

The codegen of an isolated example function similar to the patch on top
is essentially identical. It also turns out that (for that particular
case) `zerocopy`'s version, even under `debug-assertions` enabled, has
no remaining panics, unlike a few in the current code (because the
compiler can prove the remaining `ub_checks` statically).

So their "fast, safe" does indeed check out -- at least in that case.

P.S. This version of `zerocopy` has already the unstable `Ptr{,Inner}`
types -- to play with them, please use:

    make ... KRUSTFLAGS=--cfg=zerocopy_unstable_ptr

Link: https://github.com/google/zerocopy [1]
Link: https://docs.rs/zerocopy [2]
Link: https://patch.msgid.link/20260608141439.182634-1-ojeda@kernel.org
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
This commit is contained in:
Miguel Ojeda 2026-06-09 04:18:30 +02:00
commit 98cc68794c
251 changed files with 38724 additions and 27 deletions

View File

@ -1956,6 +1956,8 @@ rustfmt:
-path $(srctree)/rust/proc-macro2 \
-o -path $(srctree)/rust/quote \
-o -path $(srctree)/rust/syn \
-o -path $(srctree)/rust/zerocopy \
-o -path $(srctree)/rust/zerocopy-derive \
\) -prune -o \
-type f -a -name '*.rs' -a ! -name '*generated*' -print \
| xargs $(RUSTFMT) $(rustfmt_flags)

View File

@ -48,7 +48,7 @@ fn request_firmware(
/// Structure used to describe some firmwares, notably FWSEC-FRTS.
#[repr(C)]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, FromBytes)]
pub(crate) struct FalconUCodeDescV2 {
/// Header defined by 'NV_BIT_FALCON_UCODE_DESC_HEADER_VDESC*' in OpenRM.
hdr: u32,
@ -84,9 +84,6 @@ pub(crate) struct FalconUCodeDescV2 {
pub(crate) alt_dmem_load_size: u32,
}
// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
unsafe impl FromBytes for FalconUCodeDescV2 {}
/// Structure used to describe some firmwares, notably FWSEC-FRTS.
#[repr(C)]
#[derive(Debug, Clone)]

View File

@ -16,6 +16,8 @@
transmute::FromBytes,
};
use zerocopy::FromBytes as _;
use crate::{
driver::Bar0,
firmware::{
@ -1011,8 +1013,8 @@ pub(crate) fn header(&self) -> Result<FalconUCodeDesc> {
let data = self.base.data.get(falcon_ucode_offset..).ok_or(EINVAL)?;
match ver {
2 => {
let v2 = FalconUCodeDescV2::from_bytes_copy_prefix(data)
.ok_or(EINVAL)?
let v2 = FalconUCodeDescV2::read_from_prefix(data)
.map_err(|_| EINVAL)?
.0;
Ok(FalconUCodeDesc::V2(v2))
}

View File

@ -6,6 +6,8 @@ rustdoc_output := $(objtree)/Documentation/output/rust/rustdoc
obj-$(CONFIG_RUST) += core.o compiler_builtins.o ffi.o
always-$(CONFIG_RUST) += exports_core_generated.h
obj-$(CONFIG_RUST) += zerocopy.o
ifdef CONFIG_RUST_INLINE_HELPERS
always-$(CONFIG_RUST) += helpers/helpers.bc helpers/helpers_module.bc
else
@ -47,13 +49,14 @@ endif
# Avoids running `$(RUSTC)` when it may not be available.
ifdef CONFIG_RUST
libmacros_name := $(shell MAKEFLAGS= $(RUSTC) --print file-names --crate-name macros --crate-type proc-macro - </dev/null)
libmacros_extension := $(patsubst libmacros.%,%,$(libmacros_name))
procmacro-name = $(shell MAKEFLAGS= $(RUSTC) --print file-names --crate-name $(1) --crate-type proc-macro - </dev/null)
procmacro-extension := $(patsubst libname.%,%,$(call procmacro-name,name))
libpin_init_internal_name := $(shell MAKEFLAGS= $(RUSTC) --print file-names --crate-name pin_init_internal --crate-type proc-macro - </dev/null)
libpin_init_internal_extension := $(patsubst libpin_init_internal.%,%,$(libpin_init_internal_name))
libzerocopy_derive_name := $(call procmacro-name,zerocopy_derive)
libmacros_name := $(call procmacro-name,macros)
libpin_init_internal_name := $(call procmacro-name,pin_init_internal)
always-$(CONFIG_RUST) += $(libmacros_name) $(libpin_init_internal_name)
always-$(CONFIG_RUST) += $(libzerocopy_derive_name) $(libmacros_name) $(libpin_init_internal_name)
# `$(rust_flags)` is passed in case the user added `--sysroot`.
rustc_sysroot := $(shell MAKEFLAGS= $(RUSTC) $(rust_flags) --print sysroot)
@ -81,6 +84,12 @@ core-flags := \
--edition=$(core-edition) \
$(call cfgs-to-flags,$(core-cfgs))
zerocopy-flags := \
--cap-lints=allow
zerocopy-envs := \
CARGO_PKG_VERSION=0.8.50
proc_macro2-cfgs := \
feature="proc-macro" \
wrap_proc_macro \
@ -118,6 +127,12 @@ syn-flags := \
--extern quote \
$(call cfgs-to-flags,$(syn-cfgs))
zerocopy_derive-flags := \
--cap-lints=allow \
--extern proc_macro2 \
--extern quote \
--extern syn
pin_init_internal-cfgs := \
kernel USE_RUSTC_FEATURES
@ -144,6 +159,7 @@ doctests_modifiers_workaround := $(rustdoc_modifiers_workaround)$(if $(call rust
quiet_cmd_rustdoc = RUSTDOC $(if $(rustdoc_host),H, ) $<
cmd_rustdoc = \
$(rustc_target_envs) \
OBJTREE=$(abspath $(objtree)) \
$(RUSTDOC) $(filter-out $(skip_flags) --remap-path-scope=%,$(if $(rustdoc_host),$(rust_common_flags),$(rust_flags))) \
$(rustc_target_flags) -L$(objtree)/$(obj) \
@ -166,7 +182,7 @@ quiet_cmd_rustdoc = RUSTDOC $(if $(rustdoc_host),H, ) $<
# command-like flags to solve the issue. Meanwhile, we use the non-custom case
# and then retouch the generated files.
rustdoc: rustdoc-core rustdoc-macros rustdoc-compiler_builtins \
rustdoc-kernel rustdoc-pin_init
rustdoc-kernel rustdoc-pin_init rustdoc-zerocopy rustdoc-zerocopy_derive
$(Q)grep -Ehro '<a href="srctree/([^"]+)"' $(rustdoc_output) | \
cut -d'"' -f2 | cut -d/ -f2- | while read f; do \
if [ ! -e "$(srctree)/$$f" ]; then \
@ -199,6 +215,12 @@ rustdoc-syn: private rustc_target_flags = $(syn-flags)
rustdoc-syn: $(src)/syn/lib.rs rustdoc-clean rustdoc-quote FORCE
+$(call if_changed,rustdoc)
rustdoc-zerocopy_derive: private rustdoc_host = yes
rustdoc-zerocopy_derive: private rustc_target_flags = $(zerocopy_derive-flags) \
--extern proc_macro --crate-type proc-macro
rustdoc-zerocopy_derive: $(src)/zerocopy-derive/lib.rs rustdoc-clean rustdoc-syn FORCE
+$(call if_changed,rustdoc)
rustdoc-macros: private rustdoc_host = yes
rustdoc-macros: private rustc_target_flags = --crate-type proc-macro \
--extern proc_macro --extern proc_macro2 --extern quote --extern syn
@ -218,6 +240,13 @@ rustdoc-compiler_builtins: private is-kernel-object := y
rustdoc-compiler_builtins: $(src)/compiler_builtins.rs rustdoc-core FORCE
+$(call if_changed,rustdoc)
rustdoc-zerocopy: private rustc_target_envs := $(zerocopy-envs)
rustdoc-zerocopy: private is-kernel-object := y
rustdoc-zerocopy: private rustc_target_flags = $(zerocopy-flags) \
--extend-css $(src)/zerocopy/rustdoc/style.css
rustdoc-zerocopy: $(src)/zerocopy/src/lib.rs rustdoc-clean rustdoc-core FORCE
+$(call if_changed,rustdoc)
rustdoc-ffi: private is-kernel-object := y
rustdoc-ffi: $(src)/ffi.rs rustdoc-core FORCE
+$(call if_changed,rustdoc)
@ -239,7 +268,8 @@ rustdoc-pin_init: $(src)/pin-init/src/lib.rs rustdoc-pin_init_internal \
rustdoc-kernel: private is-kernel-object := y
rustdoc-kernel: private rustc_target_flags = --extern ffi --extern pin_init \
--extern build_error --extern macros \
--extern bindings --extern uapi
--extern bindings --extern uapi \
--extern zerocopy --extern zerocopy_derive
rustdoc-kernel: $(src)/kernel/lib.rs rustdoc-core rustdoc-ffi rustdoc-macros \
rustdoc-pin_init rustdoc-compiler_builtins $(obj)/$(libmacros_name) \
$(obj)/bindings.o FORCE
@ -250,6 +280,7 @@ rustdoc-clean: FORCE
quiet_cmd_rustc_test_library = $(RUSTC_OR_CLIPPY_QUIET) TL $<
cmd_rustc_test_library = \
$(rustc_target_envs) \
OBJTREE=$(abspath $(objtree)) \
$(RUSTC_OR_CLIPPY) $(filter-out $(skip_flags),$(rust_common_flags) $(rustc_target_flags)) \
@$(objtree)/include/generated/rustc_cfg \
@ -258,6 +289,11 @@ quiet_cmd_rustc_test_library = $(RUSTC_OR_CLIPPY_QUIET) TL $<
-L$(objtree)/$(obj)/test \
--crate-name $(subst rusttest-,,$(subst rusttestlib-,,$@)) $<
rusttestlib-zerocopy: private rustc_target_envs := $(zerocopy-envs)
rusttestlib-zerocopy: private rustc_target_flags = $(zerocopy-flags)
rusttestlib-zerocopy: $(src)/zerocopy/src/lib.rs FORCE
+$(call if_changed,rustc_test_library)
rusttestlib-build_error: $(src)/build_error.rs FORCE
+$(call if_changed,rustc_test_library)
@ -277,6 +313,12 @@ rusttestlib-syn: private rustc_target_flags = $(syn-flags)
rusttestlib-syn: $(src)/syn/lib.rs rusttestlib-quote FORCE
+$(call if_changed,rustc_test_library)
rusttestlib-zerocopy_derive: private rustc_target_flags = $(zerocopy_derive-flags) \
--extern proc_macro
rusttestlib-zerocopy_derive: private rustc_test_library_proc = yes
rusttestlib-zerocopy_derive: $(src)/zerocopy-derive/lib.rs rusttestlib-syn FORCE
+$(call if_changed,rustc_test_library)
rusttestlib-macros: private rustc_target_flags = --extern proc_macro \
--extern proc_macro2 --extern quote --extern syn
rusttestlib-macros: private rustc_test_library_proc = yes
@ -298,10 +340,11 @@ rusttestlib-pin_init: $(src)/pin-init/src/lib.rs rusttestlib-macros \
rusttestlib-kernel: private rustc_target_flags = --extern ffi \
--extern build_error --extern macros --extern pin_init \
--extern bindings --extern uapi
--extern bindings --extern uapi \
--extern zerocopy --extern zerocopy_derive
rusttestlib-kernel: $(src)/kernel/lib.rs rusttestlib-bindings rusttestlib-uapi \
rusttestlib-build_error rusttestlib-pin_init $(obj)/$(libmacros_name) \
$(obj)/bindings.o FORCE
$(obj)/bindings.o rusttestlib-zerocopy rusttestlib-zerocopy_derive FORCE
+$(call if_changed,rustc_test_library)
rusttestlib-bindings: private rustc_target_flags = --extern ffi --extern pin_init
@ -314,6 +357,7 @@ rusttestlib-uapi: $(src)/uapi/lib.rs rusttestlib-ffi rusttestlib-pin_init FORCE
quiet_cmd_rustdoc_test = RUSTDOC T $<
cmd_rustdoc_test = \
$(rustc_target_envs) \
RUST_MODFILE=test.rs \
OBJTREE=$(abspath $(objtree)) \
$(RUSTDOC) --test $(rust_common_flags) \
@ -328,11 +372,13 @@ quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $<
cmd_rustdoc_test_kernel = \
rm -rf $(objtree)/$(obj)/test/doctests/kernel; \
mkdir -p $(objtree)/$(obj)/test/doctests/kernel; \
$(rustc_target_envs) \
OBJTREE=$(abspath $(objtree)) \
$(RUSTDOC) --test $(filter-out --remap-path-scope=%,$(rust_flags)) \
-L$(objtree)/$(obj) --extern ffi --extern pin_init \
--extern kernel --extern build_error --extern macros \
--extern bindings --extern uapi \
--extern zerocopy --extern zerocopy_derive \
--no-run --crate-name kernel -Zunstable-options \
--sysroot=/dev/null \
$(doctests_modifiers_workaround) \
@ -350,6 +396,7 @@ quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $<
# so for the moment we skip `-Cpanic=abort`.
quiet_cmd_rustc_test = $(RUSTC_OR_CLIPPY_QUIET) T $<
cmd_rustc_test = \
$(rustc_target_envs) \
OBJTREE=$(abspath $(objtree)) \
$(RUSTC_OR_CLIPPY) --test $(rust_common_flags) \
@$(objtree)/include/generated/rustc_cfg \
@ -517,8 +564,9 @@ $(obj)/exports_bindings_generated.h: $(obj)/bindings.o FORCE
$(obj)/exports_kernel_generated.h: $(obj)/kernel.o FORCE
$(call if_changed,exports)
quiet_cmd_rustc_procmacrolibrary = $(RUSTC_OR_CLIPPY_QUIET) PL $@
quiet_cmd_rustc_procmacrolibrary = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) PL $@
cmd_rustc_procmacrolibrary = \
$(rustc_target_envs) \
$(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) \
$(filter-out $(skip_flags),$(rust_common_flags) $(rustc_target_flags)) \
--emit=dep-info=$(depfile) --emit=link=$@ --crate-type rlib -O \
@ -541,17 +589,24 @@ $(obj)/libsyn.rlib: private rustc_target_flags = $(syn-flags)
$(obj)/libsyn.rlib: $(src)/syn/lib.rs $(obj)/libquote.rlib FORCE
+$(call if_changed_dep,rustc_procmacrolibrary)
quiet_cmd_rustc_procmacro = $(RUSTC_OR_CLIPPY_QUIET) P $@
quiet_cmd_rustc_procmacro = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) P $@
cmd_rustc_procmacro = \
$(RUSTC_OR_CLIPPY) $(rust_common_flags) $(rustc_target_flags) \
$(rustc_target_envs) \
$(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) $(rust_common_flags) $(rustc_target_flags) \
-Clinker-flavor=gcc -Clinker=$(HOSTCC) \
-Clink-args='$(call escsq,$(KBUILD_PROCMACROLDFLAGS))' \
--emit=dep-info=$(depfile) --emit=link=$@ --extern proc_macro \
--crate-type proc-macro -L$(objtree)/$(obj) \
--crate-name $(patsubst lib%.$(libmacros_extension),%,$(notdir $@)) \
--crate-name $(patsubst lib%.$(procmacro-extension),%,$(notdir $@)) \
@$(objtree)/include/generated/rustc_cfg $<
# Procedural macros can only be used with the `rustc` that compiled it.
$(obj)/$(libzerocopy_derive_name): private skip_clippy = 1
$(obj)/$(libzerocopy_derive_name): private rustc_target_flags = $(zerocopy_derive-flags)
$(obj)/$(libzerocopy_derive_name): $(src)/zerocopy-derive/lib.rs $(obj)/libproc_macro2.rlib \
$(obj)/libquote.rlib $(obj)/libsyn.rlib FORCE
+$(call if_changed_dep,rustc_procmacro)
$(obj)/$(libmacros_name): private rustc_target_flags = \
--extern proc_macro2 --extern quote --extern syn
$(obj)/$(libmacros_name): $(src)/macros/lib.rs $(obj)/libproc_macro2.rlib \
@ -567,6 +622,7 @@ $(obj)/$(libpin_init_internal_name): $(src)/pin-init/internal/src/lib.rs \
# since Rust 1.95.0 (https://github.com/rust-lang/rust/pull/151534).
quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L $@
cmd_rustc_library = \
$(rustc_target_envs) \
OBJTREE=$(abspath $(objtree)) \
$(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) \
$(filter-out $(skip_flags),$(rust_flags)) $(rustc_target_flags) \
@ -591,6 +647,7 @@ rust-analyzer:
--cfgs='syn=$(syn-cfgs)' \
--cfgs='pin_init_internal=$(pin_init_internal-cfgs)' \
--cfgs='pin_init=$(pin_init-cfgs)' \
--envs='zerocopy=$(zerocopy-envs)' \
$(realpath $(srctree)) $(realpath $(objtree)) \
$(rustc_sysroot) $(RUST_LIB_SRC) $(if $(KBUILD_EXTMOD),$(srcroot)) \
> rust-project.json
@ -662,6 +719,13 @@ $(obj)/compiler_builtins.o: private rustc_objcopy = -w -W '__*'
$(obj)/compiler_builtins.o: $(src)/compiler_builtins.rs $(obj)/core.o FORCE
+$(call if_changed_rule,rustc_library)
$(obj)/zerocopy.o: private skip_clippy = 1
$(obj)/zerocopy.o: private skip_gendwarfksyms = 1
$(obj)/zerocopy.o: private rustc_target_envs := $(zerocopy-envs)
$(obj)/zerocopy.o: private rustc_target_flags = $(zerocopy-flags)
$(obj)/zerocopy.o: $(src)/zerocopy/src/lib.rs $(obj)/compiler_builtins.o FORCE
+$(call if_changed_rule,rustc_library)
$(obj)/pin_init.o: private skip_gendwarfksyms = 1
$(obj)/pin_init.o: private rustc_target_flags = $(pin_init-flags)
$(obj)/pin_init.o: $(src)/pin-init/src/lib.rs $(obj)/compiler_builtins.o \
@ -697,9 +761,11 @@ $(obj)/uapi.o: $(src)/uapi/lib.rs \
+$(call if_changed_rule,rustc_library)
$(obj)/kernel.o: private rustc_target_flags = --extern ffi --extern pin_init \
--extern build_error --extern macros --extern bindings --extern uapi
--extern build_error --extern macros --extern bindings --extern uapi \
--extern zerocopy --extern zerocopy_derive
$(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/build_error.o $(obj)/pin_init.o \
$(obj)/$(libmacros_name) $(obj)/bindings.o $(obj)/uapi.o FORCE
$(obj)/$(libmacros_name) $(obj)/bindings.o $(obj)/uapi.o \
$(obj)/zerocopy.o $(obj)/$(libzerocopy_derive_name) FORCE
+$(call if_changed_rule,rustc_library)
ifdef CONFIG_JUMP_LABEL

View File

@ -60,6 +60,12 @@
Zeroable, //
};
#[doc(no_inline)]
pub use zerocopy::FromBytes;
#[doc(no_inline)]
pub use zerocopy_derive::FromBytes;
#[doc(no_inline)]
pub use super::{
alloc::{

View File

@ -0,0 +1,14 @@
# `zerocopy-derive`
These source files come from the Rust `zerocopy-derive` crate, version v0.8.50
(released 2026-05-31), hosted in the <https://github.com/google/zerocopy>
repository, licensed under "BSD-2-Clause OR Apache-2.0 OR MIT" and only
modified to add the SPDX license identifiers and to remove the generation of
non-ASCII identifiers.
For copyright details, please see:
https://github.com/google/zerocopy/blob/v0.8.50/README.md?plain=1
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-BSD
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-APACHE
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-MIT

View File

@ -0,0 +1,192 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
use proc_macro2::{Span, TokenStream};
use syn::{
parse_quote, Data, DataEnum, DataStruct, DataUnion, Error, Expr, ExprLit, ExprUnary, Lit, UnOp,
WherePredicate,
};
use crate::{
derive::try_from_bytes::derive_try_from_bytes,
repr::{CompoundRepr, EnumRepr, Repr, Spanned},
util::{enum_size_from_repr, Ctx, FieldBounds, ImplBlockBuilder, Trait, TraitBound},
};
/// Returns `Ok(index)` if variant `index` of the enum has a discriminant of
/// zero. If `Err(bool)` is returned, the boolean is true if the enum has
/// unknown discriminants (e.g. discriminants set to const expressions which we
/// can't evaluate in a proc macro). If the enum has unknown discriminants, then
/// it might have a zero variant that we just can't detect.
pub(crate) fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
// Discriminants can be anywhere in the range [i128::MIN, u128::MAX] because
// the discriminant type may be signed or unsigned. Since we only care about
// tracking the discriminant when it's less than or equal to zero, we can
// avoid u128 -> i128 conversions and bounds checking by making the "next
// discriminant" value implicitly negative.
// Technically 64 bits is enough, but 128 is better for future compatibility
// with https://github.com/rust-lang/rust/issues/56071
let mut next_negative_discriminant = Some(0);
// Sometimes we encounter explicit discriminants that we can't know the
// value of (e.g. a constant expression that requires evaluation). These
// could evaluate to zero or a negative number, but we can't assume that
// they do (no false positives allowed!). So we treat them like strictly-
// positive values that can't result in any zero variants, and track whether
// we've encountered any unknown discriminants.
let mut has_unknown_discriminants = false;
for (i, v) in enm.variants.iter().enumerate() {
match v.discriminant.as_ref() {
// Implicit discriminant
None => {
match next_negative_discriminant.as_mut() {
Some(0) => return Ok(i),
// n is nonzero so subtraction is always safe
Some(n) => *n -= 1,
None => (),
}
}
// Explicit positive discriminant
Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => {
match int.base10_parse::<u128>().ok() {
Some(0) => return Ok(i),
Some(_) => next_negative_discriminant = None,
None => {
// Numbers should never fail to parse, but just in case:
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
// Explicit negative discriminant
Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr {
Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
match int.base10_parse::<u128>().ok() {
Some(0) => return Ok(i),
// x is nonzero so subtraction is always safe
Some(x) => next_negative_discriminant = Some(x - 1),
None => {
// Numbers should never fail to parse, but just in
// case:
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
// Unknown negative discriminant (e.g. const repr)
_ => {
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
},
// Unknown discriminant (e.g. const expr)
_ => {
has_unknown_discriminants = true;
next_negative_discriminant = None;
}
}
}
Err(has_unknown_discriminants)
}
pub(crate) fn derive_from_zeros(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
let try_from_bytes = derive_try_from_bytes(ctx, top_level)?;
let from_zeros = match &ctx.ast.data {
Data::Struct(strct) => derive_from_zeros_struct(ctx, strct),
Data::Enum(enm) => derive_from_zeros_enum(ctx, enm)?,
Data::Union(unn) => derive_from_zeros_union(ctx, unn),
};
Ok(IntoIterator::into_iter([try_from_bytes, from_zeros]).collect())
}
pub(crate) fn derive_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
let from_zeros = derive_from_zeros(ctx, top_level)?;
let from_bytes = match &ctx.ast.data {
Data::Struct(strct) => derive_from_bytes_struct(ctx, strct),
Data::Enum(enm) => derive_from_bytes_enum(ctx, enm)?,
Data::Union(unn) => derive_from_bytes_union(ctx, unn),
};
Ok(IntoIterator::into_iter([from_zeros, from_bytes]).collect())
}
fn derive_from_zeros_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
ImplBlockBuilder::new(ctx, strct, Trait::FromZeros, FieldBounds::ALL_SELF).build()
}
fn derive_from_zeros_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
// We don't actually care what the repr is; we just care that it's one of
// the allowed ones.
match repr {
Repr::Compound(Spanned { t: CompoundRepr::C | CompoundRepr::Primitive(_), span: _ }, _) => {
}
Repr::Transparent(_) | Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => {
return ctx.error_or_skip(
Error::new(
Span::call_site(),
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
),
);
}
}
let zero_variant = match find_zero_variant(enm) {
Ok(index) => enm.variants.iter().nth(index).unwrap(),
// Has unknown variants
Err(true) => {
return ctx.error_or_skip(Error::new_spanned(
&ctx.ast,
"FromZeros only supported on enums with a variant that has a discriminant of `0`\n\
help: This enum has discriminants which are not literal integers. One of those may \
define or imply which variant has a discriminant of zero. Use a literal integer to \
define or imply the variant with a discriminant of zero.",
));
}
// Does not have unknown variants
Err(false) => {
return ctx.error_or_skip(Error::new_spanned(
&ctx.ast,
"FromZeros only supported on enums with a variant that has a discriminant of `0`",
));
}
};
let zerocopy_crate = &ctx.zerocopy_crate;
let explicit_bounds = zero_variant
.fields
.iter()
.map(|field| {
let ty = &field.ty;
parse_quote! { #ty: #zerocopy_crate::FromZeros }
})
.collect::<Vec<WherePredicate>>();
Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromZeros, FieldBounds::Explicit(explicit_bounds))
.build())
}
fn derive_from_zeros_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
ImplBlockBuilder::new(ctx, unn, Trait::FromZeros, field_type_trait_bounds).build()
}
fn derive_from_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
ImplBlockBuilder::new(ctx, strct, Trait::FromBytes, FieldBounds::ALL_SELF).build()
}
fn derive_from_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
let variants_required = 1usize << enum_size_from_repr(&repr)?;
if enm.variants.len() != variants_required {
return ctx.error_or_skip(Error::new_spanned(
&ctx.ast,
format!(
"FromBytes only supported on {} enum with {} variants",
repr.repr_type_name(),
variants_required
),
));
}
Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromBytes, FieldBounds::ALL_SELF).build())
}
fn derive_from_bytes_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
ImplBlockBuilder::new(ctx, unn, Trait::FromBytes, field_type_trait_bounds).build()
}

View File

@ -0,0 +1,164 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type};
use crate::{
repr::{EnumRepr, StructUnionRepr},
util::{
generate_tag_enum, Ctx, DataExt, FieldBounds, ImplBlockBuilder, PaddingCheck, Trait,
TraitBound,
},
};
pub(crate) fn derive_into_bytes(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
match &ctx.ast.data {
Data::Struct(strct) => derive_into_bytes_struct(ctx, strct),
Data::Enum(enm) => derive_into_bytes_enum(ctx, enm),
Data::Union(unn) => derive_into_bytes_union(ctx, unn),
}
}
fn derive_into_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
let is_transparent = repr.is_transparent();
let is_c = repr.is_c();
let is_packed_1 = repr.is_packed_1();
let num_fields = strct.fields().len();
let (padding_check, require_unaligned_fields) = if is_transparent || is_packed_1 {
// No padding check needed.
// - repr(transparent): The layout and ABI of the whole struct is the
// same as its only non-ZST field (meaning there's no padding outside
// of that field) and we require that field to be `IntoBytes` (meaning
// there's no padding in that field).
// - repr(packed): Any inter-field padding bytes are removed, meaning
// that any padding bytes would need to come from the fields, all of
// which we require to be `IntoBytes` (meaning they don't have any
// padding). Note that this holds regardless of other `repr`
// attributes, including `repr(Rust)`. [1]
//
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-alignment-modifiers:
//
// An important consequence of these rules is that a type with
// `#[repr(packed(1))]`` (or `#[repr(packed)]``) will have no
// inter-field padding.
(None, false)
} else if is_c && !repr.is_align_gt_1() && num_fields <= 1 {
// No padding check needed. A repr(C) struct with zero or one field has
// no padding unless #[repr(align)] explicitly adds padding, which we
// check for in this branch's condition.
(None, false)
} else if ctx.ast.generics.params.is_empty() {
// Is the last field a syntactic slice, i.e., `[SomeType]`.
let is_syntactic_dst =
strct.fields().last().map(|(_, _, ty)| matches!(ty, Type::Slice(_))).unwrap_or(false);
// Since there are no generics, we can emit a padding check. All reprs
// guarantee that fields won't overlap [1], so the padding check is
// sound. This is more permissive than the next case, which requires
// that all field types implement `Unaligned`.
//
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-rust-representation:
//
// The only data layout guarantees made by [`repr(Rust)`] are those
// required for soundness. They are:
// ...
// 2. The fields do not overlap.
// ...
if is_c && is_syntactic_dst {
(Some(PaddingCheck::ReprCStruct), false)
} else {
(Some(PaddingCheck::Struct), false)
}
} else if is_c && !repr.is_align_gt_1() {
// We can't use a padding check since there are generic type arguments.
// Instead, we require all field types to implement `Unaligned`. This
// ensures that the `repr(C)` layout algorithm will not insert any
// padding unless #[repr(align)] explicitly adds padding, which we check
// for in this branch's condition.
//
// FIXME(#10): Support type parameters for non-transparent, non-packed
// structs without requiring `Unaligned`.
(None, true)
} else {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout",
));
};
let field_bounds = if require_unaligned_fields {
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Unaligned)])
} else {
FieldBounds::ALL_SELF
};
Ok(ImplBlockBuilder::new(ctx, strct, Trait::IntoBytes, field_bounds)
.padding_check(padding_check)
.build())
}
fn derive_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
if !repr.is_c() && !repr.is_primitive() {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
));
}
let tag_type_definition = generate_tag_enum(ctx, &repr, enm);
Ok(ImplBlockBuilder::new(ctx, enm, Trait::IntoBytes, FieldBounds::ALL_SELF)
.padding_check(PaddingCheck::Enum { tag_type_definition })
.build())
}
fn derive_into_bytes_union(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> {
// See #1792 for more context.
//
// By checking for `zerocopy_derive_union_into_bytes` both here and in the
// generated code, we ensure that `--cfg zerocopy_derive_union_into_bytes`
// need only be passed *either* when compiling this crate *or* when
// compiling the user's crate. The former is preferable, but in some
// situations (such as when cross-compiling using `cargo build --target`),
// it doesn't get propagated to this crate's build by default.
let cfg_compile_error = if cfg!(zerocopy_derive_union_into_bytes) {
quote!()
} else {
let core = ctx.core_path();
let error_message = "requires --cfg zerocopy_derive_union_into_bytes;
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802";
quote!(
#[allow(unused_attributes, unexpected_cfgs)]
const _: () = {
#[cfg(not(zerocopy_derive_union_into_bytes))]
#core::compile_error!(#error_message);
};
)
};
// FIXME(#10): Support type parameters.
if !ctx.ast.generics.params.is_empty() {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"unsupported on types with type parameters",
));
}
// Because we don't support generics, we don't need to worry about
// special-casing different reprs. So long as there is *some* repr which
// guarantees the layout, our `PaddingCheck::Union` guarantees that there is
// no padding.
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]",
));
}
let impl_block = ImplBlockBuilder::new(ctx, unn, Trait::IntoBytes, FieldBounds::ALL_SELF)
.padding_check(PaddingCheck::Union)
.build();
Ok(quote!(#cfg_compile_error #impl_block))
}

View File

@ -0,0 +1,350 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, Data, Error, Type};
use crate::{
repr::StructUnionRepr,
util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, SelfBounds, Trait},
};
fn derive_known_layout_for_repr_c_struct<'a>(
ctx: &'a Ctx,
repr: &StructUnionRepr,
fields: &[(&'a syn::Visibility, TokenStream, &'a Type)],
) -> Option<(SelfBounds<'a>, TokenStream, Option<TokenStream>)> {
let (trailing_field, leading_fields) = fields.split_last()?;
let (_vis, trailing_field_name, trailing_field_ty) = trailing_field;
let leading_fields_tys = leading_fields.iter().map(|(_vis, _name, ty)| ty);
let core = ctx.core_path();
let repr_align = repr
.get_align()
.map(|align| {
let align = align.t.get();
quote!(#core::num::NonZeroUsize::new(#align as usize))
})
.unwrap_or_else(|| quote!(#core::option::Option::None));
let repr_packed = repr
.get_packed()
.map(|packed| {
let packed = packed.get();
quote!(#core::num::NonZeroUsize::new(#packed as usize))
})
.unwrap_or_else(|| quote!(#core::option::Option::None));
let zerocopy_crate = &ctx.zerocopy_crate;
let make_methods = |trailing_field_ty| {
quote! {
// SAFETY:
// - The returned pointer has the same address and provenance as
// `bytes`:
// - The recursive call to `raw_from_ptr_len` preserves both
// address and provenance.
// - The `as` cast preserves both address and provenance.
// - `NonNull::new_unchecked` preserves both address and
// provenance.
// - If `Self` is a slice DST, the returned pointer encodes
// `elems` elements in the trailing slice:
// - This is true of the recursive call to `raw_from_ptr_len`.
// - `trailing.as_ptr() as *mut Self` preserves trailing slice
// element count [1].
// - `NonNull::new_unchecked` preserves trailing slice element
// count.
//
// [1] Per https://doc.rust-lang.org/reference/expressions/operator-expr.html#pointer-to-pointer-cast:
//
// `*const T`` / `*mut T` can be cast to `*const U` / `*mut U`
// with the following behavior:
// ...
// - If `T` and `U` are both unsized, the pointer is also
// returned unchanged. In particular, the metadata is
// preserved exactly.
//
// For instance, a cast from `*const [T]` to `*const [U]`
// preserves the number of elements. ... The same holds
// for str and any compound type whose unsized tail is a
// slice type, such as struct `Foo(i32, [u8])` or
// `(u64, Foo)`.
#[inline(always)]
fn raw_from_ptr_len(
bytes: #core::ptr::NonNull<u8>,
meta: <Self as #zerocopy_crate::KnownLayout>::PointerMetadata,
) -> #core::ptr::NonNull<Self> {
let trailing = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::raw_from_ptr_len(bytes, meta);
let slf = trailing.as_ptr() as *mut Self;
// SAFETY: Constructed from `trailing`, which is non-null.
unsafe { #core::ptr::NonNull::new_unchecked(slf) }
}
#[inline(always)]
fn pointer_to_metadata(ptr: *mut Self) -> <Self as #zerocopy_crate::KnownLayout>::PointerMetadata {
<#trailing_field_ty>::pointer_to_metadata(ptr as *mut _)
}
}
};
let inner_extras = {
let leading_fields_tys = leading_fields_tys.clone();
let methods = make_methods(*trailing_field_ty);
let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
quote!(
type PointerMetadata = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::PointerMetadata;
type MaybeUninit = __ZerocopyKnownLayoutMaybeUninit #ty_generics;
// SAFETY: `LAYOUT` accurately describes the layout of `Self`.
// The documentation of `DstLayout::for_repr_c_struct` vows that
// invocations in this manner will accurately describe a type,
// so long as:
//
// - that type is `repr(C)`,
// - its fields are enumerated in the order they appear,
// - the presence of `repr_align` and `repr_packed` are
// correctly accounted for.
//
// We respect all three of these preconditions here. This
// expansion is only used if `is_repr_c_struct`, we enumerate
// the fields in order, and we extract the values of `align(N)`
// and `packed(N)`.
const LAYOUT: #zerocopy_crate::DstLayout = #zerocopy_crate::DstLayout::for_repr_c_struct(
#repr_align,
#repr_packed,
&[
#(#zerocopy_crate::DstLayout::for_type::<#leading_fields_tys>(),)*
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::LAYOUT
],
);
#methods
)
};
let outer_extras = {
let ident = &ctx.ast.ident;
let vis = &ctx.ast.vis;
let params = &ctx.ast.generics.params;
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let predicates = if let Some(where_clause) = where_clause {
where_clause.predicates.clone()
} else {
Default::default()
};
// Generate a valid ident for a type-level handle to a field of a
// given `name`.
let field_index = |name: &TokenStream| ident!(("__Zerocopy_Field_{}", name), ident.span());
let field_indices: Vec<_> =
fields.iter().map(|(_vis, name, _ty)| field_index(name)).collect();
// Define the collection of type-level field handles.
let field_defs = field_indices.iter().zip(fields).map(|(idx, (vis, _, _))| {
quote! {
#vis struct #idx;
}
});
let field_impls = field_indices.iter().zip(fields).map(|(idx, (_, _, ty))| quote! {
// SAFETY: `#ty` is the type of `#ident`'s field at `#idx`.
//
// We implement `Field` for each field of the struct to create a
// projection from the field index to its type. This allows us
// to refer to the field's type in a way that respects `Self`
// hygiene. If we just copy-pasted the tokens of `#ty`, we
// would not respect `Self` hygiene, as `Self` would refer to
// the helper struct we are generating, not the derive target
// type.
unsafe impl #impl_generics #zerocopy_crate::util::macro_util::Field<#idx> for #ident #ty_generics
where
#predicates
{
type Type = #ty;
}
});
let trailing_field_index = field_index(trailing_field_name);
let leading_field_indices =
leading_fields.iter().map(|(_vis, name, _ty)| field_index(name));
// We use `Field` to project the type of the trailing field. This is
// required to ensure that if the field type uses `Self`, it
// resolves to the derive target type, not the helper struct we are
// generating.
let trailing_field_ty = quote! {
<#ident #ty_generics as
#zerocopy_crate::util::macro_util::Field<#trailing_field_index>
>::Type
};
let methods = make_methods(&parse_quote! {
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
});
let core = ctx.core_path();
quote! {
#(#field_defs)*
#(#field_impls)*
// SAFETY: This has the same layout as the derive target type,
// except that it admits uninit bytes. This is ensured by using
// the same repr as the target type, and by using field types
// which have the same layout as the target type's fields,
// except that they admit uninit bytes. We indirect through
// `Field` to ensure that occurrences of `Self` resolve to
// `#ty`, not `__ZerocopyKnownLayoutMaybeUninit` (see #2116).
#repr
#[doc(hidden)]
#vis struct __ZerocopyKnownLayoutMaybeUninit<#params> (
#(#core::mem::MaybeUninit<
<#ident #ty_generics as
#zerocopy_crate::util::macro_util::Field<#leading_field_indices>
>::Type
>,)*
// NOTE(#2302): We wrap in `ManuallyDrop` here in case the
// type we're operating on is both generic and
// `repr(packed)`. In that case, Rust needs to know that the
// type is *either* `Sized` or has a trivial `Drop`.
// `ManuallyDrop` has a trivial `Drop`, and so satisfies
// this requirement.
#core::mem::ManuallyDrop<
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
>
)
where
#trailing_field_ty: #zerocopy_crate::KnownLayout,
#predicates;
// SAFETY: We largely defer to the `KnownLayout` implementation
// on the derive target type (both by using the same tokens, and
// by deferring to impl via type-level indirection). This is
// sound, since `__ZerocopyKnownLayoutMaybeUninit` is guaranteed
// to have the same layout as the derive target type, except
// that `__ZerocopyKnownLayoutMaybeUninit` admits uninit bytes.
unsafe impl #impl_generics #zerocopy_crate::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
where
#trailing_field_ty: #zerocopy_crate::KnownLayout,
#predicates
{
fn only_derive_is_allowed_to_implement_this_trait() {}
type PointerMetadata = <#ident #ty_generics as #zerocopy_crate::KnownLayout>::PointerMetadata;
type MaybeUninit = Self;
const LAYOUT: #zerocopy_crate::DstLayout = <#ident #ty_generics as #zerocopy_crate::KnownLayout>::LAYOUT;
#methods
}
}
};
Some((SelfBounds::None, inner_extras, Some(outer_extras)))
}
pub(crate) fn derive(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
// If this is a `repr(C)` struct, then `c_struct_repr` contains the entire
// `repr` attribute.
let c_struct_repr = match &ctx.ast.data {
Data::Struct(..) => {
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
if repr.is_c() {
Some(repr)
} else {
None
}
}
Data::Enum(..) | Data::Union(..) => None,
};
let fields = ctx.ast.data.fields();
let (self_bounds, inner_extras, outer_extras) = c_struct_repr
.as_ref()
.and_then(|repr| {
derive_known_layout_for_repr_c_struct(ctx, repr, &fields)
})
.unwrap_or_else(|| {
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
// For enums, unions, and non-`repr(C)` structs, we require that
// `Self` is sized, and as a result don't need to reason about the
// internals of the type.
(
SelfBounds::SIZED,
quote!(
type PointerMetadata = ();
type MaybeUninit =
#core::mem::MaybeUninit<Self>;
// SAFETY: `LAYOUT` is guaranteed to accurately describe the
// layout of `Self`, because that is the documented safety
// contract of `DstLayout::for_type`.
const LAYOUT: #zerocopy_crate::DstLayout = #zerocopy_crate::DstLayout::for_type::<Self>();
// SAFETY: `.cast` preserves address and provenance.
//
// FIXME(#429): Add documentation to `.cast` that promises that
// it preserves provenance.
#[inline(always)]
fn raw_from_ptr_len(bytes: #core::ptr::NonNull<u8>, _meta: ()) -> #core::ptr::NonNull<Self> {
bytes.cast::<Self>()
}
#[inline(always)]
fn pointer_to_metadata(_ptr: *mut Self) -> () {}
),
None,
)
});
Ok(match &ctx.ast.data {
Data::Struct(strct) => {
let require_trait_bound_on_field_types =
if matches!(self_bounds, SelfBounds::All(&[Trait::Sized])) {
FieldBounds::None
} else {
FieldBounds::TRAILING_SELF
};
// A bound on the trailing field is required, since structs are
// unsized if their trailing field is unsized. Reflecting the layout
// of an usized trailing field requires that the field is
// `KnownLayout`.
ImplBlockBuilder::new(
ctx,
strct,
Trait::KnownLayout,
require_trait_bound_on_field_types,
)
.self_type_trait_bounds(self_bounds)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
Data::Enum(enm) => {
// A bound on the trailing field is not required, since enums cannot
// currently be unsized.
ImplBlockBuilder::new(ctx, enm, Trait::KnownLayout, FieldBounds::None)
.self_type_trait_bounds(SelfBounds::SIZED)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
Data::Union(unn) => {
// A bound on the trailing field is not required, since unions
// cannot currently be unsized.
ImplBlockBuilder::new(ctx, unn, Trait::KnownLayout, FieldBounds::None)
.self_type_trait_bounds(SelfBounds::SIZED)
.inner_extras(inner_extras)
.outer_extras(outer_extras)
.build()
}
})
}

View File

@ -0,0 +1,132 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
pub mod from_bytes;
pub mod into_bytes;
pub mod known_layout;
pub mod try_from_bytes;
pub mod unaligned;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Data, Error};
use crate::{
repr::StructUnionRepr,
util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
};
pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> TokenStream {
match &ctx.ast.data {
Data::Struct(strct) => {
ImplBlockBuilder::new(ctx, strct, Trait::Immutable, FieldBounds::ALL_SELF).build()
}
Data::Enum(enm) => {
ImplBlockBuilder::new(ctx, enm, Trait::Immutable, FieldBounds::ALL_SELF).build()
}
Data::Union(unn) => {
ImplBlockBuilder::new(ctx, unn, Trait::Immutable, FieldBounds::ALL_SELF).build()
}
}
}
pub(crate) fn derive_hash(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
// This doesn't delegate to `impl_block` because `impl_block` assumes it is
// deriving a `zerocopy`-defined trait, and these trait impls share a common
// shape that `Hash` does not. In particular, `zerocopy` traits contain a
// method that only `zerocopy_derive` macros are supposed to implement, and
// `impl_block` generating this trait method is incompatible with `Hash`.
let type_ident = &ctx.ast.ident;
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let where_predicates = where_clause.map(|clause| &clause.predicates);
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
Ok(quote! {
impl #impl_generics #core::hash::Hash for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
fn hash<H: #core::hash::Hasher>(&self, state: &mut H) {
#core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(self))
}
fn hash_slice<H: #core::hash::Hasher>(data: &[Self], state: &mut H) {
#core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(data))
}
}
})
}
pub(crate) fn derive_eq(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
// This doesn't delegate to `impl_block` because `impl_block` assumes it is
// deriving a `zerocopy`-defined trait, and these trait impls share a common
// shape that `Eq` does not. In particular, `zerocopy` traits contain a
// method that only `zerocopy_derive` macros are supposed to implement, and
// `impl_block` generating this trait method is incompatible with `Eq`.
let type_ident = &ctx.ast.ident;
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let where_predicates = where_clause.map(|clause| &clause.predicates);
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
Ok(quote! {
impl #impl_generics #core::cmp::PartialEq for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
fn eq(&self, other: &Self) -> bool {
#core::cmp::PartialEq::eq(
#zerocopy_crate::IntoBytes::as_bytes(self),
#zerocopy_crate::IntoBytes::as_bytes(other),
)
}
}
impl #impl_generics #core::cmp::Eq for #type_ident #ty_generics
where
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
#where_predicates
{
}
})
}
pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
match &ctx.ast.data {
Data::Struct(_) => {}
Data::Enum(_) | Data::Union(_) => {
return Err(Error::new(Span::call_site(), "can only be applied to structs"));
}
};
if repr.get_packed().is_some() {
return Err(Error::new(Span::call_site(), "must not have #[repr(packed)] attribute"));
}
if !(repr.is_c() || repr.is_transparent()) {
return Err(Error::new(
Span::call_site(),
"must have #[repr(C)] or #[repr(transparent)] in order to guarantee this type's layout is splitable",
));
}
let fields = ctx.ast.data.fields();
let trailing_field = if let Some(((_, _, trailing_field), _)) = fields.split_last() {
trailing_field
} else {
return Err(Error::new(Span::call_site(), "must at least one field"));
};
let zerocopy_crate = &ctx.zerocopy_crate;
// SAFETY: `#ty`, per the above checks, is `repr(C)` or `repr(transparent)`
// and is not packed; its trailing field is guaranteed to be well-aligned
// for its type. By invariant on `FieldBounds::TRAILING_SELF`, the trailing
// slice of the trailing field is also well-aligned for its type.
Ok(ImplBlockBuilder::new(ctx, &ctx.ast.data, Trait::SplitAt, FieldBounds::TRAILING_SELF)
.inner_extras(quote! {
type Elem = <#trailing_field as #zerocopy_crate::SplitAt>::Elem;
})
.build())
}

View File

@ -0,0 +1,765 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
Expr, Fields, Ident, Index, Type,
};
use crate::{
repr::{EnumRepr, StructUnionRepr},
util::{
const_block, enum_size_from_repr, generate_tag_enum, Ctx, DataExt, FieldBounds,
ImplBlockBuilder, Trait, TraitBound,
},
};
fn tag_ident(variant_ident: &Ident) -> Ident {
ident!(("___ZEROCOPY_TAG_{}", variant_ident), variant_ident.span())
}
/// Generates a constant for the tag associated with each variant of the enum.
/// When we match on the enum's tag, each arm matches one of these constants. We
/// have to use constants here because:
///
/// - The type that we're matching on is not the type of the tag, it's an
/// integer of the same size as the tag type and with the same bit patterns.
/// - We can't read the enum tag as an enum because the bytes may not represent
/// a valid variant.
/// - Patterns do not currently support const expressions, so we have to assign
/// these constants to names rather than use them inline in the `match`
/// statement.
fn generate_tag_consts(data: &DataEnum) -> TokenStream {
let tags = data.variants.iter().map(|v| {
let variant_ident = &v.ident;
let tag_ident = tag_ident(variant_ident);
quote! {
// This casts the enum variant to its discriminant, and then
// converts the discriminant to the target integral type via a
// numeric cast [1].
//
// Because these are the same size, this is defined to be a no-op
// and therefore is a lossless conversion [2].
//
// [1] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#enum-cast:
//
// Casts an enum to its discriminant.
//
// [2] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#numeric-cast:
//
// Casting between two integers of the same size (e.g. i32 -> u32)
// is a no-op.
const #tag_ident: ___ZerocopyTagPrimitive =
___ZerocopyTag::#variant_ident as ___ZerocopyTagPrimitive;
}
});
quote! {
#(#tags)*
}
}
fn variant_struct_ident(variant_ident: &Ident) -> Ident {
ident!(("___ZerocopyVariantStruct_{}", variant_ident), variant_ident.span())
}
/// Generates variant structs for the given enum variant.
///
/// These are structs associated with each variant of an enum. They are
/// `repr(C)` tuple structs with the same fields as the variant after a
/// `MaybeUninit<___ZerocopyInnerTag>`.
///
/// In order to unify the generated types for `repr(C)` and `repr(int)` enums,
/// we use a "fused" representation with fields for both an inner tag and an
/// outer tag. Depending on the repr, we will set one of these tags to the tag
/// type and the other to `()`. This lets us generate the same code but put the
/// tags in different locations.
fn generate_variant_structs(ctx: &Ctx, data: &DataEnum) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let enum_name = &ctx.ast.ident;
// All variant structs have a `PhantomData<MyEnum<...>>` field because we
// don't know which generic parameters each variant will use, and unused
// generic parameters are a compile error.
let core = ctx.core_path();
let phantom_ty = quote! {
#core::marker::PhantomData<#enum_name #ty_generics>
};
let variant_structs = data.variants.iter().filter_map(|variant| {
// We don't generate variant structs for unit variants because we only
// need to check the tag. This helps cut down our generated code a bit.
if matches!(variant.fields, Fields::Unit) {
return None;
}
let variant_struct_ident = variant_struct_ident(&variant.ident);
let field_types = variant.fields.iter().map(|f| &f.ty);
let variant_struct = parse_quote! {
#[repr(C)]
struct #variant_struct_ident #impl_generics (
#core::mem::MaybeUninit<___ZerocopyInnerTag>,
#(#field_types,)*
#phantom_ty,
) #where_clause;
};
// We do this rather than emitting `#[derive(::zerocopy::TryFromBytes)]`
// because that is not hygienic, and this is also more performant.
let try_from_bytes_impl =
derive_try_from_bytes(&ctx.with_input(&variant_struct), Trait::TryFromBytes)
.expect("derive_try_from_bytes should not fail on synthesized type");
Some(quote! {
#variant_struct
#try_from_bytes_impl
})
});
quote! {
#(#variant_structs)*
}
}
fn variants_union_field_ident(ident: &Ident) -> Ident {
// Field names are prefixed with `__field_` to prevent name collision
// with the `__nonempty` field.
ident!(("__field_{}", ident), ident.span())
}
fn generate_variants_union(ctx: &Ctx, data: &DataEnum) -> TokenStream {
let generics = &ctx.ast.generics;
let (_, ty_generics, _) = generics.split_for_impl();
let fields = data.variants.iter().filter_map(|variant| {
// We don't generate variant structs for unit variants because we only
// need to check the tag. This helps cut down our generated code a bit.
if matches!(variant.fields, Fields::Unit) {
return None;
}
let field_name = variants_union_field_ident(&variant.ident);
let variant_struct_ident = variant_struct_ident(&variant.ident);
let core = ctx.core_path();
Some(quote! {
#field_name: #core::mem::ManuallyDrop<#variant_struct_ident #ty_generics>,
})
});
let variants_union = parse_quote! {
#[repr(C)]
union ___ZerocopyVariants #generics {
#(#fields)*
// Enums can have variants with no fields, but unions must
// have at least one field. So we just add a trailing unit
// to ensure that this union always has at least one field.
// Because this union is `repr(C)`, this unit type does not
// affect the layout.
__nonempty: (),
}
};
let has_field =
derive_has_field_struct_union(&ctx.with_input(&variants_union), &variants_union.data);
quote! {
#variants_union
#has_field
}
}
/// Generates an implementation of `is_bit_valid` for an arbitrary enum.
///
/// The general process is:
///
/// 1. Generate a tag enum. This is an enum with the same repr, variants, and
/// corresponding discriminants as the original enum, but without any fields
/// on the variants. This gives us access to an enum where the variants have
/// the same discriminants as the one we're writing `is_bit_valid` for.
/// 2. Make constants from the variants of the tag enum. We need these because
/// we can't put const exprs in match arms.
/// 3. Generate variant structs. These are structs which have the same fields as
/// each variant of the enum, and are `#[repr(C)]` with an optional "inner
/// tag".
/// 4. Generate a variants union, with one field for each variant struct type.
/// 5. And finally, our raw enum is a `#[repr(C)]` struct of an "outer tag" and
/// the variants union.
///
/// See these reference links for fully-worked example decompositions.
///
/// - `repr(C)`: <https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields>
/// - `repr(int)`: <https://doc.rust-lang.org/reference/type-layout.html#primitive-representation-of-enums-with-fields>
/// - `repr(C, int)`: <https://doc.rust-lang.org/reference/type-layout.html#combining-primitive-representations-of-enums-with-fields-and-reprc>
pub(crate) fn derive_is_bit_valid(
ctx: &Ctx,
data: &DataEnum,
repr: &EnumRepr,
) -> Result<TokenStream, Error> {
let trait_path = Trait::TryFromBytes.crate_path(ctx);
let tag_enum = generate_tag_enum(ctx, repr, data);
let tag_consts = generate_tag_consts(data);
let (outer_tag_type, inner_tag_type) = if repr.is_c() {
(quote! { ___ZerocopyTag }, quote! { () })
} else if repr.is_primitive() {
(quote! { () }, quote! { ___ZerocopyTag })
} else {
return Err(Error::new(
ctx.ast.span(),
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
));
};
let variant_structs = generate_variant_structs(ctx, data);
let variants_union = generate_variants_union(ctx, data);
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
let zerocopy_crate = &ctx.zerocopy_crate;
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
.inner_extras(quote! {
type Tag = ___ZerocopyTag;
type ProjectToTag = #zerocopy_crate::pointer::cast::CastSized;
})
.build();
let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
let variant_ident = &variant.unwrap().ident;
let variants_union_field_ident = variants_union_field_ident(variant_ident);
let field: Box<syn::Type> = parse_quote!(());
fields.into_iter().enumerate().map(move |(idx, (vis, ident, ty))| {
// Rust does not presently support explicit visibility modifiers on
// enum fields, but we guard against the possibility to ensure this
// derive remains sound.
assert!(matches!(vis, syn::Visibility::Inherited));
let variant_struct_field_index = Index::from(idx + 1);
let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
let has_field_trait = Trait::HasField {
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
// Since Rust does not presently support explicit visibility
// modifiers on enum fields, any public type is suitable here;
// we use `()`.
field: field.clone(),
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
};
let has_field_path = has_field_trait.crate_path(ctx);
let has_field = ImplBlockBuilder::new(
ctx,
data,
has_field_trait,
FieldBounds::None,
)
.inner_extras(quote! {
type Type = #ty;
#[inline(always)]
fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
use #zerocopy_crate::pointer::cast::{CastSized, Projection};
slf.project::<___ZerocopyRawEnum #ty_generics, CastSized>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(variants) }>>()
.project::<_, Projection<_, { #zerocopy_crate::REPR_C_UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variants_union_field_ident) }>>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(value) }>>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>>()
.as_ptr()
}
})
.build();
let project = ImplBlockBuilder::new(
ctx,
data,
Trait::ProjectField {
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
// Since Rust does not presently support explicit visibility
// modifiers on enum fields, any public type is suitable
// here; we use `()`.
field: field.clone(),
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
},
FieldBounds::None,
)
.param_extras(vec![
parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
])
.inner_extras(quote! {
type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
type Invariants = (Aliasing, Alignment, #zerocopy_crate::invariant::Initialized);
})
.build();
quote! {
#has_field
#project
}
})
});
let core = ctx.core_path();
let match_arms = data.variants.iter().map(|variant| {
let tag_ident = tag_ident(&variant.ident);
let variant_struct_ident = variant_struct_ident(&variant.ident);
let variants_union_field_ident = variants_union_field_ident(&variant.ident);
if matches!(variant.fields, Fields::Unit) {
// Unit variants don't need any further validation beyond checking
// the tag.
quote! {
#tag_ident => true
}
} else {
quote! {
#tag_ident => {
// SAFETY: Since we know that the tag is `#tag_ident`, we
// know that no other `&`s exist which refer to this enum
// as any other variant.
let variant_md = variants.cast::<
_,
#zerocopy_crate::pointer::cast::Projection<
// #zerocopy_crate::ReadOnly<_>,
_,
{ #zerocopy_crate::REPR_C_UNION_VARIANT_ID },
{ #zerocopy_crate::ident_id!(#variants_union_field_ident) }
>,
_
>();
let variant = variant_md.cast::<
#zerocopy_crate::ReadOnly<#variant_struct_ident #ty_generics>,
#zerocopy_crate::pointer::cast::CastSized,
(#zerocopy_crate::pointer::BecauseRead, _)
>();
<
#variant_struct_ident #ty_generics as #trait_path
>::is_bit_valid(variant)
}
}
}
});
let generics = &ctx.ast.generics;
let raw_enum: DeriveInput = parse_quote! {
#[repr(C)]
struct ___ZerocopyRawEnum #generics {
tag: ___ZerocopyOuterTag,
variants: ___ZerocopyVariants #ty_generics,
}
};
let self_ident = &ctx.ast.ident;
let invariants_eq_impl = quote! {
// SAFETY: `___ZerocopyRawEnum` is designed to have the same layout,
// validity, and invariants as `Self`.
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<___ZerocopyRawEnum #ty_generics> for #self_ident #ty_generics #where_clause {}
};
let raw_enum_projections =
derive_has_field_struct_union(&ctx.with_input(&raw_enum), &raw_enum.data);
let raw_enum = quote! {
#raw_enum
#invariants_eq_impl
#raw_enum_projections
};
Ok(quote! {
// SAFETY: We use `is_bit_valid` to validate that the bit pattern of the
// enum's tag corresponds to one of the enum's discriminants. Then, we
// check the bit validity of each field of the corresponding variant.
// Thus, this is a sound implementation of `is_bit_valid`.
#[inline]
fn is_bit_valid<___ZcAlignment>(
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
#tag_enum
type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
{ #core::mem::size_of::<___ZerocopyTag>() },
>;
#tag_consts
type ___ZerocopyOuterTag = #outer_tag_type;
type ___ZerocopyInnerTag = #inner_tag_type;
#variant_structs
#variants_union
#raw_enum
#has_tag
#(#has_fields)*
let tag = {
// SAFETY:
// - The provided cast addresses a subset of the bytes addressed
// by `candidate` because it addresses the starting tag of the
// enum.
// - Because the pointer is cast from `candidate`, it has the
// same provenance as it.
// - There are no `UnsafeCell`s in the tag because it is a
// primitive integer.
// - `tag_ptr` is casted from `candidate`, whose referent is
// `Initialized`. Since we have not written uninitialized
// bytes into the referent, `tag_ptr` is also `Initialized`.
//
// FIXME(#2874): Revise this to a `cast` once `candidate`
// references a `ReadOnly<Self>`.
let tag_ptr = unsafe {
candidate.reborrow().project_transmute_unchecked::<
_,
#zerocopy_crate::invariant::Initialized,
#zerocopy_crate::pointer::cast::CastSized
>()
};
tag_ptr.recall_validity::<_, (_, (_, _))>().read::<#zerocopy_crate::BecauseImmutable>()
};
let mut raw_enum = candidate.cast::<
#zerocopy_crate::ReadOnly<___ZerocopyRawEnum #ty_generics>,
#zerocopy_crate::pointer::cast::CastSized,
(#zerocopy_crate::pointer::BecauseRead, _)
>();
let variants = #zerocopy_crate::into_inner!(raw_enum.project::<
_,
{ #zerocopy_crate::STRUCT_VARIANT_ID },
{ #zerocopy_crate::ident_id!(variants) }
>());
match tag {
#(#match_arms,)*
_ => false,
}
}
})
}
pub(crate) fn derive_try_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
match &ctx.ast.data {
Data::Struct(strct) => derive_try_from_bytes_struct(ctx, strct, top_level),
Data::Enum(enm) => derive_try_from_bytes_enum(ctx, enm, top_level),
Data::Union(unn) => Ok(derive_try_from_bytes_union(ctx, unn, top_level)),
}
}
fn derive_has_field_struct_union(ctx: &Ctx, data: &dyn DataExt) -> TokenStream {
let fields = ctx.ast.data.fields();
if fields.is_empty() {
return quote! {};
}
let field_tokens = fields.iter().map(|(vis, ident, _)| {
let ident = ident!(("__z{}", ident), ident.span());
quote!(
#vis enum #ident {}
)
});
let zerocopy_crate = &ctx.zerocopy_crate;
let variant_id: Box<Expr> = match &ctx.ast.data {
Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }),
Data::Union(_) => {
let is_repr_c = StructUnionRepr::from_attrs(&ctx.ast.attrs)
.map(|repr| repr.is_c())
.unwrap_or(false);
if is_repr_c {
parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
} else {
parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
}
}
_ => unreachable!(),
};
let core = ctx.core_path();
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
.inner_extras(quote! {
type Tag = ();
type ProjectToTag = #zerocopy_crate::pointer::cast::CastToUnit;
})
.build();
let has_fields = fields.iter().map(move |(_, ident, ty)| {
let field_token = ident!(("__z{}", ident), ident.span());
let field: Box<Type> = parse_quote!(#field_token);
let field_id: Box<Expr> = parse_quote!({ #zerocopy_crate::ident_id!(#ident) });
let has_field_trait = Trait::HasField {
variant_id: variant_id.clone(),
field: field.clone(),
field_id: field_id.clone(),
};
let has_field_path = has_field_trait.crate_path(ctx);
ImplBlockBuilder::new(
ctx,
data,
has_field_trait,
FieldBounds::None,
)
.inner_extras(quote! {
type Type = #ty;
#[inline(always)]
fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
let slf = slf.as_ptr();
// SAFETY: By invariant on `PtrInner`, `slf` is a non-null
// pointer whose referent is zero-sized or lives in a valid
// allocation. Since `#ident` is a struct or union field of
// `Self`, this projection preserves or shrinks the referent
// size, and so the resulting referent also fits in the same
// allocation.
unsafe { #core::ptr::addr_of_mut!((*slf).#ident) }
}
}).outer_extras(if matches!(&ctx.ast.data, Data::Struct(..)) {
let fields_preserve_alignment = StructUnionRepr::from_attrs(&ctx.ast.attrs)
.map(|repr| repr.get_packed().is_none())
.unwrap();
let alignment = if fields_preserve_alignment {
quote! { Alignment }
} else {
quote! { #zerocopy_crate::invariant::Unaligned }
};
// SAFETY: See comments on items.
ImplBlockBuilder::new(
ctx,
data,
Trait::ProjectField {
variant_id: variant_id.clone(),
field: field.clone(),
field_id: field_id.clone(),
invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
},
FieldBounds::None,
)
.param_extras(vec![
parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
])
.inner_extras(quote! {
// SAFETY: Projection into structs is always infallible.
type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
// SAFETY: The alignment of the projected `Ptr` is `Unaligned`
// if the structure is packed; otherwise inherited from the
// outer `Ptr`. If the validity of the outer pointer is
// `Initialized`, so too is the validity of its fields.
type Invariants = (Aliasing, #alignment, #zerocopy_crate::invariant::Initialized);
})
.build()
} else {
quote! {}
})
.build()
});
const_block(field_tokens.into_iter().chain(Some(has_tag)).chain(has_fields).map(Some))
}
fn derive_try_from_bytes_struct(
ctx: &Ctx,
strct: &DataStruct,
top_level: Trait,
) -> Result<TokenStream, Error> {
let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
let zerocopy_crate = &ctx.zerocopy_crate;
let fields = strct.fields();
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
let core = ctx.core_path();
quote!(
// SAFETY: We use `is_bit_valid` to validate that each field is
// bit-valid, and only return `true` if all of them are. The bit
// validity of a struct is just the composition of the bit
// validities of its fields, so this is a sound implementation
// of `is_bit_valid`.
#[inline]
fn is_bit_valid<___ZcAlignment>(
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
true #(&& {
let field_candidate = #zerocopy_crate::into_inner!(candidate.reborrow().project::<
_,
{ #zerocopy_crate::STRUCT_VARIANT_ID },
{ #zerocopy_crate::ident_id!(#field_names) }
>());
<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
Ok(ImplBlockBuilder::new(ctx, strct, Trait::TryFromBytes, FieldBounds::ALL_SELF)
.inner_extras(extras)
.outer_extras(derive_has_field_struct_union(ctx, strct))
.build())
}
fn derive_try_from_bytes_union(ctx: &Ctx, unn: &DataUnion, top_level: Trait) -> TokenStream {
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
let zerocopy_crate = &ctx.zerocopy_crate;
let variant_id: Box<Expr> = {
let is_repr_c =
StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap_or(false);
if is_repr_c {
parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
} else {
parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
}
};
let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
let fields = unn.fields();
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
let core = ctx.core_path();
quote!(
// SAFETY: We use `is_bit_valid` to validate that any field is
// bit-valid; we only return `true` if at least one of them is.
// The bit validity of a union is not yet well defined in Rust,
// but it is guaranteed to be no more strict than this
// definition. See #696 for a more in-depth discussion.
#[inline]
fn is_bit_valid<___ZcAlignment>(
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
false #(|| {
// SAFETY:
// - Since `ReadOnly<Self>: Immutable` unconditionally,
// neither `*slf` nor the returned pointer's referent
// permit interior mutation.
// - Both source and destination validity are
// `Initialized`, which is always a sound
// transmutation.
let field_candidate = unsafe {
candidate.reborrow().project_transmute_unchecked::<
_,
_,
#zerocopy_crate::pointer::cast::Projection<
_,
#variant_id,
{ #zerocopy_crate::ident_id!(#field_names) }
>
>()
};
<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
})*
}
)
});
ImplBlockBuilder::new(ctx, unn, Trait::TryFromBytes, field_type_trait_bounds)
.inner_extras(extras)
.outer_extras(derive_has_field_struct_union(ctx, unn))
.build()
}
fn derive_try_from_bytes_enum(
ctx: &Ctx,
enm: &DataEnum,
top_level: Trait,
) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
// If an enum has no fields, it has a well-defined integer representation,
// and every possible bit pattern corresponds to a valid discriminant tag,
// then it *could* be `FromBytes` (even if the user hasn't derived
// `FromBytes`). This holds if, for `repr(uN)` or `repr(iN)`, there are 2^N
// variants.
let could_be_from_bytes = enum_size_from_repr(&repr)
.map(|size| enm.fields().is_empty() && enm.variants.len() == 1usize << size)
.unwrap_or(false);
let trivial_is_bit_valid = try_gen_trivial_is_bit_valid(ctx, top_level);
let extra = match (trivial_is_bit_valid, could_be_from_bytes) {
(Some(is_bit_valid), _) => is_bit_valid,
// SAFETY: It would be sound for the enum to implement `FromBytes`, as
// required by `gen_trivial_is_bit_valid_unchecked`.
(None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(ctx) },
(None, false) => match derive_is_bit_valid(ctx, enm, &repr) {
Ok(extra) => extra,
Err(_) if ctx.skip_on_error => return Ok(TokenStream::new()),
Err(e) => return Err(e),
},
};
Ok(ImplBlockBuilder::new(ctx, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF)
.inner_extras(extra)
.build())
}
fn try_gen_trivial_is_bit_valid(ctx: &Ctx, top_level: Trait) -> Option<proc_macro2::TokenStream> {
// If the top-level trait is `FromBytes` and `Self` has no type parameters,
// then the `FromBytes` derive will fail compilation if `Self` is not
// actually soundly `FromBytes`, and so we can rely on that for our
// `is_bit_valid` impl. It's plausible that we could make changes - or Rust
// could make changes (such as the "trivial bounds" language feature) - that
// make this no longer true. To hedge against these, we include an explicit
// `Self: FromBytes` check in the generated `is_bit_valid`, which is
// bulletproof.
//
// If `ctx.skip_on_error` is true, we can't rely on the `FromBytes` derive
// to fail compilation if `Self` is not actually soundly `FromBytes`.
if matches!(top_level, Trait::FromBytes)
&& ctx.ast.generics.params.is_empty()
&& !ctx.skip_on_error
{
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
Some(quote!(
// SAFETY: See inline.
#[inline(always)]
fn is_bit_valid<___ZcAlignment>(
_candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
if false {
fn assert_is_from_bytes<T>()
where
T: #zerocopy_crate::FromBytes,
T: ?#core::marker::Sized,
{
}
assert_is_from_bytes::<Self>();
}
// SAFETY: The preceding code only compiles if `Self:
// FromBytes`. Thus, this code only compiles if all initialized
// byte sequences represent valid instances of `Self`.
true
}
))
} else {
None
}
}
/// # Safety
///
/// All initialized bit patterns must be valid for `Self`.
unsafe fn gen_trivial_is_bit_valid_unchecked(ctx: &Ctx) -> proc_macro2::TokenStream {
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
quote!(
// SAFETY: The caller of `gen_trivial_is_bit_valid_unchecked` has
// promised that all initialized bit patterns are valid for `Self`.
#[inline(always)]
fn is_bit_valid<___ZcAlignment>(
_candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
) -> #core::primitive::bool
where
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
{
true
}
)
}

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
use proc_macro2::{Span, TokenStream};
use syn::{Data, DataEnum, DataStruct, DataUnion, Error};
use crate::{
repr::{EnumRepr, StructUnionRepr},
util::{Ctx, FieldBounds, ImplBlockBuilder, Trait},
};
pub(crate) fn derive_unaligned(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
match &ctx.ast.data {
Data::Struct(strct) => derive_unaligned_struct(ctx, strct),
Data::Enum(enm) => derive_unaligned_enum(ctx, enm),
Data::Union(unn) => derive_unaligned_union(ctx, unn),
}
}
/// A struct is `Unaligned` if:
/// - `repr(align)` is no more than 1 and either
/// - `repr(C)` or `repr(transparent)` and
/// - all fields `Unaligned`
/// - `repr(packed)`
fn derive_unaligned_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
repr.unaligned_validate_no_align_gt_1()?;
let field_bounds = if repr.is_packed_1() {
FieldBounds::None
} else if repr.is_c() || repr.is_transparent() {
FieldBounds::ALL_SELF
} else {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment",
));
};
Ok(ImplBlockBuilder::new(ctx, strct, Trait::Unaligned, field_bounds).build())
}
/// An enum is `Unaligned` if:
/// - No `repr(align(N > 1))`
/// - `repr(u8)` or `repr(i8)`
fn derive_unaligned_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
repr.unaligned_validate_no_align_gt_1()?;
if !repr.is_u8() && !repr.is_i8() {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment",
));
}
Ok(ImplBlockBuilder::new(ctx, enm, Trait::Unaligned, FieldBounds::ALL_SELF).build())
}
/// Like structs, a union is `Unaligned` if:
/// - `repr(align)` is no more than 1 and either
/// - `repr(C)` or `repr(transparent)` and
/// - all fields `Unaligned`
/// - `repr(packed)`
fn derive_unaligned_union(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> {
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
repr.unaligned_validate_no_align_gt_1()?;
let field_type_trait_bounds = if repr.is_packed_1() {
FieldBounds::None
} else if repr.is_c() || repr.is_transparent() {
FieldBounds::ALL_SELF
} else {
return ctx.error_or_skip(Error::new(
Span::call_site(),
"must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment",
));
};
Ok(ImplBlockBuilder::new(ctx, unn, Trait::Unaligned, field_type_trait_bounds).build())
}

146
rust/zerocopy-derive/lib.rs Normal file
View File

@ -0,0 +1,146 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
// Copyright 2019 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.
//! Derive macros for [zerocopy]'s traits.
//!
//! [zerocopy]: https://docs.rs/zerocopy
// Sometimes we want to use lints which were added after our MSRV.
// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
// this attribute, any unknown lint would cause a CI failure when testing with
// our MSRV.
#![allow(unknown_lints)]
#![deny(renamed_and_removed_lints)]
#![deny(
clippy::all,
clippy::missing_safety_doc,
clippy::multiple_unsafe_ops_per_block,
clippy::undocumented_unsafe_blocks
)]
// We defer to own discretion on type complexity.
#![allow(clippy::type_complexity)]
// Inlining format args isn't supported on our MSRV.
#![allow(clippy::uninlined_format_args)]
#![deny(
rustdoc::bare_urls,
rustdoc::broken_intra_doc_links,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_html_tags,
rustdoc::invalid_rust_codeblocks,
rustdoc::missing_crate_level_docs,
rustdoc::private_intra_doc_links
)]
#![recursion_limit = "128"]
macro_rules! ident {
(($fmt:literal $(, $arg:expr)*), $span:expr) => {
syn::Ident::new(&format!($fmt $(, crate::util::to_ident_str($arg))*), $span)
};
}
mod derive;
#[cfg(test)]
mod output_tests;
mod repr;
mod util;
use syn::{DeriveInput, Error};
use crate::util::*;
// FIXME(https://github.com/rust-lang/rust/issues/54140): Some errors could be
// made better if we could add multiple lines of error output like this:
//
// error: unsupported representation
// --> enum.rs:28:8
// |
// 28 | #[repr(transparent)]
// |
// help: required by the derive of FromBytes
//
// Instead, we have more verbose error messages like "unsupported representation
// for deriving FromZeros, FromBytes, IntoBytes, or Unaligned on an enum"
//
// This will probably require Span::error
// (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error),
// which is currently unstable. Revisit this once it's stable.
/// Defines a derive function named `$outer` which parses its input
/// `TokenStream` as a `DeriveInput` and then invokes the `$inner` function.
///
/// Note that the separate `$outer` parameter is required - proc macro functions
/// are currently required to live at the crate root, and so the caller must
/// specify the name in order to avoid name collisions.
macro_rules! derive {
($trait:ident => $outer:ident => $inner:path) => {
#[proc_macro_derive($trait, attributes(zerocopy))]
pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(ts as DeriveInput);
let ctx = match Ctx::try_from_derive_input(ast) {
Ok(ctx) => ctx,
Err(e) => return e.into_compile_error().into(),
};
let ts = $inner(&ctx, Trait::$trait).into_ts();
// We wrap in `const_block` as a backstop in case any derive fails
// to wrap its output in `const_block` (and thus fails to annotate)
// with the full set of `#[allow(...)]` attributes).
let ts = const_block([Some(ts)]);
#[cfg(test)]
crate::util::testutil::check_hygiene(ts.clone());
ts.into()
}
};
}
trait IntoTokenStream {
fn into_ts(self) -> proc_macro2::TokenStream;
}
impl IntoTokenStream for proc_macro2::TokenStream {
fn into_ts(self) -> proc_macro2::TokenStream {
self
}
}
impl IntoTokenStream for Result<proc_macro2::TokenStream, Error> {
fn into_ts(self) -> proc_macro2::TokenStream {
match self {
Ok(ts) => ts,
Err(err) => err.to_compile_error(),
}
}
}
derive!(KnownLayout => derive_known_layout => crate::derive::known_layout::derive);
derive!(Immutable => derive_immutable => crate::derive::derive_immutable);
derive!(TryFromBytes => derive_try_from_bytes => crate::derive::try_from_bytes::derive_try_from_bytes);
derive!(FromZeros => derive_from_zeros => crate::derive::from_bytes::derive_from_zeros);
derive!(FromBytes => derive_from_bytes => crate::derive::from_bytes::derive_from_bytes);
derive!(IntoBytes => derive_into_bytes => crate::derive::into_bytes::derive_into_bytes);
derive!(Unaligned => derive_unaligned => crate::derive::unaligned::derive_unaligned);
derive!(ByteHash => derive_hash => crate::derive::derive_hash);
derive!(ByteEq => derive_eq => crate::derive::derive_eq);
derive!(SplitAt => derive_split_at => crate::derive::derive_split_at);
/// Deprecated: prefer [`FromZeros`] instead.
#[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")]
#[doc(hidden)]
#[proc_macro_derive(FromZeroes)]
pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_from_zeros(ts)
}
/// Deprecated: prefer [`IntoBytes`] instead.
#[deprecated(since = "0.8.0", note = "`AsBytes` was renamed to `IntoBytes`")]
#[doc(hidden)]
#[proc_macro_derive(AsBytes)]
pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_into_bytes(ts)
}

View File

@ -0,0 +1,851 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
// Copyright 2019 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.
use core::{
convert::{Infallible, TryFrom},
num::NonZeroU32,
};
use proc_macro2::{Span, TokenStream};
use quote::{quote_spanned, ToTokens, TokenStreamExt as _};
use syn::{
punctuated::Punctuated, spanned::Spanned as _, token::Comma, Attribute, Error, LitInt, Meta,
MetaList,
};
/// The computed representation of a type.
///
/// This is the result of processing all `#[repr(...)]` attributes on a type, if
/// any. A `Repr` is only capable of representing legal combinations of
/// `#[repr(...)]` attributes.
#[cfg_attr(test, derive(Copy, Clone, Debug))]
pub(crate) enum Repr<Prim, Packed> {
/// `#[repr(transparent)]`
Transparent(Span),
/// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`
/// optionally combined with `repr(packed(...))` or `repr(align(...))`
Compound(Spanned<CompoundRepr<Prim>>, Option<Spanned<AlignRepr<Packed>>>),
}
/// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`.
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
pub(crate) enum CompoundRepr<Prim> {
C,
Rust,
Primitive(Prim),
}
/// `repr(Int)`
#[derive(Copy, Clone)]
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
pub(crate) enum PrimitiveRepr {
U8,
U16,
U32,
U64,
U128,
Usize,
I8,
I16,
I32,
I64,
I128,
Isize,
}
/// `repr(packed(...))` or `repr(align(...))`
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
pub(crate) enum AlignRepr<Packed> {
Packed(Packed),
Align(NonZeroU32),
}
/// The representations which can legally appear on a struct or union type.
pub(crate) type StructUnionRepr = Repr<Infallible, NonZeroU32>;
/// The representations which can legally appear on an enum type.
pub(crate) type EnumRepr = Repr<PrimitiveRepr, Infallible>;
impl<Prim, Packed> Repr<Prim, Packed> {
/// Gets the name of this "repr type" - the non-align `repr(X)` that is used
/// in prose to refer to this type.
///
/// For example, we would refer to `#[repr(C, align(4))] struct Foo { ... }`
/// as a "`repr(C)` struct".
pub(crate) fn repr_type_name(&self) -> &str
where
Prim: Copy + With<PrimitiveRepr>,
{
use CompoundRepr::*;
use PrimitiveRepr::*;
use Repr::*;
match self {
Transparent(_span) => "repr(transparent)",
Compound(Spanned { t: repr, span: _ }, _align) => match repr {
C => "repr(C)",
Rust => "repr(Rust)",
Primitive(prim) => prim.with(|prim| match prim {
U8 => "repr(u8)",
U16 => "repr(u16)",
U32 => "repr(u32)",
U64 => "repr(u64)",
U128 => "repr(u128)",
Usize => "repr(usize)",
I8 => "repr(i8)",
I16 => "repr(i16)",
I32 => "repr(i32)",
I64 => "repr(i64)",
I128 => "repr(i128)",
Isize => "repr(isize)",
}),
},
}
}
pub(crate) fn is_transparent(&self) -> bool {
matches!(self, Repr::Transparent(_))
}
pub(crate) fn is_c(&self) -> bool {
use CompoundRepr::*;
matches!(self, Repr::Compound(Spanned { t: C, span: _ }, _align))
}
pub(crate) fn is_primitive(&self) -> bool {
use CompoundRepr::*;
matches!(self, Repr::Compound(Spanned { t: Primitive(_), span: _ }, _align))
}
pub(crate) fn get_packed(&self) -> Option<&Packed> {
use AlignRepr::*;
use Repr::*;
if let Compound(_, Some(Spanned { t: Packed(p), span: _ })) = self {
Some(p)
} else {
None
}
}
pub(crate) fn get_align(&self) -> Option<Spanned<NonZeroU32>> {
use AlignRepr::*;
use Repr::*;
if let Compound(_, Some(Spanned { t: Align(n), span })) = self {
Some(Spanned::new(*n, *span))
} else {
None
}
}
pub(crate) fn is_align_gt_1(&self) -> bool {
self.get_align().map(|n| n.t.get() > 1).unwrap_or(false)
}
/// When deriving `Unaligned`, validate that the decorated type has no
/// `#[repr(align(N))]` attribute where `N > 1`. If no such attribute exists
/// (including if `N == 1`), this returns `Ok(())`, and otherwise it returns
/// a descriptive error.
pub(crate) fn unaligned_validate_no_align_gt_1(&self) -> Result<(), Error> {
if let Some(n) = self.get_align().filter(|n| n.t.get() > 1) {
Err(Error::new(
n.span,
"cannot derive `Unaligned` on type with alignment greater than 1",
))
} else {
Ok(())
}
}
}
impl<Prim> Repr<Prim, NonZeroU32> {
/// Does `self` describe a `#[repr(packed)]` or `#[repr(packed(1))]` type?
pub(crate) fn is_packed_1(&self) -> bool {
self.get_packed().map(|n| n.get() == 1).unwrap_or(false)
}
}
impl<Packed> Repr<PrimitiveRepr, Packed> {
fn get_primitive(&self) -> Option<&PrimitiveRepr> {
use CompoundRepr::*;
use Repr::*;
if let Compound(Spanned { t: Primitive(p), span: _ }, _align) = self {
Some(p)
} else {
None
}
}
/// Does `self` describe a `#[repr(u8)]` type?
pub(crate) fn is_u8(&self) -> bool {
matches!(self.get_primitive(), Some(PrimitiveRepr::U8))
}
/// Does `self` describe a `#[repr(i8)]` type?
pub(crate) fn is_i8(&self) -> bool {
matches!(self.get_primitive(), Some(PrimitiveRepr::I8))
}
}
impl<Prim, Packed> ToTokens for Repr<Prim, Packed>
where
Prim: With<PrimitiveRepr> + Copy,
Packed: With<NonZeroU32> + Copy,
{
fn to_tokens(&self, ts: &mut TokenStream) {
use Repr::*;
match self {
Transparent(span) => ts.append_all(quote_spanned! { *span=> #[repr(transparent)] }),
Compound(repr, align) => {
repr.to_tokens(ts);
if let Some(align) = align {
align.to_tokens(ts);
}
}
}
}
}
impl<Prim: With<PrimitiveRepr> + Copy> ToTokens for Spanned<CompoundRepr<Prim>> {
fn to_tokens(&self, ts: &mut TokenStream) {
use CompoundRepr::*;
match &self.t {
C => ts.append_all(quote_spanned! { self.span=> #[repr(C)] }),
Rust => ts.append_all(quote_spanned! { self.span=> #[repr(Rust)] }),
Primitive(prim) => prim.with(|prim| Spanned::new(prim, self.span).to_tokens(ts)),
}
}
}
impl ToTokens for Spanned<PrimitiveRepr> {
fn to_tokens(&self, ts: &mut TokenStream) {
use PrimitiveRepr::*;
match self.t {
U8 => ts.append_all(quote_spanned! { self.span => #[repr(u8)] }),
U16 => ts.append_all(quote_spanned! { self.span => #[repr(u16)] }),
U32 => ts.append_all(quote_spanned! { self.span => #[repr(u32)] }),
U64 => ts.append_all(quote_spanned! { self.span => #[repr(u64)] }),
U128 => ts.append_all(quote_spanned! { self.span => #[repr(u128)] }),
Usize => ts.append_all(quote_spanned! { self.span => #[repr(usize)] }),
I8 => ts.append_all(quote_spanned! { self.span => #[repr(i8)] }),
I16 => ts.append_all(quote_spanned! { self.span => #[repr(i16)] }),
I32 => ts.append_all(quote_spanned! { self.span => #[repr(i32)] }),
I64 => ts.append_all(quote_spanned! { self.span => #[repr(i64)] }),
I128 => ts.append_all(quote_spanned! { self.span => #[repr(i128)] }),
Isize => ts.append_all(quote_spanned! { self.span => #[repr(isize)] }),
}
}
}
impl<Packed: With<NonZeroU32> + Copy> ToTokens for Spanned<AlignRepr<Packed>> {
fn to_tokens(&self, ts: &mut TokenStream) {
use AlignRepr::*;
// We use `syn::Index` instead of `u32` because `quote_spanned!`
// serializes `u32` literals as `123u32`, not just `123`. Rust doesn't
// recognize that as a valid argument to `#[repr(align(...))]` or
// `#[repr(packed(...))]`.
let to_index = |n: NonZeroU32| syn::Index { index: n.get(), span: self.span };
match self.t {
Packed(n) => n.with(|n| {
let n = to_index(n);
ts.append_all(quote_spanned! { self.span => #[repr(packed(#n))] })
}),
Align(n) => {
let n = to_index(n);
ts.append_all(quote_spanned! { self.span => #[repr(align(#n))] })
}
}
}
}
/// The result of parsing a single `#[repr(...)]` attribute or a single
/// directive inside a compound `#[repr(..., ...)]` attribute.
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(Debug))]
pub(crate) enum RawRepr {
Transparent,
C,
Rust,
U8,
U16,
U32,
U64,
U128,
Usize,
I8,
I16,
I32,
I64,
I128,
Isize,
Align(NonZeroU32),
PackedN(NonZeroU32),
Packed,
}
/// The error from converting from a `RawRepr`.
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
pub(crate) enum FromRawReprError<E> {
/// The `RawRepr` doesn't affect the high-level repr we're parsing (e.g.
/// it's `align(...)` and we're parsing a `CompoundRepr`).
None,
/// The `RawRepr` is invalid for the high-level repr we're parsing (e.g.
/// it's `packed` repr and we're parsing an `AlignRepr` for an enum type).
Err(E),
}
/// The representation hint is not supported for the decorated type.
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
pub(crate) struct UnsupportedReprError;
impl<Prim: With<PrimitiveRepr>> TryFrom<RawRepr> for CompoundRepr<Prim> {
type Error = FromRawReprError<UnsupportedReprError>;
fn try_from(
raw: RawRepr,
) -> Result<CompoundRepr<Prim>, FromRawReprError<UnsupportedReprError>> {
use RawRepr::*;
match raw {
C => Ok(CompoundRepr::C),
Rust => Ok(CompoundRepr::Rust),
raw @ (U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize) => {
Prim::try_with_or(
|| match raw {
U8 => Ok(PrimitiveRepr::U8),
U16 => Ok(PrimitiveRepr::U16),
U32 => Ok(PrimitiveRepr::U32),
U64 => Ok(PrimitiveRepr::U64),
U128 => Ok(PrimitiveRepr::U128),
Usize => Ok(PrimitiveRepr::Usize),
I8 => Ok(PrimitiveRepr::I8),
I16 => Ok(PrimitiveRepr::I16),
I32 => Ok(PrimitiveRepr::I32),
I64 => Ok(PrimitiveRepr::I64),
I128 => Ok(PrimitiveRepr::I128),
Isize => Ok(PrimitiveRepr::Isize),
Transparent | C | Rust | Align(_) | PackedN(_) | Packed => {
Err(UnsupportedReprError)
}
},
UnsupportedReprError,
)
.map(CompoundRepr::Primitive)
.map_err(FromRawReprError::Err)
}
Transparent | Align(_) | PackedN(_) | Packed => Err(FromRawReprError::None),
}
}
}
impl<Pcked: With<NonZeroU32>> TryFrom<RawRepr> for AlignRepr<Pcked> {
type Error = FromRawReprError<UnsupportedReprError>;
fn try_from(raw: RawRepr) -> Result<AlignRepr<Pcked>, FromRawReprError<UnsupportedReprError>> {
use RawRepr::*;
match raw {
Packed | PackedN(_) => Pcked::try_with_or(
|| match raw {
Packed => Ok(NonZeroU32::new(1).unwrap()),
PackedN(n) => Ok(n),
U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize
| Transparent | C | Rust | Align(_) => Err(UnsupportedReprError),
},
UnsupportedReprError,
)
.map(AlignRepr::Packed)
.map_err(FromRawReprError::Err),
Align(n) => Ok(AlignRepr::Align(n)),
U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize
| Transparent | C | Rust => Err(FromRawReprError::None),
}
}
}
/// The error from extracting a high-level repr type from a list of `RawRepr`s.
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
enum FromRawReprsError<E> {
/// One of the `RawRepr`s is invalid for the high-level repr we're parsing
/// (e.g. there's a `packed` repr and we're parsing an `AlignRepr` for an
/// enum type).
Single(E),
/// Two `RawRepr`s appear which both affect the high-level repr we're
/// parsing (e.g., the list is `#[repr(align(2), packed)]`). Note that we
/// conservatively treat redundant reprs as conflicting (e.g.
/// `#[repr(packed, packed)]`).
Conflict,
}
/// Tries to extract a high-level repr from a list of `RawRepr`s.
fn try_from_raw_reprs<'a, E, R: TryFrom<RawRepr, Error = FromRawReprError<E>>>(
r: impl IntoIterator<Item = &'a Spanned<RawRepr>>,
) -> Result<Option<Spanned<R>>, Spanned<FromRawReprsError<E>>> {
// Walk the list of `RawRepr`s and attempt to convert each to an `R`. Bail
// if we find any errors. If we find more than one which converts to an `R`,
// bail with a `Conflict` error.
r.into_iter().try_fold(None, |found: Option<Spanned<R>>, raw| {
let new = match Spanned::<R>::try_from(*raw) {
Ok(r) => r,
// This `RawRepr` doesn't convert to an `R`, so keep the current
// found `R`, if any.
Err(FromRawReprError::None) => return Ok(found),
// This repr is unsupported for the decorated type (e.g.
// `repr(packed)` on an enum).
Err(FromRawReprError::Err(Spanned { t: err, span })) => {
return Err(Spanned::new(FromRawReprsError::Single(err), span))
}
};
if let Some(found) = found {
// We already found an `R`, but this `RawRepr` also converts to an
// `R`, so that's a conflict.
//
// `Span::join` returns `None` if the two spans are from different
// files or if we're not on the nightly compiler. In that case, just
// use `new`'s span.
let span = found.span.join(new.span).unwrap_or(new.span);
Err(Spanned::new(FromRawReprsError::Conflict, span))
} else {
Ok(Some(new))
}
})
}
/// The error returned from [`Repr::from_attrs`].
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
enum FromAttrsError {
FromRawReprs(FromRawReprsError<UnsupportedReprError>),
Unrecognized,
}
impl From<FromRawReprsError<UnsupportedReprError>> for FromAttrsError {
fn from(err: FromRawReprsError<UnsupportedReprError>) -> FromAttrsError {
FromAttrsError::FromRawReprs(err)
}
}
impl From<UnrecognizedReprError> for FromAttrsError {
fn from(_err: UnrecognizedReprError) -> FromAttrsError {
FromAttrsError::Unrecognized
}
}
impl From<Spanned<FromAttrsError>> for Error {
fn from(err: Spanned<FromAttrsError>) -> Error {
let Spanned { t: err, span } = err;
match err {
FromAttrsError::FromRawReprs(FromRawReprsError::Single(
_err @ UnsupportedReprError,
)) => Error::new(span, "unsupported representation hint for the decorated type"),
FromAttrsError::FromRawReprs(FromRawReprsError::Conflict) => {
// NOTE: This says "another" rather than "a preceding" because
// when one of the reprs involved is `transparent`, we detect
// that condition in `Repr::from_attrs`, and at that point we
// can't tell which repr came first, so we might report this on
// the first involved repr rather than the second, third, etc.
Error::new(span, "this conflicts with another representation hint")
}
FromAttrsError::Unrecognized => Error::new(span, "unrecognized representation hint"),
}
}
}
impl<Prim, Packed> Repr<Prim, Packed> {
fn from_attrs_inner(attrs: &[Attribute]) -> Result<Repr<Prim, Packed>, Spanned<FromAttrsError>>
where
Prim: With<PrimitiveRepr>,
Packed: With<NonZeroU32>,
{
let raw_reprs = RawRepr::from_attrs(attrs).map_err(Spanned::from)?;
let transparent = {
let mut transparents = raw_reprs.iter().filter_map(|Spanned { t, span }| match t {
RawRepr::Transparent => Some(span),
_ => None,
});
let first = transparents.next();
let second = transparents.next();
match (first, second) {
(None, None) => None,
(Some(span), None) => Some(*span),
(Some(_), Some(second)) => {
return Err(Spanned::new(
FromAttrsError::FromRawReprs(FromRawReprsError::Conflict),
*second,
))
}
// An iterator can't produce a value only on the second call to
// `.next()`.
(None, Some(_)) => unreachable!(),
}
};
let compound: Option<Spanned<CompoundRepr<Prim>>> =
try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?;
let align: Option<Spanned<AlignRepr<Packed>>> =
try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?;
if let Some(span) = transparent {
if compound.is_some() || align.is_some() {
// Arbitrarily report the problem on the `transparent` span. Any
// span will do.
return Err(Spanned::new(FromRawReprsError::Conflict.into(), span));
}
Ok(Repr::Transparent(span))
} else {
Ok(Repr::Compound(
compound.unwrap_or(Spanned::new(CompoundRepr::Rust, Span::call_site())),
align,
))
}
}
}
impl<Prim, Packed> Repr<Prim, Packed> {
pub(crate) fn from_attrs(attrs: &[Attribute]) -> Result<Repr<Prim, Packed>, Error>
where
Prim: With<PrimitiveRepr>,
Packed: With<NonZeroU32>,
{
Repr::from_attrs_inner(attrs).map_err(Into::into)
}
}
/// The representation hint could not be parsed or was unrecognized.
struct UnrecognizedReprError;
impl RawRepr {
fn from_attrs(
attrs: &[Attribute],
) -> Result<Vec<Spanned<RawRepr>>, Spanned<UnrecognizedReprError>> {
let mut reprs = Vec::new();
for attr in attrs {
// Ignore documentation attributes.
if attr.path().is_ident("doc") {
continue;
}
if let Meta::List(ref meta_list) = attr.meta {
if meta_list.path.is_ident("repr") {
let parsed: Punctuated<Meta, Comma> =
match meta_list.parse_args_with(Punctuated::parse_terminated) {
Ok(parsed) => parsed,
Err(_) => {
return Err(Spanned::new(
UnrecognizedReprError,
meta_list.tokens.span(),
))
}
};
for meta in parsed {
let s = meta.span();
reprs.push(
RawRepr::from_meta(&meta)
.map(|r| Spanned::new(r, s))
.map_err(|e| Spanned::new(e, s))?,
);
}
}
}
}
Ok(reprs)
}
fn from_meta(meta: &Meta) -> Result<RawRepr, UnrecognizedReprError> {
let (path, list) = match meta {
Meta::Path(path) => (path, None),
Meta::List(list) => (&list.path, Some(list)),
_ => return Err(UnrecognizedReprError),
};
let ident = path.get_ident().ok_or(UnrecognizedReprError)?;
// Only returns `Ok` for non-zero power-of-two values.
let parse_nzu64 = |list: &MetaList| {
list.parse_args::<LitInt>()
.and_then(|int| int.base10_parse::<NonZeroU32>())
.map_err(|_| UnrecognizedReprError)
.and_then(|nz| {
if nz.get().is_power_of_two() {
Ok(nz)
} else {
Err(UnrecognizedReprError)
}
})
};
use RawRepr::*;
Ok(match (ident.to_string().as_str(), list) {
("u8", None) => U8,
("u16", None) => U16,
("u32", None) => U32,
("u64", None) => U64,
("u128", None) => U128,
("usize", None) => Usize,
("i8", None) => I8,
("i16", None) => I16,
("i32", None) => I32,
("i64", None) => I64,
("i128", None) => I128,
("isize", None) => Isize,
("C", None) => C,
("transparent", None) => Transparent,
("Rust", None) => Rust,
("packed", None) => Packed,
("packed", Some(list)) => PackedN(parse_nzu64(list)?),
("align", Some(list)) => Align(parse_nzu64(list)?),
_ => return Err(UnrecognizedReprError),
})
}
}
pub(crate) use util::*;
mod util {
use super::*;
/// A value with an associated span.
#[derive(Copy, Clone)]
#[cfg_attr(test, derive(Debug))]
pub(crate) struct Spanned<T> {
pub(crate) t: T,
pub(crate) span: Span,
}
impl<T> Spanned<T> {
pub(super) fn new(t: T, span: Span) -> Spanned<T> {
Spanned { t, span }
}
pub(super) fn from<U>(s: Spanned<U>) -> Spanned<T>
where
T: From<U>,
{
let Spanned { t: u, span } = s;
Spanned::new(u.into(), span)
}
/// Delegates to `T: TryFrom`, preserving span information in both the
/// success and error cases.
pub(super) fn try_from<E, U>(
u: Spanned<U>,
) -> Result<Spanned<T>, FromRawReprError<Spanned<E>>>
where
T: TryFrom<U, Error = FromRawReprError<E>>,
{
let Spanned { t: u, span } = u;
T::try_from(u).map(|t| Spanned { t, span }).map_err(|err| match err {
FromRawReprError::None => FromRawReprError::None,
FromRawReprError::Err(e) => FromRawReprError::Err(Spanned::new(e, span)),
})
}
}
// Used to permit implementing `With<T> for T: Inhabited` and for
// `Infallible` without a blanket impl conflict.
pub(crate) trait Inhabited {}
impl Inhabited for PrimitiveRepr {}
impl Inhabited for NonZeroU32 {}
pub(crate) trait With<T> {
fn with<O, F: FnOnce(T) -> O>(self, f: F) -> O;
fn try_with_or<E, F: FnOnce() -> Result<T, E>>(f: F, err: E) -> Result<Self, E>
where
Self: Sized;
}
impl<T: Inhabited> With<T> for T {
fn with<O, F: FnOnce(T) -> O>(self, f: F) -> O {
f(self)
}
fn try_with_or<E, F: FnOnce() -> Result<T, E>>(f: F, _err: E) -> Result<Self, E> {
f()
}
}
impl<T> With<T> for Infallible {
fn with<O, F: FnOnce(T) -> O>(self, _f: F) -> O {
match self {}
}
fn try_with_or<E, F: FnOnce() -> Result<T, E>>(_f: F, err: E) -> Result<Self, E> {
Err(err)
}
}
}
#[cfg(test)]
mod tests {
use syn::parse_quote;
use super::*;
impl<T> From<T> for Spanned<T> {
fn from(t: T) -> Spanned<T> {
Spanned::new(t, Span::call_site())
}
}
// We ignore spans for equality in testing since real spans are hard to
// synthesize and don't implement `PartialEq`.
impl<T: PartialEq> PartialEq for Spanned<T> {
fn eq(&self, other: &Spanned<T>) -> bool {
self.t.eq(&other.t)
}
}
impl<T: Eq> Eq for Spanned<T> {}
impl<Prim: PartialEq, Packed: PartialEq> PartialEq for Repr<Prim, Packed> {
fn eq(&self, other: &Repr<Prim, Packed>) -> bool {
match (self, other) {
(Repr::Transparent(_), Repr::Transparent(_)) => true,
(Repr::Compound(sc, sa), Repr::Compound(oc, oa)) => (sc, sa) == (oc, oa),
_ => false,
}
}
}
fn s() -> Span {
Span::call_site()
}
#[test]
fn test() {
// Test that a given `#[repr(...)]` attribute parses and returns the
// given `Repr` or error.
macro_rules! test {
($(#[$attr:meta])* => $repr:expr) => {
test!(@inner $(#[$attr])* => Repr => Ok($repr));
};
// In the error case, the caller must explicitly provide the name of
// the `Repr` type to assist in type inference.
(@error $(#[$attr:meta])* => $typ:ident => $repr:expr) => {
test!(@inner $(#[$attr])* => $typ => Err($repr));
};
(@inner $(#[$attr:meta])* => $typ:ident => $repr:expr) => {
let attr: Attribute = parse_quote!($(#[$attr])*);
let mut got = $typ::from_attrs_inner(&[attr]);
let expect: Result<Repr<_, _>, _> = $repr;
if false {
// Force Rust to infer `got` as having the same type as
// `expect`.
got = expect;
}
assert_eq!(got, expect, stringify!($(#[$attr])*));
};
}
use AlignRepr::*;
use CompoundRepr::*;
use PrimitiveRepr::*;
let nz = |n: u32| NonZeroU32::new(n).unwrap();
test!(#[repr(transparent)] => StructUnionRepr::Transparent(s()));
test!(#[repr()] => StructUnionRepr::Compound(Rust.into(), None));
test!(#[repr(packed)] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(1)).into())));
test!(#[repr(packed(2))] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(2)).into())));
test!(#[repr(align(1))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(1)).into())));
test!(#[repr(align(2))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(2)).into())));
test!(#[repr(C)] => StructUnionRepr::Compound(C.into(), None));
test!(#[repr(C, packed)] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(1)).into())));
test!(#[repr(C, packed(2))] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(2)).into())));
test!(#[repr(C, align(1))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(1)).into())));
test!(#[repr(C, align(2))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(2)).into())));
test!(#[repr(transparent)] => EnumRepr::Transparent(s()));
test!(#[repr()] => EnumRepr::Compound(Rust.into(), None));
test!(#[repr(align(1))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(1)).into())));
test!(#[repr(align(2))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(2)).into())));
macro_rules! for_each_compound_repr {
($($r:tt => $var:expr),*) => {
$(
test!(#[repr($r)] => EnumRepr::Compound($var.into(), None));
test!(#[repr($r, align(1))] => EnumRepr::Compound($var.into(), Some(Align(nz(1)).into())));
test!(#[repr($r, align(2))] => EnumRepr::Compound($var.into(), Some(Align(nz(2)).into())));
)*
}
}
for_each_compound_repr!(
C => C,
u8 => Primitive(U8),
u16 => Primitive(U16),
u32 => Primitive(U32),
u64 => Primitive(U64),
usize => Primitive(Usize),
i8 => Primitive(I8),
i16 => Primitive(I16),
i32 => Primitive(I32),
i64 => Primitive(I64),
isize => Primitive(Isize)
);
use FromAttrsError::*;
use FromRawReprsError::*;
// Run failure tests which are valid for both `StructUnionRepr` and
// `EnumRepr`.
macro_rules! for_each_repr_type {
($($repr:ident),*) => {
$(
// Invalid packed or align attributes
test!(@error #[repr(packed(0))] => $repr => Unrecognized.into());
test!(@error #[repr(packed(3))] => $repr => Unrecognized.into());
test!(@error #[repr(align(0))] => $repr => Unrecognized.into());
test!(@error #[repr(align(3))] => $repr => Unrecognized.into());
// Conflicts
test!(@error #[repr(transparent, transparent)] => $repr => FromRawReprs(Conflict).into());
test!(@error #[repr(transparent, C)] => $repr => FromRawReprs(Conflict).into());
test!(@error #[repr(transparent, Rust)] => $repr => FromRawReprs(Conflict).into());
test!(@error #[repr(C, transparent)] => $repr => FromRawReprs(Conflict).into());
test!(@error #[repr(C, C)] => $repr => FromRawReprs(Conflict).into());
test!(@error #[repr(C, Rust)] => $repr => FromRawReprs(Conflict).into());
test!(@error #[repr(Rust, transparent)] => $repr => FromRawReprs(Conflict).into());
test!(@error #[repr(Rust, C)] => $repr => FromRawReprs(Conflict).into());
test!(@error #[repr(Rust, Rust)] => $repr => FromRawReprs(Conflict).into());
)*
}
}
for_each_repr_type!(StructUnionRepr, EnumRepr);
// Enum-specific conflicts.
//
// We don't bother to test every combination since that would be a huge
// number (enums can have primitive reprs u8, u16, u32, u64, usize, i8,
// i16, i32, i64, and isize). Instead, since the conflict logic doesn't
// care what specific value of `PrimitiveRepr` is present, we assume
// that testing against u8 alone is fine.
test!(@error #[repr(transparent, u8)] => EnumRepr => FromRawReprs(Conflict).into());
test!(@error #[repr(u8, transparent)] => EnumRepr => FromRawReprs(Conflict).into());
test!(@error #[repr(C, u8)] => EnumRepr => FromRawReprs(Conflict).into());
test!(@error #[repr(u8, C)] => EnumRepr => FromRawReprs(Conflict).into());
test!(@error #[repr(Rust, u8)] => EnumRepr => FromRawReprs(Conflict).into());
test!(@error #[repr(u8, Rust)] => EnumRepr => FromRawReprs(Conflict).into());
test!(@error #[repr(u8, u8)] => EnumRepr => FromRawReprs(Conflict).into());
// Illegal struct/union reprs
test!(@error #[repr(u8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(u16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(u32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(u64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(usize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(i8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(i16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(i32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(i64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(isize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
// Illegal enum reprs
test!(@error #[repr(packed)] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(packed(1))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
test!(@error #[repr(packed(2))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
}
}

View File

@ -0,0 +1,845 @@
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
// Copyright 2019 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.
use std::num::NonZeroU32;
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::{
parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
Expr, ExprLit, Field, GenericParam, Ident, Index, Lit, LitStr, Meta, Path, Type, Variant,
Visibility, WherePredicate,
};
use crate::repr::{CompoundRepr, EnumRepr, PrimitiveRepr, Repr, Spanned};
pub(crate) struct Ctx {
pub(crate) ast: DeriveInput,
pub(crate) zerocopy_crate: Path,
// The value of the last `#[zerocopy(on_error = ...)]` attribute, or `false`
// if none is provided.
pub(crate) skip_on_error: bool,
// The span of the last `#[zerocopy(on_error = ...)]` attribute, if any.
pub(crate) on_error_span: Option<proc_macro2::Span>,
}
impl Ctx {
/// Attempt to extract a crate path from the provided attributes. Defaults to
/// `::zerocopy` if not found.
pub(crate) fn try_from_derive_input(ast: DeriveInput) -> Result<Self, Error> {
let mut path = parse_quote!(::zerocopy);
let mut skip_on_error = false;
let mut on_error_span = None;
for attr in &ast.attrs {
if let Meta::List(ref meta_list) = attr.meta {
if meta_list.path.is_ident("zerocopy") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("crate") {
let expr = meta.value().and_then(|value| value.parse());
if let Ok(Expr::Lit(ExprLit { lit: Lit::Str(lit), .. })) = expr {
if let Ok(path_lit) = lit.parse::<Ident>() {
path = parse_quote!(::#path_lit);
return Ok(());
}
}
return Err(Error::new(
Span::call_site(),
"`crate` attribute requires a path as the value",
));
}
if meta.path.is_ident("on_error") {
on_error_span = Some(meta.path.span());
let value = meta.value()?;
let s: LitStr = value.parse()?;
match s.value().as_str() {
"skip" => skip_on_error = true,
"fail" => skip_on_error = false,
_ => return Err(Error::new(
s.span(),
"unrecognized value for `on_error` attribute from `zerocopy`; expected `skip` or `fail`",
)),
}
return Ok(());
}
Err(Error::new(
Span::call_site(),
format!(
"unknown attribute encountered: {}",
meta.path.into_token_stream()
),
))
})?;
}
}
}
Ok(Self { ast, zerocopy_crate: path, skip_on_error, on_error_span })
}
pub(crate) fn with_input(&self, input: &DeriveInput) -> Self {
Self {
ast: input.clone(),
zerocopy_crate: self.zerocopy_crate.clone(),
skip_on_error: self.skip_on_error,
on_error_span: self.on_error_span,
}
}
pub(crate) fn core_path(&self) -> TokenStream {
let zerocopy_crate = &self.zerocopy_crate;
quote!(#zerocopy_crate::util::macro_util::core_reexport)
}
pub(crate) fn cfg_compile_error(&self) -> TokenStream {
// By checking both during the compilation of the proc macro *and* in
// the generated code, we ensure that `--cfg
// zerocopy_unstable_derive_on_error` need only be passed *either* when
// compiling this crate *or* when compiling the user's crate. The former
// is preferable, but in some situations (such as when cross-compiling
// using `cargo build --target`), it doesn't get propagated to this
// crate's build by default.
if cfg!(zerocopy_unstable_derive_on_error) {
quote!()
} else if let Some(span) = self.on_error_span {
let core = self.core_path();
let error_message = "`on_error` is experimental; pass '--cfg zerocopy_unstable_derive_on_error' to enable";
quote::quote_spanned! {span=>
#[allow(unused_attributes, unexpected_cfgs)]
const _: () = {
#[cfg(not(zerocopy_unstable_derive_on_error))]
#core::compile_error!(#error_message);
};
}
} else {
quote!()
}
}
pub(crate) fn error_or_skip<E>(&self, error: E) -> Result<TokenStream, E> {
if self.skip_on_error {
Ok(self.cfg_compile_error())
} else {
Err(error)
}
}
}
pub(crate) trait DataExt {
/// Extracts the names and types of all fields. For enums, extracts the
/// names and types of fields from each variant. For tuple structs, the
/// names are the indices used to index into the struct (ie, `0`, `1`, etc).
///
/// FIXME: Extracting field names for enums doesn't really make sense. Types
/// makes sense because we don't care about where they live - we just care
/// about transitive ownership. But for field names, we'd only use them when
/// generating is_bit_valid, which cares about where they live.
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)>;
fn tag(&self) -> Option<Ident>;
}
impl DataExt for Data {
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
match self {
Data::Struct(strc) => strc.fields(),
Data::Enum(enm) => enm.fields(),
Data::Union(un) => un.fields(),
}
}
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
match self {
Data::Struct(strc) => strc.variants(),
Data::Enum(enm) => enm.variants(),
Data::Union(un) => un.variants(),
}
}
fn tag(&self) -> Option<Ident> {
match self {
Data::Struct(strc) => strc.tag(),
Data::Enum(enm) => enm.tag(),
Data::Union(un) => un.tag(),
}
}
}
impl DataExt for DataStruct {
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
map_fields(&self.fields)
}
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
vec![(None, self.fields())]
}
fn tag(&self) -> Option<Ident> {
None
}
}
impl DataExt for DataEnum {
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
map_fields(self.variants.iter().flat_map(|var| &var.fields))
}
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
self.variants.iter().map(|var| (Some(var), map_fields(&var.fields))).collect()
}
fn tag(&self) -> Option<Ident> {
Some(Ident::new("___ZerocopyTag", Span::call_site()))
}
}
impl DataExt for DataUnion {
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
map_fields(&self.fields.named)
}
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
vec![(None, self.fields())]
}
fn tag(&self) -> Option<Ident> {
None
}
}
fn map_fields<'a>(
fields: impl 'a + IntoIterator<Item = &'a Field>,
) -> Vec<(&'a Visibility, TokenStream, &'a Type)> {
fields
.into_iter()
.enumerate()
.map(|(idx, f)| {
(
&f.vis,
f.ident
.as_ref()
.map(ToTokens::to_token_stream)
.unwrap_or_else(|| Index::from(idx).to_token_stream()),
&f.ty,
)
})
.collect()
}
pub(crate) fn to_ident_str(t: &impl ToString) -> String {
let s = t.to_string();
if let Some(stripped) = s.strip_prefix("r#") {
stripped.to_string()
} else {
s
}
}
/// This enum describes what kind of padding check needs to be generated for the
/// associated impl.
pub(crate) enum PaddingCheck {
/// Check that the sum of the fields' sizes exactly equals the struct's
/// size.
Struct,
/// Check that a `repr(C)` struct has no padding.
ReprCStruct,
/// Check that the size of each field exactly equals the union's size.
Union,
/// Check that every variant of the enum contains no padding.
///
/// Because doing so requires a tag enum, this padding check requires an
/// additional `TokenStream` which defines the tag enum as `___ZerocopyTag`.
Enum { tag_type_definition: TokenStream },
}
impl PaddingCheck {
/// Returns the idents of the trait to use and the macro to call in order to
/// validate that a type passes the relevant padding check.
pub(crate) fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) {
let (trt, mcro) = match self {
PaddingCheck::Struct => ("PaddingFree", "struct_padding"),
PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"),
PaddingCheck::Union => ("PaddingFree", "union_padding"),
PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"),
};
let trt = Ident::new(trt, Span::call_site());
let mcro = Ident::new(mcro, Span::call_site());
(trt, mcro)
}
/// Sometimes performing the padding check requires some additional
/// "context" code. For enums, this is the definition of the tag enum.
pub(crate) fn validator_macro_context(&self) -> Option<&TokenStream> {
match self {
PaddingCheck::Struct | PaddingCheck::ReprCStruct | PaddingCheck::Union => None,
PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),
}
}
}
#[derive(Clone)]
pub(crate) enum Trait {
KnownLayout,
HasTag,
HasField {
variant_id: Box<Expr>,
field: Box<Type>,
field_id: Box<Expr>,
},
ProjectField {
variant_id: Box<Expr>,
field: Box<Type>,
field_id: Box<Expr>,
invariants: Box<Type>,
},
Immutable,
TryFromBytes,
FromZeros,
FromBytes,
IntoBytes,
Unaligned,
Sized,
ByteHash,
ByteEq,
SplitAt,
}
impl ToTokens for Trait {
fn to_tokens(&self, tokens: &mut TokenStream) {
// According to [1], the format of the derived `Debug`` output is not
// stable and therefore not guaranteed to represent the variant names.
// Indeed with the (unstable) `fmt-debug` compiler flag [2], it can
// return only a minimalized output or empty string. To make sure this
// code will work in the future and independent of the compiler flag, we
// translate the variants to their names manually here.
//
// [1] https://doc.rust-lang.org/1.81.0/std/fmt/trait.Debug.html#stability
// [2] https://doc.rust-lang.org/beta/unstable-book/compiler-flags/fmt-debug.html
let s = match self {
Trait::HasField { .. } => "HasField",
Trait::ProjectField { .. } => "ProjectField",
Trait::KnownLayout => "KnownLayout",
Trait::HasTag => "HasTag",
Trait::Immutable => "Immutable",
Trait::TryFromBytes => "TryFromBytes",
Trait::FromZeros => "FromZeros",
Trait::FromBytes => "FromBytes",
Trait::IntoBytes => "IntoBytes",
Trait::Unaligned => "Unaligned",
Trait::Sized => "Sized",
Trait::ByteHash => "ByteHash",
Trait::ByteEq => "ByteEq",
Trait::SplitAt => "SplitAt",
};
let ident = Ident::new(s, Span::call_site());
let arguments: Option<syn::AngleBracketedGenericArguments> = match self {
Trait::HasField { variant_id, field, field_id } => {
Some(parse_quote!(<#field, #variant_id, #field_id>))
}
Trait::ProjectField { variant_id, field, field_id, invariants } => {
Some(parse_quote!(<#field, #invariants, #variant_id, #field_id>))
}
Trait::KnownLayout
| Trait::HasTag
| Trait::Immutable
| Trait::TryFromBytes
| Trait::FromZeros
| Trait::FromBytes
| Trait::IntoBytes
| Trait::Unaligned
| Trait::Sized
| Trait::ByteHash
| Trait::ByteEq
| Trait::SplitAt => None,
};
tokens.extend(quote!(#ident #arguments));
}
}
impl Trait {
pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path {
let zerocopy_crate = &ctx.zerocopy_crate;
let core = ctx.core_path();
match self {
Self::Sized => parse_quote!(#core::marker::#self),
_ => parse_quote!(#zerocopy_crate::#self),
}
}
}
pub(crate) enum TraitBound {
Slf,
Other(Trait),
}
pub(crate) enum FieldBounds<'a> {
None,
All(&'a [TraitBound]),
Trailing(&'a [TraitBound]),
Explicit(Vec<WherePredicate>),
}
impl<'a> FieldBounds<'a> {
pub(crate) const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);
pub(crate) const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
}
pub(crate) enum SelfBounds<'a> {
None,
All(&'a [Trait]),
}
// FIXME(https://github.com/rust-lang/rust-clippy/issues/12908): This is a false
// positive. Explicit lifetimes are actually necessary here.
#[allow(clippy::needless_lifetimes)]
impl<'a> SelfBounds<'a> {
pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]);
}
/// Normalizes a slice of bounds by replacing [`TraitBound::Slf`] with `slf`.
pub(crate) fn normalize_bounds<'a>(
slf: &'a Trait,
bounds: &'a [TraitBound],
) -> impl 'a + Iterator<Item = Trait> {
bounds.iter().map(move |bound| match bound {
TraitBound::Slf => slf.clone(),
TraitBound::Other(trt) => trt.clone(),
})
}
pub(crate) struct ImplBlockBuilder<'a> {
ctx: &'a Ctx,
data: &'a dyn DataExt,
trt: Trait,
field_type_trait_bounds: FieldBounds<'a>,
self_type_trait_bounds: SelfBounds<'a>,
padding_check: Option<PaddingCheck>,
param_extras: Vec<GenericParam>,
inner_extras: Option<TokenStream>,
outer_extras: Option<TokenStream>,
}
impl<'a> ImplBlockBuilder<'a> {
pub(crate) fn new(
ctx: &'a Ctx,
data: &'a dyn DataExt,
trt: Trait,
field_type_trait_bounds: FieldBounds<'a>,
) -> Self {
Self {
ctx,
data,
trt,
field_type_trait_bounds,
self_type_trait_bounds: SelfBounds::None,
padding_check: None,
param_extras: Vec::new(),
inner_extras: None,
outer_extras: None,
}
}
pub(crate) fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self {
self.self_type_trait_bounds = self_type_trait_bounds;
self
}
pub(crate) fn padding_check<P: Into<Option<PaddingCheck>>>(mut self, padding_check: P) -> Self {
self.padding_check = padding_check.into();
self
}
pub(crate) fn param_extras(mut self, param_extras: Vec<GenericParam>) -> Self {
self.param_extras.extend(param_extras);
self
}
pub(crate) fn inner_extras(mut self, inner_extras: TokenStream) -> Self {
self.inner_extras = Some(inner_extras);
self
}
pub(crate) fn outer_extras<T: Into<Option<TokenStream>>>(mut self, outer_extras: T) -> Self {
self.outer_extras = outer_extras.into();
self
}
pub(crate) fn build(self) -> TokenStream {
// In this documentation, we will refer to this hypothetical struct:
//
// #[derive(FromBytes)]
// struct Foo<T, I: Iterator>
// where
// T: Copy,
// I: Clone,
// I::Item: Clone,
// {
// a: u8,
// b: T,
// c: I::Item,
// }
//
// We extract the field types, which in this case are `u8`, `T`, and
// `I::Item`. We re-use the existing parameters and where clauses. If
// `require_trait_bound == true` (as it is for `FromBytes), we add where
// bounds for each field's type:
//
// impl<T, I: Iterator> FromBytes for Foo<T, I>
// where
// T: Copy,
// I: Clone,
// I::Item: Clone,
// T: FromBytes,
// I::Item: FromBytes,
// {
// }
//
// NOTE: It is standard practice to only emit bounds for the type
// parameters themselves, not for field types based on those parameters
// (e.g., `T` vs `T::Foo`). For a discussion of why this is standard
// practice, see https://github.com/rust-lang/rust/issues/26925.
//
// The reason we diverge from this standard is that doing it that way
// for us would be unsound. E.g., consider a type, `T` where `T:
// FromBytes` but `T::Foo: !FromBytes`. It would not be sound for us to
// accept a type with a `T::Foo` field as `FromBytes` simply because `T:
// FromBytes`.
//
// While there's no getting around this requirement for us, it does have
// the pretty serious downside that, when lifetimes are involved, the
// trait solver ties itself in knots:
//
// #[derive(Unaligned)]
// #[repr(C)]
// struct Dup<'a, 'b> {
// a: PhantomData<&'a u8>,
// b: PhantomData<&'b u8>,
// }
//
// error[E0283]: type annotations required: cannot resolve `core::marker::PhantomData<&'a u8>: zerocopy::Unaligned`
// --> src/main.rs:6:10
// |
// 6 | #[derive(Unaligned)]
// | ^^^^^^^^^
// |
// = note: required by `zerocopy::Unaligned`
let type_ident = &self.ctx.ast.ident;
let trait_path = self.trt.crate_path(self.ctx);
let fields = self.data.fields();
let variants = self.data.variants();
let tag = self.data.tag();
let zerocopy_crate = &self.ctx.zerocopy_crate;
fn bound_tt(ty: &Type, traits: impl Iterator<Item = Trait>, ctx: &Ctx) -> WherePredicate {
let traits = traits.map(|t| t.crate_path(ctx));
parse_quote!(#ty: #(#traits)+*)
}
let field_type_bounds: Vec<_> = match (self.field_type_trait_bounds, &fields[..]) {
(FieldBounds::All(traits), _) => fields
.iter()
.map(|(_vis, _name, ty)| {
bound_tt(ty, normalize_bounds(&self.trt, traits), self.ctx)
})
.collect(),
(FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
(FieldBounds::Trailing(traits), [.., last]) => {
vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), self.ctx)]
}
(FieldBounds::Explicit(bounds), _) => bounds,
};
let padding_check_bound = self
.padding_check
.map(|check| {
// Parse the repr for `align` and `packed` modifiers. Note that
// `Repr::<PrimitiveRepr, NonZeroU32>` is more permissive than
// what Rust supports for structs, enums, or unions, and thus
// reliably extracts these modifiers for any kind of type.
let repr =
Repr::<PrimitiveRepr, NonZeroU32>::from_attrs(&self.ctx.ast.attrs).unwrap();
let core = self.ctx.core_path();
let option = quote! { #core::option::Option };
let nonzero = quote! { #core::num::NonZeroUsize };
let none = quote! { #option::None::<#nonzero> };
let repr_align =
repr.get_align().map(|spanned| {
let n = spanned.t.get();
quote_spanned! { spanned.span => (#nonzero::new(#n as usize)) }
}).unwrap_or(quote! { (#none) });
let repr_packed =
repr.get_packed().map(|packed| {
let n = packed.get();
quote! { (#nonzero::new(#n as usize)) }
}).unwrap_or(quote! { (#none) });
let variant_types = variants.iter().map(|(_, fields)| {
let types = fields.iter().map(|(_vis, _name, ty)| ty);
quote!([#((#types)),*])
});
let validator_context = check.validator_macro_context();
let (trt, validator_macro) = check.validator_trait_and_macro_idents();
let t = tag.iter();
parse_quote! {
(): #zerocopy_crate::util::macro_util::#trt<
Self,
{
#validator_context
#zerocopy_crate::#validator_macro!(Self, #repr_align, #repr_packed, #(#t,)* #(#variant_types),*)
}
>
}
});
let self_bounds: Option<WherePredicate> = match self.self_type_trait_bounds {
SelfBounds::None => None,
SelfBounds::All(traits) => {
Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), self.ctx))
}
};
let bounds = self
.ctx
.ast
.generics
.where_clause
.as_ref()
.map(|where_clause| where_clause.predicates.iter())
.into_iter()
.flatten()
.chain(field_type_bounds.iter())
.chain(padding_check_bound.iter())
.chain(self_bounds.iter());
// The parameters with trait bounds, but without type defaults.
let mut params: Vec<_> = self
.ctx
.ast
.generics
.params
.clone()
.into_iter()
.map(|mut param| {
match &mut param {
GenericParam::Type(ty) => ty.default = None,
GenericParam::Const(cnst) => cnst.default = None,
GenericParam::Lifetime(_) => {}
}
parse_quote!(#param)
})
.chain(self.param_extras)
.collect();
// For MSRV purposes, ensure that lifetimes precede types precede const
// generics.
params.sort_by_cached_key(|param| match param {
GenericParam::Lifetime(_) => 0,
GenericParam::Type(_) => 1,
GenericParam::Const(_) => 2,
});
// The identifiers of the parameters without trait bounds or type
// defaults.
let param_idents = self.ctx.ast.generics.params.iter().map(|param| match param {
GenericParam::Type(ty) => {
let ident = &ty.ident;
quote!(#ident)
}
GenericParam::Lifetime(l) => {
let ident = &l.lifetime;
quote!(#ident)
}
GenericParam::Const(cnst) => {
let ident = &cnst.ident;
quote!({#ident})
}
});
let inner_extras = self.inner_extras;
let allow_trivial_bounds =
if self.ctx.skip_on_error { quote!(#[allow(trivial_bounds)]) } else { quote!() };
let impl_tokens = quote! {
#allow_trivial_bounds
unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* >
where
#(#bounds,)*
{
fn only_derive_is_allowed_to_implement_this_trait() {}
#inner_extras
}
};
let outer_extras = self.outer_extras.filter(|e| !e.is_empty());
let cfg_compile_error = self.ctx.cfg_compile_error();
const_block([Some(cfg_compile_error), Some(impl_tokens), outer_extras])
}
}
// A polyfill for `Option::then_some`, which was added after our MSRV.
//
// The `#[allow(unused)]` is necessary because, on sufficiently recent toolchain
// versions, `b.then_some(...)` resolves to the inherent method rather than to
// this trait, and so this trait is considered unused.
//
// FIXME(#67): Remove this once our MSRV is >= 1.62.
#[allow(unused)]
trait BoolExt {
fn then_some<T>(self, t: T) -> Option<T>;
}
impl BoolExt for bool {
fn then_some<T>(self, t: T) -> Option<T> {
if self {
Some(t)
} else {
None
}
}
}
pub(crate) fn const_block(items: impl IntoIterator<Item = Option<TokenStream>>) -> TokenStream {
let items = items.into_iter().flatten();
quote! {
#[allow(
// FIXME(#553): Add a test that generates a warning when
// `#[allow(deprecated)]` isn't present.
deprecated,
// Required on some rustc versions due to a lint that is only
// triggered when `derive(KnownLayout)` is applied to `repr(C)`
// structs that are generated by macros. See #2177 for details.
private_bounds,
non_local_definitions,
non_camel_case_types,
non_upper_case_globals,
non_snake_case,
non_ascii_idents,
clippy::missing_inline_in_public_items,
)]
#[deny(ambiguous_associated_items)]
// While there are not currently any warnings that this suppresses
// (that we're aware of), it's good future-proofing hygiene.
#[automatically_derived]
const _: () = {
#(#items)*
};
}
}
pub(crate) fn generate_tag_enum(ctx: &Ctx, repr: &EnumRepr, data: &DataEnum) -> TokenStream {
let zerocopy_crate = &ctx.zerocopy_crate;
let variants = data.variants.iter().map(|v| {
let ident = &v.ident;
if let Some((eq, discriminant)) = &v.discriminant {
quote! { #ident #eq #discriminant }
} else {
quote! { #ident }
}
});
// Don't include any `repr(align)` when generating the tag enum, as that
// could add padding after the tag but before any variants, which is not the
// correct behavior.
let repr = match repr {
EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] },
EnumRepr::Compound(c, _) => quote! { #c },
};
quote! {
#repr
#[allow(dead_code)]
pub enum ___ZerocopyTag {
#(#variants,)*
}
// SAFETY: `___ZerocopyTag` has no fields, and so it does not permit
// interior mutation.
unsafe impl #zerocopy_crate::Immutable for ___ZerocopyTag {
fn only_derive_is_allowed_to_implement_this_trait() {}
}
}
}
pub(crate) fn enum_size_from_repr(repr: &EnumRepr) -> Result<usize, Error> {
use CompoundRepr::*;
use PrimitiveRepr::*;
use Repr::*;
match repr {
Transparent(span)
| Compound(
Spanned {
t: C | Rust | Primitive(U32 | I32 | U64 | I64 | U128 | I128 | Usize | Isize),
span,
},
_,
) => Err(Error::new(
*span,
"`FromBytes` only supported on enums with `#[repr(...)]` attributes `u8`, `i8`, `u16`, or `i16`",
)),
Compound(Spanned { t: Primitive(U8 | I8), span: _ }, _align) => Ok(8),
Compound(Spanned { t: Primitive(U16 | I16), span: _ }, _align) => Ok(16),
}
}
#[cfg(test)]
pub(crate) mod testutil {
use proc_macro2::TokenStream;
use syn::visit::{self, Visit};
/// Checks for hygiene violations in the generated code.
///
/// # Panics
///
/// Panics if a hygiene violation is found.
pub(crate) fn check_hygiene(ts: TokenStream) {
struct AmbiguousItemVisitor;
impl<'ast> Visit<'ast> for AmbiguousItemVisitor {
fn visit_path(&mut self, i: &'ast syn::Path) {
if i.segments.len() > 1 && i.segments.first().unwrap().ident == "Self" {
panic!(
"Found ambiguous path `{}` in generated output. \
All associated item access must be fully qualified (e.g., `<Self as Trait>::Item`) \
to prevent hygiene issues.",
quote::quote!(#i)
);
}
visit::visit_path(self, i);
}
}
let file = syn::parse2::<syn::File>(ts).expect("failed to parse generated output as File");
AmbiguousItemVisitor.visit_file(&file);
}
#[test]
fn test_check_hygiene_success() {
check_hygiene(quote::quote! {
fn foo() {
let _ = <Self as Trait>::Item;
}
});
}
#[test]
#[should_panic(expected = "Found ambiguous path `Self :: Ambiguous`")]
fn test_check_hygiene_failure() {
check_hygiene(quote::quote! {
fn foo() {
let _ = Self::Ambiguous;
}
});
}
}

14
rust/zerocopy/README.md Normal file
View File

@ -0,0 +1,14 @@
# `zerocopy`
These source files come from the Rust `zerocopy` crate, version v0.8.50
(released 2026-05-31), hosted in the <https://github.com/google/zerocopy>
repository, licensed under "BSD-2-Clause OR Apache-2.0 OR MIT" and only
modified to add the SPDX license identifiers and to remove `Display`
for `f32` and `f64`.
For copyright details, please see:
https://github.com/google/zerocopy/blob/v0.8.50/README.md?plain=1
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-BSD
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-APACHE
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-MIT

View File

@ -0,0 +1,9 @@
use zerocopy::*;
#[path = "formats/coco_dynamic_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_as_bytes_dynamic_size(source: &format::CocoPacket) -> &[u8] {
source.as_bytes()
}

View File

@ -0,0 +1,5 @@
bench_as_bytes_dynamic_size:
mov rax, rdi
lea rdx, [2*rsi + 5]
and rdx, -2
ret

View File

@ -0,0 +1,47 @@
Iterations: 100
Instructions: 400
Total Cycles: 137
Total uOps: 400
Dispatch Width: 4
uOps Per Cycle: 2.92
IPC: 2.92
Block RThroughput: 1.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 mov rax, rdi
1 1 0.50 lea rdx, [2*rsi + 5]
1 1 0.33 and rdx, -2
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 1.33 1.33 - 1.34 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - 0.66 - 0.34 - - mov rax, rdi
- - 0.33 0.67 - - - - lea rdx, [2*rsi + 5]
- - 1.00 - - - - - and rdx, -2
- - - - - 1.00 - - ret

View File

@ -0,0 +1,9 @@
use zerocopy::*;
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_as_bytes_static_size(source: &format::CocoPacket) -> &[u8] {
source.as_bytes()
}

View File

@ -0,0 +1,4 @@
bench_as_bytes_static_size:
mov rax, rdi
mov edx, 6
ret

View File

@ -0,0 +1,45 @@
Iterations: 100
Instructions: 300
Total Cycles: 104
Total uOps: 300
Dispatch Width: 4
uOps Per Cycle: 2.88
IPC: 2.88
Block RThroughput: 1.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 mov rax, rdi
1 1 0.33 mov edx, 6
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 0.99 1.00 - 1.01 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.99 - - 0.01 - - mov rax, rdi
- - - 1.00 - - - - mov edx, 6
- - - - - 1.00 - - ret

View File

@ -0,0 +1,9 @@
use zerocopy::*;
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_extend_vec_zeroed(v: &mut Vec<format::LocoPacket>, additional: usize) -> Option<()> {
FromZeros::extend_vec_zeroed(v, additional).ok()
}

View File

@ -0,0 +1,60 @@
bench_extend_vec_zeroed:
push r15
push r14
push r13
push r12
push rbx
sub rsp, 32
mov rbx, rdi
mov rax, qword ptr [rdi]
mov r12, qword ptr [rdi + 16]
mov rcx, rax
sub rcx, r12
cmp rsi, rcx
jbe .LBB6_3
mov r15, r12
add r15, rsi
jae .LBB6_6
.LBB6_2:
xor eax, eax
jmp .LBB6_5
.LBB6_3:
mov rax, qword ptr [rbx + 8]
lea r15, [r12 + rsi]
.LBB6_4:
lea rcx, [r12 + 2*r12]
lea rdi, [rax + 2*rcx]
add rsi, rsi
lea rdx, [rsi + 2*rsi]
xor esi, esi
call qword ptr [rip + memset@GOTPCREL]
mov qword ptr [rbx + 16], r15
mov al, 1
.LBB6_5:
add rsp, 32
pop rbx
pop r12
pop r13
pop r14
pop r15
ret
.LBB6_6:
mov r13, rsi
lea rcx, [rax + rax]
cmp r15, rcx
cmova rcx, r15
cmp rcx, 5
mov r14d, 4
cmovae r14, rcx
mov rdx, qword ptr [rbx + 8]
lea rdi, [rsp + 8]
mov rsi, rax
mov rcx, r14
call <alloc::raw_vec::RawVecInner>::finish_grow
cmp dword ptr [rsp + 8], 1
je .LBB6_2
mov rax, qword ptr [rsp + 16]
mov qword ptr [rbx + 8], rax
mov qword ptr [rbx], r14
mov rsi, r13
jmp .LBB6_4

View File

@ -0,0 +1,147 @@
Iterations: 100
Instructions: 5400
Total Cycles: 6595
Total uOps: 6800
Dispatch Width: 4
uOps Per Cycle: 1.03
IPC: 0.82
Block RThroughput: 17.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
2 5 1.00 * push r15
2 5 1.00 * push r14
2 5 1.00 * push r13
2 5 1.00 * push r12
2 5 1.00 * push rbx
1 1 0.33 sub rsp, 32
1 1 0.33 mov rbx, rdi
1 5 0.50 * mov rax, qword ptr [rdi]
1 5 0.50 * mov r12, qword ptr [rdi + 16]
1 1 0.33 mov rcx, rax
1 1 0.33 sub rcx, r12
1 1 0.33 cmp rsi, rcx
1 1 1.00 jbe .LBB6_3
1 1 0.33 mov r15, r12
1 1 0.33 add r15, rsi
1 1 1.00 jae .LBB6_6
1 0 0.25 xor eax, eax
1 1 1.00 jmp .LBB6_5
1 5 0.50 * mov rax, qword ptr [rbx + 8]
1 1 0.50 lea r15, [r12 + rsi]
1 1 0.50 lea rcx, [r12 + 2*r12]
1 1 0.50 lea rdi, [rax + 2*rcx]
1 1 0.33 add rsi, rsi
1 1 0.50 lea rdx, [rsi + 2*rsi]
1 0 0.25 xor esi, esi
4 7 1.00 * call qword ptr [rip + memset@GOTPCREL]
1 1 1.00 * mov qword ptr [rbx + 16], r15
1 1 0.33 mov al, 1
1 1 0.33 add rsp, 32
1 6 0.50 * pop rbx
1 6 0.50 * pop r12
1 6 0.50 * pop r13
1 6 0.50 * pop r14
1 6 0.50 * pop r15
1 1 1.00 U ret
1 1 0.33 mov r13, rsi
1 1 0.50 lea rcx, [rax + rax]
1 1 0.33 cmp r15, rcx
3 3 1.00 cmova rcx, r15
1 1 0.33 cmp rcx, 5
1 1 0.33 mov r14d, 4
2 2 0.67 cmovae r14, rcx
1 5 0.50 * mov rdx, qword ptr [rbx + 8]
1 1 0.50 lea rdi, [rsp + 8]
1 1 0.33 mov rsi, rax
1 1 0.33 mov rcx, r14
3 5 1.00 call <alloc::raw_vec::RawVecInner>::finish_grow
2 6 0.50 * cmp dword ptr [rsp + 8], 1
1 1 1.00 je .LBB6_2
1 5 0.50 * mov rax, qword ptr [rsp + 16]
1 1 1.00 * mov qword ptr [rbx + 8], rax
1 1 1.00 * mov qword ptr [rbx], r14
1 1 0.33 mov rsi, r13
1 1 1.00 jmp .LBB6_4
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 12.00 12.00 10.00 13.00 11.00 11.00
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - 1.00 - 0.49 0.51 push r15
- - - - 1.00 - 0.51 0.49 push r14
- - - - 1.00 - 0.50 0.50 push r13
- - - - 1.00 - 0.50 0.50 push r12
- - - - 1.00 - 0.50 0.50 push rbx
- - 0.01 0.99 - - - - sub rsp, 32
- - - - - 1.00 - - mov rbx, rdi
- - - - - - 0.50 0.50 mov rax, qword ptr [rdi]
- - - - - - 0.50 0.50 mov r12, qword ptr [rdi + 16]
- - - 1.00 - - - - mov rcx, rax
- - - 0.99 - 0.01 - - sub rcx, r12
- - - - - 1.00 - - cmp rsi, rcx
- - - - - 1.00 - - jbe .LBB6_3
- - 0.01 0.98 - 0.01 - - mov r15, r12
- - 0.99 0.01 - - - - add r15, rsi
- - - - - 1.00 - - jae .LBB6_6
- - - - - - - - xor eax, eax
- - - - - 1.00 - - jmp .LBB6_5
- - - - - - 0.50 0.50 mov rax, qword ptr [rbx + 8]
- - 1.00 - - - - - lea r15, [r12 + rsi]
- - 0.98 0.02 - - - - lea rcx, [r12 + 2*r12]
- - 0.99 0.01 - - - - lea rdi, [rax + 2*rcx]
- - - 1.00 - - - - add rsi, rsi
- - 0.99 0.01 - - - - lea rdx, [rsi + 2*rsi]
- - - - - - - - xor esi, esi
- - - - 1.00 1.00 1.00 1.00 call qword ptr [rip + memset@GOTPCREL]
- - - - 1.00 - 0.50 0.50 mov qword ptr [rbx + 16], r15
- - 0.01 0.99 - - - - mov al, 1
- - 1.00 - - - - - add rsp, 32
- - - - - - 0.50 0.50 pop rbx
- - - - - - 0.50 0.50 pop r12
- - - - - - 0.50 0.50 pop r13
- - - - - - 0.50 0.50 pop r14
- - - - - - 0.50 0.50 pop r15
- - - - - 1.00 - - ret
- - 1.00 - - - - - mov r13, rsi
- - 0.01 0.99 - - - - lea rcx, [rax + rax]
- - 0.99 0.01 - - - - cmp r15, rcx
- - 2.00 0.01 - 0.99 - - cmova rcx, r15
- - 0.01 0.99 - - - - cmp rcx, 5
- - 0.01 0.99 - - - - mov r14d, 4
- - 1.00 0.01 - 0.99 - - cmovae r14, rcx
- - - - - - 0.50 0.50 mov rdx, qword ptr [rbx + 8]
- - 0.01 0.99 - - - - lea rdi, [rsp + 8]
- - - 1.00 - - - - mov rsi, rax
- - - 0.01 - 0.99 - - mov rcx, r14
- - - - 1.00 1.00 0.50 0.50 call <alloc::raw_vec::RawVecInner>::finish_grow
- - - 0.99 - 0.01 0.50 0.50 cmp dword ptr [rsp + 8], 1
- - - - - 1.00 - - je .LBB6_2
- - - - - - 0.50 0.50 mov rax, qword ptr [rsp + 16]
- - - - 1.00 - 0.49 0.51 mov qword ptr [rbx + 8], rax
- - - - 1.00 - 0.51 0.49 mov qword ptr [rbx], r14
- - 0.99 0.01 - - - - mov rsi, r13
- - - - - 1.00 - - jmp .LBB6_4

View File

@ -0,0 +1,24 @@
use zerocopy_derive::*;
// The only valid value of this type are the bytes `0xC0C0`.
#[derive(TryFromBytes, KnownLayout, Immutable)]
#[repr(u16)]
pub enum C0C0 {
_XC0C0 = 0xC0C0,
}
#[derive(FromBytes, KnownLayout, Immutable, SplitAt)]
#[repr(C, align(4))]
pub struct Packet<Magic> {
magic_number: Magic,
milk: u8,
mug_size: u8,
temperature: [u8; 5],
marshmallows: [[u8; 3]],
}
/// A packet begining with the magic number `0xC0C0`.
pub type CocoPacket = Packet<C0C0>;
/// A packet beginning with any two initialized bytes.
pub type LocoPacket = Packet<[u8; 2]>;

View File

@ -0,0 +1,27 @@
use zerocopy_derive::*;
// The only valid value of this type are the bytes `0xC0C0`.
#[derive(TryFromBytes, KnownLayout, Immutable, IntoBytes)]
#[repr(u16)]
pub enum C0C0 {
_XC0C0 = 0xC0C0,
}
macro_rules! define_packet {
($name: ident, $trait: ident, $leading_field: ty) => {
#[derive($trait, KnownLayout, Immutable, IntoBytes, SplitAt)]
#[repr(C, align(2))]
pub struct $name {
magic_number: $leading_field,
mug_size: u8,
temperature: u8,
marshmallows: [[u8; 2]],
}
};
}
/// Packet begins with bytes 0xC0C0.
define_packet!(CocoPacket, TryFromBytes, C0C0);
/// Packet begins with any two bytes.
define_packet!(LocoPacket, FromBytes, [u8; 2]);

View File

@ -0,0 +1,27 @@
use zerocopy_derive::*;
// The only valid value of this type are the bytes `0xC0C0`.
#[derive(TryFromBytes, KnownLayout, Immutable, IntoBytes)]
#[repr(u16)]
pub enum C0C0 {
_XC0C0 = 0xC0C0,
}
macro_rules! define_packet {
($name: ident, $trait: ident, $leading_field: ty) => {
#[derive($trait, KnownLayout, Immutable, IntoBytes)]
#[repr(C, align(2))]
pub struct $name {
magic_number: $leading_field,
mug_size: u8,
temperature: u8,
marshmallows: [u8; 2],
}
};
}
/// Packet begins with bytes 0xC0C0.
define_packet!(CocoPacket, TryFromBytes, C0C0);
/// Packet begins with any two bytes.
define_packet!(LocoPacket, FromBytes, [u8; 2]);

View File

@ -0,0 +1,13 @@
use zerocopy::*;
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_insert_vec_zeroed(
v: &mut Vec<format::LocoPacket>,
position: usize,
additional: usize,
) -> Option<()> {
FromZeros::insert_vec_zeroed(v, position, additional).ok()
}

View File

@ -0,0 +1,79 @@
bench_insert_vec_zeroed:
push rbp
push r15
push r14
push r13
push r12
push rbx
sub rsp, 24
mov r12, qword ptr [rdi + 16]
mov r13, r12
sub r13, rsi
jb .LBB6_10
mov rbx, rdi
mov rax, qword ptr [rdi]
mov rcx, rax
sub rcx, r12
cmp rdx, rcx
jbe .LBB6_4
add r12, rdx
jae .LBB6_7
.LBB6_3:
xor eax, eax
jmp .LBB6_6
.LBB6_4:
mov rax, qword ptr [rbx + 8]
add r12, rdx
.LBB6_5:
lea rcx, [rsi + 2*rsi]
lea r14, [rax + 2*rcx]
add rdx, rdx
lea r15, [rdx + 2*rdx]
lea rdi, [r14 + r15]
add r13, r13
lea rdx, [2*r13]
add rdx, r13
mov rsi, r14
call qword ptr [rip + memmove@GOTPCREL]
mov rdi, r14
xor esi, esi
mov rdx, r15
call qword ptr [rip + memset@GOTPCREL]
mov qword ptr [rbx + 16], r12
mov al, 1
.LBB6_6:
add rsp, 24
pop rbx
pop r12
pop r13
pop r14
pop r15
pop rbp
ret
.LBB6_7:
mov r15, rsi
mov rbp, rdx
lea rcx, [rax + rax]
cmp r12, rcx
cmova rcx, r12
cmp rcx, 5
mov r14d, 4
cmovae r14, rcx
mov rdx, qword ptr [rbx + 8]
mov rdi, rsp
mov rsi, rax
mov rcx, r14
call <alloc::raw_vec::RawVecInner>::finish_grow
cmp dword ptr [rsp], 1
je .LBB6_3
mov rax, qword ptr [rsp + 8]
mov qword ptr [rbx + 8], rax
mov qword ptr [rbx], r14
mov rdx, rbp
mov rsi, r15
jmp .LBB6_5
.LBB6_10:
lea rdi, [rip + .Lanon.HASH.1]
lea rdx, [rip + .Lanon.HASH.3]
mov esi, 37
call qword ptr [rip + core::panicking::panic@GOTPCREL]

View File

@ -0,0 +1,183 @@
Iterations: 100
Instructions: 7200
Total Cycles: 7648
Total uOps: 9300
Dispatch Width: 4
uOps Per Cycle: 1.22
IPC: 0.94
Block RThroughput: 23.3
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
2 5 1.00 * push rbp
2 5 1.00 * push r15
2 5 1.00 * push r14
2 5 1.00 * push r13
2 5 1.00 * push r12
2 5 1.00 * push rbx
1 1 0.33 sub rsp, 24
1 5 0.50 * mov r12, qword ptr [rdi + 16]
1 1 0.33 mov r13, r12
1 1 0.33 sub r13, rsi
1 1 1.00 jb .LBB6_10
1 1 0.33 mov rbx, rdi
1 5 0.50 * mov rax, qword ptr [rdi]
1 1 0.33 mov rcx, rax
1 1 0.33 sub rcx, r12
1 1 0.33 cmp rdx, rcx
1 1 1.00 jbe .LBB6_4
1 1 0.33 add r12, rdx
1 1 1.00 jae .LBB6_7
1 0 0.25 xor eax, eax
1 1 1.00 jmp .LBB6_6
1 5 0.50 * mov rax, qword ptr [rbx + 8]
1 1 0.33 add r12, rdx
1 1 0.50 lea rcx, [rsi + 2*rsi]
1 1 0.50 lea r14, [rax + 2*rcx]
1 1 0.33 add rdx, rdx
1 1 0.50 lea r15, [rdx + 2*rdx]
1 1 0.50 lea rdi, [r14 + r15]
1 1 0.33 add r13, r13
1 1 0.50 lea rdx, [2*r13]
1 1 0.33 add rdx, r13
1 1 0.33 mov rsi, r14
4 7 1.00 * call qword ptr [rip + memmove@GOTPCREL]
1 1 0.33 mov rdi, r14
1 0 0.25 xor esi, esi
1 1 0.33 mov rdx, r15
4 7 1.00 * call qword ptr [rip + memset@GOTPCREL]
1 1 1.00 * mov qword ptr [rbx + 16], r12
1 1 0.33 mov al, 1
1 1 0.33 add rsp, 24
1 6 0.50 * pop rbx
1 6 0.50 * pop r12
1 6 0.50 * pop r13
1 6 0.50 * pop r14
1 6 0.50 * pop r15
1 6 0.50 * pop rbp
1 1 1.00 U ret
1 1 0.33 mov r15, rsi
1 1 0.33 mov rbp, rdx
1 1 0.50 lea rcx, [rax + rax]
1 1 0.33 cmp r12, rcx
3 3 1.00 cmova rcx, r12
1 1 0.33 cmp rcx, 5
1 1 0.33 mov r14d, 4
2 2 0.67 cmovae r14, rcx
1 5 0.50 * mov rdx, qword ptr [rbx + 8]
1 1 0.33 mov rdi, rsp
1 1 0.33 mov rsi, rax
1 1 0.33 mov rcx, r14
3 5 1.00 call <alloc::raw_vec::RawVecInner>::finish_grow
2 6 0.50 * cmp dword ptr [rsp], 1
1 1 1.00 je .LBB6_3
1 5 0.50 * mov rax, qword ptr [rsp + 8]
1 1 1.00 * mov qword ptr [rbx + 8], rax
1 1 1.00 * mov qword ptr [rbx], r14
1 1 0.33 mov rdx, rbp
1 1 0.33 mov rsi, r15
1 1 1.00 jmp .LBB6_5
1 1 0.50 lea rdi, [rip + .Lanon.HASH.1]
1 1 0.50 lea rdx, [rip + .Lanon.HASH.3]
1 1 0.33 mov esi, 37
4 7 1.00 * call qword ptr [rip + core::panicking::panic@GOTPCREL]
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 17.02 16.50 13.00 19.48 14.00 14.00
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - 1.00 - 0.98 0.02 push rbp
- - - - 1.00 - 0.02 0.98 push r15
- - - - 1.00 - 0.99 0.01 push r14
- - - - 1.00 - 0.01 0.99 push r13
- - - - 1.00 - 0.99 0.01 push r12
- - - - 1.00 - 0.01 0.99 push rbx
- - 0.49 0.51 - - - - sub rsp, 24
- - - - - - 0.04 0.96 mov r12, qword ptr [rdi + 16]
- - 0.49 0.50 - 0.01 - - mov r13, r12
- - 0.48 0.51 - 0.01 - - sub r13, rsi
- - - - - 1.00 - - jb .LBB6_10
- - 0.49 0.49 - 0.02 - - mov rbx, rdi
- - - - - - 0.97 0.03 mov rax, qword ptr [rdi]
- - 0.51 0.49 - - - - mov rcx, rax
- - 0.49 0.02 - 0.49 - - sub rcx, r12
- - 0.49 0.50 - 0.01 - - cmp rdx, rcx
- - - - - 1.00 - - jbe .LBB6_4
- - 0.02 0.49 - 0.49 - - add r12, rdx
- - - - - 1.00 - - jae .LBB6_7
- - - - - - - - xor eax, eax
- - - - - 1.00 - - jmp .LBB6_6
- - - - - - 0.97 0.03 mov rax, qword ptr [rbx + 8]
- - 0.51 0.49 - - - - add r12, rdx
- - 0.49 0.51 - - - - lea rcx, [rsi + 2*rsi]
- - 0.50 0.50 - - - - lea r14, [rax + 2*rcx]
- - 0.51 0.49 - - - - add rdx, rdx
- - 0.50 0.50 - - - - lea r15, [rdx + 2*rdx]
- - 0.49 0.51 - - - - lea rdi, [r14 + r15]
- - 0.50 0.49 - 0.01 - - add r13, r13
- - 0.51 0.49 - - - - lea rdx, [2*r13]
- - 0.01 0.01 - 0.98 - - add rdx, r13
- - 0.01 - - 0.99 - - mov rsi, r14
- - - - 1.00 1.00 1.98 0.02 call qword ptr [rip + memmove@GOTPCREL]
- - 0.49 0.50 - 0.01 - - mov rdi, r14
- - - - - - - - xor esi, esi
- - 0.50 0.50 - - - - mov rdx, r15
- - - - 1.00 1.00 1.96 0.04 call qword ptr [rip + memset@GOTPCREL]
- - - - 1.00 - 0.01 0.99 mov qword ptr [rbx + 16], r12
- - 0.50 - - 0.50 - - mov al, 1
- - 0.51 0.49 - - - - add rsp, 24
- - - - - - 0.02 0.98 pop rbx
- - - - - - 0.03 0.97 pop r12
- - - - - - 0.03 0.97 pop r13
- - - - - - 0.97 0.03 pop r14
- - - - - - 0.03 0.97 pop r15
- - - - - - 0.01 0.99 pop rbp
- - - - - 1.00 - - ret
- - 0.49 0.51 - - - - mov r15, rsi
- - 0.51 0.49 - - - - mov rbp, rdx
- - 0.49 0.51 - - - - lea rcx, [rax + rax]
- - 0.49 0.50 - 0.01 - - cmp r12, rcx
- - 1.04 0.50 - 1.46 - - cmova rcx, r12
- - 0.49 0.49 - 0.02 - - cmp rcx, 5
- - 0.50 - - 0.50 - - mov r14d, 4
- - 0.50 0.51 - 0.99 - - cmovae r14, rcx
- - - - - - 0.97 0.03 mov rdx, qword ptr [rbx + 8]
- - - 0.51 - 0.49 - - mov rdi, rsp
- - 0.01 0.50 - 0.49 - - mov rsi, rax
- - 0.49 0.50 - 0.01 - - mov rcx, r14
- - - - 1.00 1.00 0.99 0.01 call <alloc::raw_vec::RawVecInner>::finish_grow
- - 0.51 0.49 - - 0.50 0.50 cmp dword ptr [rsp], 1
- - - - - 1.00 - - je .LBB6_3
- - - - - - 0.50 0.50 mov rax, qword ptr [rsp + 8]
- - - - 1.00 - 0.99 0.01 mov qword ptr [rbx + 8], rax
- - - - 1.00 - 0.01 0.99 mov qword ptr [rbx], r14
- - 0.49 0.50 - 0.01 - - mov rdx, rbp
- - 0.50 0.01 - 0.49 - - mov rsi, r15
- - - - - 1.00 - - jmp .LBB6_5
- - 0.01 0.99 - - - - lea rdi, [rip + .Lanon.HASH.1]
- - 0.99 0.01 - - - - lea rdx, [rip + .Lanon.HASH.3]
- - 0.02 0.49 - 0.49 - - mov esi, 37
- - - - 1.00 1.00 0.02 1.98 call qword ptr [rip + core::panicking::panic@GOTPCREL]

View File

@ -0,0 +1,9 @@
use zerocopy::*;
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_new_box_zeroed() -> Option<Box<format::LocoPacket>> {
FromZeros::new_box_zeroed().ok()
}

View File

@ -0,0 +1,7 @@
bench_new_box_zeroed:
push rax
call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
mov edi, 6
mov esi, 2
pop rax
jmp qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]

View File

@ -0,0 +1,51 @@
Iterations: 100
Instructions: 600
Total Cycles: 1197
Total uOps: 1100
Dispatch Width: 4
uOps Per Cycle: 0.92
IPC: 0.50
Block RThroughput: 2.8
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
2 5 1.00 * push rax
4 7 1.00 * call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
1 1 0.33 mov edi, 6
1 1 0.33 mov esi, 2
1 6 0.50 * pop rax
2 6 1.00 * jmp qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 0.99 1.00 2.00 2.01 2.07 2.93
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - 1.00 - 0.93 0.07 push rax
- - - - 1.00 1.00 0.12 1.88 call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
- - 0.99 - - 0.01 - - mov edi, 6
- - - 1.00 - - - - mov esi, 2
- - - - - - 0.94 0.06 pop rax
- - - - - 1.00 0.08 0.92 jmp qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]

View File

@ -0,0 +1,11 @@
use zerocopy::*;
#[path = "formats/coco_dynamic_padding.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_new_box_zeroed_with_elems_dynamic_padding(
count: usize,
) -> Option<Box<format::LocoPacket>> {
FromZeros::new_box_zeroed_with_elems(count).ok()
}

View File

@ -0,0 +1,24 @@
bench_new_box_zeroed_with_elems_dynamic_padding:
push r14
push rbx
push rax
mov rbx, rdi
movabs rax, 3074457345618258598
cmp rdi, rax
ja .LBB5_1
lea r14, [rbx + 2*rbx]
or r14, 3
add r14, 9
call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
mov esi, 4
mov rdi, r14
call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
jmp .LBB5_3
.LBB5_1:
xor eax, eax
.LBB5_3:
mov rdx, rbx
add rsp, 8
pop rbx
pop r14
ret

View File

@ -0,0 +1,81 @@
Iterations: 100
Instructions: 2100
Total Cycles: 2990
Total uOps: 3000
Dispatch Width: 4
uOps Per Cycle: 1.00
IPC: 0.70
Block RThroughput: 7.5
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
2 5 1.00 * push r14
2 5 1.00 * push rbx
2 5 1.00 * push rax
1 1 0.33 mov rbx, rdi
1 1 0.33 movabs rax, 3074457345618258598
1 1 0.33 cmp rdi, rax
1 1 1.00 ja .LBB5_1
1 1 0.50 lea r14, [rbx + 2*rbx]
1 1 0.33 or r14, 3
1 1 0.33 add r14, 9
4 7 1.00 * call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
1 1 0.33 mov esi, 4
1 1 0.33 mov rdi, r14
4 7 1.00 * call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
1 1 1.00 jmp .LBB5_3
1 0 0.25 xor eax, eax
1 1 0.33 mov rdx, rbx
1 1 0.33 add rsp, 8
1 6 0.50 * pop rbx
1 6 0.50 * pop r14
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 4.49 4.50 5.00 6.01 4.50 4.50
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - 1.00 - 0.50 0.50 push r14
- - - - 1.00 - 0.50 0.50 push rbx
- - - - 1.00 - 0.50 0.50 push rax
- - 0.49 0.50 - 0.01 - - mov rbx, rdi
- - 0.50 0.50 - - - - movabs rax, 3074457345618258598
- - 0.50 0.50 - - - - cmp rdi, rax
- - - - - 1.00 - - ja .LBB5_1
- - 0.50 0.50 - - - - lea r14, [rbx + 2*rbx]
- - 0.50 0.50 - - - - or r14, 3
- - 0.50 - - 0.50 - - add r14, 9
- - - - 1.00 1.00 1.00 1.00 call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
- - - 0.50 - 0.50 - - mov esi, 4
- - 0.50 0.50 - - - - mov rdi, r14
- - - - 1.00 1.00 1.00 1.00 call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
- - - - - 1.00 - - jmp .LBB5_3
- - - - - - - - xor eax, eax
- - 0.51 0.49 - - - - mov rdx, rbx
- - 0.49 0.51 - - - - add rsp, 8
- - - - - - 0.50 0.50 pop rbx
- - - - - - 0.50 0.50 pop r14
- - - - - 1.00 - - ret

View File

@ -0,0 +1,9 @@
use zerocopy::*;
#[path = "formats/coco_dynamic_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_new_box_zeroed_with_elems_dynamic_size(count: usize) -> Option<Box<format::LocoPacket>> {
FromZeros::new_box_zeroed_with_elems(count).ok()
}

View File

@ -0,0 +1,22 @@
bench_new_box_zeroed_with_elems_dynamic_size:
push r14
push rbx
push rax
mov rbx, rdi
movabs rax, 4611686018427387901
cmp rdi, rax
ja .LBB5_1
lea r14, [2*rbx + 4]
call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
mov esi, 2
mov rdi, r14
call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
jmp .LBB5_3
.LBB5_1:
xor eax, eax
.LBB5_3:
mov rdx, rbx
add rsp, 8
pop rbx
pop r14
ret

View File

@ -0,0 +1,77 @@
Iterations: 100
Instructions: 1900
Total Cycles: 2990
Total uOps: 2800
Dispatch Width: 4
uOps Per Cycle: 0.94
IPC: 0.64
Block RThroughput: 7.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
2 5 1.00 * push r14
2 5 1.00 * push rbx
2 5 1.00 * push rax
1 1 0.33 mov rbx, rdi
1 1 0.33 movabs rax, 4611686018427387901
1 1 0.33 cmp rdi, rax
1 1 1.00 ja .LBB5_1
1 1 0.50 lea r14, [2*rbx + 4]
4 7 1.00 * call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
1 1 0.33 mov esi, 2
1 1 0.33 mov rdi, r14
4 7 1.00 * call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
1 1 1.00 jmp .LBB5_3
1 0 0.25 xor eax, eax
1 1 0.33 mov rdx, rbx
1 1 0.33 add rsp, 8
1 6 0.50 * pop rbx
1 6 0.50 * pop r14
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 3.97 3.98 5.00 5.05 4.50 4.50
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - 1.00 - 0.50 0.50 push r14
- - - - 1.00 - 0.50 0.50 push rbx
- - - - 1.00 - 0.50 0.50 push rax
- - 0.05 0.94 - 0.01 - - mov rbx, rdi
- - 0.94 0.06 - - - - movabs rax, 4611686018427387901
- - 0.06 0.94 - - - - cmp rdi, rax
- - - - - 1.00 - - ja .LBB5_1
- - 0.94 0.06 - - - - lea r14, [2*rbx + 4]
- - - - 1.00 1.00 1.00 1.00 call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
- - 0.98 0.02 - - - - mov esi, 2
- - 0.02 0.94 - 0.04 - - mov rdi, r14
- - - - 1.00 1.00 1.00 1.00 call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
- - - - - 1.00 - - jmp .LBB5_3
- - - - - - - - xor eax, eax
- - 0.94 0.06 - - - - mov rdx, rbx
- - 0.04 0.96 - - - - add rsp, 8
- - - - - - 0.50 0.50 pop rbx
- - - - - - 0.50 0.50 pop r14
- - - - - 1.00 - - ret

View File

@ -0,0 +1,9 @@
use zerocopy::*;
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_new_vec_zeroed(len: usize) -> Option<Vec<format::LocoPacket>> {
FromZeros::new_vec_zeroed(len).ok()
}

View File

@ -0,0 +1,40 @@
bench_new_vec_zeroed:
mov rax, rdi
movabs rcx, 1537228672809129301
cmp rsi, rcx
ja .LBB5_5
test rsi, rsi
je .LBB5_2
push r15
push r14
push rbx
lea rcx, [rsi + rsi]
lea rbx, [rcx + 2*rcx]
mov r14, rax
mov r15, rsi
call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
mov esi, 2
mov rdi, rbx
call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
mov rsi, r15
mov rcx, rax
mov rax, r14
test rcx, rcx
pop rbx
pop r14
pop r15
je .LBB5_5
mov qword ptr [rax], rsi
mov qword ptr [rax + 8], rcx
mov qword ptr [rax + 16], rsi
ret
.LBB5_5:
movabs rcx, -9223372036854775808
mov qword ptr [rax], rcx
ret
.LBB5_2:
mov ecx, 2
mov qword ptr [rax], rsi
mov qword ptr [rax + 8], rcx
mov qword ptr [rax + 16], rsi
ret

View File

@ -0,0 +1,113 @@
Iterations: 100
Instructions: 3700
Total Cycles: 3486
Total uOps: 4600
Dispatch Width: 4
uOps Per Cycle: 1.32
IPC: 1.06
Block RThroughput: 12.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 mov rax, rdi
1 1 0.33 movabs rcx, 1537228672809129301
1 1 0.33 cmp rsi, rcx
1 1 1.00 ja .LBB5_5
1 1 0.33 test rsi, rsi
1 1 1.00 je .LBB5_2
2 5 1.00 * push r15
2 5 1.00 * push r14
2 5 1.00 * push rbx
1 1 0.50 lea rcx, [rsi + rsi]
1 1 0.50 lea rbx, [rcx + 2*rcx]
1 1 0.33 mov r14, rax
1 1 0.33 mov r15, rsi
4 7 1.00 * call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
1 1 0.33 mov esi, 2
1 1 0.33 mov rdi, rbx
4 7 1.00 * call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
1 1 0.33 mov rsi, r15
1 1 0.33 mov rcx, rax
1 1 0.33 mov rax, r14
1 1 0.33 test rcx, rcx
1 6 0.50 * pop rbx
1 6 0.50 * pop r14
1 6 0.50 * pop r15
1 1 1.00 je .LBB5_5
1 1 1.00 * mov qword ptr [rax], rsi
1 1 1.00 * mov qword ptr [rax + 8], rcx
1 1 1.00 * mov qword ptr [rax + 16], rsi
1 1 1.00 U ret
1 1 0.33 movabs rcx, -9223372036854775808
1 1 1.00 * mov qword ptr [rax], rcx
1 1 1.00 U ret
1 1 0.33 mov ecx, 2
1 1 1.00 * mov qword ptr [rax], rsi
1 1 1.00 * mov qword ptr [rax + 8], rcx
1 1 1.00 * mov qword ptr [rax + 16], rsi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 6.99 6.99 12.00 10.02 8.00 9.00
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.01 0.98 - 0.01 - - mov rax, rdi
- - 0.98 0.02 - - - - movabs rcx, 1537228672809129301
- - 0.02 0.98 - - - - cmp rsi, rcx
- - - - - 1.00 - - ja .LBB5_5
- - 0.98 - - 0.02 - - test rsi, rsi
- - - - - 1.00 - - je .LBB5_2
- - - - 1.00 - - 1.00 push r15
- - - - 1.00 - 1.00 - push r14
- - - - 1.00 - - 1.00 push rbx
- - - 1.00 - - - - lea rcx, [rsi + rsi]
- - - 1.00 - - - - lea rbx, [rcx + 2*rcx]
- - 1.00 - - - - - mov r14, rax
- - 1.00 - - - - - mov r15, rsi
- - - - 1.00 1.00 2.00 - call qword ptr [rip + __rustc::__rust_no_alloc_shim_is_unstable_v2@GOTPCREL]
- - - 0.01 - 0.99 - - mov esi, 2
- - 0.01 0.99 - - - - mov rdi, rbx
- - - - 1.00 1.00 - 2.00 call qword ptr [rip + __rustc::__rust_alloc_zeroed@GOTPCREL]
- - 0.01 - - 0.99 - - mov rsi, r15
- - 0.99 0.01 - - - - mov rcx, rax
- - - 0.99 - 0.01 - - mov rax, r14
- - 0.99 0.01 - - - - test rcx, rcx
- - - - - - - 1.00 pop rbx
- - - - - - 1.00 - pop r14
- - - - - - - 1.00 pop r15
- - - - - 1.00 - - je .LBB5_5
- - - - 1.00 - 1.00 - mov qword ptr [rax], rsi
- - - - 1.00 - - 1.00 mov qword ptr [rax + 8], rcx
- - - - 1.00 - 1.00 - mov qword ptr [rax + 16], rsi
- - - - - 1.00 - - ret
- - 0.01 0.99 - - - - movabs rcx, -9223372036854775808
- - - - 1.00 - - 1.00 mov qword ptr [rax], rcx
- - - - - 1.00 - - ret
- - 0.99 0.01 - - - - mov ecx, 2
- - - - 1.00 - 1.00 - mov qword ptr [rax], rsi
- - - - 1.00 - - 1.00 mov qword ptr [rax + 8], rcx
- - - - 1.00 - 1.00 - mov qword ptr [rax + 16], rsi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,9 @@
use zerocopy::*;
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_new_zeroed() -> format::LocoPacket {
FromZeros::new_zeroed()
}

View File

@ -0,0 +1,3 @@
bench_new_zeroed:
xor eax, eax
ret

View File

@ -0,0 +1,43 @@
Iterations: 100
Instructions: 200
Total Cycles: 103
Total uOps: 200
Dispatch Width: 4
uOps Per Cycle: 1.94
IPC: 1.94
Block RThroughput: 1.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 0 0.25 xor eax, eax
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - - - - 1.00 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - - - - - xor eax, eax
- - - - - 1.00 - - ret

View File

@ -0,0 +1,7 @@
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_read_from_bytes_static_size(source: &[u8]) -> Option<format::LocoPacket> {
zerocopy::FromBytes::read_from_bytes(source).ok()
}

View File

@ -0,0 +1,15 @@
bench_read_from_bytes_static_size:
mov rcx, rsi
cmp rsi, 6
jne .LBB5_2
mov eax, dword ptr [rdi]
movzx ecx, word ptr [rdi + 4]
shl rcx, 32
or rcx, rax
.LBB5_2:
shl rcx, 16
inc rcx
xor eax, eax
cmp rsi, 6
cmove rax, rcx
ret

View File

@ -0,0 +1,65 @@
Iterations: 100
Instructions: 1300
Total Cycles: 377
Total uOps: 1400
Dispatch Width: 4
uOps Per Cycle: 3.71
IPC: 3.45
Block RThroughput: 3.5
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 mov rcx, rsi
1 1 0.33 cmp rsi, 6
1 1 1.00 jne .LBB5_2
1 5 0.50 * mov eax, dword ptr [rdi]
1 5 0.50 * movzx ecx, word ptr [rdi + 4]
1 1 0.50 shl rcx, 32
1 1 0.33 or rcx, rax
1 1 0.50 shl rcx, 16
1 1 0.33 inc rcx
1 0 0.25 xor eax, eax
1 1 0.33 cmp rsi, 6
2 2 0.67 cmove rax, rcx
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 3.66 3.67 - 3.67 1.00 1.00
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.63 0.36 - 0.01 - - mov rcx, rsi
- - 0.05 0.05 - 0.90 - - cmp rsi, 6
- - - - - 1.00 - - jne .LBB5_2
- - - - - - - 1.00 mov eax, dword ptr [rdi]
- - - - - - 1.00 - movzx ecx, word ptr [rdi + 4]
- - 0.97 - - 0.03 - - shl rcx, 32
- - 0.02 0.35 - 0.63 - - or rcx, rax
- - 0.98 - - 0.02 - - shl rcx, 16
- - - 0.98 - 0.02 - - inc rcx
- - - - - - - - xor eax, eax
- - 0.03 0.93 - 0.04 - - cmp rsi, 6
- - 0.98 1.00 - 0.02 - - cmove rax, rcx
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_read_from_prefix_static_size(source: &[u8]) -> Option<format::LocoPacket> {
match zerocopy::FromBytes::read_from_prefix(source) {
Ok((packet, _rest)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,14 @@
bench_read_from_prefix_static_size:
cmp rsi, 5
jbe .LBB5_2
mov eax, dword ptr [rdi]
movzx edi, word ptr [rdi + 4]
shl rdi, 32
or rdi, rax
.LBB5_2:
shl rdi, 16
inc rdi
xor eax, eax
cmp rsi, 6
cmovae rax, rdi
ret

View File

@ -0,0 +1,63 @@
Iterations: 100
Instructions: 1200
Total Cycles: 905
Total uOps: 1300
Dispatch Width: 4
uOps Per Cycle: 1.44
IPC: 1.33
Block RThroughput: 3.3
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 cmp rsi, 5
1 1 1.00 jbe .LBB5_2
1 5 0.50 * mov eax, dword ptr [rdi]
1 5 0.50 * movzx edi, word ptr [rdi + 4]
1 1 0.50 shl rdi, 32
1 1 0.33 or rdi, rax
1 1 0.50 shl rdi, 16
1 1 0.33 inc rdi
1 0 0.25 xor eax, eax
1 1 0.33 cmp rsi, 6
2 2 0.67 cmovae rax, rdi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 3.32 3.32 - 3.36 1.00 1.00
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.05 0.94 - 0.01 - - cmp rsi, 5
- - - - - 1.00 - - jbe .LBB5_2
- - - - - - - 1.00 mov eax, dword ptr [rdi]
- - - - - - 1.00 - movzx edi, word ptr [rdi + 4]
- - 0.71 - - 0.29 - - shl rdi, 32
- - - 0.64 - 0.36 - - or rdi, rax
- - 1.00 - - - - - shl rdi, 16
- - 0.31 0.40 - 0.29 - - inc rdi
- - - - - - - - xor eax, eax
- - 0.34 0.35 - 0.31 - - cmp rsi, 6
- - 0.91 0.99 - 0.10 - - cmovae rax, rdi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_read_from_suffix_static_size(source: &[u8]) -> Option<format::LocoPacket> {
match zerocopy::FromBytes::read_from_suffix(source) {
Ok((_rest, packet)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,15 @@
bench_read_from_suffix_static_size:
mov rcx, rsi
cmp rsi, 6
jb .LBB5_2
mov eax, dword ptr [rdi + rsi - 6]
movzx ecx, word ptr [rdi + rsi - 2]
shl rcx, 32
or rcx, rax
.LBB5_2:
shl rcx, 16
inc rcx
xor eax, eax
cmp rsi, 6
cmovae rax, rcx
ret

View File

@ -0,0 +1,65 @@
Iterations: 100
Instructions: 1300
Total Cycles: 377
Total uOps: 1400
Dispatch Width: 4
uOps Per Cycle: 3.71
IPC: 3.45
Block RThroughput: 3.5
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 mov rcx, rsi
1 1 0.33 cmp rsi, 6
1 1 1.00 jb .LBB5_2
1 5 0.50 * mov eax, dword ptr [rdi + rsi - 6]
1 5 0.50 * movzx ecx, word ptr [rdi + rsi - 2]
1 1 0.50 shl rcx, 32
1 1 0.33 or rcx, rax
1 1 0.50 shl rcx, 16
1 1 0.33 inc rcx
1 0 0.25 xor eax, eax
1 1 0.33 cmp rsi, 6
2 2 0.67 cmovae rax, rcx
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 3.66 3.67 - 3.67 1.00 1.00
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.63 0.36 - 0.01 - - mov rcx, rsi
- - 0.05 0.05 - 0.90 - - cmp rsi, 6
- - - - - 1.00 - - jb .LBB5_2
- - - - - - - 1.00 mov eax, dword ptr [rdi + rsi - 6]
- - - - - - 1.00 - movzx ecx, word ptr [rdi + rsi - 2]
- - 0.97 - - 0.03 - - shl rcx, 32
- - 0.02 0.35 - 0.63 - - or rcx, rax
- - 0.98 - - 0.02 - - shl rcx, 16
- - - 0.98 - 0.02 - - inc rcx
- - - - - - - - xor eax, eax
- - 0.03 0.93 - 0.04 - - cmp rsi, 6
- - 0.98 1.00 - 0.02 - - cmovae rax, rcx
- - - - - 1.00 - - ret

View File

@ -0,0 +1,7 @@
#[path = "formats/coco_dynamic_padding.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_bytes_dynamic_padding(source: &[u8]) -> Option<&format::LocoPacket> {
zerocopy::FromBytes::ref_from_bytes(source).ok()
}

View File

@ -0,0 +1,22 @@
bench_ref_from_bytes_dynamic_padding:
test dil, 3
jne .LBB5_3
movabs rax, 9223372036854775804
and rax, rsi
cmp rax, 9
jb .LBB5_3
add rax, -9
movabs rcx, -6148914691236517205
mul rcx
shr rdx
lea rax, [rdx + 2*rdx]
or rax, 3
add rax, 9
cmp rsi, rax
je .LBB5_4
.LBB5_3:
xor edi, edi
mov rdx, rsi
.LBB5_4:
mov rax, rdi
ret

View File

@ -0,0 +1,77 @@
Iterations: 100
Instructions: 1900
Total Cycles: 645
Total uOps: 2000
Dispatch Width: 4
uOps Per Cycle: 3.10
IPC: 2.95
Block RThroughput: 5.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 test dil, 3
1 1 1.00 jne .LBB5_3
1 1 0.33 movabs rax, 9223372036854775804
1 1 0.33 and rax, rsi
1 1 0.33 cmp rax, 9
1 1 1.00 jb .LBB5_3
1 1 0.33 add rax, -9
1 1 0.33 movabs rcx, -6148914691236517205
2 4 1.00 mul rcx
1 1 0.50 shr rdx
1 1 0.50 lea rax, [rdx + 2*rdx]
1 1 0.33 or rax, 3
1 1 0.33 add rax, 9
1 1 0.33 cmp rsi, rax
1 1 1.00 je .LBB5_4
1 0 0.25 xor edi, edi
1 1 0.33 mov rdx, rsi
1 1 0.33 mov rax, rdi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 6.32 6.33 - 6.35 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.64 0.35 - 0.01 - - test dil, 3
- - - - - 1.00 - - jne .LBB5_3
- - 0.34 0.65 - 0.01 - - movabs rax, 9223372036854775804
- - 0.35 0.65 - - - - and rax, rsi
- - 0.33 0.34 - 0.33 - - cmp rax, 9
- - - - - 1.00 - - jb .LBB5_3
- - 0.35 - - 0.65 - - add rax, -9
- - 0.97 0.01 - 0.02 - - movabs rcx, -6148914691236517205
- - 1.00 1.00 - - - - mul rcx
- - 0.99 - - 0.01 - - shr rdx
- - 0.33 0.67 - - - - lea rax, [rdx + 2*rdx]
- - 0.34 0.66 - - - - or rax, 3
- - 0.33 0.66 - 0.01 - - add rax, 9
- - 0.01 0.99 - - - - cmp rsi, rax
- - - - - 1.00 - - je .LBB5_4
- - - - - - - - xor edi, edi
- - 0.32 0.01 - 0.67 - - mov rdx, rsi
- - 0.02 0.34 - 0.64 - - mov rax, rdi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,7 @@
#[path = "formats/coco_dynamic_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_bytes_dynamic_size(source: &[u8]) -> Option<&format::LocoPacket> {
zerocopy::FromBytes::ref_from_bytes(source).ok()
}

View File

@ -0,0 +1,20 @@
bench_ref_from_bytes_dynamic_size:
mov rdx, rsi
cmp rsi, 4
setb al
or al, dil
test al, 1
je .LBB5_2
xor eax, eax
ret
.LBB5_2:
lea rcx, [rdx - 4]
mov rsi, rcx
and rsi, -2
add rsi, 4
shr rcx
xor eax, eax
cmp rdx, rsi
cmove rdx, rcx
cmove rax, rdi
ret

View File

@ -0,0 +1,75 @@
Iterations: 100
Instructions: 1800
Total Cycles: 704
Total uOps: 2000
Dispatch Width: 4
uOps Per Cycle: 2.84
IPC: 2.56
Block RThroughput: 5.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 mov rdx, rsi
1 1 0.33 cmp rsi, 4
1 1 0.50 setb al
1 1 0.33 or al, dil
1 1 0.33 test al, 1
1 1 1.00 je .LBB5_2
1 0 0.25 xor eax, eax
1 1 1.00 U ret
1 1 0.50 lea rcx, [rdx - 4]
1 1 0.33 mov rsi, rcx
1 1 0.33 and rsi, -2
1 1 0.33 add rsi, 4
1 1 0.50 shr rcx
1 0 0.25 xor eax, eax
1 1 0.33 cmp rdx, rsi
2 2 0.67 cmove rdx, rcx
2 2 0.67 cmove rax, rdi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 5.97 5.98 - 6.05 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.97 0.01 - 0.02 - - mov rdx, rsi
- - 0.01 0.02 - 0.97 - - cmp rsi, 4
- - 0.03 - - 0.97 - - setb al
- - 0.01 0.02 - 0.97 - - or al, dil
- - - 0.98 - 0.02 - - test al, 1
- - - - - 1.00 - - je .LBB5_2
- - - - - - - - xor eax, eax
- - - - - 1.00 - - ret
- - 0.98 0.02 - - - - lea rcx, [rdx - 4]
- - 0.01 0.99 - - - - mov rsi, rcx
- - - 0.98 - 0.02 - - and rsi, -2
- - 0.98 0.01 - 0.01 - - add rsi, 4
- - 0.99 - - 0.01 - - shr rcx
- - - - - - - - xor eax, eax
- - 0.02 0.97 - 0.01 - - cmp rdx, rsi
- - 0.99 0.99 - 0.02 - - cmove rdx, rcx
- - 0.98 0.99 - 0.03 - - cmove rax, rdi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,7 @@
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_bytes_static_size(source: &[u8]) -> Option<&format::LocoPacket> {
zerocopy::FromBytes::ref_from_bytes(source).ok()
}

View File

@ -0,0 +1,8 @@
bench_ref_from_bytes_static_size:
mov ecx, edi
and ecx, 1
xor rsi, 6
xor eax, eax
or rsi, rcx
cmove rax, rdi
ret

View File

@ -0,0 +1,53 @@
Iterations: 100
Instructions: 700
Total Cycles: 240
Total uOps: 800
Dispatch Width: 4
uOps Per Cycle: 3.33
IPC: 2.92
Block RThroughput: 2.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 mov ecx, edi
1 1 0.33 and ecx, 1
1 1 0.33 xor rsi, 6
1 0 0.25 xor eax, eax
1 1 0.33 or rsi, rcx
2 2 0.67 cmove rax, rdi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 2.33 2.33 - 2.34 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.01 0.98 - 0.01 - - mov ecx, edi
- - 0.02 0.66 - 0.32 - - and ecx, 1
- - 0.33 0.66 - 0.01 - - xor rsi, 6
- - - - - - - - xor eax, eax
- - 0.98 0.02 - - - - or rsi, rcx
- - 0.99 0.01 - 1.00 - - cmove rax, rdi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_dynamic_padding.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_bytes_with_elems_dynamic_padding(
source: &[u8],
count: usize,
) -> Option<&format::LocoPacket> {
zerocopy::FromBytes::ref_from_bytes_with_elems(source, count).ok()
}

View File

@ -0,0 +1,19 @@
bench_ref_from_bytes_with_elems_dynamic_padding:
movabs rax, 3074457345618258598
cmp rdx, rax
seta cl
mov rax, rdi
test al, 3
setne dil
or dil, cl
jne .LBB5_2
lea rcx, [rdx + 2*rdx]
or rcx, 3
add rcx, 9
cmp rsi, rcx
je .LBB5_3
.LBB5_2:
xor eax, eax
mov rdx, rsi
.LBB5_3:
ret

View File

@ -0,0 +1,71 @@
Iterations: 100
Instructions: 1600
Total Cycles: 539
Total uOps: 1700
Dispatch Width: 4
uOps Per Cycle: 3.15
IPC: 2.97
Block RThroughput: 4.3
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 movabs rax, 3074457345618258598
1 1 0.33 cmp rdx, rax
2 2 1.00 seta cl
1 1 0.33 mov rax, rdi
1 1 0.33 test al, 3
1 1 0.50 setne dil
1 1 0.33 or dil, cl
1 1 1.00 jne .LBB5_2
1 1 0.50 lea rcx, [rdx + 2*rdx]
1 1 0.33 or rcx, 3
1 1 0.33 add rcx, 9
1 1 0.33 cmp rsi, rcx
1 1 1.00 je .LBB5_3
1 0 0.25 xor eax, eax
1 1 0.33 mov rdx, rsi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 5.33 5.32 - 5.35 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.01 0.98 - 0.01 - - movabs rax, 3074457345618258598
- - - 1.00 - - - - cmp rdx, rax
- - 1.98 - - 0.02 - - seta cl
- - 0.02 0.98 - - - - mov rax, rdi
- - - 0.67 - 0.33 - - test al, 3
- - 0.67 - - 0.33 - - setne dil
- - 0.99 - - 0.01 - - or dil, cl
- - - - - 1.00 - - jne .LBB5_2
- - 0.01 0.99 - - - - lea rcx, [rdx + 2*rdx]
- - - 0.01 - 0.99 - - or rcx, 3
- - 0.65 0.02 - 0.33 - - add rcx, 9
- - 0.99 0.01 - - - - cmp rsi, rcx
- - - - - 1.00 - - je .LBB5_3
- - - - - - - - xor eax, eax
- - 0.01 0.66 - 0.33 - - mov rdx, rsi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_dynamic_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_bytes_with_elems_dynamic_size(
source: &[u8],
count: usize,
) -> Option<&format::LocoPacket> {
zerocopy::FromBytes::ref_from_bytes_with_elems(source, count).ok()
}

View File

@ -0,0 +1,16 @@
bench_ref_from_bytes_with_elems_dynamic_size:
movabs rax, 4611686018427387901
cmp rdx, rax
seta cl
mov rax, rdi
or dil, cl
test dil, 1
jne .LBB5_2
lea rcx, [2*rdx + 4]
cmp rsi, rcx
je .LBB5_3
.LBB5_2:
xor eax, eax
mov rdx, rsi
.LBB5_3:
ret

View File

@ -0,0 +1,65 @@
Iterations: 100
Instructions: 1300
Total Cycles: 439
Total uOps: 1400
Dispatch Width: 4
uOps Per Cycle: 3.19
IPC: 2.96
Block RThroughput: 3.5
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 movabs rax, 4611686018427387901
1 1 0.33 cmp rdx, rax
2 2 1.00 seta cl
1 1 0.33 mov rax, rdi
1 1 0.33 or dil, cl
1 1 0.33 test dil, 1
1 1 1.00 jne .LBB5_2
1 1 0.50 lea rcx, [2*rdx + 4]
1 1 0.33 cmp rsi, rcx
1 1 1.00 je .LBB5_3
1 0 0.25 xor eax, eax
1 1 0.33 mov rdx, rsi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 4.32 4.33 - 4.35 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - 0.99 - 0.01 - - movabs rax, 4611686018427387901
- - 0.33 0.67 - - - - cmp rdx, rax
- - 1.98 - - 0.02 - - seta cl
- - 0.01 0.99 - - - - mov rax, rdi
- - 1.00 - - - - - or dil, cl
- - 0.99 0.01 - - - - test dil, 1
- - - - - 1.00 - - jne .LBB5_2
- - - 1.00 - - - - lea rcx, [2*rdx + 4]
- - 0.01 - - 0.99 - - cmp rsi, rcx
- - - - - 1.00 - - je .LBB5_3
- - - - - - - - xor eax, eax
- - - 0.67 - 0.33 - - mov rdx, rsi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_dynamic_padding.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_prefix_dynamic_padding(source: &[u8]) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_prefix(source) {
Ok((packet, _rest)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,22 @@
bench_ref_from_prefix_dynamic_padding:
xor edx, edx
mov eax, 0
test dil, 3
je .LBB5_1
ret
.LBB5_1:
movabs rax, 9223372036854775804
and rsi, rax
cmp rsi, 9
jae .LBB5_3
mov edx, 1
xor eax, eax
ret
.LBB5_3:
add rsi, -9
movabs rcx, -6148914691236517205
mov rax, rsi
mul rcx
shr rdx
mov rax, rdi
ret

View File

@ -0,0 +1,77 @@
Iterations: 100
Instructions: 1900
Total Cycles: 608
Total uOps: 2000
Dispatch Width: 4
uOps Per Cycle: 3.29
IPC: 3.13
Block RThroughput: 5.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 0 0.25 xor edx, edx
1 1 0.33 mov eax, 0
1 1 0.33 test dil, 3
1 1 1.00 je .LBB5_1
1 1 1.00 U ret
1 1 0.33 movabs rax, 9223372036854775804
1 1 0.33 and rsi, rax
1 1 0.33 cmp rsi, 9
1 1 1.00 jae .LBB5_3
1 1 0.33 mov edx, 1
1 0 0.25 xor eax, eax
1 1 1.00 U ret
1 1 0.33 add rsi, -9
1 1 0.33 movabs rcx, -6148914691236517205
1 1 0.33 mov rax, rsi
2 4 1.00 mul rcx
1 1 0.50 shr rdx
1 1 0.33 mov rax, rdi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 6.00 6.00 - 6.00 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - - - - - xor edx, edx
- - 0.01 0.98 - 0.01 - - mov eax, 0
- - 0.98 0.01 - 0.01 - - test dil, 3
- - - - - 1.00 - - je .LBB5_1
- - - - - 1.00 - - ret
- - 0.01 0.99 - - - - movabs rax, 9223372036854775804
- - - 1.00 - - - - and rsi, rax
- - - 1.00 - - - - cmp rsi, 9
- - - - - 1.00 - - jae .LBB5_3
- - 1.00 - - - - - mov edx, 1
- - - - - - - - xor eax, eax
- - - - - 1.00 - - ret
- - 0.02 0.02 - 0.96 - - add rsi, -9
- - 0.99 0.01 - - - - movabs rcx, -6148914691236517205
- - 0.01 0.99 - - - - mov rax, rsi
- - 1.00 1.00 - - - - mul rcx
- - 1.00 - - - - - shr rdx
- - 0.98 - - 0.02 - - mov rax, rdi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_dynamic_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_prefix_dynamic_size(source: &[u8]) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_prefix(source) {
Ok((packet, _rest)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,17 @@
bench_ref_from_prefix_dynamic_size:
xor edx, edx
mov eax, 0
test dil, 1
jne .LBB5_4
cmp rsi, 4
jae .LBB5_3
mov edx, 1
xor eax, eax
ret
.LBB5_3:
add rsi, -4
shr rsi
mov rdx, rsi
mov rax, rdi
.LBB5_4:
ret

View File

@ -0,0 +1,67 @@
Iterations: 100
Instructions: 1400
Total Cycles: 405
Total uOps: 1400
Dispatch Width: 4
uOps Per Cycle: 3.46
IPC: 3.46
Block RThroughput: 4.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 0 0.25 xor edx, edx
1 1 0.33 mov eax, 0
1 1 0.33 test dil, 1
1 1 1.00 jne .LBB5_4
1 1 0.33 cmp rsi, 4
1 1 1.00 jae .LBB5_3
1 1 0.33 mov edx, 1
1 0 0.25 xor eax, eax
1 1 1.00 U ret
1 1 0.33 add rsi, -4
1 1 0.50 shr rsi
1 1 0.33 mov rdx, rsi
1 1 0.33 mov rax, rdi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 3.99 3.99 - 4.02 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - - - - - xor edx, edx
- - 0.01 0.98 - 0.01 - - mov eax, 0
- - 0.98 0.02 - - - - test dil, 1
- - - - - 1.00 - - jne .LBB5_4
- - 0.02 0.98 - - - - cmp rsi, 4
- - - - - 1.00 - - jae .LBB5_3
- - 0.98 0.01 - 0.01 - - mov edx, 1
- - - - - - - - xor eax, eax
- - - - - 1.00 - - ret
- - 0.01 0.99 - - - - add rsi, -4
- - 1.00 - - - - - shr rsi
- - - 1.00 - - - - mov rdx, rsi
- - 0.99 0.01 - - - - mov rax, rdi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_prefix_static_size(source: &[u8]) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_prefix(source) {
Ok((packet, _rest)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,8 @@
bench_ref_from_prefix_static_size:
xor eax, eax
cmp rsi, 6
mov rcx, rdi
cmovb rcx, rax
test dil, 1
cmove rax, rcx
ret

View File

@ -0,0 +1,53 @@
Iterations: 100
Instructions: 700
Total Cycles: 274
Total uOps: 900
Dispatch Width: 4
uOps Per Cycle: 3.28
IPC: 2.55
Block RThroughput: 2.3
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 0 0.25 xor eax, eax
1 1 0.33 cmp rsi, 6
1 1 0.33 mov rcx, rdi
2 2 0.67 cmovb rcx, rax
1 1 0.33 test dil, 1
2 2 0.67 cmove rax, rcx
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 2.66 2.67 - 2.67 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - - - - - - xor eax, eax
- - - 0.01 - 0.99 - - cmp rsi, 6
- - 0.01 0.67 - 0.32 - - mov rcx, rdi
- - 1.00 0.99 - 0.01 - - cmovb rcx, rax
- - 0.66 0.01 - 0.33 - - test dil, 1
- - 0.99 0.99 - 0.02 - - cmove rax, rcx
- - - - - 1.00 - - ret

View File

@ -0,0 +1,13 @@
#[path = "formats/coco_dynamic_padding.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_prefix_with_elems_dynamic_padding(
source: &[u8],
count: usize,
) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_prefix_with_elems(source, count) {
Ok((packet, _rest)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,26 @@
bench_ref_from_prefix_with_elems_dynamic_padding:
movabs rax, 3074457345618258598
cmp rdx, rax
ja .LBB5_1
xor ecx, ecx
mov eax, 0
test dil, 3
je .LBB5_3
mov rdx, rcx
ret
.LBB5_1:
mov edx, 1
xor eax, eax
ret
.LBB5_3:
lea rax, [rdx + 2*rdx]
or rax, 3
add rax, 9
xor r8d, r8d
cmp rax, rsi
mov ecx, 1
cmovbe rcx, rdx
cmova rdi, r8
mov rax, rdi
mov rdx, rcx
ret

View File

@ -0,0 +1,85 @@
Iterations: 100
Instructions: 2300
Total Cycles: 807
Total uOps: 2700
Dispatch Width: 4
uOps Per Cycle: 3.35
IPC: 2.85
Block RThroughput: 6.8
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 movabs rax, 3074457345618258598
1 1 0.33 cmp rdx, rax
1 1 1.00 ja .LBB5_1
1 0 0.25 xor ecx, ecx
1 1 0.33 mov eax, 0
1 1 0.33 test dil, 3
1 1 1.00 je .LBB5_3
1 1 0.33 mov rdx, rcx
1 1 1.00 U ret
1 1 0.33 mov edx, 1
1 0 0.25 xor eax, eax
1 1 1.00 U ret
1 1 0.50 lea rax, [rdx + 2*rdx]
1 1 0.33 or rax, 3
1 1 0.33 add rax, 9
1 0 0.25 xor r8d, r8d
1 1 0.33 cmp rax, rsi
1 1 0.33 mov ecx, 1
3 3 1.00 cmovbe rcx, rdx
3 3 1.00 cmova rdi, r8
1 1 0.33 mov rax, rdi
1 1 0.33 mov rdx, rcx
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 7.99 7.99 - 8.02 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.47 0.52 - 0.01 - - movabs rax, 3074457345618258598
- - 0.94 0.01 - 0.05 - - cmp rdx, rax
- - - - - 1.00 - - ja .LBB5_1
- - - - - - - - xor ecx, ecx
- - 0.03 0.97 - - - - mov eax, 0
- - 0.01 0.52 - 0.47 - - test dil, 3
- - - - - 1.00 - - je .LBB5_3
- - 0.03 0.51 - 0.46 - - mov rdx, rcx
- - - - - 1.00 - - ret
- - 0.04 0.96 - - - - mov edx, 1
- - - - - - - - xor eax, eax
- - - - - 1.00 - - ret
- - 0.01 0.99 - - - - lea rax, [rdx + 2*rdx]
- - 0.52 0.48 - - - - or rax, 3
- - 0.51 0.49 - - - - add rax, 9
- - - - - - - - xor r8d, r8d
- - 0.97 0.03 - - - - cmp rax, rsi
- - 0.01 0.99 - - - - mov ecx, 1
- - 1.04 0.97 - 0.99 - - cmovbe rcx, rdx
- - 1.44 0.54 - 1.02 - - cmova rdi, r8
- - 0.97 0.01 - 0.02 - - mov rax, rdi
- - 1.00 - - - - - mov rdx, rcx
- - - - - 1.00 - - ret

View File

@ -0,0 +1,13 @@
#[path = "formats/coco_dynamic_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_prefix_with_elems_dynamic_size(
source: &[u8],
count: usize,
) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_prefix_with_elems(source, count) {
Ok((packet, _rest)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,22 @@
bench_ref_from_prefix_with_elems_dynamic_size:
movabs rax, 4611686018427387901
cmp rdx, rax
ja .LBB5_1
mov rcx, rdx
xor edx, edx
mov eax, 0
test dil, 1
jne .LBB5_4
lea rax, [2*rcx + 4]
xor r8d, r8d
cmp rax, rsi
mov edx, 1
cmovbe rdx, rcx
cmova rdi, r8
mov rax, rdi
.LBB5_4:
ret
.LBB5_1:
mov edx, 1
xor eax, eax
ret

View File

@ -0,0 +1,77 @@
Iterations: 100
Instructions: 1900
Total Cycles: 672
Total uOps: 2300
Dispatch Width: 4
uOps Per Cycle: 3.42
IPC: 2.83
Block RThroughput: 5.8
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 movabs rax, 4611686018427387901
1 1 0.33 cmp rdx, rax
1 1 1.00 ja .LBB5_1
1 1 0.33 mov rcx, rdx
1 0 0.25 xor edx, edx
1 1 0.33 mov eax, 0
1 1 0.33 test dil, 1
1 1 1.00 jne .LBB5_4
1 1 0.50 lea rax, [2*rcx + 4]
1 0 0.25 xor r8d, r8d
1 1 0.33 cmp rax, rsi
1 1 0.33 mov edx, 1
3 3 1.00 cmovbe rdx, rcx
3 3 1.00 cmova rdi, r8
1 1 0.33 mov rax, rdi
1 1 1.00 U ret
1 1 0.33 mov edx, 1
1 0 0.25 xor eax, eax
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 6.66 6.66 - 6.68 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - 0.99 - 0.01 - - movabs rax, 4611686018427387901
- - 0.37 0.63 - - - - cmp rdx, rax
- - - - - 1.00 - - ja .LBB5_1
- - 0.63 0.37 - - - - mov rcx, rdx
- - - - - - - - xor edx, edx
- - 0.01 0.98 - 0.01 - - mov eax, 0
- - 0.98 0.02 - - - - test dil, 1
- - - - - 1.00 - - jne .LBB5_4
- - 0.01 0.99 - - - - lea rax, [2*rcx + 4]
- - - - - - - - xor r8d, r8d
- - 1.00 - - - - - cmp rax, rsi
- - - 0.67 - 0.33 - - mov edx, 1
- - 0.73 0.98 - 1.29 - - cmovbe rdx, rcx
- - 1.60 0.36 - 1.04 - - cmova rdi, r8
- - 0.99 0.01 - - - - mov rax, rdi
- - - - - 1.00 - - ret
- - 0.34 0.66 - - - - mov edx, 1
- - - - - - - - xor eax, eax
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_dynamic_padding.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_suffix_dynamic_padding(source: &[u8]) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_suffix(source) {
Ok((_rest, packet)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,23 @@
bench_ref_from_suffix_dynamic_padding:
lea eax, [rsi + rdi]
test al, 3
jne .LBB5_1
movabs rax, 9223372036854775804
and rax, rsi
cmp rax, 9
jae .LBB5_3
.LBB5_1:
xor eax, eax
ret
.LBB5_3:
add rax, -9
movabs rcx, -6148914691236517205
mul rcx
shr rdx
lea rax, [rdx + 2*rdx]
sub rsi, rax
or rax, -4
add rsi, rdi
add rax, rsi
add rax, -8
ret

View File

@ -0,0 +1,79 @@
Iterations: 100
Instructions: 2000
Total Cycles: 682
Total uOps: 2100
Dispatch Width: 4
uOps Per Cycle: 3.08
IPC: 2.93
Block RThroughput: 5.3
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.50 lea eax, [rsi + rdi]
1 1 0.33 test al, 3
1 1 1.00 jne .LBB5_1
1 1 0.33 movabs rax, 9223372036854775804
1 1 0.33 and rax, rsi
1 1 0.33 cmp rax, 9
1 1 1.00 jae .LBB5_3
1 0 0.25 xor eax, eax
1 1 1.00 U ret
1 1 0.33 add rax, -9
1 1 0.33 movabs rcx, -6148914691236517205
2 4 1.00 mul rcx
1 1 0.50 shr rdx
1 1 0.50 lea rax, [rdx + 2*rdx]
1 1 0.33 sub rsi, rax
1 1 0.33 or rax, -4
1 1 0.33 add rsi, rdi
1 1 0.33 add rax, rsi
1 1 0.33 add rax, -8
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 6.65 6.67 - 6.68 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.90 0.10 - - - - lea eax, [rsi + rdi]
- - 0.93 - - 0.07 - - test al, 3
- - - - - 1.00 - - jne .LBB5_1
- - 0.51 0.47 - 0.02 - - movabs rax, 9223372036854775804
- - - - - 1.00 - - and rax, rsi
- - - 0.09 - 0.91 - - cmp rax, 9
- - - - - 1.00 - - jae .LBB5_3
- - - - - - - - xor eax, eax
- - - - - 1.00 - - ret
- - 0.43 0.47 - 0.10 - - add rax, -9
- - 0.42 0.39 - 0.19 - - movabs rcx, -6148914691236517205
- - 1.00 1.00 - - - - mul rcx
- - 0.69 - - 0.31 - - shr rdx
- - 0.54 0.46 - - - - lea rax, [rdx + 2*rdx]
- - 0.07 0.91 - 0.02 - - sub rsi, rax
- - 0.91 0.05 - 0.04 - - or rax, -4
- - 0.08 0.90 - 0.02 - - add rsi, rdi
- - 0.09 0.91 - - - - add rax, rsi
- - 0.08 0.92 - - - - add rax, -8
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_dynamic_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_suffix_dynamic_size(source: &[u8]) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_suffix(source) {
Ok((_rest, packet)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,13 @@
bench_ref_from_suffix_dynamic_size:
mov rdx, rsi
lea ecx, [rsi + rdi]
mov eax, edx
and eax, 1
add rax, rdi
xor esi, esi
sub rdx, 4
cmovb rax, rsi
shr rdx
test cl, 1
cmovne rax, rsi
ret

View File

@ -0,0 +1,63 @@
Iterations: 100
Instructions: 1200
Total Cycles: 439
Total uOps: 1400
Dispatch Width: 4
uOps Per Cycle: 3.19
IPC: 2.73
Block RThroughput: 3.5
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 mov rdx, rsi
1 1 0.50 lea ecx, [rsi + rdi]
1 1 0.33 mov eax, edx
1 1 0.33 and eax, 1
1 1 0.33 add rax, rdi
1 0 0.25 xor esi, esi
1 1 0.33 sub rdx, 4
2 2 0.67 cmovb rax, rsi
1 1 0.50 shr rdx
1 1 0.33 test cl, 1
2 2 0.67 cmovne rax, rsi
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 4.33 4.33 - 4.34 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.02 0.32 - 0.66 - - mov rdx, rsi
- - 0.32 0.68 - - - - lea ecx, [rsi + rdi]
- - 0.66 - - 0.34 - - mov eax, edx
- - 0.02 0.33 - 0.65 - - and eax, 1
- - - 0.99 - 0.01 - - add rax, rdi
- - - - - - - - xor esi, esi
- - 0.65 - - 0.35 - - sub rdx, 4
- - 1.00 1.00 - - - - cmovb rax, rsi
- - 0.66 - - 0.34 - - shr rdx
- - - 0.01 - 0.99 - - test cl, 1
- - 1.00 1.00 - - - - cmovne rax, rsi
- - - - - 1.00 - - ret

View File

@ -0,0 +1,10 @@
#[path = "formats/coco_static_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_suffix_static_size(source: &[u8]) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_suffix(source) {
Ok((_rest, packet)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,13 @@
bench_ref_from_suffix_static_size:
lea eax, [rsi + rdi]
cmp rsi, 6
setb cl
or cl, al
test cl, 1
je .LBB5_2
xor eax, eax
ret
.LBB5_2:
lea rax, [rdi + rsi]
add rax, -6
ret

View File

@ -0,0 +1,61 @@
Iterations: 100
Instructions: 1100
Total Cycles: 338
Total uOps: 1100
Dispatch Width: 4
uOps Per Cycle: 3.25
IPC: 3.25
Block RThroughput: 3.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.50 lea eax, [rsi + rdi]
1 1 0.33 cmp rsi, 6
1 1 0.50 setb cl
1 1 0.33 or cl, al
1 1 0.33 test cl, 1
1 1 1.00 je .LBB5_2
1 0 0.25 xor eax, eax
1 1 1.00 U ret
1 1 0.50 lea rax, [rdi + rsi]
1 1 0.33 add rax, -6
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 3.32 3.33 - 3.35 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.97 0.03 - - - - lea eax, [rsi + rdi]
- - 0.33 0.32 - 0.35 - - cmp rsi, 6
- - 1.00 - - - - - setb cl
- - - 1.00 - - - - or cl, al
- - - 1.00 - - - - test cl, 1
- - - - - 1.00 - - je .LBB5_2
- - - - - - - - xor eax, eax
- - - - - 1.00 - - ret
- - 0.34 0.66 - - - - lea rax, [rdi + rsi]
- - 0.68 0.32 - - - - add rax, -6
- - - - - 1.00 - - ret

View File

@ -0,0 +1,13 @@
#[path = "formats/coco_dynamic_padding.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_suffix_with_elems_dynamic_padding(
source: &[u8],
count: usize,
) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_suffix_with_elems(source, count) {
Ok((_rest, packet)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,27 @@
bench_ref_from_suffix_with_elems_dynamic_padding:
movabs rax, 3074457345618258598
cmp rdx, rax
ja .LBB5_1
lea r8d, [rsi + rdi]
xor ecx, ecx
mov eax, 0
test r8b, 3
je .LBB5_3
mov rdx, rcx
ret
.LBB5_3:
lea rax, [rdx + 2*rdx]
or rax, 3
add rax, 9
sub rsi, rax
jae .LBB5_4
.LBB5_1:
xor eax, eax
mov edx, 1
ret
.LBB5_4:
add rdi, rsi
mov rcx, rdx
mov rax, rdi
mov rdx, rcx
ret

View File

@ -0,0 +1,85 @@
Iterations: 100
Instructions: 2300
Total Cycles: 706
Total uOps: 2300
Dispatch Width: 4
uOps Per Cycle: 3.26
IPC: 3.26
Block RThroughput: 6.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 movabs rax, 3074457345618258598
1 1 0.33 cmp rdx, rax
1 1 1.00 ja .LBB5_1
1 1 0.50 lea r8d, [rsi + rdi]
1 0 0.25 xor ecx, ecx
1 1 0.33 mov eax, 0
1 1 0.33 test r8b, 3
1 1 1.00 je .LBB5_3
1 1 0.33 mov rdx, rcx
1 1 1.00 U ret
1 1 0.50 lea rax, [rdx + 2*rdx]
1 1 0.33 or rax, 3
1 1 0.33 add rax, 9
1 1 0.33 sub rsi, rax
1 1 1.00 jae .LBB5_4
1 0 0.25 xor eax, eax
1 1 0.33 mov edx, 1
1 1 1.00 U ret
1 1 0.33 add rdi, rsi
1 1 0.33 mov rcx, rdx
1 1 0.33 mov rax, rdi
1 1 0.33 mov rdx, rcx
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 6.99 7.00 - 7.01 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - - 0.99 - 0.01 - - movabs rax, 3074457345618258598
- - 0.01 0.50 - 0.49 - - cmp rdx, rax
- - - - - 1.00 - - ja .LBB5_1
- - - 1.00 - - - - lea r8d, [rsi + rdi]
- - - - - - - - xor ecx, ecx
- - 0.50 0.49 - 0.01 - - mov eax, 0
- - 0.49 0.51 - - - - test r8b, 3
- - - - - 1.00 - - je .LBB5_3
- - 0.51 0.49 - - - - mov rdx, rcx
- - - - - 1.00 - - ret
- - 0.50 0.50 - - - - lea rax, [rdx + 2*rdx]
- - 1.00 - - - - - or rax, 3
- - 1.00 - - - - - add rax, 9
- - 0.99 0.01 - - - - sub rsi, rax
- - - - - 1.00 - - jae .LBB5_4
- - - - - - - - xor eax, eax
- - - 1.00 - - - - mov edx, 1
- - - - - 1.00 - - ret
- - 1.00 - - - - - add rdi, rsi
- - - 1.00 - - - - mov rcx, rdx
- - 0.99 0.01 - - - - mov rax, rdi
- - - 0.50 - 0.50 - - mov rdx, rcx
- - - - - 1.00 - - ret

View File

@ -0,0 +1,13 @@
#[path = "formats/coco_dynamic_size.rs"]
mod format;
#[unsafe(no_mangle)]
fn bench_ref_from_suffix_with_elems_dynamic_size(
source: &[u8],
count: usize,
) -> Option<&format::LocoPacket> {
match zerocopy::FromBytes::ref_from_suffix_with_elems(source, count) {
Ok((_rest, packet)) => Some(packet),
_ => None,
}
}

View File

@ -0,0 +1,23 @@
bench_ref_from_suffix_with_elems_dynamic_size:
movabs rax, 4611686018427387901
cmp rdx, rax
ja .LBB5_1
lea r8d, [rsi + rdi]
xor ecx, ecx
mov eax, 0
test r8b, 1
jne .LBB5_5
lea rax, [2*rdx + 4]
sub rsi, rax
jae .LBB5_4
.LBB5_1:
xor eax, eax
mov edx, 1
ret
.LBB5_4:
add rdi, rsi
mov rcx, rdx
mov rax, rdi
.LBB5_5:
mov rdx, rcx
ret

View File

@ -0,0 +1,77 @@
Iterations: 100
Instructions: 1900
Total Cycles: 571
Total uOps: 1900
Dispatch Width: 4
uOps Per Cycle: 3.33
IPC: 3.33
Block RThroughput: 5.0
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.33 movabs rax, 4611686018427387901
1 1 0.33 cmp rdx, rax
1 1 1.00 ja .LBB5_1
1 1 0.50 lea r8d, [rsi + rdi]
1 0 0.25 xor ecx, ecx
1 1 0.33 mov eax, 0
1 1 0.33 test r8b, 1
1 1 1.00 jne .LBB5_5
1 1 0.50 lea rax, [2*rdx + 4]
1 1 0.33 sub rsi, rax
1 1 1.00 jae .LBB5_4
1 0 0.25 xor eax, eax
1 1 0.33 mov edx, 1
1 1 1.00 U ret
1 1 0.33 add rdi, rsi
1 1 0.33 mov rcx, rdx
1 1 0.33 mov rax, rdi
1 1 0.33 mov rdx, rcx
1 1 1.00 U ret
Resources:
[0] - SBDivider
[1] - SBFPDivider
[2] - SBPort0
[3] - SBPort1
[4] - SBPort4
[5] - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6.0] [6.1]
- - 5.66 5.66 - 5.68 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6.0] [6.1] Instructions:
- - 0.66 0.33 - 0.01 - - movabs rax, 4611686018427387901
- - 0.01 0.99 - - - - cmp rdx, rax
- - - - - 1.00 - - ja .LBB5_1
- - 0.99 0.01 - - - - lea r8d, [rsi + rdi]
- - - - - - - - xor ecx, ecx
- - 0.33 0.33 - 0.34 - - mov eax, 0
- - 0.33 0.34 - 0.33 - - test r8b, 1
- - - - - 1.00 - - jne .LBB5_5
- - 0.34 0.66 - - - - lea rax, [2*rdx + 4]
- - - 1.00 - - - - sub rsi, rax
- - - - - 1.00 - - jae .LBB5_4
- - - - - - - - xor eax, eax
- - 1.00 - - - - - mov edx, 1
- - - - - 1.00 - - ret
- - - 1.00 - - - - add rdi, rsi
- - 1.00 - - - - - mov rcx, rdx
- - 0.32 0.68 - - - - mov rax, rdi
- - 0.68 0.32 - - - - mov rdx, rcx
- - - - - 1.00 - - ret

Some files were not shown because too many files have changed in this diff Show More