v / vlib / v2 / transformer / live.v
1178 lines · 1060 sloc · 32.69 KB · 34038bec7b93f4e3ddbbdcbe88ea21f4693eee43
Raw
1// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module transformer
5
6import os
7import v2.ast
8import v2.token
9
10// LiveReloadParts is the pure-computation bundle produced by `live_reload_parts`.
11pub struct LiveReloadParts {
12pub:
13 source_file string
14 c_decls []ast.Stmt
15 global_decls []ast.Stmt
16 preamble []ast.Stmt
17 check_stmts []ast.Stmt
18}
19
20// live_reload_parts is the pure-computation extraction of the pre-splice
21// state previously inline in `inject_live_reload`. Returns the resolved
22// source-file path plus the four stmt slices (C extern decls, GlobalDecls,
23// main() preamble, and per-for-body reload-check stmts) that the caller
24// splices into the file containing `main()`. Returns `none` when there are
25// no @[live] functions (the legacy early-return).
26//
27// Does NOT mutate `files` or any caller state. Caller decides whether to
28// splice the result into a legacy `[]ast.File` (via `inject_live_reload`)
29// or into a `FlatBuilder` (future `post_pass_to_flat`).
30//
31// Third Phase 5 (post_pass port) `_parts` extraction (follows s144's
32// `runtime_const_init_fn_stmts_parts` and s145's
33// `runtime_const_init_main_calls_parts`). Bit-equal: identical
34// `os.real_path` resolution + identical builder-helper outputs.
35pub fn (mut t Transformer) live_reload_parts() ?LiveReloadParts {
36 if t.live_fns.len == 0 {
37 return none
38 }
39 source_file := os.real_path(t.live_source_file)
40 return LiveReloadParts{
41 source_file: source_file
42 c_decls: build_live_c_decls()
43 global_decls: t.build_live_globals()
44 preamble: t.build_live_preamble(source_file)
45 check_stmts: t.build_live_check(source_file)
46 }
47}
48
49// LiveReloadFlatParts couples the locator output (file_idx of the main fn's
50// file) with the pure-computation LiveReloadParts payload, so the splice
51// step can run without re-locating.
52pub struct LiveReloadFlatParts {
53pub:
54 file_idx int
55 parts LiveReloadParts
56}
57
58// inject_live_reload_parts_from_flat locates the FIRST file in the flat AST
59// that contains a top-level non-method `fn main()` AND computes the
60// LiveReloadParts payload. Returns `none` when no @[live] functions exist
61// (live_reload_parts is empty) OR no main function is present in any file.
62//
63// The "first match wins" semantics of the legacy loop are preserved:
64// `inject_live_reload` breaks out of its file loop on the first file it
65// changes (the one containing main).
66pub fn (mut t Transformer) inject_live_reload_parts_from_flat(flat &ast.FlatAst) ?LiveReloadFlatParts {
67 parts := t.live_reload_parts() or { return none }
68 for i in 0 .. flat.files.len {
69 fc := flat.file_cursor(i)
70 stmts := fc.stmts()
71 for j in 0 .. stmts.len() {
72 c := stmts.at(j)
73 if c.kind() == .stmt_fn_decl && !c.flag(ast.flag_is_method) && c.name() == 'main' {
74 return LiveReloadFlatParts{
75 file_idx: i
76 parts: parts
77 }
78 }
79 }
80 }
81 return none
82}
83
84// inject_live_reload_to_flat is the FlatBuilder-side splice counterpart to
85// the legacy `inject_live_reload(mut []ast.File)`. Locates the main file
86// via `inject_live_reload_parts_from_flat`, cursor-prescans each non-main
87// function, rewrites matching bodies from cursors, then uses the FlatBuilder
88// primitives to splice the result back:
89// - `replace_fn_body_stmts(old_fn_id, new_body_ids)` for each rewritten FnDecl
90// - `replace_file_stmt(file_idx, stmt_idx, new_fn_id)` to rewire the file
91// - `prepend_file_stmts(file_idx, c_decl_ids + global_decl_ids)` for the
92// file-level C-extern + GlobalDecl prepend
93//
94// Bit-equal w.r.t. `signature()` to running legacy
95// `inject_live_reload(mut files)` followed by `ast.flatten_files(files)`.
96pub fn (mut t Transformer) inject_live_reload_to_flat(mut out ast.FlatBuilder) {
97 flat_parts := t.inject_live_reload_parts_from_flat(&out.flat) or { return }
98 file_idx := flat_parts.file_idx
99 parts := flat_parts.parts
100
101 // Per-FnDecl rewrite: capture (stmt_idx, new_fn_id) replacements; apply
102 // them AFTER the loop while stmt indices are still stable (prepend
103 // happens after all replaces). file_stmts is built from the OLD file
104 // root and references OLD fn ids — those nodes stay reachable from the
105 // builder even when the file's stmts list gets rewired.
106 fc := out.flat.file_cursor(file_idx)
107 file_stmts := fc.stmts()
108
109 mut replacement_idxs := []int{}
110 mut replacement_ids := []ast.FlatNodeId{}
111
112 for j in 0 .. file_stmts.len() {
113 stmt_cursor := file_stmts.at(j)
114 if !stmt_cursor.is_valid() || stmt_cursor.kind() != .stmt_fn_decl {
115 continue
116 }
117 is_main := !stmt_cursor.flag(ast.flag_is_method) && stmt_cursor.name() == 'main'
118 if !is_main && !t.fn_body_may_need_live_rewrite_from_flat(stmt_cursor.list_at(3)) {
119 continue
120 }
121 body_stmts := stmt_cursor.list_at(3)
122 mut new_body_ids := []ast.FlatNodeId{}
123 mut had_change := false
124 if is_main {
125 new_body_ids = []ast.FlatNodeId{cap: parts.preamble.len + body_stmts.len()}
126 for ps in parts.preamble {
127 new_body_ids << out.emit_stmt(ps)
128 }
129 live_ids, _ :=
130 t.inject_live_into_cursor_stmts_to_flat(body_stmts, parts.check_stmts, mut out)
131 new_body_ids << live_ids
132 had_change = true
133 } else {
134 live_ids, fn_changed := t.inject_live_into_cursor_stmts_to_flat(body_stmts,
135 parts.check_stmts, mut out)
136 if !fn_changed {
137 continue
138 }
139 new_body_ids = live_ids.clone()
140 had_change = true
141 }
142 if !had_change {
143 continue
144 }
145 old_fn_id := stmt_cursor.id
146 new_fn_id := out.replace_fn_body_stmts(old_fn_id, new_body_ids)
147 replacement_idxs << j
148 replacement_ids << new_fn_id
149 }
150
151 for k in 0 .. replacement_idxs.len {
152 out.replace_file_stmt(file_idx, replacement_idxs[k], replacement_ids[k])
153 }
154
155 mut prepended_ids := []ast.FlatNodeId{cap: parts.c_decls.len + parts.global_decls.len}
156 for cd in parts.c_decls {
157 prepended_ids << out.emit_stmt(cd)
158 }
159 for gd in parts.global_decls {
160 prepended_ids << out.emit_stmt(gd)
161 }
162 out.prepend_file_stmts(file_idx, prepended_ids)
163}
164
165fn (t &Transformer) inject_live_into_cursor_stmts_to_flat(stmts ast.CursorList, check_stmts []ast.Stmt, mut out ast.FlatBuilder) ([]ast.FlatNodeId, bool) {
166 mut result := []ast.FlatNodeId{cap: stmts.len()}
167 mut any_changed := false
168 for i in 0 .. stmts.len() {
169 stmt := stmts.at(i)
170 if stmt.kind() == .stmt_for {
171 any_changed = true
172 mut new_body_ids := []ast.FlatNodeId{cap: check_stmts.len + stmt.for_body_list().len()}
173 for cs in check_stmts {
174 new_body_ids << out.emit_stmt(cs)
175 }
176 body := stmt.for_body_list()
177 for bi in 0 .. body.len() {
178 body_stmt_id, _ := t.rewrite_live_call_in_stmt_cursor_to_flat(body.at(bi), mut out)
179 new_body_ids << body_stmt_id
180 }
181 init_id := out.copy_subtree_from(stmt.edge(0).flat, stmt.edge(0).id)
182 cond_id := out.copy_subtree_from(stmt.edge(1).flat, stmt.edge(1).id)
183 post_id := out.copy_subtree_from(stmt.edge(2).flat, stmt.edge(2).id)
184 result << out.emit_for_stmt_by_ids(init_id, cond_id, post_id, new_body_ids)
185 continue
186 }
187 new_stmt_id, changed := t.rewrite_live_call_in_stmt_cursor_to_flat(stmt, mut out)
188 if changed {
189 any_changed = true
190 }
191 result << new_stmt_id
192 }
193 return result, any_changed
194}
195
196fn (t &Transformer) rewrite_live_call_in_stmt_cursor_to_flat(stmt ast.Cursor, mut out ast.FlatBuilder) (ast.FlatNodeId, bool) {
197 match stmt.kind() {
198 .stmt_expr {
199 new_expr_id, changed :=
200 t.rewrite_live_call_in_expr_cursor_to_flat(stmt.edge(0), mut out)
201 if changed {
202 return out.emit_expr_stmt_by_id(new_expr_id), true
203 }
204 }
205 .stmt_assign {
206 lhs_len := stmt.extra_int()
207 mut rhs_ids := []ast.FlatNodeId{cap: stmt.edge_count() - lhs_len}
208 mut any_changed := false
209 for i in lhs_len .. stmt.edge_count() {
210 rhs_id, changed := t.rewrite_live_call_in_expr_cursor_to_flat(stmt.edge(i), mut out)
211 rhs_ids << rhs_id
212 if changed {
213 any_changed = true
214 }
215 }
216 if any_changed {
217 mut lhs_ids := []ast.FlatNodeId{cap: lhs_len}
218 for i in 0 .. lhs_len {
219 lhs := stmt.edge(i)
220 lhs_ids << out.copy_subtree_from(lhs.flat, lhs.id)
221 }
222 op := unsafe { token.Token(int(stmt.aux())) }
223 return out.emit_assign_stmt_by_ids(op, lhs_ids, rhs_ids, token.Pos{}), true
224 }
225 }
226 else {}
227 }
228
229 return out.copy_subtree_from(stmt.flat, stmt.id), false
230}
231
232fn (t &Transformer) rewrite_live_call_in_expr_cursor_to_flat(expr ast.Cursor, mut out ast.FlatBuilder) (ast.FlatNodeId, bool) {
233 match expr.kind() {
234 .expr_call {
235 lhs := expr.edge(0)
236 if lhs.kind() == .expr_ident {
237 for lf in t.live_fns {
238 if !lf.is_method && lhs.name() == lf.decl_name {
239 arg_ids := transform_mut_arg_cursors_to_flat(expr, mut out)
240 lhs_id := out.emit_ident_by_name('__live_${lf.mangled_name}', token.Pos{})
241 return out.emit_call_expr_by_ids(lhs_id, arg_ids, token.Pos{}), true
242 }
243 }
244 }
245 if lhs.kind() == .expr_selector {
246 for lf in t.live_fns {
247 if lf.is_method && lhs.edge(1).name() == lf.decl_name {
248 receiver := lhs.edge(0)
249 receiver_id := out.copy_subtree_from(receiver.flat, receiver.id)
250 receiver_arg_id :=
251 out.emit_prefix_expr_by_id(.amp, receiver_id, token.Pos{})
252 mut arg_ids := []ast.FlatNodeId{cap: expr.edge_count()}
253 arg_ids << receiver_arg_id
254 arg_ids << transform_mut_arg_cursors_to_flat(expr, mut out)
255 lhs_id := out.emit_ident_by_name('__live_${lf.mangled_name}', token.Pos{})
256 return out.emit_call_expr_by_ids(lhs_id, arg_ids, token.Pos{}), true
257 }
258 }
259 }
260 }
261 .expr_ident {
262 for lf in t.live_fns {
263 if !lf.is_method && expr.name() == lf.decl_name {
264 return out.emit_ident_by_name('__live_${lf.mangled_name}', token.Pos{}), true
265 }
266 }
267 }
268 else {}
269 }
270
271 return out.copy_subtree_from(expr.flat, expr.id), false
272}
273
274fn transform_mut_arg_cursors_to_flat(call ast.Cursor, mut out ast.FlatBuilder) []ast.FlatNodeId {
275 args_cap := if call.edge_count() > 1 { call.edge_count() - 1 } else { 0 }
276 mut arg_ids := []ast.FlatNodeId{cap: args_cap}
277 for i in 1 .. call.edge_count() {
278 arg := call.edge(i)
279 if arg.kind() == .expr_modifier && unsafe { token.Token(int(arg.aux())) } == .key_mut {
280 inner := arg.edge(0)
281 arg_ids << out.copy_subtree_from(inner.flat, inner.id)
282 continue
283 }
284 arg_ids << out.copy_subtree_from(arg.flat, arg.id)
285 }
286 return arg_ids
287}
288
289fn (t &Transformer) fn_body_may_need_live_rewrite_from_flat(stmts ast.CursorList) bool {
290 for i in 0 .. stmts.len() {
291 if t.stmt_may_need_live_rewrite_from_flat(stmts.at(i)) {
292 return true
293 }
294 }
295 return false
296}
297
298fn (t &Transformer) stmt_may_need_live_rewrite_from_flat(stmt_c ast.Cursor) bool {
299 if !stmt_c.is_valid() {
300 return false
301 }
302 match stmt_c.kind() {
303 .stmt_for {
304 return true
305 }
306 .stmt_expr {
307 return t.expr_may_need_live_rewrite_from_flat(stmt_c.edge(0))
308 }
309 .stmt_assign {
310 lhs_len := stmt_c.extra_int()
311 for i in lhs_len .. stmt_c.edge_count() {
312 if t.expr_may_need_live_rewrite_from_flat(stmt_c.edge(i)) {
313 return true
314 }
315 }
316 }
317 else {}
318 }
319
320 return false
321}
322
323fn (t &Transformer) expr_may_need_live_rewrite_from_flat(expr_c ast.Cursor) bool {
324 if !expr_c.is_valid() {
325 return false
326 }
327 match expr_c.kind() {
328 .expr_call {
329 return t.call_lhs_may_need_live_rewrite_from_flat(expr_c.edge(0))
330 }
331 .expr_ident {
332 return t.is_live_plain_fn_name(expr_c.name())
333 }
334 else {}
335 }
336
337 return false
338}
339
340fn (t &Transformer) call_lhs_may_need_live_rewrite_from_flat(lhs ast.Cursor) bool {
341 match lhs.kind() {
342 .expr_ident {
343 return t.is_live_plain_fn_name(lhs.name())
344 }
345 .expr_selector {
346 return t.is_live_method_name(lhs.edge(1).name())
347 }
348 else {}
349 }
350
351 return false
352}
353
354fn (t &Transformer) is_live_plain_fn_name(name string) bool {
355 for lf in t.live_fns {
356 if !lf.is_method && name == lf.decl_name {
357 return true
358 }
359 }
360 return false
361}
362
363fn (t &Transformer) is_live_method_name(name string) bool {
364 for lf in t.live_fns {
365 if lf.is_method && name == lf.decl_name {
366 return true
367 }
368 }
369 return false
370}
371
372// inject_live_reload generates hot code reloading infrastructure for @[live] functions.
373//
374// Approach: function pointer indirection with direct memory patching.
375// @[live] functions are recompiled via `v2 -backend arm64 -hot-fn` which extracts
376// raw machine code. The code is read into an mmap'd executable page and the
377// global function pointer is updated to point at it.
378//
379// Supports multiple @[live] functions (including methods) across different callers.
380//
381// 1. Injects GlobalDecl for function pointers, code page, and mtime tracking
382// 2. Adds initialization code to main() (mmap code page, init pointers, read initial mtime)
383// 3. Replaces calls to @[live] functions with indirect calls through global pointers
384// 4. Injects reload checks at the top of for-loop bodies in ALL functions
385fn (mut t Transformer) inject_live_reload(mut files []ast.File) {
386 parts := t.live_reload_parts() or { return }
387 c_decls := parts.c_decls
388 global_decls := parts.global_decls
389 preamble := parts.preamble
390 check_stmts := parts.check_stmts
391
392 // Find the user file that contains main()
393 for i, file in files {
394 mut has_main := false
395 for stmt in file.stmts {
396 if stmt is ast.FnDecl && !stmt.is_method && stmt.name == 'main' {
397 has_main = true
398 break
399 }
400 }
401 if !has_main {
402 continue
403 }
404
405 mut new_stmts := []ast.Stmt{cap: c_decls.len + global_decls.len + file.stmts.len}
406
407 // Prepend C function declarations
408 for cd in c_decls {
409 new_stmts << cd
410 }
411
412 // Prepend GlobalDecl statements
413 for gd in global_decls {
414 new_stmts << gd
415 }
416
417 mut changed := false
418 for stmt in file.stmts {
419 if stmt is ast.FnDecl {
420 if !stmt.is_method && stmt.name == 'main' {
421 // Inject preamble + rewrite calls + inject reload checks
422 mut fn_stmts := []ast.Stmt{cap: preamble.len + stmt.stmts.len}
423 for ps in preamble {
424 fn_stmts << ps
425 }
426 live_stmts, _ := t.inject_live_into_stmts(stmt.stmts, check_stmts)
427 for fs in live_stmts {
428 fn_stmts << fs
429 }
430 new_stmts << ast.FnDecl{
431 attributes: stmt.attributes
432 is_public: stmt.is_public
433 is_method: stmt.is_method
434 is_static: stmt.is_static
435 receiver: stmt.receiver
436 language: stmt.language
437 name: stmt.name
438 typ: stmt.typ
439 stmts: fn_stmts
440 pos: stmt.pos
441 }
442 changed = true
443 continue
444 }
445 // For ALL other functions: rewrite calls + inject reload checks into for loops
446 live_stmts, fn_changed := t.inject_live_into_stmts(stmt.stmts, check_stmts)
447 if fn_changed {
448 new_stmts << ast.FnDecl{
449 attributes: stmt.attributes
450 is_public: stmt.is_public
451 is_method: stmt.is_method
452 is_static: stmt.is_static
453 receiver: stmt.receiver
454 language: stmt.language
455 name: stmt.name
456 typ: stmt.typ
457 stmts: live_stmts
458 pos: stmt.pos
459 }
460 changed = true
461 continue
462 }
463 }
464 new_stmts << stmt
465 }
466
467 if changed {
468 files[i] = ast.File{
469 attributes: file.attributes
470 mod: file.mod
471 name: file.name
472 stmts: new_stmts
473 imports: file.imports
474 }
475 break
476 }
477 }
478}
479
480// inject_live_into_stmts walks a statement list, prepends reload-check
481// statements to the body of every ForStmt, and replaces calls to @[live]
482// functions with indirect calls through global pointers.
483// Returns the modified stmts and whether any changes were made.
484fn (t &Transformer) inject_live_into_stmts(stmts []ast.Stmt, check_stmts []ast.Stmt) ([]ast.Stmt, bool) {
485 mut result := []ast.Stmt{cap: stmts.len}
486 mut any_changed := false
487 for stmt in stmts {
488 if stmt is ast.ForStmt {
489 any_changed = true
490 // Prepend check stmts to the for-loop body, then rewrite calls in body
491 mut new_body := []ast.Stmt{cap: check_stmts.len + stmt.stmts.len}
492 for cs in check_stmts {
493 new_body << cs
494 }
495 for fs in stmt.stmts {
496 new_body << t.rewrite_live_call_in_stmt(fs)
497 }
498 result << ast.Stmt(ast.ForStmt{
499 init: stmt.init
500 cond: stmt.cond
501 post: stmt.post
502 stmts: new_body
503 })
504 continue
505 }
506 new_stmt, changed := t.rewrite_live_call_in_stmt_b(stmt)
507 if changed {
508 any_changed = true
509 }
510 result << new_stmt
511 }
512 return result, any_changed
513}
514
515// rewrite_live_call_in_stmt rewrites calls to @[live] functions in a statement
516// with indirect calls through global pointers. Always returns the rewritten stmt.
517fn (t &Transformer) rewrite_live_call_in_stmt(stmt ast.Stmt) ast.Stmt {
518 new_stmt, _ := t.rewrite_live_call_in_stmt_b(stmt)
519 return new_stmt
520}
521
522// rewrite_live_call_in_stmt_b rewrites calls to @[live] functions in a statement
523// with indirect calls through global pointers. Returns the stmt and whether it changed.
524fn (t &Transformer) rewrite_live_call_in_stmt_b(stmt ast.Stmt) (ast.Stmt, bool) {
525 if stmt is ast.ExprStmt {
526 new_expr, changed := t.rewrite_live_call_in_expr(stmt.expr)
527 if changed {
528 return ast.Stmt(ast.ExprStmt{
529 expr: new_expr
530 }), true
531 }
532 } else if stmt is ast.AssignStmt {
533 mut new_rhs := []ast.Expr{cap: stmt.rhs.len}
534 mut any_changed := false
535 for rhs in stmt.rhs {
536 new_rhs_expr, changed := t.rewrite_live_call_in_expr(rhs)
537 new_rhs << new_rhs_expr
538 if changed {
539 any_changed = true
540 }
541 }
542 if any_changed {
543 return ast.Stmt(ast.AssignStmt{
544 op: stmt.op
545 lhs: stmt.lhs
546 rhs: new_rhs
547 }), true
548 }
549 }
550 return stmt, false
551}
552
553// rewrite_live_call_in_expr checks if an expression is a call to a @[live] function
554// and if so, replaces it with an indirect call through the global pointer.
555fn (t &Transformer) rewrite_live_call_in_expr(expr ast.Expr) (ast.Expr, bool) {
556 if expr is ast.CallExpr {
557 // Check regular function calls: fn_name(args)
558 if expr.lhs is ast.Ident {
559 for lf in t.live_fns {
560 if !lf.is_method && expr.lhs.name == lf.decl_name {
561 return ast.Expr(ast.CallExpr{
562 lhs: mk_ident('__live_${lf.mangled_name}')
563 args: transform_mut_args(expr.args)
564 }), true
565 }
566 }
567 }
568 // Check method calls: obj.method_name(args)
569 if expr.lhs is ast.SelectorExpr {
570 for lf in t.live_fns {
571 if lf.is_method && expr.lhs.rhs.name == lf.decl_name {
572 mut new_args := []ast.Expr{cap: 1 + expr.args.len}
573 new_args << ast.Expr(ast.PrefixExpr{
574 op: .amp
575 expr: expr.lhs.lhs
576 })
577 for arg in transform_mut_args(expr.args) {
578 new_args << arg
579 }
580 return ast.Expr(ast.CallExpr{
581 lhs: mk_ident('__live_${lf.mangled_name}')
582 args: new_args
583 }), true
584 }
585 }
586 }
587 }
588 // Check function references in struct init: frame_fn: frame → frame_fn: __live_frame
589 if expr is ast.Ident {
590 for lf in t.live_fns {
591 if !lf.is_method && expr.name == lf.decl_name {
592 return mk_ident('__live_${lf.mangled_name}'), true
593 }
594 }
595 }
596 return expr, false
597}
598
599// transform_mut_args strips `mut` modifiers from arguments so that call_indirect
600// (which has no function signature) passes the value directly.
601fn transform_mut_args(args []ast.Expr) []ast.Expr {
602 mut result := []ast.Expr{cap: args.len}
603 for arg in args {
604 if arg is ast.ModifierExpr && arg.kind == .key_mut {
605 result << arg.expr
606 } else {
607 result << arg
608 }
609 }
610 return result
611}
612
613// build_live_globals generates GlobalDecl statements for live reload state.
614fn (t &Transformer) build_live_globals() []ast.Stmt {
615 mut stmts := []ast.Stmt{}
616
617 // __global __live_mtime : i64
618 stmts << mk_global_decl('__live_mtime', 'i64')
619
620 // __global __live_code_page : voidptr (mmap'd RWX page for hot code)
621 stmts << mk_global_decl('__live_code_page', 'voidptr')
622
623 // __global __live_code_offset : i64 (current write offset into code page)
624 stmts << mk_global_decl('__live_code_offset', 'i64')
625
626 // Per live function: global function pointer
627 for lf in t.live_fns {
628 stmts << mk_global_decl('__live_${lf.mangled_name}', 'voidptr')
629 }
630
631 return stmts
632}
633
634// build_live_preamble generates the statements prepended to main() body.
635// mmap a code page, init function pointers, read initial source file mtime.
636fn (t &Transformer) build_live_preamble(source_file string) []ast.Stmt {
637 mut stmts := []ast.Stmt{}
638
639 // Per live function: init function pointer to original function address
640 for lf in t.live_fns {
641 // __live_<mangled> = voidptr(&fn_name)
642 stmts << mk_assign('__live_${lf.mangled_name}', mk_cast('voidptr', ast.Expr(ast.PrefixExpr{
643 op: .amp
644 expr: mk_ident(lf.mangled_name)
645 })))
646 }
647
648 // __live_code_page = C.mmap(nil, 0x4000, 3, 0x1802, -1, 0)
649 // PROT_READ|PROT_WRITE=3, MAP_ANON|MAP_PRIVATE|MAP_JIT=0x1802
650 stmts << mk_assign('__live_code_page', mk_call('C.mmap', [
651 mk_unsafe_nil(),
652 mk_hex('0x4000'),
653 mk_int('3'),
654 mk_hex('0x1802'),
655 mk_neg_one(),
656 mk_int('0'),
657 ]))
658
659 // mut __live_sb := [144]u8{}
660 stmts << mk_decl_assign('__live_sb', mk_fixed_array_init('144', 'u8'))
661
662 // if C.stat(c'source.v', &__live_sb[0]) == 0 {
663 // __live_mtime = unsafe { *(&i64(&__live_sb[48])) }
664 // }
665 stmts << mk_stat_check('__live_sb', source_file, '__live_mtime')
666
667 return stmts
668}
669
670// build_live_check generates the reload-check statements injected at the top
671// of for-loop bodies. Checks if source file changed, recompiles each @[live]
672// function to raw machine code, reads it into the code page, and updates the pointer.
673fn (t &Transformer) build_live_check(source_file string) []ast.Stmt {
674 mut stmts := []ast.Stmt{}
675
676 // mut __live_ns := [144]u8{}
677 stmts << mk_decl_assign('__live_ns', mk_fixed_array_init('144', 'u8'))
678
679 // if C.stat(c'source.v', &__live_ns[0]) == 0 { ... }
680 stat_call := mk_call('C.stat', [
681 mk_cstring(source_file),
682 ast.Expr(ast.PrefixExpr{
683 op: .amp
684 expr: ast.Expr(ast.IndexExpr{
685 lhs: mk_ident('__live_ns')
686 expr: mk_int('0')
687 })
688 }),
689 ])
690
691 // Inner: __live_nm := unsafe { *(&i64(&__live_ns[48])) }
692 mtime_read := mk_decl_assign('__live_nm', mk_unsafe_deref_i64('__live_ns', '48'))
693
694 // if __live_nm > __live_mtime { ... reload all live functions ... }
695 mut reload_body := []ast.Stmt{}
696
697 // __live_mtime = __live_nm
698 reload_body << mk_assign('__live_mtime', mk_ident('__live_nm'))
699
700 // __live_t0 := C.clock_gettime_nsec_np(4) -- CLOCK_MONOTONIC_RAW
701 reload_body << mk_decl_assign('__live_t0', mk_call('C.clock_gettime_nsec_np', [
702 mk_int('4'),
703 ]))
704
705 // C.printf(c'[live] reloading...\n')
706 reload_body << mk_call_stmt('C.printf', [mk_cstring('[live] reloading...\\n')])
707 reload_body << mk_call_stmt('C.fflush', [mk_unsafe_nil()])
708
709 // Resolve v2 binary path
710 v2_path := if t.pref != unsafe { nil } && t.pref.vroot.len > 0 {
711 t.pref.vroot + '/cmd/v2/v2'
712 } else {
713 './v2'
714 }
715
716 // For each @[live] function: recompile, fread, patch
717 for lf in t.live_fns {
718 bin_path := '/tmp/_hot_${lf.mangled_name}.bin'
719 compile_cmd := '${v2_path} -backend arm64 -nocache -hot-fn ${lf.mangled_name} -o ${bin_path} ${source_file} >/dev/null 2>&1'
720
721 // C.system(c'v2 -backend arm64 -nocache -hot-fn <name> -o /tmp/_hot_<name>.bin source.v ...')
722 reload_body << mk_call_stmt('C.system', [mk_cstring(compile_cmd)])
723
724 // __live_f := C.fopen(c'/tmp/_hot_<name>.bin', c'rb')
725 f_var := '__live_f_${lf.mangled_name}'
726 reload_body << mk_decl_assign(f_var, mk_call('C.fopen', [
727 mk_cstring(bin_path),
728 mk_cstring('rb'),
729 ]))
730
731 // if __live_f != nil { ... read code, patch pointer ... }
732 mut patch_body := []ast.Stmt{}
733
734 // C.fseek(f, 0, 2) -- SEEK_END
735 patch_body << mk_call_stmt('C.fseek', [mk_ident(f_var),
736 mk_int('0'), mk_int('2')])
737
738 // __live_sz := int(C.ftell(f))
739 sz_var := '__live_sz_${lf.mangled_name}'
740 patch_body << mk_decl_assign(sz_var, mk_cast('int', mk_call('C.ftell', [
741 mk_ident(f_var),
742 ])))
743
744 // C.fseek(f, 0, 0) -- SEEK_SET
745 patch_body << mk_call_stmt('C.fseek', [mk_ident(f_var),
746 mk_int('0'), mk_int('0')])
747
748 // __live_dest := voidptr(u64(__live_code_page) + u64(__live_code_offset))
749 dest_var := '__live_dest_${lf.mangled_name}'
750 patch_body << mk_decl_assign(dest_var, mk_cast('voidptr', ast.Expr(ast.InfixExpr{
751 op: .plus
752 lhs: mk_cast('u64', mk_ident('__live_code_page'))
753 rhs: mk_cast('u64', mk_ident('__live_code_offset'))
754 })))
755
756 // C.mprotect(__live_code_page, 0x4000, 3) -- PROT_READ|PROT_WRITE
757 patch_body << mk_call_stmt('C.mprotect', [
758 mk_ident('__live_code_page'),
759 mk_hex('0x4000'),
760 mk_int('3'),
761 ])
762
763 // C.fread(__live_dest, 1, usize(__live_sz), f)
764 patch_body << mk_call_stmt('C.fread', [
765 mk_ident(dest_var),
766 mk_int('1'),
767 mk_cast('usize', mk_ident(sz_var)),
768 mk_ident(f_var),
769 ])
770
771 // C.fclose(f)
772 patch_body << mk_call_stmt('C.fclose', [mk_ident(f_var)])
773
774 // C.mprotect(__live_code_page, 0x4000, 5) -- PROT_READ|PROT_EXEC
775 patch_body << mk_call_stmt('C.mprotect', [
776 mk_ident('__live_code_page'),
777 mk_hex('0x4000'),
778 mk_int('5'),
779 ])
780
781 // C.sys_icache_invalidate(__live_code_page, 0x4000)
782 patch_body << mk_call_stmt('C.sys_icache_invalidate', [
783 mk_ident('__live_code_page'),
784 mk_hex('0x4000'),
785 ])
786
787 // __live_<name> = __live_dest
788 patch_body << mk_assign('__live_${lf.mangled_name}', mk_ident(dest_var))
789
790 // __live_code_offset = __live_code_offset + i64(__live_sz)
791 // Align to 16 bytes: offset = (offset + sz + 15) & ~15
792 patch_body << mk_assign('__live_code_offset', ast.Expr(ast.InfixExpr{
793 op: .amp
794 lhs: ast.Expr(ast.InfixExpr{
795 op: .plus
796 lhs: ast.Expr(ast.InfixExpr{
797 op: .plus
798 lhs: mk_ident('__live_code_offset')
799 rhs: mk_cast('i64', mk_ident(sz_var))
800 })
801 rhs: mk_int('15')
802 })
803 rhs: mk_neg('16')
804 }))
805
806 // C.printf(c'[live] patched %s (%d bytes)\n', c'<name>', __live_sz)
807 patch_body << mk_call_stmt('C.printf', [
808 mk_cstring('[live] patched %s (%d bytes)\\n'),
809 mk_cstring(lf.mangled_name),
810 mk_ident(sz_var),
811 ])
812
813 // if __live_f != nil { ...patch_body... }
814 reload_body << ast.Stmt(ast.ExprStmt{
815 expr: ast.IfExpr{
816 cond: ast.Expr(ast.InfixExpr{
817 op: .ne
818 lhs: mk_ident(f_var)
819 rhs: mk_unsafe_nil()
820 })
821 stmts: patch_body
822 }
823 })
824 }
825
826 // __live_t1 := C.clock_gettime_nsec_np(4)
827 reload_body << mk_decl_assign('__live_t1', mk_call('C.clock_gettime_nsec_np', [
828 mk_int('4'),
829 ]))
830
831 // __live_ms := (__live_t1 - __live_t0) / 1000000
832 reload_body << mk_decl_assign('__live_ms', ast.Expr(ast.InfixExpr{
833 op: .div
834 lhs: ast.Expr(ast.InfixExpr{
835 op: .minus
836 lhs: mk_ident('__live_t1')
837 rhs: mk_ident('__live_t0')
838 })
839 rhs: mk_int('1000000')
840 }))
841
842 // C.printf(c'[live] done (%lldms)\n', __live_ms)
843 reload_body << mk_call_stmt('C.printf', [
844 mk_cstring('[live] done (%lldms)\\n'),
845 mk_ident('__live_ms'),
846 ])
847 reload_body << mk_call_stmt('C.fflush', [mk_unsafe_nil()])
848
849 // if __live_nm > __live_mtime { ...reload_body... }
850 inner_if := ast.Stmt(ast.ExprStmt{
851 expr: ast.IfExpr{
852 cond: ast.Expr(ast.InfixExpr{
853 op: .gt
854 lhs: mk_ident('__live_nm')
855 rhs: mk_ident('__live_mtime')
856 })
857 stmts: reload_body
858 }
859 })
860
861 // Full if C.stat(...) == 0 { mtime_read; inner_if }
862 stmts << ast.Stmt(ast.ExprStmt{
863 expr: ast.IfExpr{
864 cond: ast.Expr(ast.InfixExpr{
865 op: .eq
866 lhs: stat_call
867 rhs: mk_int('0')
868 })
869 stmts: [mtime_read, inner_if]
870 }
871 })
872
873 return stmts
874}
875
876// mk_global_decl generates: __global <name> : <type>
877fn mk_global_decl(name string, typ_name string) ast.Stmt {
878 return ast.Stmt(ast.GlobalDecl{
879 fields: [
880 ast.FieldDecl{
881 name: name
882 typ: mk_ident(typ_name)
883 is_mut: true
884 },
885 ]
886 })
887}
888
889// mk_stat_check generates:
890// if C.stat(c'<source>', &<buf_name>[0]) == 0 {
891// <mtime_var> = unsafe { *(&i64(&<buf_name>[48])) }
892// }
893fn mk_stat_check(buf_name string, source_file string, mtime_var string) ast.Stmt {
894 stat_call := mk_call('C.stat', [
895 mk_cstring(source_file),
896 ast.Expr(ast.PrefixExpr{
897 op: .amp
898 expr: ast.Expr(ast.IndexExpr{
899 lhs: mk_ident(buf_name)
900 expr: mk_int('0')
901 })
902 }),
903 ])
904
905 mtime_assign := mk_assign(mtime_var, mk_unsafe_deref_i64(buf_name, '48'))
906
907 return ast.Stmt(ast.ExprStmt{
908 expr: ast.IfExpr{
909 cond: ast.Expr(ast.InfixExpr{
910 op: .eq
911 lhs: stat_call
912 rhs: mk_int('0')
913 })
914 stmts: [mtime_assign]
915 }
916 })
917}
918
919// mk_unsafe_deref_i64 generates: unsafe { *(&i64(&<buf>[<offset>])) }
920fn mk_unsafe_deref_i64(buf_name string, offset string) ast.Expr {
921 return ast.Expr(ast.UnsafeExpr{
922 stmts: [
923 ast.Stmt(ast.ExprStmt{
924 expr: ast.Expr(ast.PrefixExpr{
925 op: .mul
926 expr: ast.Expr(ast.PrefixExpr{
927 op: .amp
928 expr: mk_cast('i64', ast.Expr(ast.PrefixExpr{
929 op: .amp
930 expr: ast.Expr(ast.IndexExpr{
931 lhs: mk_ident(buf_name)
932 expr: mk_int(offset)
933 })
934 }))
935 })
936 })
937 }),
938 ]
939 })
940}
941
942// Helper functions for constructing AST nodes
943
944fn mk_ident(name string) ast.Expr {
945 return ast.Expr(ast.Ident{
946 name: name
947 })
948}
949
950fn mk_int(value string) ast.Expr {
951 return ast.Expr(ast.BasicLiteral{
952 kind: .number
953 value: value
954 })
955}
956
957fn mk_hex(value string) ast.Expr {
958 return ast.Expr(ast.BasicLiteral{
959 kind: .number
960 value: value
961 })
962}
963
964fn mk_neg_one() ast.Expr {
965 return ast.Expr(ast.PrefixExpr{
966 op: .minus
967 expr: mk_int('1')
968 })
969}
970
971fn mk_neg(value string) ast.Expr {
972 return ast.Expr(ast.PrefixExpr{
973 op: .minus
974 expr: mk_int(value)
975 })
976}
977
978fn mk_cstring(value string) ast.Expr {
979 return ast.Expr(ast.StringLiteral{
980 kind: .c
981 value: "'${value}'"
982 })
983}
984
985fn mk_cast(type_name string, expr ast.Expr) ast.Expr {
986 return ast.Expr(ast.CallOrCastExpr{
987 lhs: mk_ident(type_name)
988 expr: expr
989 })
990}
991
992fn mk_call(fn_name string, args []ast.Expr) ast.Expr {
993 // For C functions (C.stat, C.mmap, etc.), create a SelectorExpr so the SSA
994 // builder's resolve_call_name correctly strips the C module prefix.
995 lhs := if fn_name.starts_with('C.') {
996 ast.Expr(ast.SelectorExpr{
997 lhs: mk_ident('C')
998 rhs: ast.Ident{
999 name: fn_name[2..]
1000 }
1001 })
1002 } else {
1003 mk_ident(fn_name)
1004 }
1005 return ast.Expr(ast.CallExpr{
1006 lhs: lhs
1007 args: args
1008 })
1009}
1010
1011fn mk_call_stmt(fn_name string, args []ast.Expr) ast.Stmt {
1012 return ast.Stmt(ast.ExprStmt{
1013 expr: mk_call(fn_name, args)
1014 })
1015}
1016
1017fn mk_decl_assign(name string, value ast.Expr) ast.Stmt {
1018 return ast.Stmt(ast.AssignStmt{
1019 op: .decl_assign
1020 lhs: [ast.Expr(ast.Ident{
1021 name: name
1022 })]
1023 rhs: [value]
1024 })
1025}
1026
1027fn mk_assign(name string, value ast.Expr) ast.Stmt {
1028 return ast.Stmt(ast.AssignStmt{
1029 op: .assign
1030 lhs: [ast.Expr(ast.Ident{
1031 name: name
1032 })]
1033 rhs: [value]
1034 })
1035}
1036
1037fn mk_fixed_array_init(size string, elem_type string) ast.Expr {
1038 return ast.Expr(ast.ArrayInitExpr{
1039 typ: ast.Expr(ast.Type(ast.ArrayFixedType{
1040 len: ast.Expr(ast.BasicLiteral{
1041 kind: .number
1042 value: size
1043 })
1044 elem_type: ast.Expr(ast.Ident{
1045 name: elem_type
1046 })
1047 }))
1048 })
1049}
1050
1051fn mk_unsafe_nil() ast.Expr {
1052 return ast.Expr(ast.UnsafeExpr{
1053 stmts: [
1054 ast.Stmt(ast.ExprStmt{
1055 expr: ast.Expr(ast.Keyword{
1056 tok: .key_nil
1057 })
1058 }),
1059 ]
1060 })
1061}
1062
1063// build_live_c_decls generates C extern function declarations needed by live reload.
1064fn build_live_c_decls() []ast.Stmt {
1065 mut decls := []ast.Stmt{}
1066
1067 // fn C.stat(path &u8, buf &u8) int
1068 decls << mk_c_fn_decl('stat', [
1069 mk_param('path', '&u8'),
1070 mk_param('buf', '&u8'),
1071 ], 'int')
1072
1073 // fn C.mmap(addr voidptr, len usize, prot int, flags int, fd int, offset i64) voidptr
1074 decls << mk_c_fn_decl('mmap', [
1075 mk_param('addr', 'voidptr'),
1076 mk_param('len', 'usize'),
1077 mk_param('prot', 'int'),
1078 mk_param('flags', 'int'),
1079 mk_param('fd', 'int'),
1080 mk_param('offset', 'i64'),
1081 ], 'voidptr')
1082
1083 // fn C.mprotect(addr voidptr, len usize, prot int) int
1084 decls << mk_c_fn_decl('mprotect', [
1085 mk_param('addr', 'voidptr'),
1086 mk_param('len', 'usize'),
1087 mk_param('prot', 'int'),
1088 ], 'int')
1089
1090 // fn C.sys_icache_invalidate(start voidptr, size usize)
1091 decls << mk_c_fn_decl('sys_icache_invalidate', [
1092 mk_param('start', 'voidptr'),
1093 mk_param('size', 'usize'),
1094 ], '')
1095
1096 // fn C.fopen(path &u8, mode &u8) voidptr
1097 decls << mk_c_fn_decl('fopen', [
1098 mk_param('path', '&u8'),
1099 mk_param('mode', '&u8'),
1100 ], 'voidptr')
1101
1102 // fn C.fread(buf voidptr, size usize, count usize, f voidptr) usize
1103 decls << mk_c_fn_decl('fread', [
1104 mk_param('buf', 'voidptr'),
1105 mk_param('size', 'usize'),
1106 mk_param('count', 'usize'),
1107 mk_param('f', 'voidptr'),
1108 ], 'usize')
1109
1110 // fn C.fseek(f voidptr, offset i64, whence int) int
1111 decls << mk_c_fn_decl('fseek', [
1112 mk_param('f', 'voidptr'),
1113 mk_param('offset', 'i64'),
1114 mk_param('whence', 'int'),
1115 ], 'int')
1116
1117 // fn C.ftell(f voidptr) i64
1118 decls << mk_c_fn_decl('ftell', [
1119 mk_param('f', 'voidptr'),
1120 ], 'i64')
1121
1122 // fn C.fclose(f voidptr) int
1123 decls << mk_c_fn_decl('fclose', [
1124 mk_param('f', 'voidptr'),
1125 ], 'int')
1126
1127 // fn C.system(cmd &u8) int
1128 decls << mk_c_fn_decl('system', [
1129 mk_param('cmd', '&u8'),
1130 ], 'int')
1131
1132 // fn C.printf(fmt &u8) int (variadic)
1133 decls << mk_c_fn_decl('printf', [
1134 mk_param('fmt', '&u8'),
1135 ], 'int')
1136
1137 // fn C.fflush(f voidptr) int
1138 decls << mk_c_fn_decl('fflush', [
1139 mk_param('f', 'voidptr'),
1140 ], 'int')
1141
1142 // fn C.clock_gettime_nsec_np(clock_id int) u64
1143 decls << mk_c_fn_decl('clock_gettime_nsec_np', [
1144 mk_param('clock_id', 'int'),
1145 ], 'u64')
1146
1147 return decls
1148}
1149
1150fn mk_c_fn_decl(name string, params []ast.Parameter, ret_type string) ast.Stmt {
1151 return ast.Stmt(ast.FnDecl{
1152 language: .c
1153 name: name
1154 typ: ast.FnType{
1155 params: params
1156 return_type: if ret_type.len > 0 {
1157 mk_ident(ret_type)
1158 } else {
1159 ast.empty_expr
1160 }
1161 }
1162 })
1163}
1164
1165fn mk_param(name string, typ_name string) ast.Parameter {
1166 typ_expr := if typ_name.starts_with('&') {
1167 ast.Expr(ast.PrefixExpr{
1168 op: .amp
1169 expr: mk_ident(typ_name[1..])
1170 })
1171 } else {
1172 mk_ident(typ_name)
1173 }
1174 return ast.Parameter{
1175 name: name
1176 typ: typ_expr
1177 }
1178}
1179