Commit7a9ad48cRecorded15 Jan 2026Repositorysigil-app

perf: Add type-specialized fast paths for fixnum arithmetic

Message

Add fast paths that skip double conversion when both operands are fixnums. Affects OPADD, OPSUB, and comparison operators (LT, LE, GT, GE, NUM_EQ).

Benchmark results on pure fixnum arithmetic: - Before: ~0.83s - After: ~0.73s - Improvement: ~12%

Also removes leftover debug output from call error handling.

Changed
 packages/sigil-lib/include/sigil/sigil.h |  4 ++++
 packages/sigil-lib/src/vm.c              | 58 ++++++++++++++++++++++++++++++++++++++++++++++------------
 2 files changed, 50 insertions(+), 12 deletions(-)
Diff
packages/sigil-lib/include/sigil/sigil.hmodified
@@ -61,6 +61,10 @@ typedef uint64_t Value;
61
/* Payload mask (for fixnums and other immediate values) */
62
#define SIGIL_PAYLOAD_MASK ((uint64_t)0x0000FFFFFFFFFFFF)
63
+64
/* Fixnum range (48-bit signed integer) */
+65
#define SIGIL_FIXNUM_MIN (-(1LL << 47))
+66
#define SIGIL_FIXNUM_MAX ((1LL << 47) - 1)
+67
68
/* Special constant IDs */
69
#define SIGIL_SPECIAL_NIL 0
70
#define SIGIL_SPECIAL_TRUE 1
packages/sigil-lib/src/vm.cmodified
@@ -881,6 +881,27 @@ static Value sigil__vm_run(SigilVM *vm)
881
push(vm, number_value(as_number(a) op as_number(b))); \
882
} while (0)
883
+884
/* Fast path for fixnum arithmetic (add/sub only - mul needs overflow check) */
+885
#define BINARY_OP_FAST(op, name) \
+886
do { \
+887
Value b = pop(vm); \
+888
Value a = pop(vm); \
+889
if (sigil_is_fixnum(a) && sigil_is_fixnum(b)) { \
+890
int64_t ia = sigil_as_fixnum(a); \
+891
int64_t ib = sigil_as_fixnum(b); \
+892
int64_t result = ia op ib; \
+893
if (result >= SIGIL_FIXNUM_MIN && result <= SIGIL_FIXNUM_MAX) { \
+894
push(vm, sigil_fixnum(result)); \
+895
break; \
+896
} \
+897
push(vm, sigil_flonum((double)result)); \
+898
break; \
+899
} \
+900
if (!check_number(vm, a, name) || !check_number(vm, b, name)) \
+901
goto vm_error_abort; \
+902
push(vm, number_value(as_number(a) op as_number(b))); \
+903
} while (0)
+904
905
#define COMPARE_OP(op, name) \
906
do { \
907
Value b = pop(vm); \
@@ -890,6 +911,20 @@ static Value sigil__vm_run(SigilVM *vm)
911
push(vm, sigil_bool(as_number(a) op as_number(b))); \
912
} while (0)
913
+914
/* Fast path for fixnum comparisons */
+915
#define COMPARE_OP_FAST(op, name) \
+916
do { \
+917
Value b = pop(vm); \
+918
Value a = pop(vm); \
+919
if (sigil_is_fixnum(a) && sigil_is_fixnum(b)) { \
+920
push(vm, sigil_bool(sigil_as_fixnum(a) op sigil_as_fixnum(b))); \
+921
break; \
+922
} \
+923
if (!check_number(vm, a, name) || !check_number(vm, b, name)) \
+924
goto vm_error_abort; \
+925
push(vm, sigil_bool(as_number(a) op as_number(b))); \
+926
} while (0)
+927
928
for (;;) {
929
uint8_t op = READ_U8(ip);
930
@@ -1304,9 +1339,6 @@ static Value sigil__vm_run(SigilVM *vm)
1339
}
1340
1341
if (!sigil_is_closure(callee)) {
1307
fprintf(stderr, "DEBUG: call callee not a procedure, callee=");
1308
sigil__print_value(callee, stderr);
1309
fprintf(stderr, " is_ptr=%d\n", sigil_is_ptr(callee));
1342
sigil__vm_error(vm, SIGIL_ERR_TYPE, "call: not a procedure");
1343
return SIGIL_UNDEFINED;
1344
}
@@ -1595,9 +1627,6 @@ static Value sigil__vm_run(SigilVM *vm)
1627
}
1628
1629
if (!sigil_is_closure(callee)) {
1598
fprintf(stderr, "DEBUG: call callee not a procedure, callee=");
1599
sigil__print_value(callee, stderr);
1600
fprintf(stderr, " is_ptr=%d\n", sigil_is_ptr(callee));
1630
sigil__vm_error(vm, SIGIL_ERR_TYPE, "call: not a procedure");
1631
return SIGIL_UNDEFINED;
1632
}
@@ -1881,11 +1910,11 @@ static Value sigil__vm_run(SigilVM *vm)
1910
}
1911
1912
case OP_ADD:
1884
BINARY_OP(+, "+");
+1913
BINARY_OP_FAST(+, "+");
1914
break;
1915
1916
case OP_SUB:
1888
BINARY_OP(-, "-");
+1917
BINARY_OP_FAST(-, "-");
1918
break;
1919
1920
case OP_MUL:
@@ -1941,24 +1970,29 @@ static Value sigil__vm_run(SigilVM *vm)
1970
}
1971
1972
case OP_LT:
1944
COMPARE_OP(<, "<");
+1973
COMPARE_OP_FAST(<, "<");
1974
break;
1975
1976
case OP_GT:
1948
COMPARE_OP(>, ">");
+1977
COMPARE_OP_FAST(>, ">");
1978
break;
1979
1980
case OP_LE:
1952
COMPARE_OP(<=, "<=");
+1981
COMPARE_OP_FAST(<=, "<=");
1982
break;
1983
1984
case OP_GE:
1956
COMPARE_OP(>=, ">=");
+1985
COMPARE_OP_FAST(>=, ">=");
1986
break;
1987
1988
case OP_NUM_EQ: {
1989
Value b = pop(vm);
1990
Value a = pop(vm);
+1991
/* Fast path: both fixnums */
+1992
if (sigil_is_fixnum(a) && sigil_is_fixnum(b)) {
+1993
push(vm, sigil_bool(sigil_as_fixnum(a) == sigil_as_fixnum(b)));
+1994
break;
+1995
}
1996
if (!check_number(vm, a, "=") || !check_number(vm, b, "=")) {
1997
return SIGIL_UNDEFINED;
1998
}