- Remove 'No __umulsidi3' claim from top comment — the rare path
does use it via /1000. Reworded to 'pure 32-bit ops on the common
path'.
- Extract MILLIS_RARE_PATH_THRESHOLD_US and US_PER_MS constants to
replace magic numbers 10000 and 1000.
Arduino's delay() uses esp_suspend() with a one-shot os_timer for
efficient single-suspension waiting. Our replacement polls millis()
with optimistic_yield(), which also enters esp_schedule/esp_suspend
via yield() but resumes repeatedly. Functionally correct for ESPHome
(delay is cold path, SDK tasks run via yield), just less power-
efficient for long delays.
Split the μs→ms conversion into two paths to keep the interrupt-
disabled critical section bounded:
- Common path (delta < 10 ms): while loop runs at most 10 iterations
(~100 ns). This covers the normal hot-path case where millis() is
called thousands of times per second.
- Rare path (delta >= 10 ms): constant-time multiply-by-reciprocal
via /1000 (compiled to __umulsidi3, ~2.5 μs). Only fires after a
long block (WiFi scan, boot, component stall), where the extra
latency is negligible relative to the block that caused it.
Addresses the concern that a multi-second WiFi scan block could cause
the unbounded while loop to hold interrupts for tens of microseconds.
Merge the separate millis_accumulator() function directly into millis()
to eliminate the extra function call overhead and prologue/epilogue.
Pack the three statics into a struct so the compiler loads one base
address instead of three literal pool entries.
Saves ~12 bytes of IRAM (87+6 → 81 bytes).
Arduino's micros64() reads the same overflow tracking statics that
init() sets up. Wrapping init() as a no-op would cause micros64()
to silently return wrong values after 71 minutes. The overflow timer
cost is negligible (~3 μs per 60 seconds).
Arduino's delay(0) calls yield() once — used as a yield point by some
code. Our millis-based loop would skip yielding entirely since
0 < 0 is false. Add explicit delay(0) → yield() handling.
Arduino's ::delay() is implemented in core_esp8266_wiring.cpp alongside
millis(). Because __delay calls millis() within the same compilation
unit, --wrap=millis can't intercept that intra-object reference, which
prevents the linker from garbage-collecting the original millis body
(~80 bytes of IRAM).
Replace esphome::delay() with a simple yield loop that uses our fast
millis() accumulator. This has the same behavior as Arduino's delay:
feeds the watchdog and processes SDK tasks via yield(), keeping WiFi
alive. With __delay unreferenced, the linker can now GC both __delay
and the original millis function.