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 1packages/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
}