Skip to content
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Captures the current high-resolution time in ticks, suitable for measuring elapsed intervals with `get_time_usec`.
Returns a monotonic timestamp in nanoseconds since an unspecified epoch (the same epoch within a process; not comparable across processes or reboots). Use with `get_time_usec(ref)` / `get_time_nsec(ref)` for elapsed-interval math; raw subtraction `now - then` is also valid since the unit is always nanoseconds on every platform.
23 changes: 23 additions & 0 deletions modules/dasHV/src/dasHV.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
#include "daScript/misc/platform.h"

#include <future>
#include <cstdlib>

#include "../../../src/builtin/module_builtin_rtti.h"

#include "dasHV.h"

#include <hv/hlog.h>

IMPLEMENT_EXTERNAL_TYPE_FACTORY(WebSocketClient,hv::WebSocketClient)
IMPLEMENT_EXTERNAL_TYPE_FACTORY(WebSocketServer,hv::WebSocketServer)
IMPLEMENT_EXTERNAL_TYPE_FACTORY(WebSocketChannel,hv::WebSocketChannel)
Expand Down Expand Up @@ -1167,6 +1170,26 @@ char * das_httpreq_get_url_encoded ( HttpRequest * req, const char * key, Contex
class Module_HV : public Module {
public:
Module_HV() : Module("dashv") {
// libhv defaults to file_logger (bin/libhv.YYYYMMDD.log). Invisible
// under popen + CI runners. Two env-gated opt-outs:
// DASLIVE_HV_LOG=stderr (or =1) → redirect libhv to stderr
// DASLIVE_HV_LOG=stdout → redirect libhv to stdout
// DASLIVE_HV_LOG=silent → disable libhv logging entirely
// DASLIVE_HV_LOG_LEVEL=DEBUG/INFO/WARN/ERROR overrides the level.
// Default unset → file_logger (unchanged).
if (const char * route = std::getenv("DASLIVE_HV_LOG")) {
if (!strcmp(route, "stderr") || !strcmp(route, "1")) {
hlog_set_handler(stderr_logger);
} else if (!strcmp(route, "stdout")) {
hlog_set_handler(stdout_logger);
} else if (!strcmp(route, "silent")) {
hlog_disable();
}
}
if (const char * lvl = std::getenv("DASLIVE_HV_LOG_LEVEL")) {
hlog_set_level_by_str(lvl);
}

ModuleLibrary lib;
lib.addModule(this);
lib.addBuiltInModule();
Expand Down
6 changes: 5 additions & 1 deletion site/blog/build_blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,10 @@ def render_index(posts: list[Entry], md) -> str:
<h1 class="forge-h2" style="font-size:44px;">Notes from the language.</h1>
<p class="forge-p" style="max-width:640px;">
Design rationale, refactor stories, and the occasional shipping
announcement. Newest first. <a href="feed.xml">RSS</a>.
announcement. Newest first.
</p>
<p style="max-width:640px; margin:8px 0 0; font-family:var(--font-mono); font-size:11.5px; text-transform:uppercase; letter-spacing:1.4px;">
<a href="feed.xml" style="color:var(--amber);">RSS</a>
</p>
<div class="forge-blog-list">
{chr(10).join(items)}
Expand Down Expand Up @@ -405,6 +408,7 @@ def render_atom_feed(posts: list[Entry], site_url: str) -> str:
<link href="{site_url}/blog/"/>
<updated>{updated}T00:00:00Z</updated>
<id>{site_url}/blog/</id>
<author><name>Boris Batkin</name></author>
{chr(10).join(entries)}
</feed>
"""
Expand Down
1 change: 1 addition & 0 deletions site/downloads.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<meta name="description" content="Daslang releases, repository, blog, and ecosystem links." />

<link rel="icon" type="image/svg+xml" href="files/forge-favicon.svg" />
<link rel="alternate" type="application/atom+xml" title="Daslang blog" href="blog/feed.xml" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=JetBrains+Mono:wght@400;500;600;700&display=swap" />
Expand Down
1 change: 1 addition & 0 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

<link rel="icon" type="image/svg+xml" href="files/forge-favicon.svg" />
<link rel="alternate icon" type="image/x-icon" href="doc/_static/daslang.ico" />
<link rel="alternate" type="application/atom+xml" title="Daslang blog" href="blog/feed.xml" />

<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
Expand Down
80 changes: 57 additions & 23 deletions src/hal/performance_time.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,71 @@ namespace das {

#endif

// ref_time_ticks() returns CLOCK_MONOTONIC-style nanoseconds on every platform.
// Prior to this normalization, Windows returned raw QueryPerformanceCounter
// ticks (~10 MHz typical) while Linux/macOS already returned ns — so callers
// that did `ref_time_ticks() + int64(timeout_sec * 1_000_000)` got 30 s on
// Windows (lucky math at 10 MHz) but 30 ms on POSIX. The footgun is gone: raw
// subtraction `now - then` always yields nanoseconds elapsed.
//
// Prefer `get_time_usec(start)` / `get_time_nsec(start)` for elapsed-time
// comparisons — those wrap the subtraction and continue to work portably.

#ifdef _MSC_VER

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

// QueryPerformanceFrequency is invariant after boot — cache once per process
// to avoid a syscall on every ref_time_ticks() call. Race-tolerant: parallel
// initialisers all compute the same value, and int64 stores are atomic on
// x64/arm64.
//
// We also precompute `qpc_ns_per_tick = 1e9 / freq` when it divides cleanly
// (the universal Win 7+ case where QPF = 10 MHz → 100 ns/tick). The fast path
// is one multiply per call, so ref_time_ticks() stays within ~1 ns of the
// bare QueryPerformanceCounter cost — critical for the function profiler,
// which brackets every call. Fallback split path handles non-divisible
// frequencies (theoretical; not observed on modern Windows).
static int64_t qpc_ns_per_tick = 0; // 0 -> use the fallback split path

static int64_t qpc_freq() {
static int64_t cached = 0;
if ( cached == 0 ) {
LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
cached = f.QuadPart;
qpc_ns_per_tick = (1000000000LL % cached == 0) ? (1000000000LL / cached) : 0;
}
return cached;
}

extern "C" int64_t ref_time_ticks () {
LARGE_INTEGER t0;
LARGE_INTEGER t0;
QueryPerformanceCounter(&t0);
return t0.QuadPart;
const int64_t freq = qpc_freq();
if ( qpc_ns_per_tick ) {
return t0.QuadPart * qpc_ns_per_tick;
}
// Fallback: convert QPC counter to nanoseconds without overflowing int64:
// ns = (ticks / freq) * 1e9 + (ticks % freq) * 1e9 / freq
// freq is typically 10 MHz, so (ticks / freq) fits comfortably and the
// remainder * 1e9 also fits (max ~1e16, well under 2^63).
const int64_t whole = t0.QuadPart / freq;
const int64_t rem = t0.QuadPart % freq;
return whole * 1000000000LL + (rem * 1000000000LL) / freq;
}

extern "C" int get_time_usec ( int64_t reft ) {
int64_t t0 = ref_time_ticks();
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
return int((t0-reft)*1000000LL/freq.QuadPart);
return int((ref_time_ticks() - reft) / 1000LL);
}

extern "C" int64_t get_time_nsec ( int64_t reft ) {
int64_t t0 = ref_time_ticks();
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
return int64_t((t0-reft)*1000000000LL/freq.QuadPart);
return ref_time_ticks() - reft;
}

extern "C" int64_t ref_time_delta_to_usec(int64_t ref)
{
LARGE_INTEGER freq;
QueryPerformanceCounter(&freq);
return ref * 1000000LL/freq.QuadPart;
extern "C" int64_t ref_time_delta_to_usec ( int64_t ref ) {
return ref / 1000LL;
}

#elif __linux__ || defined(_EMSCRIPTEN_VER) || defined __HAIKU__
Expand All @@ -59,37 +94,36 @@ extern "C" int64_t ref_time_ticks () {
DAS_ASSERT(false);
return -1;
}

return ts.tv_sec * NSEC_IN_SEC + ts.tv_nsec;
}

extern "C" int get_time_usec ( int64_t reft ) {
return int((ref_time_ticks() - reft) / (NSEC_IN_SEC/1000000LL));
return int((ref_time_ticks() - reft) / 1000LL);
}

extern "C" int64_t get_time_nsec ( int64_t reft ) {
return ref_time_ticks() - reft;
return ref_time_ticks() - reft;
}

extern "C" int64_t ref_time_delta_to_usec(int64_t ref) { return ref / (NSEC_IN_SEC/1000000LL); }
extern "C" int64_t ref_time_delta_to_usec ( int64_t ref ) { return ref / 1000LL; }


#else // osx

#include <time.h>

extern "C" int64_t ref_time_ticks() {
extern "C" int64_t ref_time_ticks () {
return clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW);
}

extern "C" int get_time_usec ( int64_t reft ) {
return (clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) - reft)/1000LL;
return int((ref_time_ticks() - reft) / 1000LL);
}

extern "C" int64_t get_time_nsec ( int64_t reft ) {
return clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) - reft;
return ref_time_ticks() - reft;
}

extern "C" int64_t ref_time_delta_to_usec(int64_t ref) { return ref / 1000LL; }
extern "C" int64_t ref_time_delta_to_usec ( int64_t ref ) { return ref / 1000LL; }

#endif
Loading