Skip to content

Fix 620 - #626

Merged
kchanqvq merged 3 commits into
jscl-project:masterfrom
iwami4438:fix-620
Aug 18, 2026
Merged

kchanqvq merged 3 commits into
jscl-project:masterfrom
iwami4438:fix-620

Conversation

@iwami4438

Copy link
Copy Markdown
Contributor
  • Ref: list-length should accept proper or circular lists #620
  • list-length, which previously counted elements without accounting for circular lists.
  • Implemented Floyd's Cycle Detection algorithm.
  • Added a regression test. Since writing the code exactly as described in the issue caused an Out-of-Memory (OOM) error, I used setf to construct the circular list.
Details
(test
 (eq
  (let ((result (list-length '#1=(1 2 . #1#))))
    result)
  nil)) 

<--- Last few GCs --->

[709969:0x821d000] 29859 ms: Scavenge (interleaved) 4039.2 (4072.6) -> 4011.3 (4074.6) MB, pooled: 0 MB, 4.43 / 0.00 ms (average mu = 0.209, current mu = 0.144) allocation failure;
[709969:0x821d000] 31441 ms: Mark-Compact (reduce) 4011.5 (4074.6) -> 4011.3 (4014.1) MB, pooled: 0 MB, 1516.22 / 0.00 ms (+ 3.1 ms in 4 steps since start of marking, biggest step 2.9 ms, walltime since start of marking 1523 ms) (average mu = 0.159, c
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

----- Native stack trace -----

1: 0x75356c node::OOMErrorHandler(char const*, v8::OOMDetails const&) [node]
2: 0xc52cd0 [node]
3: 0xc52dbf [node]
4: 0xef6695 [node]
5: 0xef66c2 [node]
6: 0xef69ba [node]
7: 0xf076ba [node]
8: 0xf0ba60 [node]
9: 0x199e0d1 [node]

- Fixed `list-length`, which previously counted elements
without accounting for circular lists.
- Implemented Floyd's Cycle Detection algorithm.
@kchanqvq

Copy link
Copy Markdown
Member

Cool and thank you for the interest for JSCL!

The functionality looks correct. But I think this could be done more efficiently if you do only one pass through the list, return nil if cycle is detected, and return the length if hit nil.

Maybe copy the SBCL algorithm:

(defun list-length (list)
  "Return the length of LIST, or NIL if LIST is circular."
  (do ((n 0 (+ n 2))
       (y list (cddr y))
       (z list (cdr z)))
      (())
    (declare (type fixnum n)
             (type list y z))
    (when (endp y) (return n))
    (when (endp (cdr y)) (return (+ n 1)))
    (when (and (eq y z) (> n 0)) (return nil))))

... and probably rewrite it with while because IIRC that is compiled more efficiently.

Since list-length is potentially used a lot I'd love to see some report of microbenchmark numbers! Like

(let ((x (make-list 1000)))
  (time (dotimes (i 10000) (list-length x))))

@iwami4438

Copy link
Copy Markdown
Contributor Author

Thank you for the review. The suggestion you made and the SBCL optimization seem promising, so I will implement them.

Also, I went ahead and generated a diff of the current state.

JSCL:

Welcome to JSCL (version dev-c0666e82-dirty built on 17 August 2026)

JSCL is a Common Lisp implementation on Javascript.
For more information, visit the project page at https://github.com/jscl-project/jscl.

CL-USER> (let ((x (make-list 1000)))
|   (time (dotimes (i 10000) (list-length x))))
Execution took 3.423 seconds.
NIL

SBCL (2.2.9 debian)

* (let ((x (make-list 1000)))
  (time (dotimes (i 10000) (list-length x))))
Evaluation took:
  0.000 seconds of real time
  0.000009 seconds of total run time (0.000008 user, 0.000001 system)
  100.00% CPU
  19,656 processor cycles
  0 bytes consed
  
NIL

- Use `while` instead of `tagbody` and `go`.
@iwami4438

Copy link
Copy Markdown
Contributor Author

I have made improvements, and it is now much faster than before.

I am sharing the report, so please take a look.

Welcome to JSCL (version dev-2c82a24c-dirty built on 18 August 2026)

JSCL is a Common Lisp implementation on Javascript.
For more information, visit the project page at https://github.com/jscl-project/jscl.

CL-USER> (let ((x (make-list 1000)))
|   (time (dotimes (i 10000) (list-length x))))
Execution took 0.155 seconds.
NIL

@kchanqvq
kchanqvq merged commit 9946a6f into jscl-project:master Aug 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants