isqrt: add integer square root builtin to all three impls

Semantics: (isqrt n) → floor(sqrt(n)). Negative argument errors.
Matches Python 3.8+ math.isqrt and R7RS exact-integer-sqrt contract.

- Python: wraps math.isqrt via lambda registration
- C: hand-rolled bit-by-bit algorithm in bi_isqrt (O(log n), no FPU)
- asm: new BI_ISQRT=109, bit-by-bit algorithm in integer registers
       (%r8/%r9/%r10). Negative input → stderr + exit(1) like other
       errors. GC builtin constants bumped to 110-114.

Tests:
- tests/functional.lsp: 7 shared tests (0, 1, perfect squares, floor
  cases, large values). Python + C now 196 each (was 189).
- asm/test.sh: 5 asm-local tests. asm suite now 142 (was 137).

MOAD: all three implementations O(log n), no O(N²) hazards.
This commit is contained in:
russell@unturf.com 2026-04-20 08:50:24 -04:00
parent f57de49f7b
commit 6e9d3ea52f
9 changed files with 98 additions and 8 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -194,15 +194,16 @@
.equ BI_HS_SIZE, 106
.equ BI_HS_LIST, 107
.equ BI_TCPSENDFILE, 108
.equ BI_ISQRT, 109
.ifdef GC_NAIVE
.equ BI_GC_COLLECT, 109
.equ BI_GC_STATS, 110
.equ BI_WITH_ARENA, 111
.equ BI_ARENA_STATS, 112
.equ BI_ARENA_SET_MODE, 113
.equ BI_COUNT, 114
.equ BI_GC_COLLECT, 110
.equ BI_GC_STATS, 111
.equ BI_WITH_ARENA, 112
.equ BI_ARENA_STATS, 113
.equ BI_ARENA_SET_MODE, 114
.equ BI_COUNT, 115
.else
.equ BI_COUNT, 109
.equ BI_COUNT, 110
.endif
# ============================================================
@ -299,6 +300,7 @@ bn_listp: .byte 5; .ascii "list?"
bn_substr: .byte 9; .ascii "substring"
bn_expt: .byte 4; .ascii "expt"
bn_gcd: .byte 3; .ascii "gcd"
bn_isqrt: .byte 5; .ascii "isqrt"
bn_integerp: .byte 8; .ascii "integer?"
bn_portalsave: .byte 11; .ascii "portal-save"
bn_portalresume:.byte 13; .ascii "portal-resume"
@ -388,6 +390,7 @@ bi_names:
.quad bn_htexists, bn_htsize, bn_htkeys, bn_htvals, bn_htalist
.quad bn_hsmake, bn_hsp, bn_hsadd, bn_hshas, bn_hssize, bn_hslist
.quad bn_tcpsendfile
.quad bn_isqrt
.ifdef GC_NAIVE
.quad bn_gccollect, bn_gcstats, bn_witharena, bn_arenastats, bn_arenamode
.endif
@ -399,6 +402,8 @@ err_notproc: .ascii "Error: not a procedure\n"
.equ err_notproc_len, . - err_notproc
err_oom: .ascii "Error: out of memory\n"
.equ err_oom_len, . - err_oom
err_isqrt_neg: .ascii "Error: isqrt: negative argument\n"
.equ err_isqrt_neg_len, . - err_isqrt_neg
# Print strings
s_true: .ascii "#t"
@ -3355,6 +3360,8 @@ eval_list:
je bi_expt
cmpq $BI_GCD, %rax
je bi_gcd
cmpq $BI_ISQRT, %rax
je bi_isqrt
cmpq $BI_INTEGERP, %rax
je bi_integerp
cmpq $BI_PORTALSAVE, %rax
@ -4663,6 +4670,58 @@ bi_gcd:
call make_int
RET_VAL
# bi_isqrt: floor of integer square root (bit-by-bit, no FPU).
# Negative input -> error exit. Registers: %r8 = x, %r9 = res, %r10 = bit.
bi_isqrt:
GETARG %rax
sarq $3, %rax # untag
testq %rax, %rax
js .bisqrt_neg
cmpq $2, %rax
jl .bisqrt_small # v < 2 -> result is v itself
movq %rax, %r8 # x
xorq %r9, %r9 # res
movq $1, %r10
shlq $62, %r10 # bit = 1<<62
.bisqrt_align:
cmpq %r8, %r10
jbe .bisqrt_loop
shrq $2, %r10
jmp .bisqrt_align
.bisqrt_loop:
testq %r10, %r10
jz .bisqrt_ret
movq %r9, %rcx
addq %r10, %rcx # rcx = res + bit
cmpq %rcx, %r8
jb .bisqrt_no
subq %rcx, %r8 # x -= res + bit
shrq $1, %r9
addq %r10, %r9 # res = (res>>1) + bit
jmp .bisqrt_next
.bisqrt_no:
shrq $1, %r9 # res >>= 1
.bisqrt_next:
shrq $2, %r10 # bit >>= 2
jmp .bisqrt_loop
.bisqrt_ret:
movq %r9, %rdi
call make_int
RET_VAL
.bisqrt_small:
movq %rax, %rdi
call make_int
RET_VAL
.bisqrt_neg:
movq $SYS_WRITE, %rax
movq $2, %rdi
leaq err_isqrt_neg(%rip), %rsi
movq $err_isqrt_neg_len, %rdx
syscall
movq $SYS_EXIT, %rax
movq $1, %rdi
syscall
bi_vector:
# (vector e1 e2 ...) build from remaining args in %r12
# Count args

View file

@ -52,6 +52,11 @@ check "max" "(max 3 1 4)" "4"
check "expt" "(* 2 2 2 2 2 2 2 2 2 2)" "1024"
check "div" "(/ 10 2)" "5"
check "expt-bi" "(expt 2 10)" "1024"
check "isqrt-0" "(isqrt 0)" "0"
check "isqrt-1" "(isqrt 1)" "1"
check "isqrt-perfect" "(isqrt 144)" "12"
check "isqrt-floor" "(isqrt 10)" "3"
check "isqrt-big" "(isqrt 1000000000000)" "1000000"
check "odd?" "(odd? 3)" "#t"
check "odd?-even" "(odd? 4)" "#f"
check "even?" "(even? 4)" "#t"