Fun API Documentation 0.42.1
The programming language that makes you have fun!
Loading...
Searching...
No Matches
vm.c
Go to the documentation of this file.
1/*
2 * This file is part of the Fun programming language.
3 * https://fun-lang.xyz/
4 *
5 * Copyright 2025 Johannes Findeisen <you@hanez.org>
6 * Licensed under the terms of the Apache-2.0 license.
7 * https://opensource.org/license/apache-2-0
8 */
9
10/**
11 * @file vm.c
12 * @brief Core virtual machine implementation and opcode dispatch for Fun.
13 *
14 * Defines the VM state, stack helpers, debugger/stepping support, built-in
15 * opcode handlers (included as amalgamated C files), platform I/O helpers,
16 * and the main interpreter loop that executes bytecode produced by the Fun
17 * compiler. This file is the central runtime of the language.
18 */
19
20/* Ensure POSIX/XSI prototypes (nanosleep, wcwidth, etc.) are available
21 * before any system headers are included by amalgamated .c files. */
22#ifndef _WIN32
23#ifndef _POSIX_C_SOURCE
24#define _POSIX_C_SOURCE 200809L
25#endif
26#ifndef _XOPEN_SOURCE
27#define _XOPEN_SOURCE 700
28#endif
29#endif
30
31#include <math.h>
32#include <stdarg.h>
33#include <stddef.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <time.h>
38
39#ifdef __unix__
40#include <netdb.h>
41#include <netinet/in.h>
42#include <sys/socket.h>
43#include <sys/types.h>
44#include <sys/un.h>
45#include <sys/wait.h>
46#include <unistd.h>
47/* For hidden input (password) handling in OP_INPUT_LINE */
48#include <termios.h>
49/* For non-blocking sockets and polling */
50#include <fcntl.h>
51#include <poll.h>
52// #include <arpa/inet.h>
53#endif
54
55#ifdef _WIN32
56#include <windows.h>
57#endif
58
59/* Bring in split-out built-ins without changing the build system yet */
60#include "iter.c"
61#include "map.c"
62#include "string.c"
63#include "value.h"
64#include "vm.h"
65
66// Optional by extensions commonly used code. #ifdef's are in each single file.
67#include "extensions/curl.c"
68#include "extensions/ini.c"
69#include "extensions/json.c"
70#include "extensions/openssl.c"
71#include "extensions/pcre2.c"
72#include "extensions/pcsc.c"
73#include "extensions/sqlite.c"
74#include "extensions/xml2.c"
75#include "extensions/kcgi.c"
76#include "extensions/redis.c"
77
78/* forward declarations for include mapping used in error reporting */
79extern char *preprocess_includes(const char *src);
80extern int map_expanded_line_to_include_path(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line);
81
82/* Threading internals (registry and platform glue) */
83#include "vm/os/thread_common.c"
84
85/* Track the currently running VM to annotate error messages */
86#ifdef _WIN32
87static __declspec(thread) VM *g_active_vm = NULL;
88#else
89static __thread VM *g_active_vm = NULL;
90#endif
91
92/**
93 * @brief fprintf-like wrapper that annotates stderr messages with VM source context.
94 *
95 * When writing to stderr and a VM is active, this function appends file name,
96 * line number, function name, opcode and instruction pointer information to the
97 * message. It attempts to map expanded preprocessed line numbers back to the
98 * original included file for clearer diagnostics.
99 *
100 * @param stream Output stream (typically stderr or stdout).
101 * @param fmt printf-style format string.
102 * @param ap Variable argument list corresponding to fmt.
103 * @return Number of characters written, as returned by vfprintf.
104 */
105static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap) {
106 int written = vfprintf(stream, fmt, ap);
107 if (stream == stderr && g_active_vm) {
108 /* Determine current frame and instruction pointer of the faulting op */
109 const char *opname = "unknown";
110 const char *fname = NULL;
111 const char *sfile = NULL;
112 int ip = -1;
113 int line = -1;
114 if (g_active_vm->fp >= 0) {
115 Frame *f = &g_active_vm->frames[g_active_vm->fp];
116 ip = f->ip - 1; /* last executed instruction */
117 if (f->fn) {
118 fname = f->fn->name;
119 sfile = f->fn->source_file;
120 /* derive opcode name */
121 if (ip >= 0 && ip < f->fn->instr_count) {
122 int op = f->fn->instructions[ip].op;
123 if (op >= 0 && op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) {
124 opname = opcode_names[op];
125 }
126 }
127 /* derive source line by scanning back to the most recent OP_LINE marker */
128 for (int i = ip; i >= 0; --i) {
129 Instruction prev = f->fn->instructions[i];
130 if (prev.op == OP_LINE) {
131 line = prev.operand;
132 break;
133 }
134 }
135 /* fallback to VM's last recorded line if no marker found */
136 if (line <= 0) line = g_active_vm->current_line > 0 ? g_active_vm->current_line : 1;
137
138 /* Map expanded line back to the real included file.
139 * Important: OP_LINE operands refer to the expanded, top-level source produced by
140 * the preprocessor. Therefore we must pass the TOP-LEVEL script path to the mapper,
141 * not the current function's own source_file (which may point at an included file).
142 */
143 if (line > 0) {
144 /* Attempt to retrieve the entry frame's source file as the true top-level path */
145 const char *top_path = NULL;
146 if (g_active_vm->fp >= 0) {
147 Frame *entry = &g_active_vm->frames[0];
148 if (entry && entry->fn && entry->fn->source_file) top_path = entry->fn->source_file;
149 }
150 /* Fallback: use current function's source when entry is unavailable */
151 if (!top_path) top_path = sfile;
152 if (top_path) {
153 char mapped_path[1024];
154 int mapped_line = line;
155 if (map_expanded_line_to_include_path(top_path, line, mapped_path, sizeof(mapped_path), &mapped_line)) {
156 sfile = strdup(mapped_path); /* leak acceptable on error paths */
157 line = mapped_line;
158 }
159 }
160 }
161 }
162 }
163 fprintf(stream == stderr ? stderr : stream,
164 " (at %s:%d in %s, op %s @ip %d)\n",
165 sfile ? sfile : "<unknown>",
166 line > 0 ? line : 1,
167 fname ? fname : "<entry>",
168 opname,
169 ip);
170 }
171 return written;
172}
173
174/**
175 * @brief fprintf wrapper forwarding to fun_vm_vfprintf.
176 *
177 * Convenience wrapper that collects varargs and calls fun_vm_vfprintf so that
178 * VM-aware diagnostics are consistently applied in this translation unit.
179 *
180 * @param stream Output stream.
181 * @param fmt printf-style format string.
182 * @return Number of characters written.
183 */
184static int fun_vm_fprintf(FILE *stream, const char *fmt, ...) {
185 va_list ap;
186 va_start(ap, fmt);
187 int r = fun_vm_vfprintf(stream, fmt, ap);
188 va_end(ap);
189 return r;
190}
191
192
193/* Redirect fprintf within this translation unit so opcode handlers use our wrapper */
194#define fprintf fun_vm_fprintf
195
196/* Intercept exit() in this translation unit so VM errors don't terminate the process outright */
197#include <setjmp.h>
198
199static __thread jmp_buf g_vm_err_jmp;
200
201/**
202 * @brief Replacement for exit() used within this translation unit.
203 *
204 * If REPL-on-error is enabled for the active VM, this performs a longjmp back
205 * to vm_run so the REPL can be entered with the current VM state intact.
206 * Otherwise, terminates the process immediately using _Exit/_exit.
207 *
208 * @param code Exit status code (non-zero values indicate error conditions).
209 */
210static void fun_vm_exit(int code) {
211 if (g_active_vm && g_active_vm->repl_on_error) {
212 /* Jump back to vm_run to allow dropping into the REPL with intact VM state */
213 longjmp(g_vm_err_jmp, code ? code : 1);
214 }
215 /* Fallback: terminate immediately if not in REPL-on-error mode */
216#ifdef _WIN32
217 _exit(code);
218#else
219 _Exit(code);
220#endif
221}
222
223/* Redirect exit inside this TU (affects included opcode handlers) */
224#define exit(code) fun_vm_exit(code)
225
226/* Forward decl for stack push used by vm_raise_error */
227static void push_value(VM *vm, Value v);
228/* Forward decl for diagnostic helper used by vm_raise_error */
229static int vm_ip_to_line(const Bytecode *bc, int ip);
230
231/* Raise a runtime error that respects try/catch/finally semantics.
232 * If a handler is installed for the current frame, jump to it and push
233 * an error string for the catch clause. Otherwise, print and stop VM. */
234/**
235 * @brief Raise a runtime error inside the VM, honoring try/catch/finally.
236 *
237 * If the current frame has a pending try handler, control is transferred to
238 * that handler and the error message is pushed onto the stack for the catch
239 * clause. If no handler exists, the error is printed and the VM is stopped.
240 *
241 * @param vm Pointer to the VM instance.
242 * @param msg Human-readable error message (may be NULL).
243 */
244void vm_raise_error(VM *vm, const char *msg) {
245 if (!vm || vm->fp < 0) {
246 fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
247 return;
248 }
249 Frame *f = &vm->frames[vm->fp];
250 if (f->try_sp >= 0) {
251 char buf[256];
252 if (msg) {
253 snprintf(buf, sizeof(buf), "Runtime error: %s", msg);
254 } else {
255 snprintf(buf, sizeof(buf), "Runtime error");
256 }
257 /* push error value and transfer control to handler target */
258 Value err = make_string(buf);
259 push_value(vm, err);
260 int try_idx = f->try_stack[f->try_sp--];
261 int target = f->fn->instructions[try_idx].operand;
262 f->ip = target;
263 return;
264 }
265 /* No handler: print annotated message, stack trace, and terminate VM */
266 Bytecode *bc = f->fn;
267 const char *fname = (bc && bc->name) ? bc->name : "<anon>";
268 const char *src = (bc && bc->source_file) ? bc->source_file : "<unknown>";
269 int last_ip = f->ip - 1;
270 int line = vm_ip_to_line(bc, last_ip);
271 const char *opname = "OP?";
272 if (bc && last_ip >= 0 && last_ip < bc->instr_count) {
273 int op = bc->instructions[last_ip].op;
274 if (op >= 0 && op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) {
275 opname = opcode_names[op];
276 }
277 }
278 fprintf(stderr, "Runtime error at %s:%d in %s (ip=%d, %s): %s\n",
279 src, line > 0 ? line : 0, fname, last_ip, opname, msg ? msg : "<error>");
281 vm->fp = -1; /* stop execution */
282}
283
284/*
285Opcode case include index (vm_case_*.inc):
286- Core/stack/frame:
287 vm_case_nop.inc, vm_case_halt.inc,
288 vm_case_load_const.inc, vm_case_load_local.inc, vm_case_store_local.inc,
289 vm_case_load_global.inc, vm_case_store_global.inc,
290 vm_case_pop.inc, vm_case_dup.inc, vm_case_swap.inc,
291 vm_case_call.inc, vm_case_return.inc, vm_case_print.inc, vm_case_jump.inc, vm_case_jump_if_false.inc
292- Arithmetic and logic:
293 vm_case_add.inc, vm_case_sub.inc, vm_case_mul.inc, vm_case_div.inc,
294 vm_case_mod.inc, vm_case_lt.inc, vm_case_lte.inc, vm_case_gt.inc, vm_case_gte.inc,
295 vm_case_eq.inc, vm_case_neq.inc, vm_case_and.inc, vm_case_or.inc, vm_case_not.inc
296- Conversions:
297 vm_case_to_number.inc, vm_case_to_string.inc
298- Arrays and slices:
299 vm_case_make_array.inc, vm_case_len.inc,
300 vm_case_index_get.inc, vm_case_index_set.inc,
301 vm_case_arr_push.inc, vm_case_arr_pop.inc, vm_case_arr_set.inc, vm_case_arr_insert.inc, vm_case_arr_remove.inc,
302 vm_case_slice.inc
303- Strings and iteration helpers:
304 vm_case_split.inc, vm_case_join.inc, vm_case_substr.inc, vm_case_find.inc,
305 vm_case_enumerate.inc, vm_case_zip.inc
306- Maps and I/O:
307 vm_case_make_map.inc, vm_case_keys.inc, vm_case_values.inc, vm_case_has_key.inc,
308 vm_case_read_file.inc, vm_case_write_file.inc
309- Math utils / RNG:
310 vm_case_min.inc, vm_case_max.inc, vm_case_clamp.inc, vm_case_abs.inc, vm_case_pow.inc,
311 vm_case_random_seed.inc, vm_case_random_int.inc
312
313Dev tips:
314- When adding a new opcode:
315 1) Define OP_<NAME> in bytecode.h and opcode_names[] in vm.h.
316 2) Implement its VM handler in src/vm_case_<lowercase>.inc.
317 3) Include it in the switch below.
318 4) Run scripts/check_op_includes.py to verify coverage.
319- You can run scripts/run_examples.sh to sanity-check examples quickly.
320*/
321
322/**
323 * @brief Get a human-readable name for a ValueType.
324 *
325 * @param t The value type enum.
326 * @return Constant string naming the type (e.g., "int", "string").
327 */
328static const char *value_type_name(ValueType t) {
329 switch (t) {
330 case VAL_FUNCTION:
331 return "function";
332 case VAL_INT:
333 return "int";
334 case VAL_FLOAT:
335 return "float";
336 case VAL_BOOL:
337 return "boolean";
338 case VAL_ARRAY:
339 return "array";
340 case VAL_MAP:
341 return "map";
342 case VAL_NIL:
343 return "nil";
344 case VAL_STRING:
345 return "string";
346 default:
347 return "unknown";
348 }
349}
350
351/**
352 * @brief Clear the VM's buffered output values and partial flags.
353 *
354 * Frees any dynamic storage held by buffered output Values and resets the
355 * output counters and partial line indicators.
356 *
357 * @param vm VM instance whose output buffer should be cleared.
358 */
360 for (int i = 0; i < vm->output_count; ++i) {
361 free_value(vm->output[i]);
362 }
363 vm->output_count = 0;
364 // reset partial flags
365 for (int i = 0; i < OUTPUT_SIZE; ++i)
366 vm->output_is_partial[i] = 0;
367}
368
369/**
370 * @brief Free resources owned directly by the VM structure.
371 *
372 * Currently a no-op because the VM does not allocate persistent internal
373 * resources outside frames, globals and outputs, which are managed elsewhere.
374 *
375 * @param vm VM instance to free resources for.
376 */
377void vm_free(VM *vm) {
378 // currently nothing persistent allocated inside VM itself
379}
380
381/* forward declaration for helper used in vm_reset */
382static void vm_pop_frame(VM *vm);
383
384/**
385 * @brief Reset the VM to a clean state.
386 *
387 * Pops all frames (releasing local variables), clears the operand stack and
388 * globals, resets the output buffer and debugger state, and zeros the exit code.
389 *
390 * @param vm VM instance to reset.
391 */
392void vm_reset(VM *vm) {
393 // Pop all frames (free locals)
394 while (vm->fp >= 0) {
395 vm_pop_frame(vm);
396 }
397 // Clear stack
398 vm->sp = -1;
399 // Free globals
400 for (int i = 0; i < MAX_GLOBALS; ++i) {
401 free_value(vm->globals[i]);
402 vm->globals[i] = make_nil();
403 }
404 // Clear output buffer
405 vm_clear_output(vm);
406 // Reset exit code
407 vm->exit_code = 0;
408
409#ifdef FUN_TRACE
410 // Reset opcode counters
411 for (int i = 0; i < OPCODE_COUNT; ++i) vm->op_counts[i] = 0;
412#endif
413
414 // Reset debugger state (breakpoints, stepping)
415 vm_debug_reset(vm);
416}
417
418/**
419 * @brief Print all non-nil global variables to stdout for debugging.
420 *
421 * @param vm VM whose globals should be dumped.
422 */
424 printf("=== globals ===\n");
425 for (int i = 0; i < MAX_GLOBALS; ++i) {
426 if (vm->globals[i].type != VAL_NIL) {
427 printf("[%d] ", i);
428 print_value(&vm->globals[i]);
429 printf("\n");
430 }
431 }
432 printf("===============\n");
433}
434
435/* --- Debugger API impl --- */
436
437/**
438 * @brief Reset debugger state: breakpoints and stepping controls.
439 *
440 * Clears all breakpoints, disables stepping modes, and resets counters.
441 *
442 * @param vm VM instance with debugger state to reset.
443 */
445 for (int i = 0; i < vm->break_count; ++i) {
446 if (vm->breakpoints[i].file) {
447 free(vm->breakpoints[i].file);
448 vm->breakpoints[i].file = NULL;
449 }
450 vm->breakpoints[i].active = 0;
451 vm->breakpoints[i].line = 0;
452 }
453 vm->break_count = 0;
454 vm->debug_step_mode = 0;
455 vm->debug_step_target_fp = -1;
457 vm->debug_stop_requested = 0;
458}
459
460/**
461 * @brief Add a source breakpoint.
462 *
463 * @param vm VM instance.
464 * @param file Source file path (must not be NULL).
465 * @param line One-based source line number (> 0).
466 * @return Breakpoint id (>=0) on success, -1 on failure (invalid args or full).
467 */
468int vm_debug_add_breakpoint(VM *vm, const char *file, int line) {
469 if (!file || line <= 0) return -1;
470 if (vm->break_count >= (int)(sizeof(vm->breakpoints) / sizeof(vm->breakpoints[0]))) return -1;
471 int id = vm->break_count++;
472 vm->breakpoints[id].file = strdup(file);
473 vm->breakpoints[id].line = line;
474 vm->breakpoints[id].active = 1;
475 return id;
476}
477
478/**
479 * @brief Delete a breakpoint by id.
480 *
481 * Compacts the internal breakpoint list to keep ids dense.
482 *
483 * @param vm VM instance.
484 * @param id Breakpoint identifier previously returned by add.
485 * @return 1 if deleted, 0 if id was invalid.
486 */
488 if (id < 0 || id >= vm->break_count) return 0;
489 if (vm->breakpoints[id].file) free(vm->breakpoints[id].file);
490 for (int i = id + 1; i < vm->break_count; ++i) {
491 vm->breakpoints[i - 1] = vm->breakpoints[i];
492 }
493 vm->break_count--;
494 if (vm->break_count >= 0) {
495 vm->breakpoints[vm->break_count].file = NULL;
496 vm->breakpoints[vm->break_count].line = 0;
497 vm->breakpoints[vm->break_count].active = 0;
498 }
499 return 1;
500}
501
502/**
503 * @brief Remove all breakpoints from the VM.
504 *
505 * @param vm VM instance.
506 */
510
511/**
512 * @brief Print active breakpoints to stdout.
513 *
514 * @param vm VM instance.
515 */
517 if (vm->break_count <= 0) {
518 printf("(no breakpoints)\n");
519 return;
520 }
521 for (int i = 0; i < vm->break_count; ++i) {
522 if (!vm->breakpoints[i].active) continue;
523 printf(" [%d] %s:%d\n", i, vm->breakpoints[i].file ? vm->breakpoints[i].file : "<unknown>", vm->breakpoints[i].line);
524 }
525}
526
527/**
528 * @brief Request single-step execution (stop after next instruction).
529 *
530 * @param vm VM instance.
531 */
533 vm->debug_step_mode = 1; // step
535 vm->debug_stop_requested = 0;
536}
537
538/**
539 * @brief Request step-over (stop after next instruction in current frame).
540 *
541 * @param vm VM instance.
542 */
544 vm->debug_step_mode = 2; // next (step over)
545 vm->debug_step_target_fp = vm->fp;
547 vm->debug_stop_requested = 0;
548}
549
550/**
551 * @brief Request finish (run until the current frame returns).
552 *
553 * @param vm VM instance.
554 */
556 vm->debug_step_mode = 3; // finish (until return)
557 vm->debug_step_target_fp = vm->fp;
558 vm->debug_stop_requested = 0;
559}
560
561/**
562 * @brief Resume normal execution (clear stepping state and stop flag).
563 *
564 * @param vm VM instance.
565 */
567 vm->debug_step_mode = 0;
568 vm->debug_stop_requested = 0;
569}
570
571/* --- Centralized operand stack safety helpers --- */
572/** Return current number of values on the stack. */
573static inline int vm_stack_count(const VM *vm) { return vm->sp + 1; }
574
575/** Return available space left on the stack (in Values). */
576static inline int vm_stack_space(const VM *vm) { return STACK_SIZE - (vm->sp + 1); }
577
578/** Ensure at least n values are available to pop; aborts on underflow. */
579static inline void vm_require_stack(VM *vm, int n) {
580 if (n < 0) n = 0;
581 if (vm_stack_count(vm) < n) {
582 fprintf(stderr, "Runtime error: stack underflow (need %d, have %d)\n", n, vm_stack_count(vm));
583 exit(1);
584 }
585}
586
587/**
588 * @brief Push a Value onto the VM operand stack.
589 *
590 * Takes ownership of the provided Value. Aborts execution on overflow.
591 *
592 * @param vm VM instance.
593 * @param v Value to push (ownership transferred).
594 */
595static void push_value(VM *vm, Value v) {
596 if (vm->sp >= STACK_SIZE - 1) {
597 fprintf(stderr, "Runtime error: stack overflow\n");
598 exit(1);
599 }
600 vm->stack[++vm->sp] = v; /* take ownership of v */
601}
602
603/**
604 * @brief Pop a Value from the VM operand stack.
605 *
606 * Caller takes ownership of the returned Value. Aborts execution on underflow.
607 *
608 * @param vm VM instance.
609 * @return The top Value from the stack.
610 */
611static Value pop_value(VM *vm) {
612 if (vm->sp < 0) {
613 fprintf(stderr, "Runtime error: stack underflow\n");
614 exit(1);
615 }
616 return vm->stack[vm->sp--]; /* caller owns returned Value */
617}
618
619/* --- C ABI helpers for Rust FFI --- */
620/**
621 * @brief Pop a numeric Value and convert it to a 64-bit integer (C ABI helper).
622 *
623 * Accepts int or float Values on the stack. Other types raise a runtime type
624 * error. The popped Value is freed.
625 *
626 * @param vm VM instance.
627 * @return The numeric value converted to int64_t.
628 */
629int64_t vm_pop_i64(VM *vm) {
630 Value v = pop_value(vm);
631 int64_t out = 0;
632 if (v.type == VAL_INT) {
633 out = v.i;
634 } else if (v.type == VAL_FLOAT) {
635 out = (int64_t)v.d;
636 } else {
637 fprintf(stderr, "Runtime type error: expected int/float on stack, got %s\n", value_type_name(v.type));
638 free_value(v);
639 exit(1);
640 }
641 /* free any dynamic payload (no-op for int/float) */
642 free_value(v);
643 return out;
644}
645
646/**
647 * @brief Push a 64-bit integer as a VM int Value (C ABI helper).
648 *
649 * @param vm VM instance.
650 * @param v Integer value to push.
651 */
652void vm_push_i64(VM *vm, int64_t v) {
653 push_value(vm, make_int(v));
654}
655
656/* --- Extended C ABI for Rust to access VM internals (unsafe) --- */
657/**
658 * @brief Return sizeof(VM) for external FFI consumers.
659 *
660 * @return Size of the VM struct in bytes.
661 */
662size_t vm_sizeof(void) {
663 return sizeof(VM);
664}
665
666/**
667 * @brief Return sizeof(Value) for external FFI consumers.
668 *
669 * @return Size of the Value struct in bytes.
670 */
671size_t vm_value_sizeof(void) {
672 return sizeof(Value);
673}
674
675/**
676 * @brief Cast the VM pointer to an opaque mutable void* (unsafe FFI helper).
677 *
678 * @param vm VM instance pointer.
679 * @return The same pointer reinterpreted as void*.
680 */
681void *vm_as_mut_ptr(VM *vm) {
682 return (void *)vm;
683}
684
685/**
686 * @brief Obtain offsetof(VM, exit_code) for FFI struct field access.
687 *
688 * @return Byte offset of the exit_code field within VM.
689 */
691 return offsetof(VM, exit_code);
692}
693
694/**
695 * @brief Obtain offsetof(VM, sp) for FFI struct field access.
696 *
697 * @return Byte offset of the sp field within VM.
698 */
699size_t vm_offset_of_sp(void) {
700 return offsetof(VM, sp);
701}
702
703/**
704 * @brief Obtain offsetof(VM, stack) for FFI struct field access.
705 *
706 * @return Byte offset of the stack field within VM.
707 */
708size_t vm_offset_of_stack(void) {
709 return offsetof(VM, stack);
710}
711
712/**
713 * @brief Obtain offsetof(VM, globals) for FFI struct field access.
714 *
715 * @return Byte offset of the globals field within VM.
716 */
718 return offsetof(VM, globals);
719}
720
721/**
722 * @brief Initialize a call frame to a clean state.
723 *
724 * Sets function pointer and instruction pointer, zeroes locals to nil and
725 * resets the try-stack pointer.
726 *
727 * @param f Frame to initialize.
728 */
729static void frame_init(Frame *f) {
730 f->fn = NULL;
731 f->ip = 0;
732 for (int i = 0; i < MAX_FRAME_LOCALS; ++i)
733 f->locals[i] = make_nil();
734 f->try_sp = -1;
735}
736
737/**
738 * @brief Initialize a VM instance to its default state.
739 *
740 * Resets stack/frame pointers, output buffers, instruction counters, debugger
741 * state and globals. Does not allocate memory.
742 *
743 * @param vm VM instance to initialize.
744 */
745void vm_init(VM *vm) {
746 vm->sp = -1;
747 vm->fp = -1;
748 vm->output_count = 0;
749 for (int i = 0; i < OUTPUT_SIZE; ++i)
750 vm->output_is_partial[i] = 0;
751 vm->instr_count = 0;
752 vm->exit_code = 0;
753 vm->trace_enabled = 0;
754 vm->repl_on_error = 0;
755 vm->on_error_repl = NULL;
756
757#ifdef FUN_TRACE
758 for (int i = 0; i < OPCODE_COUNT; ++i) vm->op_counts[i] = 0;
759#endif
760
761 /* Debugger state */
762 vm->debug_step_mode = 0;
763 vm->debug_step_target_fp = -1;
764 vm->debug_step_start_ic = 0;
765 vm->debug_stop_requested = 0;
766 vm->break_count = 0;
767 for (int i = 0; i < (int)(sizeof(vm->breakpoints) / sizeof(vm->breakpoints[0])); ++i) {
768 vm->breakpoints[i].file = NULL;
769 vm->breakpoints[i].line = 0;
770 vm->breakpoints[i].active = 0;
771 }
772
773 for (int i = 0; i < MAX_GLOBALS; ++i)
774 vm->globals[i] = make_nil();
775}
776
777/* push a new frame, transferring ownership of args[] into frame->locals[0..argc-1] */
778/**
779 * @brief Push a new call frame for a function and transfer arguments.
780 *
781 * The first argc Values from args are moved (ownership transfer) into the new
782 * frame's local slots starting at index 0. Aborts on frame stack overflow.
783 *
784 * @param vm VM instance.
785 * @param fn Function bytecode to execute in the new frame.
786 * @param argc Number of arguments provided.
787 * @param args Array of argument Values (may be NULL if argc == 0).
788 */
789static void vm_push_frame(VM *vm, Bytecode *fn, int argc, Value *args) {
790 if (vm->fp >= MAX_FRAMES - 1) {
791 fprintf(stderr, "Runtime error: too many frames\n");
792 exit(1);
793 }
794 Frame *f = &vm->frames[++vm->fp];
795 frame_init(f);
796 f->fn = fn;
797 f->ip = 0;
798 /* move args into locals 0..argc-1 */
799 for (int i = 0; i < argc && i < MAX_FRAME_LOCALS; ++i) {
800 f->locals[i] = args[i]; /* transfer ownership */
801 }
802}
803
804/* pop current frame and free its locals */
805/**
806 * @brief Pop the current call frame and free its local variables.
807 *
808 * Aborts if there is no active frame.
809 *
810 * @param vm VM instance.
811 */
812static void vm_pop_frame(VM *vm) {
813 if (vm->fp < 0) {
814 fprintf(stderr, "Runtime error: pop frame with empty frame stack\n");
815 exit(1);
816 }
817 Frame *f = &vm->frames[vm->fp];
818 for (int i = 0; i < MAX_FRAME_LOCALS; ++i) {
819 free_value(f->locals[i]);
820 f->locals[i] = make_nil();
821 }
822 vm->fp--;
823}
824
825/**
826 * @brief Print the VM's buffered output values to stdout.
827 *
828 * Emits a newline after each value unless the corresponding partial flag is set.
829 *
830 * @param vm VM instance whose output should be printed.
831 */
833 for (int i = 0; i < vm->output_count; ++i) {
834 print_value(&vm->output[i]);
835 if (!vm->output_is_partial[i]) {
836 printf("\n");
837 }
838 }
839}
840
841/* ---- Diagnostics helpers ---- */
842/* Find best-effort source line for given ip by scanning backwards for OP_LINE. */
843static int vm_ip_to_line(const Bytecode *bc, int ip) {
844 if (!bc || !bc->instructions || ip < 0) return 0;
845 if (ip >= bc->instr_count) ip = bc->instr_count - 1;
846 for (int i = ip; i >= 0; --i) {
847 if (bc->instructions[i].op == OP_LINE) {
848 return bc->instructions[i].operand;
849 }
850 }
851 return 0;
852}
853
854/* Print a human-readable stack trace (top frame first). */
856 if (!vm) return;
857 fprintf(stderr, "Stack trace (most recent call first):\n");
858 for (int fp = vm->fp; fp >= 0; --fp) {
859 Frame *fr = &vm->frames[fp];
860 Bytecode *bc = fr->fn;
861 const char *fname = (bc && bc->name) ? bc->name : "<anon>";
862 const char *src = (bc && bc->source_file) ? bc->source_file : "<unknown>";
863 int ip = fr->ip - 1; /* last executed */
864 int line = vm_ip_to_line(bc, ip);
865 const char *opname = "OP?";
866 if (bc && ip >= 0 && ip < bc->instr_count) {
867 int op = bc->instructions[ip].op;
868 if (op >= 0 && op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) {
869 opname = opcode_names[op];
870 }
871 }
872 fprintf(stderr, " at %s:%d in %s (ip=%d, %s)\n", src, line > 0 ? line : 0, fname, ip, opname);
873 }
874}
875
876#ifdef FUN_TRACE
877/**
878 * @brief Dump non-zero opcode execution counters to stdout.
879 */
880void vm_dump_opcode_counters(VM *vm) {
881 if (!vm) return;
882 long long total = 0;
883 for (int i = 0; i < OPCODE_COUNT; ++i) total += vm->op_counts[i];
884 printf("=== opcode counters (total %lld) ===\n", total);
885 for (int i = 0; i < OPCODE_COUNT; ++i) {
886 long long c = vm->op_counts[i];
887 if (c > 0) {
888 const char *name = (i >= 0 && i < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) ? opcode_names[i] : "OP?";
889 printf("%3d %-16s %lld\n", i, name, c);
890 }
891 }
892}
893#endif
894
895/**
896 * @brief Execute a bytecode program starting from the given entry point.
897 *
898 * Sets up the initial frame and runs the main interpreter loop until there are
899 * no more frames. Honors debugger stepping/finish/continue requests and, when
900 * enabled, traps exit paths to enter a REPL via on_error_repl.
901 *
902 * @param vm VM instance to run.
903 * @param entry Entry function bytecode (must not be NULL).
904 */
905void vm_run(VM *vm, Bytecode *entry) {
906 /* reset instruction count for this run */
907 vm->instr_count = 0;
908 vm->current_line = 1;
909 g_active_vm = vm;
910
911 /* set error trap if REPL-on-error is enabled */
912 if (vm->repl_on_error) {
913 int jcode = setjmp(g_vm_err_jmp);
914 if (jcode != 0) {
915 /* We got here from a trapped exit() in an error path */
916 fprintf(stderr, "Entering REPL due to runtime error (code %d)\n", jcode);
917 if (vm->on_error_repl) {
918 vm->on_error_repl(vm);
919 }
920 g_active_vm = NULL;
921 return;
922 }
923 }
924
925 /* start with entry frame (no args) */
926 vm_push_frame(vm, entry, 0, NULL);
927
928 while (vm->fp >= 0) {
929 Frame *f = &vm->frames[vm->fp];
930
931 /* Stop conditions at top of loop (stepping/finish) */
932 if (vm->on_error_repl) {
933 int should_stop = 0;
934 if (vm->debug_stop_requested) {
935 should_stop = 1;
936 } else if (vm->debug_step_mode == 1 && vm->instr_count > vm->debug_step_start_ic) { /* step */
937 should_stop = 1;
938 vm->debug_step_mode = 0;
939 } else if (vm->debug_step_mode == 2 && vm->instr_count > vm->debug_step_start_ic && vm->fp <= vm->debug_step_target_fp) { /* next */
940 should_stop = 1;
941 vm->debug_step_mode = 0;
942 } else if (vm->debug_step_mode == 3 && vm->fp < vm->debug_step_target_fp) { /* finish */
943 should_stop = 1;
944 vm->debug_step_mode = 0;
945 }
946 if (should_stop) {
947 vm->debug_stop_requested = 0;
948 fprintf(stderr, "Paused (debug)\n");
949 vm->on_error_repl(vm);
950 /* Frame pointer might have changed (reset/cont); refresh f */
951 if (vm->fp < 0) break;
952 f = &vm->frames[vm->fp];
953 }
954 }
955
956 if (f->ip < 0 || f->ip >= f->fn->instr_count) {
957 /* no more instructions in this frame -> implicit return nil */
958 Value nilv = make_nil();
959 vm_pop_frame(vm);
960 push_value(vm, nilv);
961 continue;
962 }
963
964 Instruction inst = f->fn->instructions[f->ip++];
965 vm->instr_count++; /* count each executed instruction */
966
967 /* Validate opcode defensively */
968 if (!opcode_is_valid(inst.op)) {
969 char buf[256];
970 Bytecode *bc = f->fn;
971 const char *fname = (bc && bc->name) ? bc->name : "<anon>";
972 const char *src = (bc && bc->source_file) ? bc->source_file : "<unknown>";
973 int ip = f->ip - 1;
974 int line = vm_ip_to_line(bc, ip);
975 const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names)/sizeof(opcode_names[0]))) ? opcode_names[inst.op] : "OP?";
976 snprintf(buf, sizeof(buf), "invalid opcode %d (%s) at %s:%d in %s (ip=%d)", inst.op, opname, src, line > 0 ? line : 0, fname, ip);
977 vm_raise_error(vm, buf);
978 break;
979 }
980
981#ifdef FUN_TRACE
982 /* Increment per-opcode counter */
983 if (inst.op >= 0 && inst.op < OPCODE_COUNT) {
984 vm->op_counts[inst.op]++;
985 }
986#endif
987
988 if (vm->trace_enabled) {
989 const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0])))
990 ? opcode_names[inst.op]
991 : "???";
992 const char *fname = f->fn && f->fn->name ? f->fn->name : "<entry>";
993 const char *sfile = f->fn && f->fn->source_file ? f->fn->source_file : "<unknown>";
994 /* Dump up to top 4 stack values */
995 int count = vm->sp + 1;
996 int start = count - 4;
997 if (start < 0) start = 0;
998 fprintf(stdout, "TRACE %s:%d %s ip=%d %-14s %d | stack[%d]=[", sfile, vm->current_line, fname, f->ip - 1, opname, inst.operand, count);
999 for (int i = start; i < count; ++i) {
1000 char *sv = value_to_string_alloc(&vm->stack[i]);
1001 if (!sv) sv = strdup("<oom>");
1002 fprintf(stdout, "%s%s", sv, (i == count - 1 ? "" : ", "));
1003 free(sv);
1004 }
1005 fprintf(stdout, "]\n");
1006 }
1007
1008 /* Breakpoint hit detection: breakpoints are set on source_file:line via LINE markers */
1009 if (vm->on_error_repl && inst.op == OP_LINE && vm->break_count > 0) {
1010 const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : NULL;
1011 int line = inst.operand;
1012 for (int bi = 0; bi < vm->break_count; ++bi) {
1013 if (!vm->breakpoints[bi].active) continue;
1014 if (vm->breakpoints[bi].line != line) continue;
1015 if (!sfile || !vm->breakpoints[bi].file) continue;
1016 if (strcmp(vm->breakpoints[bi].file, sfile) != 0) continue;
1017 fprintf(stderr, "Breakpoint %d hit at %s:%d\n", bi, sfile, line);
1018 vm->on_error_repl(vm);
1019 /* After returning, refresh frame pointer and frame */
1020 if (vm->fp < 0) break;
1021 f = &vm->frames[vm->fp];
1022 break;
1023 }
1024 }
1025
1026 switch (inst.op) {
1027/* All opcode handlers as .c includes */
1028#include "vm/arithmetic/add.c"
1029#include "vm/arithmetic/div.c"
1030#include "vm/arithmetic/mul.c"
1031#include "vm/arithmetic/sub.c"
1032
1033#include "vm/arrays/apop.c"
1034#include "vm/arrays/clear.c"
1035#include "vm/arrays/contains.c"
1036#include "vm/arrays/enumerate.c"
1037#include "vm/arrays/index_get.c"
1038#include "vm/arrays/index_of.c"
1039#include "vm/arrays/index_set.c"
1040#include "vm/arrays/insert.c"
1041#include "vm/arrays/join.c"
1042#include "vm/arrays/make_array.c"
1043#include "vm/arrays/push.c"
1044#include "vm/arrays/remove.c"
1045#include "vm/arrays/set.c"
1046#include "vm/arrays/slice.c"
1047#include "vm/arrays/zip.c"
1048
1049/* Bitwise and shifts/rotates */
1050#include "vm/bitwise/band.c"
1051#include "vm/bitwise/bnot.c"
1052#include "vm/bitwise/bor.c"
1053#include "vm/bitwise/bxor.c"
1054#include "vm/bitwise/rol.c"
1055#include "vm/bitwise/ror.c"
1056#include "vm/bitwise/shl.c"
1057#include "vm/bitwise/shr.c"
1058
1059#include "vm/core/call.c"
1060#include "vm/core/dup.c"
1061#include "vm/core/exit.c"
1062#include "vm/core/halt.c"
1063#include "vm/core/jump.c"
1064#include "vm/core/jump_if_false.c"
1065#include "vm/core/load_const.c"
1066#include "vm/core/load_global.c"
1067#include "vm/core/load_local.c"
1068#include "vm/core/nop.c"
1069#include "vm/core/pop.c"
1070#include "vm/core/return.c"
1071#include "vm/core/store_global.c"
1072#include "vm/core/store_local.c"
1073#include "vm/core/swap.c"
1074#include "vm/core/throw.c"
1075#include "vm/core/try_pop.c"
1076#include "vm/core/try_push.c"
1077
1078#include "vm/io/input_line.c"
1079#include "vm/io/read_file.c"
1080#include "vm/io/write_file.c"
1081
1082#include "vm/logic/and.c"
1083#include "vm/logic/eq.c"
1084#include "vm/logic/gt.c"
1085#include "vm/logic/gte.c"
1086#include "vm/logic/lt.c"
1087#include "vm/logic/lte.c"
1088#include "vm/logic/neq.c"
1089#include "vm/logic/not.c"
1090#include "vm/logic/or.c"
1091
1092#include "vm/maps/has_key.c"
1093#include "vm/maps/keys.c"
1094#include "vm/maps/make_map.c"
1095#include "vm/maps/values.c"
1096
1097#include "vm/math/abs.c"
1098#include "vm/math/ceil.c"
1099#include "vm/math/clamp.c"
1100#include "vm/math/cos.c"
1101#include "vm/math/exp.c"
1102#include "vm/math/floor.c"
1103#include "vm/math/fmax.c"
1104#include "vm/math/fmin.c"
1105#include "vm/math/gcd.c"
1106#include "vm/math/isqrt.c"
1107#include "vm/math/lcm.c"
1108#include "vm/math/log.c"
1109#include "vm/math/log10.c"
1110#include "vm/math/max.c"
1111#include "vm/math/min.c"
1112#include "vm/math/mod.c"
1113#include "vm/math/pow.c"
1114#include "vm/math/random_int.c"
1115#include "vm/math/random_seed.c"
1116#include "vm/math/round.c"
1117#include "vm/math/sign.c"
1118#include "vm/math/sin.c"
1119#include "vm/math/sqrt.c"
1120#include "vm/math/tan.c"
1121#include "vm/math/trunc.c"
1122
1123/* Rust FFI demo opcode(s) */
1124#include "vm/rust/get_sp.c"
1125#include "vm/rust/hello.c"
1126#include "vm/rust/hello_args.c"
1127#include "vm/rust/hello_args_return.c"
1128#include "vm/rust/set_exit.c"
1129
1130#include "vm/os/clock_mono_ms.c"
1131#include "vm/os/date_format.c"
1132#include "vm/os/env.c"
1133#include "vm/os/env_all.c"
1134#include "vm/os/fun_version.c"
1135#include "vm/os/proc_run.c"
1136#include "vm/os/proc_system.c"
1137#include "vm/os/random_number.c"
1138#include "vm/os/serial_close.c"
1139#include "vm/os/serial_config.c"
1140#include "vm/os/serial_open.c"
1141#include "vm/os/serial_recv.c"
1142#include "vm/os/serial_send.c"
1143#include "vm/os/sleep_ms.c"
1144#include "vm/os/thread_join.c"
1145#include "vm/os/thread_spawn.c"
1146#include "vm/os/time_now_ms.c"
1147
1148/* Socket ops */
1149#include "vm/os/socket_close.c"
1150#include "vm/os/socket_recv.c"
1151#include "vm/os/socket_send.c"
1152#include "vm/os/socket_tcp_accept.c"
1153#include "vm/os/socket_tcp_connect.c"
1154#include "vm/os/socket_tcp_listen.c"
1155#include "vm/os/socket_unix_connect.c"
1156#include "vm/os/socket_unix_listen.c"
1157
1158/* Async-friendly FD helpers (UNIX) */
1159#include "vm/os/fd_set_nonblock.c"
1160#include "vm/os/fd_poll_read.c"
1161#include "vm/os/fd_poll_write.c"
1162
1163#ifdef FUN_WITH_PCSC
1164#include "vm/pcsc/connect.c"
1165#include "vm/pcsc/disconnect.c"
1166#include "vm/pcsc/establish.c"
1167#include "vm/pcsc/list_readers.c"
1168#include "vm/pcsc/release.c"
1169#include "vm/pcsc/transmit.c"
1170#endif
1171
1172/* JSON ops (implemented in jsonc.c, included above) */
1173#ifdef FUN_WITH_JSON
1174#include "vm/json/from_file.c"
1175#include "vm/json/parse.c"
1176#include "vm/json/stringify.c"
1177#include "vm/json/to_file.c"
1178#endif
1179
1180/* XML ops (libxml2) */
1181#ifdef FUN_WITH_XML2
1182#include "vm/xml/name.c"
1183#include "vm/xml/parse.c"
1184#include "vm/xml/root.c"
1185#include "vm/xml/text.c"
1186#endif
1187
1188/* INI ops (iniparser 4.2.6) */
1189#ifdef FUN_WITH_INI
1190#include "vm/ini/free.c"
1191#include "vm/ini/get_bool.c"
1192#include "vm/ini/get_double.c"
1193#include "vm/ini/get_int.c"
1194#include "vm/ini/get_string.c"
1195#include "vm/ini/load.c"
1196#include "vm/ini/save.c"
1197#include "vm/ini/set.c"
1198#include "vm/ini/unset.c"
1199#else
1200#include "vm/ini/stubs.c"
1201#endif
1202
1203/* CURL ops */
1204#ifdef FUN_WITH_CURL
1205#include "vm/curl/download.c"
1206#include "vm/curl/get.c"
1207#include "vm/curl/post.c"
1208#endif
1209
1210/* KCGI ops */
1211#ifdef FUN_WITH_KCGI
1212#include "vm/kcgi/parse.c"
1213#include "vm/kcgi/reply_start.c"
1214#include "vm/kcgi/write.c"
1215#include "vm/kcgi/end.c"
1216#endif
1217
1218/* OpenSSL ops (md5/sha256/sha512/ripemd160) */
1219#ifdef FUN_WITH_OPENSSL
1220#include "vm/openssl/md5.c"
1221#include "vm/openssl/ripemd160.c"
1222#include "vm/openssl/sha256.c"
1223#include "vm/openssl/sha512.c"
1224#endif
1225
1226/* SQLite ops */
1227#ifdef FUN_WITH_SQLITE
1228#include "vm/sqlite/close.c"
1229#include "vm/sqlite/exec.c"
1230#include "vm/sqlite/open.c"
1231#include "vm/sqlite/query.c"
1232#endif
1233
1234/* Redis ops */
1235#ifdef FUN_WITH_REDIS
1236#include "vm/redis/connect.c"
1237#include "vm/redis/cmd.c"
1238#include "vm/redis/close.c"
1239#endif
1240
1241/* C++ demo opcodes (guarded) */
1242#ifdef FUN_WITH_CPP
1243 case OP_CPP_ADD: {
1244 int rc = fun_op_cpp_add(vm);
1245 if (rc != 0) {
1246 vm_raise_error(vm, "cpp_add failed");
1247 }
1248 break;
1249 }
1250#else
1251 case OP_CPP_ADD: {
1252 vm_raise_error(vm, "CPP support is not enabled (build with -DFUN_WITH_CPP=ON)");
1253 break;
1254 }
1255#endif
1256
1257/* PCRE2 ops */
1258#ifdef FUN_WITH_PCRE2
1259#include "vm/pcre2/findall.c"
1260#include "vm/pcre2/match.c"
1261#include "vm/pcre2/test.c"
1262#endif
1263
1264#include "vm/strings/find.c"
1265#include "vm/strings/regex_match.c"
1266#include "vm/strings/regex_replace.c"
1267#include "vm/strings/regex_search.c"
1268#include "vm/strings/split.c"
1269#include "vm/strings/substr.c"
1270
1271#include "vm/cast.c"
1272#include "vm/echo.c"
1273#include "vm/len.c"
1274#include "vm/line.c"
1275#include "vm/os/list_dir.c"
1276#include "vm/print.c"
1277#include "vm/sclamp.c"
1278#include "vm/to_number.c"
1279#include "vm/to_string.c"
1280#include "vm/typeof.c"
1281#include "vm/uclamp.c"
1282
1283 default:
1284 if (!opcode_is_valid(inst.op)) {
1285 fprintf(stderr, "Runtime error: unknown opcode %d (%s) at instruction %d\n",
1286 inst.op,
1287 (inst.op >= 0 && inst.op < sizeof(opcode_names) / sizeof(opcode_names[0]))
1288 ? opcode_names[inst.op]
1289 : "???",
1290 f->ip - 1);
1291 exit(1);
1292 }
1293 break;
1294 }
1295
1296 /* Stream console output in realtime for scripts:
1297 * When PRINT/ECHO pushed items into the VM's output buffer, flush them
1298 * immediately to stdout and clear the buffer to avoid end-of-run bursts.
1299 * This keeps REPL compatibility (REPL still prints after each submit),
1300 * while regular script execution shows progress bars live.
1301 */
1302 if (inst.op == OP_PRINT || inst.op == OP_ECHO) {
1303 vm_print_output(vm);
1304 vm_clear_output(vm);
1305 fflush(stdout);
1306 }
1307 }
1308 g_active_vm = NULL;
1309#ifdef FUN_TRACE
1310 if (vm->trace_enabled) {
1312 }
1313#endif
1314}
#define OPCODE_COUNT
Definition bytecode.h:300
@ OP_CPP_ADD
Definition bytecode.h:296
@ OP_LINE
Definition bytecode.h:67
@ OP_ECHO
Definition bytecode.h:64
@ OP_PRINT
Definition bytecode.h:63
libcurl helpers and buffers used by HTTP-related VM opcodes.
iniparser helpers for Fun VM INI-related opcodes (conditional build).
Iterator-style helpers exposed as built-ins (enumerate, zip).
json-c helpers for Fun VM JSON-related opcodes (conditional build).
Thin kcgi integration helpers used by VM opcodes under src/vm/kcgi/.
Simple string-keyed map implementation backing VAL_MAP Values.
OpenSSL-based hashing helpers used by crypto-related VM opcodes.
int count
Definition parser.c:286
PCRE2 helpers for Fun VM extension opcodes (conditional build).
PC/SC smartcard helper registries and lookup utilities for VM opcodes.
Hiredis handle registry and reply mapping helpers for the Fun VM.
SQLite handle registry and helper utilities for the Fun VM extension.
String built-ins wrappers used by VM opcodes.
Instruction * instructions
Definition bytecode.h:308
const char * source_file
Definition bytecode.h:316
const char * name
Definition bytecode.h:315
int instr_count
Definition bytecode.h:309
Call frame representing one active function invocation.
Definition vm.h:95
int try_sp
Definition vm.h:101
Bytecode * fn
Definition vm.h:96
Value locals[MAX_FRAME_LOCALS]
Definition vm.h:98
int try_stack[16]
Definition vm.h:100
int ip
Definition vm.h:97
OpCode op
Definition bytecode.h:303
int32_t operand
Definition bytecode.h:304
The Fun virtual machine state.
Definition vm.h:112
int sp
Definition vm.h:114
long long instr_count
Definition vm.h:125
int debug_step_target_fp
Definition vm.h:142
Value output[OUTPUT_SIZE]
Definition vm.h:121
int line
Definition vm.h:148
int current_line
Definition vm.h:132
int repl_on_error
Definition vm.h:137
int fp
Definition vm.h:117
int output_is_partial[OUTPUT_SIZE]
Definition vm.h:123
int(* on_error_repl)(struct VM *vm)
Definition vm.h:138
int debug_stop_requested
Definition vm.h:144
int break_count
Definition vm.h:151
int active
Definition vm.h:149
int trace_enabled
Definition vm.h:136
int exit_code
Definition vm.h:134
int output_count
Definition vm.h:122
struct VM::@204221333366357065317066305241116055104274166224 breakpoints[64]
char * file
Definition vm.h:147
Value globals[MAX_GLOBALS]
Definition vm.h:119
int debug_step_mode
Definition vm.h:141
long long debug_step_start_ic
Definition vm.h:143
Frame frames[MAX_FRAMES]
Definition vm.h:116
Value stack[STACK_SIZE]
Definition vm.h:113
Tagged union representing a Fun value.
Definition value.h:68
int64_t i
Definition value.h:71
double d
Definition value.h:72
ValueType type
Definition value.h:69
void print_value(const Value *v)
Print a human-readable representation of a Value to stdout.
Definition value.c:552
Value make_nil(void)
Construct a nil Value.
Definition value.c:126
Value make_string(const char *s)
Construct a string Value by duplicating the given C string.
Definition value.c:95
void free_value(Value v)
Free dynamic storage owned by a Value.
Definition value.c:517
char * value_to_string_alloc(const Value *v)
Allocate a printable C string for a Value.
Definition value.c:641
Value make_int(int64_t v)
Construct a Value representing a 64-bit integer.
Definition value.c:51
Defines the Value type and associated functions for the Fun VM.
ValueType
Enumeration of all runtime value types supported by Fun.
Definition value.h:50
@ VAL_BOOL
Definition value.h:52
@ VAL_ARRAY
Definition value.h:55
@ VAL_MAP
Definition value.h:56
@ VAL_STRING
Definition value.h:53
@ VAL_FUNCTION
Definition value.h:54
@ VAL_NIL
Definition value.h:57
@ VAL_INT
Definition value.h:51
@ VAL_FLOAT
Definition value.h:58
size_t vm_offset_of_exit_code(void)
Obtain offsetof(VM, exit_code) for FFI struct field access.
Definition vm.c:690
static int fun_vm_fprintf(FILE *stream, const char *fmt,...)
fprintf wrapper forwarding to fun_vm_vfprintf.
Definition vm.c:184
void vm_print_stacktrace(VM *vm)
Definition vm.c:855
size_t vm_offset_of_globals(void)
Obtain offsetof(VM, globals) for FFI struct field access.
Definition vm.c:717
static Value pop_value(VM *vm)
Pop a Value from the VM operand stack.
Definition vm.c:611
size_t vm_value_sizeof(void)
Return sizeof(Value) for external FFI consumers.
Definition vm.c:671
void vm_raise_error(VM *vm, const char *msg)
Raise a runtime error inside the VM, honoring try/catch/finally.
Definition vm.c:244
static const char * value_type_name(ValueType t)
Get a human-readable name for a ValueType.
Definition vm.c:328
static void vm_pop_frame(VM *vm)
Pop the current call frame and free its local variables.
Definition vm.c:812
void vm_debug_request_finish(VM *vm)
Request finish (run until the current frame returns).
Definition vm.c:555
void vm_debug_clear_breakpoints(VM *vm)
Remove all breakpoints from the VM.
Definition vm.c:507
void vm_debug_request_continue(VM *vm)
Resume normal execution (clear stepping state and stop flag).
Definition vm.c:566
void vm_debug_request_step(VM *vm)
Request single-step execution (stop after next instruction).
Definition vm.c:532
static void frame_init(Frame *f)
Initialize a call frame to a clean state.
Definition vm.c:729
int vm_debug_delete_breakpoint(VM *vm, int id)
Delete a breakpoint by id.
Definition vm.c:487
void vm_push_i64(VM *vm, int64_t v)
Push a 64-bit integer as a VM int Value (C ABI helper).
Definition vm.c:652
static int vm_stack_space(const VM *vm)
Definition vm.c:576
static void push_value(VM *vm, Value v)
Push a Value onto the VM operand stack.
Definition vm.c:595
static void fun_vm_exit(int code)
Replacement for exit() used within this translation unit.
Definition vm.c:210
void vm_init(VM *vm)
Initialize a VM instance to its default state.
Definition vm.c:745
static int fun_vm_vfprintf(FILE *stream, const char *fmt, va_list ap)
fprintf-like wrapper that annotates stderr messages with VM source context.
Definition vm.c:105
static __thread jmp_buf g_vm_err_jmp
Definition vm.c:199
void vm_debug_reset(VM *vm)
Reset debugger state: breakpoints and stepping controls.
Definition vm.c:444
static void vm_push_frame(VM *vm, Bytecode *fn, int argc, Value *args)
Push a new call frame for a function and transfer arguments.
Definition vm.c:789
void vm_debug_request_next(VM *vm)
Request step-over (stop after next instruction in current frame).
Definition vm.c:543
static __thread VM * g_active_vm
Definition vm.c:89
#define fprintf
Definition vm.c:194
size_t vm_offset_of_stack(void)
Obtain offsetof(VM, stack) for FFI struct field access.
Definition vm.c:708
static void vm_require_stack(VM *vm, int n)
Definition vm.c:579
static int vm_ip_to_line(const Bytecode *bc, int ip)
Definition vm.c:843
size_t vm_offset_of_sp(void)
Obtain offsetof(VM, sp) for FFI struct field access.
Definition vm.c:699
#define exit(code)
Definition vm.c:224
void vm_free(VM *vm)
Free resources owned directly by the VM structure.
Definition vm.c:377
size_t vm_sizeof(void)
Return sizeof(VM) for external FFI consumers.
Definition vm.c:662
int vm_debug_add_breakpoint(VM *vm, const char *file, int line)
Add a source breakpoint.
Definition vm.c:468
void vm_clear_output(VM *vm)
Clear the VM's buffered output values and partial flags.
Definition vm.c:359
int map_expanded_line_to_include_path(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line)
Map a line number in expanded source back to original include path/line.
void vm_reset(VM *vm)
Reset the VM to a clean state.
Definition vm.c:392
void * vm_as_mut_ptr(VM *vm)
Cast the VM pointer to an opaque mutable void* (unsafe FFI helper).
Definition vm.c:681
void vm_debug_list_breakpoints(VM *vm)
Print active breakpoints to stdout.
Definition vm.c:516
char * preprocess_includes(const char *src)
Public wrapper to preprocess includes without a current path.
void vm_print_output(VM *vm)
Print the VM's buffered output values to stdout.
Definition vm.c:832
static int vm_stack_count(const VM *vm)
Definition vm.c:573
int64_t vm_pop_i64(VM *vm)
Pop a numeric Value and convert it to a 64-bit integer (C ABI helper).
Definition vm.c:629
void vm_dump_globals(VM *vm)
Print all non-nil global variables to stdout for debugging.
Definition vm.c:423
Core virtual machine data structures and public VM API.
#define MAX_GLOBALS
Definition vm.h:34
int fun_op_cpp_add(struct VM *vm)
#define MAX_FRAMES
Definition vm.h:26
#define STACK_SIZE
Definition vm.h:42
#define OUTPUT_SIZE
Definition vm.h:38
void vm_run(VM *vm, Bytecode *entry)
Execute the provided entry bytecode in the VM. Pushes an initial frame and runs until HALT or an unre...
static const char * opcode_names[]
Definition vm.h:45
void vm_dump_opcode_counters(VM *vm)
static int opcode_is_valid(int op)
Check whether an integer value corresponds to a defined opcode.
Definition vm.h:239
#define MAX_FRAME_LOCALS
Definition vm.h:30
libxml2 handle registries and helper utilities for the Fun VM extension.