mirror of
https://github.com/esphome/esphome.git
synced 2026-09-19 02:58:38 +00:00
[light] Replace soft-float range check with union bit-cast + unsigned compare
The inlined `value < 0.0f || value > 1.0f` check in the clamp loop costs ~50 B per iteration on ESP8266 (two libgcc soft-float calls with register spills). IEEE 754 floats in [0.0f, 1.0f] have bit patterns in [0x00000000, 0x3F800000]; anything out of range — values > 1.0f, negatives (sign bit set → huge unsigned interpretation), NaN, Infinity — has a strictly larger unsigned interpretation. A single `pun.u > 0x3F800000u` covers every case. Using a union for the type-pun rather than memcpy/bit_cast because those don't optimize to a no-op on xtensa-gcc (same reason api/proto.h's float_to_raw() uses a union). The loop body is now two instructions for the range check: l32i a2, a9, 0 ; load raw u32 bgeu a10, a2, ... ; compare against pre-hoisted 1.0f bits Size delta vs. the prior float-compare form: ESP32-IDF: validate_ -16 B, net -12 B ESP8266: validate_ -20 B, net -20 B vs. dev baseline (isolated light build, matched funcs): ESP32-IDF: -103 B code, +32 B PROGMEM table = -71 B net ESP8266: -42 B code, +32 B PROGMEM table = -10 B net
This commit is contained in:
@@ -334,8 +334,16 @@ LightColorValues LightCall::validate_() {
|
||||
// by bit position directly. Iterate only the set bits via __builtin_ctz +
|
||||
// clear-lowest-bit: HA can drive high-frequency automations through
|
||||
// perform(), so the hot path runs in O(popcount) instead of always
|
||||
// scanning all eight slots. The range check is inlined here (cold path
|
||||
// is the out-of-line helper) so an in-range value skips the call entirely.
|
||||
// scanning all eight slots.
|
||||
//
|
||||
// The range check is done on the IEEE 754 bit pattern as an unsigned int,
|
||||
// not on the float itself. Values in [0.0f, 1.0f] have bits in
|
||||
// [0x00000000, 0x3F800000]; anything greater (as unsigned) is out of range:
|
||||
// values > 1.0f have a larger bit pattern, and negative values have the
|
||||
// sign bit (0x80000000) set which makes their unsigned interpretation
|
||||
// enormous. One unsigned compare replaces two soft-float __ltsf2/__gtsf2
|
||||
// calls on ESP8266 and is essentially free on targets with an FPU too.
|
||||
constexpr uint32_t ONE_F_BITS = 0x3F800000u; // bit pattern of 1.0f
|
||||
float *const src_fields = &this->brightness_;
|
||||
float *const dst_fields = &v.brightness_;
|
||||
unsigned active = this->flags_ & CLAMP_FLAGS_MASK;
|
||||
@@ -343,7 +351,14 @@ LightColorValues LightCall::validate_() {
|
||||
unsigned bit = __builtin_ctz(active);
|
||||
active &= active - 1; // clear lowest set bit
|
||||
float &value = src_fields[bit];
|
||||
if (value < 0.0f || value > 1.0f)
|
||||
// Union type-pun (GCC/Clang extension): bit_cast/memcpy don't optimize to
|
||||
// a no-op on xtensa-gcc, same reasoning as api/proto.h float_to_raw().
|
||||
union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
} pun;
|
||||
pun.f = value;
|
||||
if (pun.u > ONE_F_BITS)
|
||||
log_out_of_range_and_clamp_(name, value, &FIELD_NAMES[bit], 0.0f, 1.0f);
|
||||
dst_fields[bit] = value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user