blob: cbd893559392af046e9489e71ff4c6308010eb2d [file] [edit]
// Copyright 2024 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#ifndef ZIRCON_KERNEL_VM_INCLUDE_VM_ATTRIBUTION_H_
#define ZIRCON_KERNEL_VM_INCLUDE_VM_ATTRIBUTION_H_
#include <cassert>
#include <ffl/fixed.h>
namespace vm {
// Structure to store fractional counts of bytes in fixed point with 63 bits of precision. The max
// fractional value is thus `2-Fraction::Epsilon()`. Counts are strictly unsigned.
//
// This structure supports accumulation with other fractional counts and the code will handle the
// internal bookkeeping around moving whole bytes from the fractional sum to the integral sum.
//
// The structure also supports addition, subtraction, and division with whole integers. If overflow
// occurs in either direction, an assert is fired. Multiplication is not supported.
//
// We always check and guarantee that any fractional sums >=1 have the excess byte stripped off
// and rolled over to the integral value. Thus, since accumulation always operates on fractions
// `<=1-Fraction::Epsilon()`, it always generates results `<= 2-Fraction::Epsilon()` and there is
// never overflow in the fractional fields.
struct FractionalBytes {
using Fraction = ffl::Fixed<uint64_t, 63>;
constexpr static Fraction kOneByte = Fraction(1);
FractionalBytes() = default;
constexpr explicit FractionalBytes(uint64_t whole_bytes) : integral(whole_bytes) {}
constexpr explicit FractionalBytes(uint64_t numerator, uint64_t denominator)
: integral(numerator / denominator),
fractional((kOneByte / denominator) * (numerator % denominator)) {}
FractionalBytes operator+(const uint64_t& other) const {
FractionalBytes ret{*this};
ret += other;
return ret;
}
FractionalBytes operator-(const uint64_t& other) const {
FractionalBytes ret{*this};
ret -= other;
return ret;
}
FractionalBytes operator/(const uint64_t& other) const {
FractionalBytes ret{*this};
ret /= other;
return ret;
}
FractionalBytes& operator+=(const uint64_t& other) {
[[maybe_unused]] bool overflow = __builtin_add_overflow(integral, other, &integral);
DEBUG_ASSERT(!overflow);
return *this;
}
FractionalBytes& operator-=(const uint64_t& other) {
[[maybe_unused]] bool overflow = __builtin_sub_overflow(integral, other, &integral);
DEBUG_ASSERT(!overflow);
return *this;
}
FractionalBytes& operator/=(const uint64_t& other) {
// Input fraction must always be <1 to guard against overflow.
// If this is true, the sum of fractions must be <1:
// The sum is:
// `(fractional / other) + (1 / other) * remainder`
// which we can rewrite as
// `(fractional + remainder) / other`
// We know that fractional < 1 and remainder < other, thus (fractional + remainder) < other and
// the rewritten sum cannot be >=1.
DEBUG_ASSERT(fractional < kOneByte);
const uint64_t remainder = integral % other;
const Fraction scaled_remainder = (kOneByte / other) * remainder;
fractional = (fractional / other) + scaled_remainder;
DEBUG_ASSERT(fractional < kOneByte);
integral /= other;
return *this;
}
FractionalBytes operator+(const FractionalBytes& other) const {
FractionalBytes ret{*this};
ret += other;
return ret;
}
FractionalBytes& operator+=(const FractionalBytes& other) {
// Input fractions must always be <1 to guard against overflow.
// If the fractional sum is >=1, then roll that overflow byte into the integral part.
DEBUG_ASSERT(fractional < kOneByte);
DEBUG_ASSERT(other.fractional < kOneByte);
fractional += other.fractional;
if (fractional >= kOneByte) {
[[maybe_unused]] bool overflow = __builtin_add_overflow(integral, 1, &integral);
DEBUG_ASSERT(!overflow);
fractional -= kOneByte;
}
[[maybe_unused]] bool overflow = __builtin_add_overflow(integral, other.integral, &integral);
DEBUG_ASSERT(!overflow);
return *this;
}
bool operator==(const FractionalBytes& other) const {
return integral == other.integral && fractional == other.fractional;
}
bool operator!=(const FractionalBytes& other) const { return !(*this == other); }
size_t integral = 0;
Fraction fractional = Fraction::FromRaw(0);
};
// Structure to store counts of memory attributed to VMOs or portions thereof.
//
// These counts can be accumulated to support attributing memory across composite objects such as
// address spaces or processes.
//
// The `scaled_bytes` fields may contain a fractional number of bytes, and the structure stores the
// fractional counts in fixed point with 63 bits of precision.
struct AttributionCounts {
size_t total_bytes() const { return uncompressed_bytes + compressed_bytes; }
size_t total_private_bytes() const {
return private_uncompressed_bytes + private_compressed_bytes;
}
FractionalBytes total_scaled_bytes() const {
return scaled_uncompressed_bytes + scaled_compressed_bytes;
}
AttributionCounts& operator+=(const AttributionCounts& other) {
uncompressed_bytes += other.uncompressed_bytes;
compressed_bytes += other.compressed_bytes;
private_uncompressed_bytes += other.private_uncompressed_bytes;
private_compressed_bytes += other.private_compressed_bytes;
scaled_uncompressed_bytes += other.scaled_uncompressed_bytes;
scaled_compressed_bytes += other.scaled_compressed_bytes;
return *this;
}
bool operator==(const AttributionCounts& other) const {
return uncompressed_bytes == other.uncompressed_bytes &&
compressed_bytes == other.compressed_bytes &&
private_uncompressed_bytes == other.private_uncompressed_bytes &&
private_compressed_bytes == other.private_compressed_bytes &&
scaled_uncompressed_bytes == other.scaled_uncompressed_bytes &&
scaled_compressed_bytes == other.scaled_compressed_bytes;
}
bool operator!=(const AttributionCounts& other) const { return !(*this == other); }
size_t uncompressed_bytes = 0;
size_t compressed_bytes = 0;
size_t private_uncompressed_bytes = 0;
size_t private_compressed_bytes = 0;
FractionalBytes scaled_uncompressed_bytes;
FractionalBytes scaled_compressed_bytes;
};
} // namespace vm
#endif // ZIRCON_KERNEL_VM_INCLUDE_VM_ATTRIBUTION_H_