Skip to content

Commit 0da8f5e

Browse files
authored
feat: implement generics, explicit type casting, and static globals (#306)
This commit introduces the foundational infrastructure for generic programming, explicit type casting via the `as` operator, and global static variables. It also significantly matures the target-specific preprocessing and backend control. Changes: - **Generics Support**: - **AST & Parser**: Added `generic_params` to `FunctionNode` and `StructNode`. Added `type_args` to function calls. - **Parsing**: Implemented `parse_generic_param_names` and enhanced the type parser to handle nested generic arguments and top-level split logic. - **Stdlib**: Refactored core modules to provide generic utilities, including `TypedBuffer<T>`, `ptr_swap<T>`, and generic math functions (`num_abs`, `num_min`, etc.). - **Monomorphization**: Integrated a monomorphization pass into the compilation runner. - **Language Enhancements**: - **Type Casting**: Introduced the `as` keyword and `Expression::Cast` to support explicit type conversions in both runtime and compile-time contexts. - **Statics**: Added the `static` keyword for global variables, including backend support for global LLVM symbols. - **Null Support**: Added a native `null` literal and ensured type compatibility with pointers. - **Backend & Cross-Platform**: - **Target Control**: Expanded the `BackendOptions` to allow overriding target triples, CPU architectures, and ABIs. - **ABI Refinement**: Updated `abi_c.rs` to support both x86_64 and Arm64 for Linux and Darwin (macOS), including improved aggregate splitting logic for ARM64. - **Robust Preprocessing**: Overhauled `#[target(os="...")]` preprocessing to correctly ignore braces and semicolons inside comments or string literals. - **CLI & Documentation**: - Updated `README.md` to document the new **Tiered Platform Policy** and added a section for building from source. - Expanded the CLI with advanced `--llvm` and linker options. - Added a reference to the `doom.wave` example. These features significantly increase the expressiveness of the Wave language and provide the tools necessary for writing reusable, platform-aware systems code. Signed-off-by: LunaStev <luna@lunastev.org>
1 parent b222d9e commit 0da8f5e

35 files changed

+2548
-287
lines changed
Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
import("std::env::cwd");
2+
import("std::env::environ");
3+
import("std::path::copy");
4+
import("std::string::len");
5+
import("std::string::trim");
6+
import("std::string::ascii");
7+
import("std::string::hash");
8+
import("std::math::int");
9+
import("std::math::bits");
10+
import("std::math::float");
11+
import("std::math::num");
12+
import("std::time::clock");
13+
import("std::time::diff");
14+
import("std::time::sleep");
15+
import("std::buffer::alloc");
16+
import("std::buffer::write");
17+
import("std::buffer::read");
18+
import("std::mem::ops");
19+
import("std::net::tcp");
20+
21+
const DEFAULT_PORT: i32 = 18080;
22+
const ALERT_THRESHOLD: i32 = 3;
23+
const MAX_ROUTE_SCORE: i32 = 100;
24+
25+
static GLOBAL_EVENT_SEQ: i64 = 0;
26+
27+
type TenantId = i64;
28+
29+
enum Severity -> i32 {
30+
INFO = 1,
31+
WARN = 2,
32+
ERROR = 3
33+
}
34+
35+
#[target(os="linux")]
36+
fun default_data_root() -> str {
37+
return "/var/lib/wave";
38+
}
39+
40+
#[target(os="macos")]
41+
fun default_data_root() -> str {
42+
return "/usr/local/var/wave";
43+
}
44+
45+
struct KeyValue<K, V> {
46+
key: K;
47+
value: V;
48+
}
49+
50+
struct Window<T> {
51+
at_ns: i64;
52+
data: T;
53+
}
54+
55+
struct ApiResult<T> {
56+
ok: bool;
57+
data: T;
58+
}
59+
60+
struct RequestCtx {
61+
tenant: TenantId;
62+
method: str;
63+
route: str;
64+
}
65+
66+
struct Metrics {
67+
total: i32;
68+
info: i32;
69+
warn: i32;
70+
error: i32;
71+
}
72+
73+
struct IngestRecord {
74+
seq: i64;
75+
ctx: RequestCtx;
76+
severity: Severity;
77+
message: str;
78+
took_ns: i64;
79+
route_score: i32;
80+
route_hash: i64;
81+
}
82+
83+
proto Metrics {
84+
fun record(self: Metrics, sev: Severity) -> Metrics {
85+
var next: Metrics = self;
86+
next.total += 1;
87+
88+
if (sev == INFO) {
89+
next.info += 1;
90+
} else if (sev == WARN) {
91+
next.warn += 1;
92+
} else {
93+
next.error += 1;
94+
}
95+
96+
return next;
97+
}
98+
99+
fun is_alert(self: Metrics, threshold: i32) -> bool {
100+
if (self.error >= threshold) {
101+
return true;
102+
}
103+
return false;
104+
}
105+
}
106+
107+
proto RequestCtx {
108+
fun is_api(self: RequestCtx) -> bool {
109+
return is_api_route(self.route);
110+
}
111+
}
112+
113+
fun identity<T>(x: T) -> T {
114+
return x;
115+
}
116+
117+
fun choose<T>(left: T, right: T, pick_left: bool) -> T {
118+
if (pick_left) {
119+
return left;
120+
}
121+
return right;
122+
}
123+
124+
fun make_kv<K, V>(k: K, v: V) -> KeyValue<K, V> {
125+
var pair_value: KeyValue<K, V>;
126+
pair_value.key = k;
127+
pair_value.value = v;
128+
return pair_value;
129+
}
130+
131+
fun wrap_window<T>(data: T, at_ns: i64) -> Window<T> {
132+
var w: Window<T>;
133+
w.at_ns = at_ns;
134+
w.data = data;
135+
return w;
136+
}
137+
138+
fun ok<T>(payload: T) -> ApiResult<T> {
139+
var r: ApiResult<T>;
140+
r.ok = true;
141+
r.data = payload;
142+
return r;
143+
}
144+
145+
fun is_api_route(route: str) -> bool {
146+
var n: i32 = len(route);
147+
if (n < 5) {
148+
return false;
149+
}
150+
151+
if (route[0] != 47) {
152+
return false;
153+
}
154+
155+
if (to_lower(route[1]) != 97) {
156+
return false;
157+
}
158+
159+
if (to_lower(route[2]) != 112) {
160+
return false;
161+
}
162+
163+
if (to_lower(route[3]) != 105) {
164+
return false;
165+
}
166+
167+
if (route[4] != 47) {
168+
return false;
169+
}
170+
171+
return true;
172+
}
173+
174+
fun parse_severity(line: str) -> Severity {
175+
var i: i32 = trim_left_index(line);
176+
var c: u8 = to_upper(line[i]);
177+
178+
if (c == 69) {
179+
return ERROR;
180+
}
181+
182+
if (c == 87) {
183+
return WARN;
184+
}
185+
186+
return INFO;
187+
}
188+
189+
fun score_route(route: str) -> i32 {
190+
var n: i32 = len(route);
191+
var score: i32 = clamp(n * 2, 0, MAX_ROUTE_SCORE);
192+
193+
if (path_has_ext(route)) {
194+
score += 12;
195+
}
196+
197+
if (!path_is_abs(route)) {
198+
score += 4;
199+
}
200+
201+
var ext_start: i32 = path_ext_start(route);
202+
if (ext_start >= 0) {
203+
var tail: i32 = n - ext_start;
204+
score += min(tail * 3, 20);
205+
}
206+
207+
score += popcount(n);
208+
score = align_up(score, 4);
209+
score = clamp(score, 0, MAX_ROUTE_SCORE);
210+
211+
return score;
212+
}
213+
214+
fun build_log_frame(ctx: RequestCtx, sev: Severity) -> i64 {
215+
var frame: Buffer = buffer_new_default();
216+
217+
var tenant_kv: KeyValue<str, TenantId> = make_kv<str, TenantId>("tenant", ctx.tenant);
218+
219+
buffer_append_str(&frame, tenant_kv.key);
220+
buffer_push(&frame, 61);
221+
222+
if (sev == ERROR) {
223+
buffer_append_str(&frame, "ERROR");
224+
} else if (sev == WARN) {
225+
buffer_append_str(&frame, "WARN");
226+
} else {
227+
buffer_append_str(&frame, "INFO");
228+
}
229+
230+
buffer_push(&frame, 124);
231+
buffer_append_str(&frame, ctx.route);
232+
233+
var first_byte: u8 = buffer_at(frame, 0);
234+
235+
var a: array<u8, 4>;
236+
var b: array<u8, 4>;
237+
238+
mem_set(&a[0], first_byte, 4);
239+
mem_copy(&b[0], &a[0], 4);
240+
241+
var ok_mem: bool = (mem_cmp(&a[0], &b[0], 4) == 0);
242+
var used: i64 = frame.len;
243+
244+
if (!ok_mem) {
245+
used = -1;
246+
}
247+
248+
buffer_free(&frame);
249+
return used;
250+
}
251+
252+
fun ingest_one(
253+
ctx: RequestCtx,
254+
line: str,
255+
base_metrics: Metrics
256+
) -> ApiResult<Window<IngestRecord>> {
257+
var start_ts: TimeSpec;
258+
time_now_monotonic(&start_ts);
259+
260+
GLOBAL_EVENT_SEQ += 1;
261+
262+
var sev: Severity = parse_severity(line);
263+
var next_metrics: Metrics = base_metrics.record(sev);
264+
265+
var end_ts: TimeSpec;
266+
time_now_monotonic(&end_ts);
267+
268+
var took_ns: i64 = time_diff_ns(start_ts, end_ts);
269+
270+
var rec: IngestRecord;
271+
rec.seq = GLOBAL_EVENT_SEQ;
272+
rec.ctx = ctx;
273+
rec.severity = sev;
274+
rec.message = line;
275+
rec.took_ns = took_ns;
276+
rec.route_score = score_route(ctx.route);
277+
rec.route_hash = fnv1a_64(ctx.route);
278+
279+
var wrapped: Window<IngestRecord> = wrap_window<IngestRecord>(
280+
rec,
281+
time_now_monotonic_ns()
282+
);
283+
284+
if (next_metrics.is_alert(ALERT_THRESHOLD)) {
285+
println("ALERT tenant={} errors={}", ctx.tenant, next_metrics.error);
286+
}
287+
288+
return ok<Window<IngestRecord>>(wrapped);
289+
}
290+
291+
fun bootstrap_runtime() {
292+
var cwd_buf: array<u8, 512>;
293+
var cwd_len: i64 = env_getcwd(&cwd_buf[0], 512);
294+
295+
var env_port_raw: array<u8, 64>;
296+
var env_port_len: i64 = env_get("WAVE_PORT", &env_port_raw[0], 64);
297+
298+
var configured_port: i32 = env_get_i32_default("WAVE_PORT", DEFAULT_PORT);
299+
var port: i32 = clamp(configured_port, 1024, 65535);
300+
301+
var listener: TcpListener = tcp_bind(port as i16);
302+
tcp_close_listener(listener);
303+
304+
var root: str = default_data_root();
305+
306+
var log_path_buf: array<u8, 256>;
307+
var base_buf: array<u8, 128>;
308+
var dir_buf: array<u8, 128>;
309+
310+
var log_path_len: i32 = path_join2(&log_path_buf[0], 256, root, "spool/events.log");
311+
var base_len: i32 = path_basename_copy(&base_buf[0], 128, root);
312+
var dir_len: i32 = path_dirname_copy(&dir_buf[0], 128, root);
313+
314+
println("platform root: {}", root);
315+
println("cwd length: {}", cwd_len);
316+
println("env WAVE_PORT len: {}", env_port_len);
317+
println("log path bytes: {}", log_path_len);
318+
println("base len: {} dirname len: {}", base_len, dir_len);
319+
println("has HOME? {}", env_exists("HOME"));
320+
}
321+
322+
fun main() {
323+
bootstrap_runtime();
324+
325+
var metrics: Metrics = Metrics {
326+
total: 0,
327+
info: 0,
328+
warn: 0,
329+
error: 0
330+
};
331+
332+
var ctx: RequestCtx = RequestCtx {
333+
tenant: 42,
334+
method: "POST",
335+
route: "/api/v1/orders/create.csv"
336+
};
337+
338+
if (ctx.is_api()) {
339+
println("api route accepted: {}", ctx.route);
340+
}
341+
342+
var r1: ApiResult<Window<IngestRecord>> = ingest_one(
343+
ctx,
344+
"WARN order latency reached 240ms",
345+
metrics
346+
);
347+
348+
metrics = metrics.record(r1.data.data.severity);
349+
350+
var r2: ApiResult<Window<IngestRecord>> = ingest_one(
351+
ctx,
352+
"ERROR payment provider timeout",
353+
metrics
354+
);
355+
356+
metrics = metrics.record(r2.data.data.severity);
357+
358+
var normalized_score: i32 = identity<i32>(r2.data.data.route_score);
359+
var stronger_score: i32 = choose<i32>(normalized_score, 64, normalized_score > 64);
360+
361+
var jitter_raw: i32 = abs((r2.data.data.took_ns % 97) as i32);
362+
var jitter_aligned: i32 = align_down(jitter_raw + 7, 4);
363+
var jitter_factor: f32 = clamp_f32((jitter_aligned as f32) / 10.0, 0.1, 10.0);
364+
365+
var frame_size: i64 = build_log_frame(ctx, r2.data.data.severity);
366+
var shard: i32 = ilog2_ceil(max(1, stronger_score));
367+
var bucket: i32 = gcd((r2.data.data.route_hash % 1000) as i32, 360);
368+
369+
println(
370+
"seq={} score={} frame={} took={}ns",
371+
r2.data.data.seq,
372+
stronger_score,
373+
frame_size,
374+
r2.data.data.took_ns
375+
);
376+
377+
println(
378+
"hash={} shard={} bucket={} jitter={}",
379+
r2.data.data.route_hash,
380+
shard,
381+
bucket,
382+
jitter_factor
383+
);
384+
385+
println(
386+
"metrics total={} info={} warn={} error={}",
387+
metrics.total,
388+
metrics.info,
389+
metrics.warn,
390+
metrics.error
391+
);
392+
393+
time_sleep_ms(1);
394+
}

0 commit comments

Comments
 (0)