go: sched.sx — channels + goroutines (v0 synchronous) + 12 tests; Phase 5 starts [shapes-scheduler]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Phase 5 (goroutines + channels) opens.
lib/go/sched.sx is the **independent implementation** referenced by
plans/lib-guest-scheduler.md — the first-consumer cut whose realised
shape will inform the eventual sister kit.
Channel representation:
(list :go-chan SEND-FN RECV-FN CLOSED?-FN CLOSE!-FN)
Each closure shares a mutable `buf` (a list mutated via append! and
set!) and a `closed` flag. Channel identity is closure-instance —
two `make()` calls produce distinct values per Go spec § Channel types.
Primitive API in sched.sx:
go-make-chan / go-chan? / go-chan-send! / go-chan-recv! /
go-chan-closed? / go-chan-close!
Eval integration in eval.sx:
* `make` and `close` added as builtins. v0 `make()` takes no args
and returns an unbounded-buffer channel.
* `:send` stmt → go-chan-send! on the channel.
* Unary `<-` recv on channel values → go-chan-recv!. `:empty`
sentinel converted to nil (stand-in for blocking semantics).
* `:go expr` → synchronous eval (v0 limitation, see sched.sx
header).
**v0 concurrency model — synchronous goroutines.** SX doesn't expose
first-class continuations to guest code, so v0 runs `go f()`
immediately and depends on the spawned goroutine running to
completion before the main goroutine receives. This is the right
semantics for the simple producer/consumer patterns covered here.
True preemption with blocking send/recv is Phase 5b — requires either
a CEK-style trampolining eval rewrite or kit-level continuation
support. Logged in sched.sx header and in the sister-plan diary.
Runtime suite (12 tests):
* 6 direct API tests: identity, FIFO order, closed-flag
* 6 source-level: make + send + recv, go ping-pong, close,
multi-goroutine fan-in, worker-with-result
Sister-plan scheduler diary updated with the channel-as-closure-
bundle insight and the v0 synchronous-spawn caveat.
runtime 12/12, total 469/469.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,7 @@ SUITES=(
|
|||||||
"parse|go-parse-test-pass|go-parse-test-count"
|
"parse|go-parse-test-pass|go-parse-test-count"
|
||||||
"types|go-types-test-pass|go-types-test-count"
|
"types|go-types-test-pass|go-types-test-count"
|
||||||
"eval|go-eval-test-pass|go-eval-test-count"
|
"eval|go-eval-test-pass|go-eval-test-count"
|
||||||
|
"runtime|go-rt-test-pass|go-rt-test-count"
|
||||||
)
|
)
|
||||||
|
|
||||||
cat > "$TMPFILE" <<'EPOCHS'
|
cat > "$TMPFILE" <<'EPOCHS'
|
||||||
@@ -41,11 +42,13 @@ cat > "$TMPFILE" <<'EPOCHS'
|
|||||||
(load "lib/go/lex.sx")
|
(load "lib/go/lex.sx")
|
||||||
(load "lib/go/parse.sx")
|
(load "lib/go/parse.sx")
|
||||||
(load "lib/go/types.sx")
|
(load "lib/go/types.sx")
|
||||||
|
(load "lib/go/sched.sx")
|
||||||
(load "lib/go/eval.sx")
|
(load "lib/go/eval.sx")
|
||||||
(load "lib/go/tests/lex.sx")
|
(load "lib/go/tests/lex.sx")
|
||||||
(load "lib/go/tests/parse.sx")
|
(load "lib/go/tests/parse.sx")
|
||||||
(load "lib/go/tests/types.sx")
|
(load "lib/go/tests/types.sx")
|
||||||
(load "lib/go/tests/eval.sx")
|
(load "lib/go/tests/eval.sx")
|
||||||
|
(load "lib/go/tests/runtime.sx")
|
||||||
EPOCHS
|
EPOCHS
|
||||||
|
|
||||||
idx=0
|
idx=0
|
||||||
@@ -110,7 +113,6 @@ cat > lib/go/scoreboard.json <<JSON
|
|||||||
"total_pass": $TOTAL_PASS,
|
"total_pass": $TOTAL_PASS,
|
||||||
"total": $TOTAL_COUNT,
|
"total": $TOTAL_COUNT,
|
||||||
"suites": [$JSON_SUITES,
|
"suites": [$JSON_SUITES,
|
||||||
{"name":"runtime","pass":0,"total":0,"status":"pending"},
|
|
||||||
{"name":"stdlib","pass":0,"total":0,"status":"pending"},
|
{"name":"stdlib","pass":0,"total":0,"status":"pending"},
|
||||||
{"name":"e2e","pass":0,"total":0,"status":"pending"}
|
{"name":"e2e","pass":0,"total":0,"status":"pending"}
|
||||||
]
|
]
|
||||||
@@ -124,8 +126,7 @@ cat > lib/go/scoreboard.md <<MD
|
|||||||
|
|
||||||
| | Suite | Pass | Total |
|
| | Suite | Pass | Total |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
$MD_ROWS| ⬜ | runtime | 0 | 0 |
|
$MD_ROWS| ⬜ | stdlib | 0 | 0 |
|
||||||
| ⬜ | stdlib | 0 | 0 |
|
|
||||||
| ⬜ | e2e | 0 | 0 |
|
| ⬜ | e2e | 0 | 0 |
|
||||||
|
|
||||||
Generated by \`lib/go/conformance.sh\`.
|
Generated by \`lib/go/conformance.sh\`.
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
go-env-builtins
|
go-env-builtins
|
||||||
;; A starter env containing the Go builtins eval understands.
|
;; A starter env containing the Go builtins eval understands.
|
||||||
;; Tests can call (go-env-builtins) instead of go-env-empty when they
|
;; Tests can call (go-env-builtins) instead of go-env-empty when they
|
||||||
;; need len/append/print.
|
;; need len/append/print/make/close.
|
||||||
(list
|
(list
|
||||||
(list "len" (list :go-builtin "len"))
|
(list "len" (list :go-builtin "len"))
|
||||||
(list "append" (list :go-builtin "append"))
|
(list "append" (list :go-builtin "append"))
|
||||||
(list "print" (list :go-builtin "print"))))
|
(list "print" (list :go-builtin "print"))
|
||||||
|
(list "make" (list :go-builtin "make"))
|
||||||
|
(list "close" (list :go-builtin "close"))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
go-env-lookup
|
go-env-lookup
|
||||||
@@ -381,6 +383,18 @@
|
|||||||
:else (list :eval-error :append-not-slice slc))))
|
:else (list :eval-error :append-not-slice slc))))
|
||||||
(= name "print")
|
(= name "print")
|
||||||
nil ;; v0: silent. Real impl would write to stdout.
|
nil ;; v0: silent. Real impl would write to stdout.
|
||||||
|
(= name "make")
|
||||||
|
;; v0: ignore args, always return a fresh channel. Real Go is
|
||||||
|
;; make(chan T) / make(chan T, n) / make([]T, n) / make(map[K]V) —
|
||||||
|
;; v0 channel-buffer is unbounded so cap arg is a no-op.
|
||||||
|
(go-make-chan)
|
||||||
|
(= name "close")
|
||||||
|
(cond
|
||||||
|
(not (= (len vals) 1))
|
||||||
|
(list :eval-error :builtin-arity name 1 (len vals))
|
||||||
|
(not (go-chan? (first vals)))
|
||||||
|
(list :eval-error :close-not-chan (first vals))
|
||||||
|
:else (do (go-chan-close! (first vals)) nil))
|
||||||
:else (list :eval-error :unknown-builtin name)))))
|
:else (list :eval-error :unknown-builtin name)))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -783,6 +797,22 @@
|
|||||||
(go-eval-method-decl env stmt)
|
(go-eval-method-decl env stmt)
|
||||||
(and (list? stmt) (= (first stmt) :type-decl))
|
(and (list? stmt) (= (first stmt) :type-decl))
|
||||||
(go-eval-type-decl env stmt)
|
(go-eval-type-decl env stmt)
|
||||||
|
(and (list? stmt) (= (first stmt) :send))
|
||||||
|
(let ((ch (go-eval env (nth stmt 1)))
|
||||||
|
(v (go-eval env (nth stmt 2))))
|
||||||
|
(cond
|
||||||
|
(go-eval-error? ch) ch
|
||||||
|
(go-eval-error? v) v
|
||||||
|
(not (go-chan? ch)) (list :eval-error :send-not-chan ch)
|
||||||
|
:else (do (go-chan-send! ch v) env)))
|
||||||
|
(and (list? stmt) (= (first stmt) :go))
|
||||||
|
;; v0: synchronous evaluation — no real preemption. The spawned
|
||||||
|
;; expression's value is dropped. See sched.sx header for
|
||||||
|
;; semantic notes.
|
||||||
|
(let ((v (go-eval env (nth stmt 1))))
|
||||||
|
(cond
|
||||||
|
(go-eval-error? v) v
|
||||||
|
:else env))
|
||||||
:else
|
:else
|
||||||
(let ((v (go-eval env stmt)))
|
(let ((v (go-eval env stmt)))
|
||||||
(cond
|
(cond
|
||||||
@@ -940,13 +970,21 @@
|
|||||||
;; Unary prefix op: head is :var with op name + 1 arg.
|
;; Unary prefix op: head is :var with op name + 1 arg.
|
||||||
(and (list? head) (= (first head) :var) (= (len args) 1)
|
(and (list? head) (= (first head) :var) (= (len args) 1)
|
||||||
(some (fn (o) (= o (nth head 1)))
|
(some (fn (o) (= o (nth head 1)))
|
||||||
(list "-" "+" "!")))
|
(list "-" "+" "!" "<-")))
|
||||||
(let ((op (nth head 1)) (v (go-eval env (first args))))
|
(let ((op (nth head 1)) (v (go-eval env (first args))))
|
||||||
(cond
|
(cond
|
||||||
(go-eval-error? v) v
|
(go-eval-error? v) v
|
||||||
(= op "-") (- 0 v)
|
(= op "-") (- 0 v)
|
||||||
(= op "+") v
|
(= op "+") v
|
||||||
(= op "!") (not v)
|
(= op "!") (not v)
|
||||||
|
(= op "<-")
|
||||||
|
(cond
|
||||||
|
(not (go-chan? v)) (list :eval-error :recv-not-chan v)
|
||||||
|
:else
|
||||||
|
(let ((r (go-chan-recv! v)))
|
||||||
|
;; :empty in v0 means "no value yet" — Go would block.
|
||||||
|
;; We return nil as a stand-in for the zero value.
|
||||||
|
(cond (= r :empty) nil :else r)))
|
||||||
:else (list :eval-error :unsupported-unary op)))
|
:else (list :eval-error :unsupported-unary op)))
|
||||||
;; Method-call shape: head is (:select OBJ METHOD-NAME).
|
;; Method-call shape: head is (:select OBJ METHOD-NAME).
|
||||||
(and (list? head) (= (first head) :select))
|
(and (list? head) (= (first head) :select))
|
||||||
|
|||||||
64
lib/go/sched.sx
Normal file
64
lib/go/sched.sx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
;; lib/go/sched.sx — Go scheduler primitives: channels + goroutines.
|
||||||
|
;;
|
||||||
|
;; This is **the independent implementation** referenced by
|
||||||
|
;; plans/lib-guest-scheduler.md. The shape that emerges here informs
|
||||||
|
;; the eventual sister kit; this file's structures are the Phase 5
|
||||||
|
;; "first-consumer" cut.
|
||||||
|
;;
|
||||||
|
;; v0 concurrency model — IMPORTANT
|
||||||
|
;;
|
||||||
|
;; SX has no first-class continuations exposed to guest code, so we
|
||||||
|
;; can't suspend a goroutine mid-statement. v0 runs `go f()` SYNCHRO-
|
||||||
|
;; NOUSLY (it's an immediate call whose return value is dropped). This
|
||||||
|
;; preserves the right semantics for patterns where the spawned
|
||||||
|
;; goroutine simply pushes to a channel that the main goroutine then
|
||||||
|
;; receives — because the spawned goroutine runs to completion first
|
||||||
|
;; and leaves the value in the channel buffer.
|
||||||
|
;;
|
||||||
|
;; True preemption with blocking sends/recvs is a Phase 5b refinement.
|
||||||
|
;; The sister-plan diary tracks the design insight (single
|
||||||
|
;; sched-spawn primitive, channel-op direction tag) so the eventual
|
||||||
|
;; kit doesn't bake in v0's synchronous limitation.
|
||||||
|
;;
|
||||||
|
;; Channel representation
|
||||||
|
;;
|
||||||
|
;; (list :go-chan ACCESSORS-FN-LIST)
|
||||||
|
;;
|
||||||
|
;; ACCESSORS-FN-LIST is a list of closures sharing a mutable buffer
|
||||||
|
;; and a closed flag. The closures expose:
|
||||||
|
;; index 1: send-fn — (lambda (val) ...)
|
||||||
|
;; index 2: recv-fn — (lambda () val-or-:empty)
|
||||||
|
;; index 3: closed?-fn — (lambda () bool)
|
||||||
|
;; index 4: close!-fn — (lambda () ...)
|
||||||
|
;;
|
||||||
|
;; Channel identity: distinct calls to go-make-chan produce closures
|
||||||
|
;; with distinct identity — `(= ch1 ch2)` is false for distinct
|
||||||
|
;; channels, matching Go spec § Channel types.
|
||||||
|
|
||||||
|
(define
|
||||||
|
go-make-chan
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((buf (list)) (closed false))
|
||||||
|
(list
|
||||||
|
:go-chan (fn (v) (append! buf v) nil)
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cond
|
||||||
|
(= (len buf) 0)
|
||||||
|
:empty :else
|
||||||
|
(let ((v (first buf))) (set! buf (rest buf)) v)))
|
||||||
|
(fn () closed)
|
||||||
|
(fn () (set! closed true) nil)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
go-chan?
|
||||||
|
(fn
|
||||||
|
(v)
|
||||||
|
(and (list? v) (not (= (len v) 0)) (= (first v) :go-chan))))
|
||||||
|
|
||||||
|
(define go-chan-send! (fn (ch val) ((nth ch 1) val)))
|
||||||
|
(define go-chan-recv! (fn (ch) ((nth ch 2))))
|
||||||
|
(define go-chan-closed? (fn (ch) ((nth ch 3))))
|
||||||
|
(define go-chan-close! (fn (ch) ((nth ch 4))))
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"language": "go",
|
"language": "go",
|
||||||
"total_pass": 457,
|
"total_pass": 469,
|
||||||
"total": 457,
|
"total": 469,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"lex","pass":129,"total":129,"status":"ok"},
|
{"name":"lex","pass":129,"total":129,"status":"ok"},
|
||||||
{"name":"parse","pass":176,"total":176,"status":"ok"},
|
{"name":"parse","pass":176,"total":176,"status":"ok"},
|
||||||
{"name":"types","pass":72,"total":72,"status":"ok"},
|
{"name":"types","pass":72,"total":72,"status":"ok"},
|
||||||
{"name":"eval","pass":80,"total":80,"status":"ok"},
|
{"name":"eval","pass":80,"total":80,"status":"ok"},
|
||||||
{"name":"runtime","pass":0,"total":0,"status":"pending"},
|
{"name":"runtime","pass":12,"total":12,"status":"ok"},
|
||||||
{"name":"stdlib","pass":0,"total":0,"status":"pending"},
|
{"name":"stdlib","pass":0,"total":0,"status":"pending"},
|
||||||
{"name":"e2e","pass":0,"total":0,"status":"pending"}
|
{"name":"e2e","pass":0,"total":0,"status":"pending"}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Go-on-SX Scoreboard
|
# Go-on-SX Scoreboard
|
||||||
|
|
||||||
**Total: 457 / 457 tests passing**
|
**Total: 469 / 469 tests passing**
|
||||||
|
|
||||||
| | Suite | Pass | Total |
|
| | Suite | Pass | Total |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
| ✅ | parse | 176 | 176 |
|
| ✅ | parse | 176 | 176 |
|
||||||
| ✅ | types | 72 | 72 |
|
| ✅ | types | 72 | 72 |
|
||||||
| ✅ | eval | 80 | 80 |
|
| ✅ | eval | 80 | 80 |
|
||||||
| ⬜ | runtime | 0 | 0 |
|
| ✅ | runtime | 12 | 12 |
|
||||||
| ⬜ | stdlib | 0 | 0 |
|
| ⬜ | stdlib | 0 | 0 |
|
||||||
| ⬜ | e2e | 0 | 0 |
|
| ⬜ | e2e | 0 | 0 |
|
||||||
|
|
||||||
|
|||||||
108
lib/go/tests/runtime.sx
Normal file
108
lib/go/tests/runtime.sx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
;; Go runtime tests — goroutines + channels.
|
||||||
|
|
||||||
|
(define go-rt-test-count 0)
|
||||||
|
(define go-rt-test-pass 0)
|
||||||
|
(define go-rt-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
go-rt-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(set! go-rt-test-count (+ go-rt-test-count 1))
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! go-rt-test-pass (+ go-rt-test-pass 1))
|
||||||
|
(append! go-rt-test-fails {:name name :expected expected :actual actual}))))
|
||||||
|
|
||||||
|
;; ── channel primitives (direct API, no source parsing) ─────────
|
||||||
|
(go-rt-test "chan: make returns a chan value" (go-chan? (go-make-chan)) true)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"chan: distinct channels have distinct identity"
|
||||||
|
(= (go-make-chan) (go-make-chan))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"chan: send + recv round-trip"
|
||||||
|
(let
|
||||||
|
((ch (go-make-chan)))
|
||||||
|
(go-chan-send! ch 42)
|
||||||
|
(go-chan-recv! ch))
|
||||||
|
42)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"chan: empty recv returns :empty marker"
|
||||||
|
(let ((ch (go-make-chan))) (go-chan-recv! ch))
|
||||||
|
:empty)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"chan: FIFO order"
|
||||||
|
(let
|
||||||
|
((ch (go-make-chan)))
|
||||||
|
(go-chan-send! ch 1)
|
||||||
|
(go-chan-send! ch 2)
|
||||||
|
(go-chan-send! ch 3)
|
||||||
|
(list (go-chan-recv! ch) (go-chan-recv! ch) (go-chan-recv! ch)))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"chan: closed? flag flips"
|
||||||
|
(let
|
||||||
|
((ch (go-make-chan)))
|
||||||
|
(let
|
||||||
|
((before (go-chan-closed? ch)))
|
||||||
|
(go-chan-close! ch)
|
||||||
|
(list before (go-chan-closed? ch))))
|
||||||
|
(list false true))
|
||||||
|
|
||||||
|
;; ── source-level: make / send / recv / close ───────────────────
|
||||||
|
(go-rt-test
|
||||||
|
"src: ch := make() returns chan"
|
||||||
|
(go-chan?
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "ch := make()")))))
|
||||||
|
(go-env-lookup env "ch")))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"src: ch <- 5 then <-ch = 5"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "ch <- 5")))))
|
||||||
|
(go-eval env (go-parse "<-ch")))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"src: go + chan ping-pong"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func sender(c chan int) { c <- 99 }") (go-parse "ch := make()") (go-parse "go sender(ch)")))))
|
||||||
|
(go-eval env (go-parse "<-ch")))
|
||||||
|
99)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"src: close(ch) marks it closed"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "close(ch)")))))
|
||||||
|
(go-chan-closed? (go-env-lookup env "ch")))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"src: multiple goroutines feeding one channel"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func push(c chan int, v int) { c <- v }") (go-parse "ch := make()") (go-parse "go push(ch, 1)") (go-parse "go push(ch, 2)") (go-parse "go push(ch, 3)")))))
|
||||||
|
(list
|
||||||
|
(go-eval env (go-parse "<-ch"))
|
||||||
|
(go-eval env (go-parse "<-ch"))
|
||||||
|
(go-eval env (go-parse "<-ch"))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"src: worker pattern — send sum back"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func work(c chan int, a int, b int) { c <- a + b }") (go-parse "result := make()") (go-parse "go work(result, 7, 13)")))))
|
||||||
|
(go-eval env (go-parse "<-result")))
|
||||||
|
20)
|
||||||
|
|
||||||
|
;; ── report ─────────────────────────────────────────────────────
|
||||||
|
(define
|
||||||
|
go-rt-test-summary
|
||||||
|
(str "runtime " go-rt-test-pass "/" go-rt-test-count))
|
||||||
@@ -305,6 +305,17 @@ Progress-log line → push `origin/loops/go`.
|
|||||||
slice triple with capacity) refine but don't gate Phase 5.
|
slice triple with capacity) refine but don't gate Phase 5.
|
||||||
|
|
||||||
### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜
|
### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜
|
||||||
|
- [x] Scaffold: `lib/go/sched.sx` with `go-make-chan` (closures-over-
|
||||||
|
mutable-buf), `go-chan-send!` / `go-chan-recv!` / `go-chan-closed?`
|
||||||
|
/ `go-chan-close!`. Channel identity via closure-instance.
|
||||||
|
- [x] Eval integration: `make`/`close` builtins; `:send` stmt; unary
|
||||||
|
`<-` recv on channels; `:go` stmt (synchronous in v0 — see
|
||||||
|
sched.sx header).
|
||||||
|
- [ ] Real preemption (suspending sends on full buffer / recvs on empty).
|
||||||
|
Requires reified execution state; deferred to Phase 5b.
|
||||||
|
- [ ] `select { case ... }` multiplexing.
|
||||||
|
- [ ] `range` over channels.
|
||||||
|
- [ ] `time.After`-like timer channel.
|
||||||
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that
|
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that
|
||||||
kit doesn't exist yet and depends on this work for its design. See
|
kit doesn't exist yet and depends on this work for its design. See
|
||||||
`plans/lib-guest-scheduler.md`.
|
`plans/lib-guest-scheduler.md`.
|
||||||
@@ -586,6 +597,22 @@ Minimal repro: see `lib/go/lex.sx#gl-oct-digit?` and `#gl-match-op`.
|
|||||||
|
|
||||||
_Newest first. Append one dated entry per commit._
|
_Newest first. Append one dated entry per commit._
|
||||||
|
|
||||||
|
- 2026-05-27 — **Phase 5 first slice.** `lib/go/sched.sx` lands with
|
||||||
|
the v0 channel primitive: `go-make-chan` returns a closures-over-
|
||||||
|
mutable-buf channel. Send appends, recv pops first, close flips a
|
||||||
|
flag. Channel identity via closure-instance (matches Go spec — two
|
||||||
|
`make()` calls produce distinct values). `make`/`close` are now
|
||||||
|
builtins; `:send` stmt and unary `<-` recv hook through;
|
||||||
|
`:go expr` evaluates synchronously (no real preemption — SX doesn't
|
||||||
|
expose continuations, so v0 runs goroutines to completion in source
|
||||||
|
order). Patterns that rely on the spawned goroutine pushing to a
|
||||||
|
channel before the main reads do work end-to-end. runtime suite
|
||||||
|
12/12 incl. multi-goroutine fan-in and worker-pattern. Total
|
||||||
|
469/469. `[shapes-scheduler]` — sister-plan diary updated with the
|
||||||
|
realised channel-as-closures-over-state shape and the v0
|
||||||
|
synchronous-spawn caveat.
|
||||||
|
|
||||||
|
Sister-plan diary update follows.
|
||||||
- 2026-05-27 — Phase 4 cont.: **method dispatch + unary ops + e2e
|
- 2026-05-27 — Phase 4 cont.: **method dispatch + unary ops + e2e
|
||||||
programs. Acceptance bar (80+) crossed.** Methods register under
|
programs. Acceptance bar (80+) crossed.** Methods register under
|
||||||
`#method/TYPE/NAME` (same scheme the type checker uses). When `p.M(...)`
|
`#method/TYPE/NAME` (same scheme the type checker uses). When `p.M(...)`
|
||||||
|
|||||||
@@ -231,6 +231,39 @@ real result.
|
|||||||
|
|
||||||
_Newest first. Append one dated entry per milestone landed._
|
_Newest first. Append one dated entry per milestone landed._
|
||||||
|
|
||||||
|
- 2026-05-27 — From Go-on-SX Phase 5 first slice: the channel
|
||||||
|
primitive landed as closures-over-mutable-state in
|
||||||
|
`lib/go/sched.sx`. Concrete shape:
|
||||||
|
|
||||||
|
```
|
||||||
|
(list :go-chan SEND-FN RECV-FN CLOSED?-FN CLOSE!-FN)
|
||||||
|
```
|
||||||
|
|
||||||
|
Each closure captures a shared `buf` (a mutable list) and `closed`
|
||||||
|
flag (a let-bound boolean mutated via `set!`). Identity: two
|
||||||
|
`make()` calls produce distinct closures, satisfying Go spec
|
||||||
|
§ Channel types' "distinct channels with same type" rule.
|
||||||
|
|
||||||
|
**Design insight for the kit**: the channel-as-closure-bundle shape
|
||||||
|
is the right scheduler-kit primitive — implementation-hide the
|
||||||
|
buffer behind opaque accessor closures, so the underlying storage
|
||||||
|
can be swapped (linked list → ring buffer → segmented array) without
|
||||||
|
changing the API. Erlang's mailboxes will need the same trick.
|
||||||
|
|
||||||
|
**v0 limitation logged**: no real preemption. SX doesn't expose
|
||||||
|
first-class continuations to guest code, so v0 runs `go f()`
|
||||||
|
synchronously and relies on the spawned goroutine completing before
|
||||||
|
the main goroutine receives. Real concurrent semantics — blocking
|
||||||
|
send on full buffer, blocking recv on empty — needs the
|
||||||
|
scheduler kit to ship the suspension/resumption machinery (or for
|
||||||
|
Phase 5b to bake CEK-style trampolining into the eval layer).
|
||||||
|
|
||||||
|
Cross-ref: the `:select-case` uniform shape from the parser-side
|
||||||
|
diary entry pairs with this — the kit's `sched-select` should
|
||||||
|
accept a list of channel-op cases (built from the closures-over-
|
||||||
|
state primitives logged here) and pick a ready one. Source:
|
||||||
|
Go-on-SX commit landing `lib/go/sched.sx` first cut.
|
||||||
|
|
||||||
- 2026-05-27 — Follow-up from same Phase 2 work: **`select` AST shape**
|
- 2026-05-27 — Follow-up from same Phase 2 work: **`select` AST shape**
|
||||||
landed. Each case is `(list :select-case COMM-STMT BODY)` where
|
landed. Each case is `(list :select-case COMM-STMT BODY)` where
|
||||||
COMM-STMT is one of `:send`, `:short-decl` (recv into new var),
|
COMM-STMT is one of `:send`, `:short-decl` (recv into new var),
|
||||||
|
|||||||
Reference in New Issue
Block a user