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)
|
||||
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_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
|
||||
# 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
|
||||
# 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
|
||||
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...)
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ if(FUN_DEBUG)
|
|||
target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1)
|
||||
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
|
||||
target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}")
|
||||
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
|
||||
* and may free it independently.
|
||||
* The value is compared against existing constants and if an equal constant is
|
||||
* 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 v Value to store (copied).
|
||||
* @return The index of the stored constant (zero-based).
|
||||
* @param v Value to store (copied on insert).
|
||||
* @return The index of the stored or matched existing constant (zero-based).
|
||||
*/
|
||||
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[bc->const_count] = copy_value(&v);
|
||||
return bc->const_count++;
|
||||
|
|
|
|||
|
|
@ -291,6 +291,9 @@ typedef enum {
|
|||
OP_CPP_ADD, // pops b, a; pushes (a + b)
|
||||
} OpCode;
|
||||
|
||||
/* Total number of opcodes (keep in sync with OpCode enum). */
|
||||
#define OPCODE_COUNT (OP_CPP_ADD + 1)
|
||||
|
||||
typedef struct {
|
||||
OpCode op;
|
||||
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 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 calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col);
|
||||
|
||||
/* ---- parser error state ---- */
|
||||
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 size_t g_err_pos = 0;
|
||||
static char g_err_msg[256];
|
||||
|
|
@ -137,6 +144,22 @@ static int fun_debug_enabled(void) {
|
|||
* @param ... Arguments matching 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_err_pos = pos;
|
||||
va_list ap;
|
||||
|
|
@ -7608,8 +7631,16 @@ Bytecode *parse_file_to_bytecode(const char *path) {
|
|||
g_err_line = 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_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;
|
||||
Bytecode *bc = compile_minimal(compile_src, compile_len);
|
||||
/* assign debug metadata to module bytecode */
|
||||
|
|
@ -7624,6 +7655,10 @@ Bytecode *parse_file_to_bytecode(const char *path) {
|
|||
}
|
||||
/* restore previous */
|
||||
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) {
|
||||
int line = 1, col = 1;
|
||||
|
|
@ -7791,8 +7826,16 @@ Bytecode *parse_string_to_bytecode(const char *source) {
|
|||
g_err_line = 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_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;
|
||||
Bytecode *bc = compile_minimal(compile_src, len);
|
||||
if (bc) {
|
||||
|
|
@ -7802,6 +7845,10 @@ Bytecode *parse_string_to_bytecode(const char *source) {
|
|||
bc->name = strdup("<input>");
|
||||
}
|
||||
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) {
|
||||
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 */
|
||||
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.
|
||||
* 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;
|
||||
return;
|
||||
}
|
||||
/* No handler: print annotated message and terminate VM */
|
||||
fprintf(stderr, "Runtime error: %s\n", msg ? msg : "<error>");
|
||||
/* No handler: print annotated message, stack trace, and terminate VM */
|
||||
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 */
|
||||
}
|
||||
|
||||
|
|
@ -389,6 +405,11 @@ void vm_reset(VM *vm) {
|
|||
// Reset exit code
|
||||
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)
|
||||
vm_debug_reset(vm);
|
||||
}
|
||||
|
|
@ -732,6 +753,10 @@ void vm_init(VM *vm) {
|
|||
vm->repl_on_error = 0;
|
||||
vm->on_error_repl = NULL;
|
||||
|
||||
#ifdef FUN_TRACE
|
||||
for (int i = 0; i < OPCODE_COUNT; ++i) vm->op_counts[i] = 0;
|
||||
#endif
|
||||
|
||||
/* Debugger state */
|
||||
vm->debug_step_mode = 0;
|
||||
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.
|
||||
*
|
||||
|
|
@ -886,11 +965,25 @@ void vm_run(VM *vm, Bytecode *entry) {
|
|||
|
||||
/* Validate opcode defensively */
|
||||
if (!opcode_is_valid(inst.op)) {
|
||||
fprintf(stderr, "Runtime error: invalid opcode %d at ip=%d\n", inst.op, f->ip - 1);
|
||||
vm_raise_error(vm, "Invalid opcode");
|
||||
char buf[256];
|
||||
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;
|
||||
}
|
||||
|
||||
#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) {
|
||||
const char *opname = (inst.op >= 0 && inst.op < (int)(sizeof(opcode_names) / sizeof(opcode_names[0])))
|
||||
? opcode_names[inst.op]
|
||||
|
|
@ -1205,4 +1298,9 @@ void vm_run(VM *vm, Bytecode *entry) {
|
|||
}
|
||||
}
|
||||
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
|
|
@ -121,6 +121,11 @@ struct VM {
|
|||
int output_is_partial[OUTPUT_SIZE]; // 1 when the corresponding output entry should not end with newline (echo)
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -163,6 +168,10 @@ void vm_clear_output(VM *vm);
|
|||
* @param vm VM instance.
|
||||
*/
|
||||
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).
|
||||
* The VM object itself is not freed when allocated on the stack.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
*/
|
||||
|
||||
case OP_ADD: {
|
||||
vm_require_stack(vm, 2);
|
||||
Value b = 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)) {
|
||||
|
|
|
|||
|
|
@ -28,10 +28,7 @@
|
|||
*/
|
||||
|
||||
case OP_DUP: {
|
||||
if (vm->sp < 0) {
|
||||
fprintf(stderr, "Runtime error: stack underflow for DUP\n");
|
||||
exit(1);
|
||||
}
|
||||
vm_require_stack(vm, 1);
|
||||
Value top = vm->stack[vm->sp];
|
||||
push_value(vm, copy_value(&top));
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -26,10 +26,7 @@
|
|||
*/
|
||||
|
||||
case OP_POP: {
|
||||
if (vm->sp < 0) {
|
||||
fprintf(stderr, "Runtime error: stack underflow for POP\n");
|
||||
exit(1);
|
||||
}
|
||||
vm_require_stack(vm, 1);
|
||||
Value v = pop_value(vm);
|
||||
free_value(v);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@
|
|||
*/
|
||||
|
||||
case OP_SWAP: {
|
||||
if (vm->sp < 1) {
|
||||
fprintf(stderr, "Runtime error: stack underflow for SWAP\n");
|
||||
exit(1);
|
||||
}
|
||||
vm_require_stack(vm, 2);
|
||||
Value a = vm->stack[vm->sp];
|
||||
Value b = vm->stack[vm->sp - 1];
|
||||
vm->stack[vm->sp] = b;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue