Some more internal optimizations. (0.41.15)
This commit is contained in:
parent
0d6685abcd
commit
a5de8b167a
13 changed files with 194 additions and 25 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.41.14 LANGUAGES C)
|
project(fun VERSION 0.41.15 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
2
Doxyfile
2
Doxyfile
|
|
@ -48,7 +48,7 @@ PROJECT_NAME = "Fun API Documentation"
|
||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 0.41.10
|
PROJECT_NUMBER = 0.41.15
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewers a
|
# for a project that appears at the top of each page and should give viewers a
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ endif()
|
||||||
# Debug option to enable verbose parser/VM logging
|
# Debug option to enable verbose parser/VM logging
|
||||||
option(FUN_DEBUG "Enable extra debug logging in Fun" OFF)
|
option(FUN_DEBUG "Enable extra debug logging in Fun" OFF)
|
||||||
|
|
||||||
|
# Optional: VM trace and opcode counters (disabled by default)
|
||||||
|
option(FUN_TRACE "Enable VM tracing and opcode execution counters" OFF)
|
||||||
|
|
||||||
# VM settings (configurable via -D...)
|
# VM settings (configurable via -D...)
|
||||||
set(MAX_FRAMES 128 CACHE STRING "Maximum depth of the call stack (frames)")
|
set(MAX_FRAMES 128 CACHE STRING "Maximum depth of the call stack (frames)")
|
||||||
set(MAX_FRAME_LOCALS 64 CACHE STRING "Maximum number of local variables per frame")
|
set(MAX_FRAME_LOCALS 64 CACHE STRING "Maximum number of local variables per frame")
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ if(FUN_DEBUG)
|
||||||
target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1)
|
target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Enable VM tracing/counters when requested
|
||||||
|
if(FUN_TRACE)
|
||||||
|
target_compile_definitions(fun_core PUBLIC FUN_TRACE=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Provide default stdlib directory and version to the runtime
|
# Provide default stdlib directory and version to the runtime
|
||||||
target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}")
|
target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}")
|
||||||
target_compile_definitions(fun_core PUBLIC DEFAULT_LIB_DIR="${DEFAULT_LIB_DIR}")
|
target_compile_definitions(fun_core PUBLIC DEFAULT_LIB_DIR="${DEFAULT_LIB_DIR}")
|
||||||
|
|
|
||||||
|
|
@ -36,16 +36,28 @@ Bytecode *bytecode_new(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Append a constant to a Bytecode's constant table.
|
* @brief Append a constant to a Bytecode's constant table with de-duplication.
|
||||||
*
|
*
|
||||||
* The value is deep-copied into the table; the caller retains ownership of v
|
* The value is compared against existing constants and if an equal constant is
|
||||||
* and may free it independently.
|
* already present, its index is returned without modifying the table. Equality
|
||||||
|
* uses value_equals which supports numeric cross-type (int/float) equality and
|
||||||
|
* string content equality. The caller retains ownership of @p v in all cases.
|
||||||
|
* When a new constant is inserted, a deep copy is stored in the table.
|
||||||
*
|
*
|
||||||
* @param bc Target bytecode (must not be NULL).
|
* @param bc Target bytecode (must not be NULL).
|
||||||
* @param v Value to store (copied).
|
* @param v Value to store (copied on insert).
|
||||||
* @return The index of the stored constant (zero-based).
|
* @return The index of the stored or matched existing constant (zero-based).
|
||||||
*/
|
*/
|
||||||
int bytecode_add_constant(Bytecode *bc, Value v) {
|
int bytecode_add_constant(Bytecode *bc, Value v) {
|
||||||
|
/* Linear scan for a match. This is fast enough for small constant pools and
|
||||||
|
* avoids duplicates (smaller bytecode, better cache locality). */
|
||||||
|
for (int i = 0; i < bc->const_count; ++i) {
|
||||||
|
if (value_equals(&bc->constants[i], &v)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Not found: append a copy */
|
||||||
bc->constants = (Value *)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1));
|
bc->constants = (Value *)realloc(bc->constants, sizeof(Value) * (bc->const_count + 1));
|
||||||
bc->constants[bc->const_count] = copy_value(&v);
|
bc->constants[bc->const_count] = copy_value(&v);
|
||||||
return bc->const_count++;
|
return bc->const_count++;
|
||||||
|
|
|
||||||
|
|
@ -291,6 +291,9 @@ typedef enum {
|
||||||
OP_CPP_ADD, // pops b, a; pushes (a + b)
|
OP_CPP_ADD, // pops b, a; pushes (a + b)
|
||||||
} OpCode;
|
} OpCode;
|
||||||
|
|
||||||
|
/* Total number of opcodes (keep in sync with OpCode enum). */
|
||||||
|
#define OPCODE_COUNT (OP_CPP_ADD + 1)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
OpCode op;
|
OpCode op;
|
||||||
int32_t operand;
|
int32_t operand;
|
||||||
|
|
|
||||||
51
src/parser.c
51
src/parser.c
|
|
@ -56,9 +56,16 @@ extern char *preprocess_includes_with_path(const char *src, const char *current_
|
||||||
static void skip_to_eol(const char *src, size_t len, size_t *pos);
|
static void skip_to_eol(const char *src, size_t len, size_t *pos);
|
||||||
static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent);
|
static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent);
|
||||||
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent);
|
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent);
|
||||||
|
static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col);
|
||||||
|
|
||||||
/* ---- parser error state ---- */
|
/* ---- parser error state ---- */
|
||||||
static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */
|
static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */
|
||||||
|
/* Track active source buffer for error line computation and cascade control */
|
||||||
|
static const char *g_active_src = NULL;
|
||||||
|
static size_t g_active_len = 0;
|
||||||
|
static int g_last_err_line = -1;
|
||||||
|
static int g_line_err_count = 0;
|
||||||
|
static const int G_ERRS_PER_LINE_CAP = 3; /* suppress floods after this many per line */
|
||||||
static int g_has_error = 0;
|
static int g_has_error = 0;
|
||||||
static size_t g_err_pos = 0;
|
static size_t g_err_pos = 0;
|
||||||
static char g_err_msg[256];
|
static char g_err_msg[256];
|
||||||
|
|
@ -137,6 +144,22 @@ static int fun_debug_enabled(void) {
|
||||||
* @param ... Arguments matching fmt.
|
* @param ... Arguments matching fmt.
|
||||||
*/
|
*/
|
||||||
static void parser_fail(size_t pos, const char *fmt, ...) {
|
static void parser_fail(size_t pos, const char *fmt, ...) {
|
||||||
|
/* Optional flood guard: suppress excessive errors on the same source line */
|
||||||
|
if (g_active_src && g_active_len > 0) {
|
||||||
|
int line = 0, col = 0;
|
||||||
|
calc_line_col(g_active_src, g_active_len, pos, &line, &col);
|
||||||
|
if (line == g_last_err_line) {
|
||||||
|
if (g_line_err_count >= G_ERRS_PER_LINE_CAP) {
|
||||||
|
/* Too many errors on this line; ignore to avoid cascades */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_line_err_count++;
|
||||||
|
} else {
|
||||||
|
g_last_err_line = line;
|
||||||
|
g_line_err_count = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g_has_error = 1;
|
g_has_error = 1;
|
||||||
g_err_pos = pos;
|
g_err_pos = pos;
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
@ -7608,8 +7631,16 @@ Bytecode *parse_file_to_bytecode(const char *path) {
|
||||||
g_err_line = 0;
|
g_err_line = 0;
|
||||||
g_err_col = 0;
|
g_err_col = 0;
|
||||||
|
|
||||||
/* Set current source path for nested bytecodes to inherit */
|
/* Set active source for error mapping/cascade control and current source path for nested bytecodes */
|
||||||
const char *prev_source = g_current_source_path;
|
const char *prev_source = g_current_source_path;
|
||||||
|
const char *prev_active_src = g_active_src;
|
||||||
|
size_t prev_active_len = g_active_len;
|
||||||
|
int prev_last_line = g_last_err_line;
|
||||||
|
int prev_line_count = g_line_err_count;
|
||||||
|
g_active_src = compile_src;
|
||||||
|
g_active_len = compile_len;
|
||||||
|
g_last_err_line = -1;
|
||||||
|
g_line_err_count = 0;
|
||||||
g_current_source_path = path;
|
g_current_source_path = path;
|
||||||
Bytecode *bc = compile_minimal(compile_src, compile_len);
|
Bytecode *bc = compile_minimal(compile_src, compile_len);
|
||||||
/* assign debug metadata to module bytecode */
|
/* assign debug metadata to module bytecode */
|
||||||
|
|
@ -7624,6 +7655,10 @@ Bytecode *parse_file_to_bytecode(const char *path) {
|
||||||
}
|
}
|
||||||
/* restore previous */
|
/* restore previous */
|
||||||
g_current_source_path = prev_source;
|
g_current_source_path = prev_source;
|
||||||
|
g_active_src = prev_active_src;
|
||||||
|
g_active_len = prev_active_len;
|
||||||
|
g_last_err_line = prev_last_line;
|
||||||
|
g_line_err_count = prev_line_count;
|
||||||
|
|
||||||
if (g_has_error) {
|
if (g_has_error) {
|
||||||
int line = 1, col = 1;
|
int line = 1, col = 1;
|
||||||
|
|
@ -7791,8 +7826,16 @@ Bytecode *parse_string_to_bytecode(const char *source) {
|
||||||
g_err_line = 0;
|
g_err_line = 0;
|
||||||
g_err_col = 0;
|
g_err_col = 0;
|
||||||
|
|
||||||
/* Set current source path to <input> for nested bytecodes */
|
/* Set active source for error mapping/cascade control and current source path to <input> for nested bytecodes */
|
||||||
const char *prev_src = g_current_source_path;
|
const char *prev_src = g_current_source_path;
|
||||||
|
const char *prev_active_src = g_active_src;
|
||||||
|
size_t prev_active_len = g_active_len;
|
||||||
|
int prev_last_line = g_last_err_line;
|
||||||
|
int prev_line_count = g_line_err_count;
|
||||||
|
g_active_src = compile_src;
|
||||||
|
g_active_len = len;
|
||||||
|
g_last_err_line = -1;
|
||||||
|
g_line_err_count = 0;
|
||||||
g_current_source_path = NULL;
|
g_current_source_path = NULL;
|
||||||
Bytecode *bc = compile_minimal(compile_src, len);
|
Bytecode *bc = compile_minimal(compile_src, len);
|
||||||
if (bc) {
|
if (bc) {
|
||||||
|
|
@ -7802,6 +7845,10 @@ Bytecode *parse_string_to_bytecode(const char *source) {
|
||||||
bc->name = strdup("<input>");
|
bc->name = strdup("<input>");
|
||||||
}
|
}
|
||||||
g_current_source_path = prev_src;
|
g_current_source_path = prev_src;
|
||||||
|
g_active_src = prev_active_src;
|
||||||
|
g_active_len = prev_active_len;
|
||||||
|
g_last_err_line = prev_last_line;
|
||||||
|
g_line_err_count = prev_line_count;
|
||||||
|
|
||||||
if (g_has_error) {
|
if (g_has_error) {
|
||||||
int line = 1, col = 1;
|
int line = 1, col = 1;
|
||||||
|
|
|
||||||
106
src/vm.c
106
src/vm.c
|
|
@ -224,6 +224,8 @@ static void fun_vm_exit(int code) {
|
||||||
|
|
||||||
/* Forward decl for stack push used by vm_raise_error */
|
/* Forward decl for stack push used by vm_raise_error */
|
||||||
static void push_value(VM *vm, Value v);
|
static void push_value(VM *vm, Value v);
|
||||||
|
/* Forward decl for diagnostic helper used by vm_raise_error */
|
||||||
|
static int vm_ip_to_line(const Bytecode *bc, int ip);
|
||||||
|
|
||||||
/* Raise a runtime error that respects try/catch/finally semantics.
|
/* Raise a runtime error that respects try/catch/finally semantics.
|
||||||
* If a handler is installed for the current frame, jump to it and push
|
* If a handler is installed for the current frame, jump to it and push
|
||||||
|
|
@ -259,8 +261,22 @@ void vm_raise_error(VM *vm, const char *msg) {
|
||||||
f->ip = target;
|
f->ip = target;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* No handler: print annotated message and terminate VM */
|
/* No handler: print annotated message, stack trace, and terminate VM */
|
||||||
fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
|
Bytecode *bc = f->fn;
|
||||||
|
const char *fname = (bc && bc->name) ? bc->name : "<anon>";
|
||||||
|
const char *src = (bc && bc->source_file) ? bc->source_file : "<unknown>";
|
||||||
|
int last_ip = f->ip - 1;
|
||||||
|
int line = vm_ip_to_line(bc, last_ip);
|
||||||
|
const char *opname = "OP?";
|
||||||
|
if (bc && last_ip >= 0 && last_ip < bc->instr_count) {
|
||||||
|
int op = bc->instructions[last_ip].op;
|
||||||
|
if (op >= 0 && op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) {
|
||||||
|
opname = opcode_names[op];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stderr, "Runtime error at %s:%d in %s (ip=%d, %s): %s\n",
|
||||||
|
src, line > 0 ? line : 0, fname, last_ip, opname, msg ? msg : "<error>");
|
||||||
|
vm_print_stacktrace(vm);
|
||||||
vm->fp = -1; /* stop execution */
|
vm->fp = -1; /* stop execution */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,6 +405,11 @@ void vm_reset(VM *vm) {
|
||||||
// Reset exit code
|
// Reset exit code
|
||||||
vm->exit_code = 0;
|
vm->exit_code = 0;
|
||||||
|
|
||||||
|
#ifdef FUN_TRACE
|
||||||
|
// Reset opcode counters
|
||||||
|
for (int i = 0; i < OPCODE_COUNT; ++i) vm->op_counts[i] = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Reset debugger state (breakpoints, stepping)
|
// Reset debugger state (breakpoints, stepping)
|
||||||
vm_debug_reset(vm);
|
vm_debug_reset(vm);
|
||||||
}
|
}
|
||||||
|
|
@ -732,6 +753,10 @@ void vm_init(VM *vm) {
|
||||||
vm->repl_on_error = 0;
|
vm->repl_on_error = 0;
|
||||||
vm->on_error_repl = NULL;
|
vm->on_error_repl = NULL;
|
||||||
|
|
||||||
|
#ifdef FUN_TRACE
|
||||||
|
for (int i = 0; i < OPCODE_COUNT; ++i) vm->op_counts[i] = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Debugger state */
|
/* Debugger state */
|
||||||
vm->debug_step_mode = 0;
|
vm->debug_step_mode = 0;
|
||||||
vm->debug_step_target_fp = -1;
|
vm->debug_step_target_fp = -1;
|
||||||
|
|
@ -812,6 +837,60 @@ void vm_print_output(VM *vm) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Diagnostics helpers ---- */
|
||||||
|
/* Find best-effort source line for given ip by scanning backwards for OP_LINE. */
|
||||||
|
static int vm_ip_to_line(const Bytecode *bc, int ip) {
|
||||||
|
if (!bc || !bc->instructions || ip < 0) return 0;
|
||||||
|
if (ip >= bc->instr_count) ip = bc->instr_count - 1;
|
||||||
|
for (int i = ip; i >= 0; --i) {
|
||||||
|
if (bc->instructions[i].op == OP_LINE) {
|
||||||
|
return bc->instructions[i].operand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print a human-readable stack trace (top frame first). */
|
||||||
|
void vm_print_stacktrace(VM *vm) {
|
||||||
|
if (!vm) return;
|
||||||
|
fprintf(stderr, "Stack trace (most recent call first):\n");
|
||||||
|
for (int fp = vm->fp; fp >= 0; --fp) {
|
||||||
|
Frame *fr = &vm->frames[fp];
|
||||||
|
Bytecode *bc = fr->fn;
|
||||||
|
const char *fname = (bc && bc->name) ? bc->name : "<anon>";
|
||||||
|
const char *src = (bc && bc->source_file) ? bc->source_file : "<unknown>";
|
||||||
|
int ip = fr->ip - 1; /* last executed */
|
||||||
|
int line = vm_ip_to_line(bc, ip);
|
||||||
|
const char *opname = "OP?";
|
||||||
|
if (bc && ip >= 0 && ip < bc->instr_count) {
|
||||||
|
int op = bc->instructions[ip].op;
|
||||||
|
if (op >= 0 && op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) {
|
||||||
|
opname = opcode_names[op];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stderr, " at %s:%d in %s (ip=%d, %s)\n", src, line > 0 ? line : 0, fname, ip, opname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef FUN_TRACE
|
||||||
|
/**
|
||||||
|
* @brief Dump non-zero opcode execution counters to stdout.
|
||||||
|
*/
|
||||||
|
void vm_dump_opcode_counters(VM *vm) {
|
||||||
|
if (!vm) return;
|
||||||
|
long long total = 0;
|
||||||
|
for (int i = 0; i < OPCODE_COUNT; ++i) total += vm->op_counts[i];
|
||||||
|
printf("=== opcode counters (total %lld) ===\n", total);
|
||||||
|
for (int i = 0; i < OPCODE_COUNT; ++i) {
|
||||||
|
long long c = vm->op_counts[i];
|
||||||
|
if (c > 0) {
|
||||||
|
const char *name = (i >= 0 && i < (int)(sizeof(opcode_names) / sizeof(opcode_names[0]))) ? opcode_names[i] : "OP?";
|
||||||
|
printf("%3d %-16s %lld\n", i, name, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Execute a bytecode program starting from the given entry point.
|
* @brief Execute a bytecode program starting from the given entry point.
|
||||||
*
|
*
|
||||||
|
|
@ -886,11 +965,25 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
|
|
||||||
/* Validate opcode defensively */
|
/* Validate opcode defensively */
|
||||||
if (!opcode_is_valid(inst.op)) {
|
if (!opcode_is_valid(inst.op)) {
|
||||||
fprintf(stderr, "Runtime error: invalid opcode %d at ip=%d\n", inst.op, f->ip - 1);
|
char buf[256];
|
||||||
vm_raise_error(vm, "Invalid opcode");
|
Bytecode *bc = f->fn;
|
||||||
|
const char *fname = (bc && bc->name) ? bc->name : "<anon>";
|
||||||
|
const char *src = (bc && bc->source_file) ? bc->source_file : "<unknown>";
|
||||||
|
int ip = f->ip - 1;
|
||||||
|
int line = vm_ip_to_line(bc, ip);
|
||||||
|
const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names)/sizeof(opcode_names[0]))) ? opcode_names[inst.op] : "OP?";
|
||||||
|
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);
|
||||||
|
vm_raise_error(vm, buf);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef FUN_TRACE
|
||||||
|
/* Increment per-opcode counter */
|
||||||
|
if (inst.op >= 0 && inst.op < OPCODE_COUNT) {
|
||||||
|
vm->op_counts[inst.op]++;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (vm->trace_enabled) {
|
if (vm->trace_enabled) {
|
||||||
const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0])))
|
const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0])))
|
||||||
? opcode_names[inst.op]
|
? opcode_names[inst.op]
|
||||||
|
|
@ -1205,4 +1298,9 @@ void vm_run(VM *vm, Bytecode *entry) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g_active_vm = NULL;
|
g_active_vm = NULL;
|
||||||
|
#ifdef FUN_TRACE
|
||||||
|
if (vm->trace_enabled) {
|
||||||
|
vm_dump_opcode_counters(vm);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
src/vm.h
9
src/vm.h
|
|
@ -122,6 +122,11 @@ struct VM {
|
||||||
|
|
||||||
long long instr_count; // executed instructions in the last vm_run
|
long long instr_count; // executed instructions in the last vm_run
|
||||||
|
|
||||||
|
#ifdef FUN_TRACE
|
||||||
|
/* Per-opcode execution counters (enabled when FUN_TRACE=1) */
|
||||||
|
long long op_counts[OPCODE_COUNT];
|
||||||
|
#endif
|
||||||
|
|
||||||
int current_line; // last executed source line (debug)
|
int current_line; // last executed source line (debug)
|
||||||
|
|
||||||
int exit_code; // process exit code set by OP_EXIT
|
int exit_code; // process exit code set by OP_EXIT
|
||||||
|
|
@ -163,6 +168,10 @@ void vm_clear_output(VM *vm);
|
||||||
* @param vm VM instance.
|
* @param vm VM instance.
|
||||||
*/
|
*/
|
||||||
void vm_print_output(VM *vm);
|
void vm_print_output(VM *vm);
|
||||||
|
/** Dump per-opcode execution counters (when FUN_TRACE enabled). */
|
||||||
|
void vm_dump_opcode_counters(VM *vm);
|
||||||
|
/** Print a human-readable VM stack trace to stderr (top frame first). */
|
||||||
|
void vm_print_stacktrace(VM *vm);
|
||||||
/**
|
/**
|
||||||
* @brief Free all resources owned by the VM (globals, frames, output buffers).
|
* @brief Free all resources owned by the VM (globals, frames, output buffers).
|
||||||
* The VM object itself is not freed when allocated on the stack.
|
* The VM object itself is not freed when allocated on the stack.
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_ADD: {
|
case OP_ADD: {
|
||||||
|
vm_require_stack(vm, 2);
|
||||||
Value b = pop_value(vm);
|
Value b = pop_value(vm);
|
||||||
Value a = pop_value(vm);
|
Value a = pop_value(vm);
|
||||||
if ((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) {
|
if ((a.type == VAL_INT || a.type == VAL_FLOAT) && (b.type == VAL_INT || b.type == VAL_FLOAT)) {
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_DUP: {
|
case OP_DUP: {
|
||||||
if (vm->sp < 0) {
|
vm_require_stack(vm, 1);
|
||||||
fprintf(stderr, "Runtime error: stack underflow for DUP\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Value top = vm->stack[vm->sp];
|
Value top = vm->stack[vm->sp];
|
||||||
push_value(vm, copy_value(&top));
|
push_value(vm, copy_value(&top));
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_POP: {
|
case OP_POP: {
|
||||||
if (vm->sp < 0) {
|
vm_require_stack(vm, 1);
|
||||||
fprintf(stderr, "Runtime error: stack underflow for POP\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Value v = pop_value(vm);
|
Value v = pop_value(vm);
|
||||||
free_value(v);
|
free_value(v);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
case OP_SWAP: {
|
case OP_SWAP: {
|
||||||
if (vm->sp < 1) {
|
vm_require_stack(vm, 2);
|
||||||
fprintf(stderr, "Runtime error: stack underflow for SWAP\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
Value a = vm->stack[vm->sp];
|
Value a = vm->stack[vm->sp];
|
||||||
Value b = vm->stack[vm->sp - 1];
|
Value b = vm->stack[vm->sp - 1];
|
||||||
vm->stack[vm->sp] = b;
|
vm->stack[vm->sp] = b;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue