Change NNAPI time from steady_clock to boot_clock -- hal
Previously, the NNAPI used std::chrono::steady_clock to represent and
measure timings. However, steady_clock does not count while the system
is suspended. Instead, boot_clock is monotonic like steady_clock but
does include the time when the system is suspended.
This change also indicates that services may convert from
std::chrono::steady_clock::time_point to
android::base::boot_clock::time_point in the HIDL 1.3 NN HAL.
Bug: 183118340
Test: mma
Test: VtsHalNeuralnetworksV1_3TargetTest
Test: VtsHalNeuralnetworksTargetTest
Test: presubmit
Change-Id: I5a7d039a31d9ce98602a301387ec99635f279f42
diff --git a/neuralnetworks/1.3/IDevice.hal b/neuralnetworks/1.3/IDevice.hal
index e0b04a8..de889e4 100644
--- a/neuralnetworks/1.3/IDevice.hal
+++ b/neuralnetworks/1.3/IDevice.hal
@@ -131,6 +131,14 @@
* ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
* ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due
* to an abort must be sent the same way as other errors, described above.
+ * The deadline is represented as nanoseconds since the epoch of the steady
+ * clock (as if from std::chrono::steady_clock::time_point), but the service
+ * may convert it to the nanoseconds since boot time (as if from
+ * clock_gettime(CLOCK_BOOTTIME, &ts) or
+ * android::base::boot_clock::time_point) to account for time when the
+ * system is suspended. This conversion can by done by finding the timeout
+ * duration remaining compared to the steady_clock and adding it to the
+ * current boot_clock time.
*
* Optionally, the driver may save the prepared model to cache during the
* asynchronous preparation. Any error that occurs when saving to cache must
@@ -249,7 +257,15 @@
* ErrorStatus::MISSED_DEADLINE_TRANSIENT}
* or {@link ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The
* error due to an abort must be sent the same way as other errors,
- * described above.
+ * described above. The deadline is represented as nanoseconds since the
+ * epoch of the steady clock (as if from
+ * std::chrono::steady_clock::time_point), but the service may convert it to
+ * the nanoseconds since boot time (as if from
+ * clock_gettime(CLOCK_BOOTTIME, &ts) or
+ * android::base::boot_clock::time_point) to account for time when the
+ * system is suspended. This conversion can by done by finding the timeout
+ * duration remaining compared to the steady_clock and adding it to the
+ * current boot_clock time.
*
* The only information that may be unknown to the model at this stage is
* the shape of the tensors, which may only be known at execution time. As
diff --git a/neuralnetworks/1.3/IPreparedModel.hal b/neuralnetworks/1.3/IPreparedModel.hal
index e7d63f4..8b86a1a 100644
--- a/neuralnetworks/1.3/IPreparedModel.hal
+++ b/neuralnetworks/1.3/IPreparedModel.hal
@@ -74,6 +74,14 @@
* ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
* ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due
* to an abort must be sent the same way as other errors, described above.
+ * The deadline is represented as nanoseconds since the epoch of the steady
+ * clock (as if from std::chrono::steady_clock::time_point), but the service
+ * may convert it to the nanoseconds since boot time (as if from
+ * clock_gettime(CLOCK_BOOTTIME, &ts) or
+ * android::base::boot_clock::time_point) to account for time when the
+ * system is suspended. This conversion can by done by finding the timeout
+ * duration remaining compared to the steady_clock and adding it to the
+ * current boot_clock time.
*
* Any number of calls to the execute* and executeSynchronously* functions,
* in any combination, may be made concurrently, even on the same
@@ -150,6 +158,14 @@
* ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
* ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due
* to an abort must be sent the same way as other errors, described above.
+ * The deadline is represented as nanoseconds since the epoch of the steady
+ * clock (as if from std::chrono::steady_clock::time_point), but the service
+ * may convert it to the nanoseconds since boot time (as if from
+ * clock_gettime(CLOCK_BOOTTIME, &ts) or
+ * android::base::boot_clock::time_point) to account for time when the
+ * system is suspended. This conversion can by done by finding the timeout
+ * duration remaining compared to the steady_clock and adding it to the
+ * current boot_clock time.
*
* Any number of calls to the execute* and executeSynchronously* functions,
* in any combination, may be made concurrently, even on the same
@@ -231,6 +247,14 @@
* {@link ErrorStatus::MISSED_DEADLINE_TRANSIENT} or {@link
* ErrorStatus::MISSED_DEADLINE_PERSISTENT} may be returned. The error due
* to an abort must be sent the same way as other errors, described above.
+ * The deadline is represented as nanoseconds since the epoch of the steady
+ * clock (as if from std::chrono::steady_clock::time_point), but the service
+ * may convert it to the nanoseconds since boot time (as if from
+ * clock_gettime(CLOCK_BOOTTIME, &ts) or
+ * android::base::boot_clock::time_point) to account for time when the
+ * system is suspended. This conversion can by done by finding the timeout
+ * duration remaining compared to the steady_clock and adding it to the
+ * current boot_clock time.
*
* If any of the sync fences in waitFor changes to error status after the executeFenced
* call succeeds, or the execution is aborted because it cannot finish before the deadline
diff --git a/neuralnetworks/1.3/utils/src/Conversions.cpp b/neuralnetworks/1.3/utils/src/Conversions.cpp
index 9788fe1..a400240 100644
--- a/neuralnetworks/1.3/utils/src/Conversions.cpp
+++ b/neuralnetworks/1.3/utils/src/Conversions.cpp
@@ -40,6 +40,23 @@
namespace {
+std::chrono::nanoseconds makeNanosFromUint64(uint64_t nanoseconds) {
+ constexpr auto kMaxCount = std::chrono::nanoseconds::max().count();
+ using CommonType = std::common_type_t<std::chrono::nanoseconds::rep, uint64_t>;
+ const auto count = std::min<CommonType>(kMaxCount, nanoseconds);
+ return std::chrono::nanoseconds{static_cast<std::chrono::nanoseconds::rep>(count)};
+}
+
+uint64_t makeUint64FromNanos(std::chrono::nanoseconds nanoseconds) {
+ if (nanoseconds < std::chrono::nanoseconds::zero()) {
+ return 0;
+ }
+ constexpr auto kMaxCount = std::numeric_limits<uint64_t>::max();
+ using CommonType = std::common_type_t<std::chrono::nanoseconds::rep, uint64_t>;
+ const auto count = std::min<CommonType>(kMaxCount, nanoseconds.count());
+ return static_cast<uint64_t>(count);
+}
+
template <typename Type>
constexpr std::underlying_type_t<Type> underlyingType(Type value) {
return static_cast<std::underlying_type_t<Type>>(value);
@@ -276,8 +293,32 @@
switch (optionalTimePoint.getDiscriminator()) {
case Discriminator::none:
return {};
- case Discriminator::nanosecondsSinceEpoch:
- return TimePoint{Duration{optionalTimePoint.nanosecondsSinceEpoch()}};
+ case Discriminator::nanosecondsSinceEpoch: {
+ const auto currentSteadyTime = std::chrono::steady_clock::now();
+ const auto currentBootTime = Clock::now();
+
+ const auto timeSinceEpoch =
+ makeNanosFromUint64(optionalTimePoint.nanosecondsSinceEpoch());
+ const auto steadyTimePoint = std::chrono::steady_clock::time_point{timeSinceEpoch};
+
+ // Both steadyTimePoint and currentSteadyTime are guaranteed to be non-negative, so this
+ // subtraction will never overflow or underflow.
+ const auto timeRemaining = steadyTimePoint - currentSteadyTime;
+
+ // currentBootTime is guaranteed to be non-negative, so this code only protects against
+ // an overflow.
+ nn::TimePoint bootTimePoint;
+ constexpr auto kZeroNano = std::chrono::nanoseconds::zero();
+ constexpr auto kMaxTime = nn::TimePoint::max();
+ if (timeRemaining > kZeroNano && currentBootTime > kMaxTime - timeRemaining) {
+ bootTimePoint = kMaxTime;
+ } else {
+ bootTimePoint = currentBootTime + timeRemaining;
+ }
+
+ constexpr auto kZeroTime = nn::TimePoint{};
+ return std::max(bootTimePoint, kZeroTime);
+ }
}
return NN_ERROR(nn::ErrorStatus::GENERAL_FAILURE)
<< "Invalid OptionalTimePoint discriminator "
@@ -601,9 +642,33 @@
nn::GeneralResult<OptionalTimePoint> unvalidatedConvert(
const nn::OptionalTimePoint& optionalTimePoint) {
+ const auto currentSteadyTime = std::chrono::steady_clock::now();
+ const auto currentBootTime = nn::Clock::now();
+
OptionalTimePoint ret;
if (optionalTimePoint.has_value()) {
- const auto count = optionalTimePoint.value().time_since_epoch().count();
+ const auto bootTimePoint = optionalTimePoint.value();
+
+ if (bootTimePoint < nn::TimePoint{}) {
+ return NN_ERROR() << "Trying to cast invalid time point";
+ }
+
+ // Both bootTimePoint and currentBootTime are guaranteed to be non-negative, so this
+ // subtraction will never overflow or underflow.
+ const auto timeRemaining = bootTimePoint - currentBootTime;
+
+ // currentSteadyTime is guaranteed to be non-negative, so this code only protects against an
+ // overflow.
+ std::chrono::steady_clock::time_point steadyTimePoint;
+ constexpr auto kZeroNano = std::chrono::nanoseconds::zero();
+ constexpr auto kMaxTime = std::chrono::steady_clock::time_point::max();
+ if (timeRemaining > kZeroNano && currentSteadyTime > kMaxTime - timeRemaining) {
+ steadyTimePoint = kMaxTime;
+ } else {
+ steadyTimePoint = currentSteadyTime + timeRemaining;
+ }
+
+ const uint64_t count = makeUint64FromNanos(steadyTimePoint.time_since_epoch());
ret.nanosecondsSinceEpoch(count);
}
return ret;