From eec70d1507702a7e7228b16fdd44baf7dde908ca Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Tue, 7 Jul 2026 12:51:11 +0800 Subject: [PATCH] fix(codegen): route hour()/minute()/... to engine pine_* helpers (KI-35) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The time/date builtins (hour, minute, second, dayofmonth, dayofweek, month, year, weekofyear) were emitted as an inline setenv("TZ")+tzset()+localtime_r lambda evaluated once per bar. On macOS that tzset() hits notifyd over Mach IPC (~173us/call); across a full 1m feed it costs minutes of wall time that read as an engine hang (KI-35). Emit calls to the engine's cached pine_() helpers (session_time.hpp) instead, for both the bare-variable forms (BAR_BUILTINS in tables.py) and the explicit tz-arg call form (visit_call.py). Value-identical: same tm fields and Pine offsets (dayofweek=tm_wday+1, month=tm_mon+1, year+1900), DST-exact — the engine applies the offsets internally, so field_expr is dropped in the call form. Fresh transpile emits zero leftover setenv("TZ") lambdas; lukeborgerding trades byte-identical to baseline. Requires pineforge-engine with the paired pine_* symbols (merged first). Co-Authored-By: Claude Opus 4.8 (1M context) --- pineforge_codegen/codegen/tables.py | 8 +++++--- pineforge_codegen/codegen/visit_call.py | 11 ++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pineforge_codegen/codegen/tables.py b/pineforge_codegen/codegen/tables.py index 5c217ff..4b9ab9d 100644 --- a/pineforge_codegen/codegen/tables.py +++ b/pineforge_codegen/codegen/tables.py @@ -96,10 +96,12 @@ def tz_time_field_lambda(field_expr: str, ts_arg: str, tz_arg: str) -> str: # ``_bar_hour()`` helpers. For UTC-exchange data (the crypto corpus, # SymInfo's constructor default) the lambda takes the gmtime_r fast # path and is value-identical to the old emission. + # Route through the engine's cached pine_hour/pine_minute/... (session_time.hpp) + # instead of an inline per-call setenv+tzset lambda — the lambda's TZ churn + # caused a macOS tzset()->notifyd IPC storm (KI-35). The engine helpers are + # value-identical (same tm fields + Pine offsets) but tzset-churn-free. **{ - name: tz_time_field_lambda( - TIME_FIELD_EXPRS[name], "current_bar_.timestamp", "syminfo_.timezone" - ) + name: f"pine_{name}(current_bar_.timestamp, syminfo_.timezone)" for name in ("hour", "minute", "second", "dayofmonth", "dayofweek", "month", "year", "weekofyear") }, diff --git a/pineforge_codegen/codegen/visit_call.py b/pineforge_codegen/codegen/visit_call.py index 89b276d..a35f71f 100644 --- a/pineforge_codegen/codegen/visit_call.py +++ b/pineforge_codegen/codegen/visit_call.py @@ -715,11 +715,12 @@ def _visit_func_call(self, node: FuncCall) -> str: # mutex-guarded setenv+localtime_r block as the 2-arg # form. tz_arg = "syminfo_.timezone" - # 2-arg form — honor the tz argument. The shared - # ``tz_time_field_lambda`` (codegen/tables.py) also backs the - # bare variable forms (``hour`` etc. via BAR_BUILTINS), so the - # numbers agree across both forms. - return tz_time_field_lambda(field_expr, ts_arg, tz_arg) + # Route through the engine's cached pine_() (session_time.hpp), + # same as the bare variable forms (BAR_BUILTINS) — value-identical but + # free of the per-call setenv+tzset churn (KI-35). field_expr is unused + # now (the engine applies the Pine offsets internally). + del field_expr + return f"pine_{func_name}((int64_t)({ts_arg}), {tz_arg})" # time(timeframe) or time(timeframe, session[, tz]) if func_name == "time" and namespace is None and (node.args or node.kwargs):