#lang racket (provide generate-runtime) (require "wasm-data.rkt" "wasm-utils.rkt" "immediates.rkt" "priminfo.rkt" "parameters.rkt" "timings.rkt" "structs.rkt") ; See the end of `generate-code` in `compiler.rkt` for an ; explanation of the arguments. (define (generate-runtime dls tms entry-body result ; program specific top-vars console-bridge-bindings top-level-variable-declarations entry-locals ; general information primitives ; list of symbols used-primitives ; list of symbols referenced by generated program code string-constants ; (list (list name string) ...) bytes-constants ; (list (list name bytes) ...) symbol-constants ; (list (list name symbol) ....) ) #;(pretty-print entry-body (current-error-port)) (let* () (let* () ;; Variable definitions on the top-level ought to go in a namespace, ;; but for now we use a global WebAssembly variable. ; R = Make reference (define (R x [type #f]) (case type [(#f) (match x [(? integer?) `(ref.i31 (i32.const ,x))] [_ `(ref.cast (ref eq) ,x)])] [else `(ref.cast ,type ,x)])) ; D = Dereference (define (D x type) ; type is one of: ; 'i32 cast to signed i32 ; '(ref ) cast to (ref ) ; x evaluates to an (ref eq), cast x to type (match type ['i32 `(i31.get_s (ref.cast i31ref ,x))] ['u32 `(i31.get_u (ref.cast i31ref ,x))] [type `(ref.cast (ref ,type) ,x)])) (define (I32 x) (D x 'i32)) (define (U32 x) (D x 'u32)) (define (Half x) `(i32.shr_s ,x (i32.const 1))) (define (Double x) `(i32.shl ,x (i32.const 1))) ;;; ;;; Exception structures ;;; (define exception-struct-type-bindings '((struct:exn ensure-exn-type) (struct:exn:fail ensure-exn:fail-type) (struct:exn:fail:contract ensure-exn:fail:contract-type) (struct:exn:fail:contract:arity ensure-exn:fail:contract:arity-type) (struct:exn:fail:contract:divide-by-zero ensure-exn:fail:contract:divide-by-zero-type) (struct:exn:fail:contract:non-fixnum-result ensure-exn:fail:contract:non-fixnum-result-type) (struct:exn:fail:contract:variable ensure-exn:fail:contract:variable-type) (struct:exn:fail:read ensure-exn:fail:read-type) (struct:exn:fail:read:eof ensure-exn:fail:read:eof-type) (struct:exn:fail:read:non-char ensure-exn:fail:read:non-char-type) (struct:exn:fail:filesystem ensure-exn:fail:filesystem-type) (struct:exn:fail:syntax ensure-exn:fail:syntax-type) (struct:exn:fail:syntax:missing-module ensure-exn:fail:syntax:missing-module-type) (struct:exn:fail:syntax:unbound ensure-exn:fail:syntax:unbound-type))) ;;; ;;; Primitives ;;; (define all-primitives primitives) (define active-primitives primitives) (define all-ffi-foreigns (current-ffi-foreigns)) (define active-ffi-foreigns all-ffi-foreigns) (define active-ffi-imports-wat (current-ffi-imports-wat)) (define active-ffi-funcs-wat (current-ffi-funcs-wat)) (define active-primitives-sorted (λ () (sort active-primitives symboldescription pr)) pr))) (define active-ffi-primitive-names (λ () (for/list ([f (in-list active-ffi-foreigns)]) (foreign-racket-name f)))) (define described-primitives (filter primitive->description all-primitives)) (define ffi-primitive-name-set (for/hasheq ([f (in-list all-ffi-foreigns)]) (values (foreign-racket-name f) #t))) (define console-bridge-enabled? (pair? console-bridge-bindings)) (define console-bridge-primitive-names (for/list ([binding (in-list console-bridge-bindings)] #:do [(define kind (first binding))] #:when (eq? kind 'primitive)) (second binding))) (define (console-bridge-symbol-global const-name) ($ (string->symbol (~a "symbol:" const-name)))) (define (ffi-import-name f) (string->symbol (~a "$" (foreign-racket-name f) "/imported"))) (define primitive-func->symbol (for/hasheq ([pr (in-list described-primitives)]) (values ($ pr) pr))) (define primitive-global->symbol (for/hasheq ([pr (in-list described-primitives)]) (values ($ (prim: pr)) pr))) (define ffi-func->symbol (for/hasheq ([f (in-list all-ffi-foreigns)]) (values ($ (foreign-racket-name f)) (foreign-racket-name f)))) (define ffi-global->symbol (for/hasheq ([f (in-list all-ffi-foreigns)]) (define pr (foreign-racket-name f)) (values ($ (prim: pr)) pr))) (define (sexpr-primitive-refs x) (define refs '()) (define (walk x) (cond [(symbol? x) (define pr (or (hash-ref primitive-func->symbol x #f) (hash-ref primitive-global->symbol x #f))) (when pr (set! refs (cons pr refs)))] [(pair? x) (walk (car x)) (walk (cdr x))] [else (void)])) (walk x) (remove-duplicates refs)) (define (module-func-name form) (and (pair? form) (eq? (car form) 'func) (pair? (cdr form)) (symbol? (cadr form)) (cadr form))) (define (module-global-name form) (and (pair? form) (eq? (car form) 'global) (pair? (cdr form)) (symbol? (cadr form)) (cadr form))) (define (primitive-global-init-form? x) (and (pair? x) (eq? (car x) 'global.set) (pair? (cdr x)) (symbol? (cadr x)) (or (hash-ref primitive-global->symbol (cadr x) #f) (hash-ref ffi-global->symbol (cadr x) #f)))) (define program-function-names (for/list ([form (in-list dls)] #:do [(define name (module-func-name form))] #:when name) name)) (define (list->eq-set xs) (define ht (make-hasheq)) (eq-set-add-all! ht xs) ht) (define (eq-set-add-all! ht xs) (for ([x (in-list xs)]) (hash-set! ht x #t))) (define (eq-set-keys ht) (hash-keys ht)) (define (eq-set-keys/sorted ht) (sort (hash-keys ht) symbolvalue) (define refs (make-hasheq)) (define (walk x) (cond [(primitive-global-init-form? x) (void)] [(symbol? x) (define v (hash-ref symbol->value x #f)) (when v (hash-set! refs (if (eq? v #t) x v) #t))] [(pair? x) (walk (car x)) (walk (cdr x))] [else (void)])) (walk x) (eq-set-keys refs)) (define primitive-ref->symbol (let ([ht (make-hasheq)]) (for ([(k v) (in-hash primitive-func->symbol)]) (hash-set! ht k v)) (for ([(k v) (in-hash primitive-global->symbol)]) (hash-set! ht k v)) ht)) (define named-funcs (for/list ([form (in-list (cdr module))] #:do [(define name (module-func-name form))] #:when name) name)) (define (func-export-names form) (and (pair? form) (eq? (car form) 'func) (pair? (cdr form)) (symbol? (cadr form)) (for/list ([part (in-list (cddr form))] #:when (and (pair? part) (eq? (car part) 'export) (pair? (cdr part)) (string? (cadr part)))) (cadr part)))) (define named-funcs-ht (for/hasheq ([name (in-list named-funcs)]) (values name #t))) (define func->func-refs (make-hasheq)) (define func->primitive-refs (make-hasheq)) (for ([form (in-list (cdr module))]) (define name (module-func-name form)) (when name (define refs (for/list ([sym (in-list (walk-symbol-refs (cddr form) primitive-ref->symbol))] #:when (not (eq? sym name))) sym)) (hash-set! func->primitive-refs name refs) (hash-set! func->func-refs name (walk-symbol-refs (cddr form) named-funcs-ht)))) (define primitive-func-name? (λ (name) (and (symbol? name) (hash-ref primitive-func->symbol name #f)))) (define exported-runtime-functions (for/list ([form (in-list (cdr module))] #:do [(define name (module-func-name form))] #:when (and name (pair? (func-export-names form)) (not (primitive-func-name? name)))) name)) (define (collect-nonprimitive-function-refs x) (define refs (make-hasheq)) (define (walk x) (cond [(symbol? x) (when (and (hash-ref named-funcs-ht x #f) (not (primitive-func-name? x))) (hash-set! refs x #t))] [(pair? x) (walk (car x)) (walk (cdr x))] [else (void)])) (walk x) (eq-set-keys refs)) (define module-root-funcs (let ([roots (list->eq-set exported-runtime-functions)]) (for ([form (in-list (cdr module))] #:unless (or (module-func-name form) (and (pair? form) (eq? (car form) 'elem) (pair? (cdr form)) (eq? (cadr form) 'declare) (pair? (cddr form)) (eq? (caddr form) 'funcref)))) (eq-set-add-all! roots (collect-nonprimitive-function-refs form))) (eq-set-keys roots))) (define elem-root-functions (let ([roots (make-hasheq)]) (for ([form (in-list (cdr module))] #:when (and (pair? form) (eq? (car form) 'elem) (pair? (cdr form)) (eq? (cadr form) 'declare) (pair? (cddr form)) (eq? (caddr form) 'funcref))) (eq-set-add-all! roots (filter (λ (name) (not (primitive-func-name? name))) (walk-symbol-refs form named-funcs-ht)))) (eq-set-keys roots))) (define (reachable-runtime-funcs roots) (let loop ([pending roots] [seen (make-hasheq)]) (match pending ['() (eq-set-keys seen)] [(cons name rest) (cond [(hash-ref seen name #f) (loop rest seen)] [(primitive-func-name? name) (loop rest seen)] [else (hash-set! seen name #t) (loop (append (hash-ref func->func-refs name '()) rest) seen)])]))) (define runtime-funcs (reachable-runtime-funcs module-root-funcs)) (define runtime-root-primitives (let ([roots (make-hasheq)]) (for ([name (in-list runtime-funcs)]) (eq-set-add-all! roots (hash-ref func->primitive-refs name '()))) (eq-set-keys/sorted roots))) (define primitive-graph (for/list ([pr (in-list (sort described-primitives symbolfunc-refs start '())] [seen (let ([ht (make-hasheq)]) (hash-set! ht start #t) ht)]) (match pending ['() (eq-set-keys seen)] [(cons name rest) (cond [(hash-ref seen name #f) (loop rest seen)] [(primitive-func-name? name) (loop rest seen)] [else (hash-set! seen name #t) (loop (append (hash-ref func->func-refs name '()) rest) seen)])]))) (cons pr (let ([deps (make-hasheq)]) (for ([name (in-list reachable)]) (for ([dep (in-list (hash-ref func->primitive-refs name '()))] #:unless (eq? dep pr)) (hash-set! deps dep #t))) (eq-set-keys/sorted deps))))) (define incoming-primitives (let ([incoming (make-hasheq)]) (for ([entry (in-list primitive-graph)]) (eq-set-add-all! incoming (cdr entry))) (eq-set-keys incoming))) (define incoming-primitives-ht (list->eq-set incoming-primitives)) (define runtime-root-primitives-ht (list->eq-set runtime-root-primitives)) (define isolated-primitives (sort (for/list ([pr (in-list described-primitives)] #:unless (or (hash-ref incoming-primitives-ht pr #f) (hash-ref runtime-root-primitives-ht pr #f))) pr) symbolfunc-refs name '()) symbolprimitive-refs name '()) symbolprimitive-refs name '()))) (cons name (sort (hash-ref func->primitive-refs name '()) symboldescription (remove-duplicates (append used-primitives console-bridge-primitive-names))) symbolsymbol name #f) (not (hash-ref retained-primitive-ht (hash-ref primitive-func->symbol name) #f))) (loop rest seen)] [else (hash-set! seen name #t) (loop (append (hash-ref function-graph-ht name '()) rest) seen)])]))) (define (prune-runtime-module module retained-primitives retained-functions) (define retained-ht (for/hasheq ([name (in-list (append program-function-names retained-functions))]) (values name #t))) (define retained-primitive-ht (make-hasheq)) (for ([pr (in-list retained-primitives)]) (hash-set! retained-primitive-ht pr #t)) (for ([f (in-list active-ffi-foreigns)]) (hash-set! retained-primitive-ht (foreign-racket-name f) #t)) (define (retained-primitive-global-name? name) (define pr (or (hash-ref primitive-global->symbol name #f) (hash-ref ffi-global->symbol name #f))) (or (not pr) (hash-ref retained-primitive-ht pr #f))) (define (prune-elem-form form) (match form [`(elem declare funcref ,refs ...) `(elem declare funcref ,@(for/list ([ref (in-list refs)] #:when (match ref [`(ref.func ,name) (hash-ref retained-ht name #f)] [_ #t])) ref))] [_ form])) (define (prune-entry-form form) (match form [`(func $entry ,parts ...) `(func $entry ,@(for/list ([part (in-list parts)] #:unless (and (primitive-global-init-form? part) (not (retained-primitive-global-name? (cadr part))))) part))] [_ form])) `(module ,@(for/list ([form (in-list (cdr module))] #:unless (let ([name (module-func-name form)]) (and name (not (hash-ref retained-ht name #f)))) #:unless (let ([name (module-global-name form)]) (and name (not (retained-primitive-global-name? name))))) (cond [(eq? (module-func-name form) '$entry) (prune-entry-form form)] [(and (pair? form) (eq? (car form) 'elem)) (prune-elem-form form)] [else form])))) (define (write-runtime-primitive-report! analysis retained-primitives) (define report-path (current-runtime-primitive-report-path)) (when report-path (define program-root-primitives (sort (filter primitive->description (remove-duplicates used-primitives)) symbolinternal-representation a) (match a [(arity-at-least n) (- (- n) 1)] [(? number? a) a] [(? arity-2-3?) 2] [(? arity-3-4?) 3] [#f #f] [_ #f] [_ (error 'arity->internal-representation "got: ~a" a)])) (define (arity-entry->marker a) (match a [(? exact-nonnegative-integer? n) n] [(arity-at-least n) (- (- n) 1)] [_ #f])) (define (canonicalize-arity-markers markers) (define (dedup xs) (reverse (for/fold ([acc '()]) ([x (in-list xs)]) (if (member x acc) acc (cons x acc))))) (define exacts (for/list ([m (in-list markers)] #:when (>= m 0)) m)) (define at-least-starts (for/list ([m (in-list markers)] #:when (< m 0)) (- (- m) 1))) (define min-at-least (and (pair? at-least-starts) (apply min at-least-starts))) (define exacts-dedup (dedup exacts)) (define exacts-usable (if min-at-least (filter (λ (n) (< n min-at-least)) exacts-dedup) exacts-dedup)) (define exacts-sorted (sort exacts-usable <)) (cond [min-at-least (define m (- (- min-at-least) 1)) (if (null? exacts-sorted) (list m) (append exacts-sorted (list m)))] [else exacts-sorted])) (define (arity->markers a) (match a [(? exact-nonnegative-integer? n) (list n)] [(arity-at-least n) (list (- (- n) 1))] [(? list? as) (define ms (for/list ([x (in-list as)]) (arity-entry->marker x))) (and (andmap integer? ms) (canonicalize-arity-markers ms))] [#f #f] [_ #f])) (define (arity->procedure-field-expr a) (define ms (arity->markers a)) (cond [(not ms) (Imm #f)] [(null? ms) `(ref.cast (ref eq) (array.new_fixed $I32Array 0))] [(null? (cdr ms)) (Imm (car ms))] [else `(ref.cast (ref eq) (array.new_fixed $I32Array ,(length ms) ,@(for/list ([m (in-list ms)]) `(i32.const ,m))))])) ;; See $primitive-invoke for an explanation of shapes. (define (arity->shape a) (match a [(arity-at-least n) (+ 6 (min n 3))] [(? number? n) (if (= n 6) 26 (min n 5))] [(? (λ (a) (arity-range? a 0 1))) 16] [(? (λ (a) (arity-range? a 0 2))) 17] [(? (λ (a) (arity-range? a 0 3))) 27] [(? (λ (a) (arity-range? a 1 2))) 18] [(? (λ (a) (arity-range? a 1 3))) 19] [(? (λ (a) (arity-range? a 1 4))) 20] [(? (λ (a) (arity-range? a 1 5))) 21] [(? (λ (a) (arity-range? a 2 3))) 14] [(? (λ (a) (arity-range? a 2 4))) 22] [(? (λ (a) (arity-range? a 2 5))) 23] [(? (λ (a) (arity-range? a 3 4))) 15] [(? (λ (a) (arity-range? a 3 5))) 24] [(? (λ (a) (arity-range? a 6 7))) 25] [_ #f])) ; These functions are variadic functions that can handle the ; rest arguments both as an $Args array and as a list. ; The $Args convention is used by inlining. ; The list convention by `apply`, `map` and others. (define primitive-variadic-args '(bytes string vector vector-immutable values void)) (define primitive-shapes '(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27)) (define (primitive->shape pr [desc (primitive->description pr)]) (and desc (let ([base (arity->shape (primitive-description-arity desc))]) (and base (if (>= base 6) (if (memq pr primitive-variadic-args) (+ base 4) base) base))))) (define (shape->invoker shape) (match shape [0 'primitive-invoke:shape-0] [1 'primitive-invoke:shape-1] [2 'primitive-invoke:shape-2] [3 'primitive-invoke:shape-3] [4 'primitive-invoke:shape-4] [5 'primitive-invoke:shape-5] [6 'primitive-invoke:shape-6] [7 'primitive-invoke:shape-7] [8 'primitive-invoke:shape-8] [9 'primitive-invoke:shape-9] [10 'primitive-invoke:shape-10] [11 'primitive-invoke:shape-11] [12 'primitive-invoke:shape-12] [13 'primitive-invoke:shape-13] [14 'primitive-invoke:shape-14] [15 'primitive-invoke:shape-15] [16 'primitive-invoke:shape-16] [17 'primitive-invoke:shape-17] [18 'primitive-invoke:shape-18] [19 'primitive-invoke:shape-19] [20 'primitive-invoke:shape-20] [21 'primitive-invoke:shape-21] [22 'primitive-invoke:shape-22] [23 'primitive-invoke:shape-23] [24 'primitive-invoke:shape-24] [25 'primitive-invoke:shape-25] [26 'primitive-invoke:shape-26] [27 'primitive-invoke:shape-27] [_ 'primitive-invoke])) (define (primitive-invoker-tail shape) (match shape [0 ; exact 0 `((if (i32.eqz (local.get $argc)) (then (if (ref.test (ref $Prim0) (local.get $code)) (then (return_call_ref $Prim0 (ref.cast (ref $Prim0) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [1 ; exact 1 `((if (i32.eq (local.get $argc) (i32.const 1)) (then (if (ref.test (ref $Prim1) (local.get $code)) (then (return_call_ref $Prim1 (local.get $a0) (ref.cast (ref $Prim1) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [2 ; exact 2 `((if (i32.eq (local.get $argc) (i32.const 2)) (then (if (ref.test (ref $Prim2) (local.get $code)) (then (return_call_ref $Prim2 (local.get $a0) (local.get $a1) (ref.cast (ref $Prim2) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [3 ; exact 3 `((if (i32.eq (local.get $argc) (i32.const 3)) (then (if (ref.test (ref $Prim3) (local.get $code)) (then (return_call_ref $Prim3 (local.get $a0) (local.get $a1) (local.get $a2) (ref.cast (ref $Prim3) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [4 ; exact 4 `((if (i32.eq (local.get $argc) (i32.const 4)) (then (if (ref.test (ref $Prim4) (local.get $code)) (then (return_call_ref $Prim4 (local.get $a0) (local.get $a1) (local.get $a2) (array.get $Args (local.get $args) (i32.const 3)) (ref.cast (ref $Prim4) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [5 ; exact 5 `((if (i32.eq (local.get $argc) (i32.const 5)) (then (if (ref.test (ref $Prim5) (local.get $code)) (then (return_call_ref $Prim5 (local.get $a0) (local.get $a1) (local.get $a2) (array.get $Args (local.get $args) (i32.const 3)) (array.get $Args (local.get $args) (i32.const 4)) (ref.cast (ref $Prim5) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [6 ; at least 0, rest arguments as list `((if (ref.test (ref $Prim>=0) (local.get $code)) (then (local.set $rest (call $rest-arguments->list (local.get $args) (i32.const 0))) (return_call_ref $Prim>=0 (local.get $rest) (ref.cast (ref $Prim>=0) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [7 ; at least 1, rest arguments as list `((if (i32.ge_u (local.get $argc) (i32.const 1)) (then (if (ref.test (ref $Prim>=1) (local.get $code)) (then (local.set $rest (call $rest-arguments->list (local.get $args) (i32.const 1))) (return_call_ref $Prim>=1 (local.get $a0) (local.get $rest) (ref.cast (ref $Prim>=1) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [8 ; at least 2, rest arguments as list `((if (i32.ge_u (local.get $argc) (i32.const 2)) (then (if (ref.test (ref $Prim>=2) (local.get $code)) (then (local.set $rest (call $rest-arguments->list (local.get $args) (i32.const 2))) (return_call_ref $Prim>=2 (local.get $a0) (local.get $a1) (local.get $rest) (ref.cast (ref $Prim>=2) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [9 ; at least 3, rest arguments as list `((if (i32.ge_u (local.get $argc) (i32.const 3)) (then (if (ref.test (ref $Prim>=3) (local.get $code)) (then (local.set $rest (call $rest-arguments->list (local.get $args) (i32.const 3))) (return_call_ref $Prim>=3 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $rest) (ref.cast (ref $Prim>=3) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [10 ; at least 0, rest arguments as $Args array `((if (ref.test (ref $Prim>=0) (local.get $code)) (then (return_call_ref $Prim>=0 (local.get $args) (ref.cast (ref $Prim>=0) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [11 ; at least 1, rest arguments as $Args array `((if (i32.ge_u (local.get $argc) (i32.const 1)) (then (if (ref.test (ref $Prim>=1) (local.get $code)) (then (return_call_ref $Prim>=1 (local.get $a0) (call $rest-arguments->args (local.get $args) (i32.const 1)) (ref.cast (ref $Prim>=1) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [12 ; at least 2, rest arguments as $Args array `((if (i32.ge_u (local.get $argc) (i32.const 2)) (then (if (ref.test (ref $Prim>=2) (local.get $code)) (then (return_call_ref $Prim>=2 (local.get $a0) (local.get $a1) (call $rest-arguments->args (local.get $args) (i32.const 2)) (ref.cast (ref $Prim>=2) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [13 ; at least 3, rest arguments as $Args array `((if (i32.ge_u (local.get $argc) (i32.const 3)) (then (if (ref.test (ref $Prim>=3) (local.get $code)) (then (return_call_ref $Prim>=3 (local.get $a0) (local.get $a1) (local.get $a2) (call $rest-arguments->args (local.get $args) (i32.const 3)) (ref.cast (ref $Prim>=3) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [14 ; between 2 and 3 arguments `((if (i32.lt_u (local.get $argc) (i32.const 2)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 3)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim23) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 2)) (then (local.set $a2 (global.get $missing)))) (return_call_ref $Prim23 (local.get $a0) (local.get $a1) (local.get $a2) (ref.cast (ref $Prim23) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [15 ; between 3 and 4 arguments `((if (i32.lt_u (local.get $argc) (i32.const 3)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 4)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim34) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 3)) (then (local.set $a3 (global.get $missing))) (else (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))))) (return_call_ref $Prim34 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $a3) (ref.cast (ref $Prim34) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [16 ; between 0 and 1 arguments `((if (i32.gt_u (local.get $argc) (i32.const 1)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim01) (local.get $code)) (then (if (i32.eqz (local.get $argc)) (then (local.set $a0 (global.get $missing)))) (return_call_ref $Prim01 (local.get $a0) (ref.cast (ref $Prim01) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [17 ; between 0 and 2 arguments `((if (i32.gt_u (local.get $argc) (i32.const 2)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim02) (local.get $code)) (then (if (i32.eqz (local.get $argc)) (then (local.set $a0 (global.get $missing)) (local.set $a1 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 1)) (then (local.set $a1 (global.get $missing)))))) (return_call_ref $Prim02 (local.get $a0) (local.get $a1) (ref.cast (ref $Prim02) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [27 ; between 0 and 3 arguments `((if (i32.gt_u (local.get $argc) (i32.const 3)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim03) (local.get $code)) (then (if (i32.eqz (local.get $argc)) (then (local.set $a0 (global.get $missing)) (local.set $a1 (global.get $missing)) (local.set $a2 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 1)) (then (local.set $a1 (global.get $missing)) (local.set $a2 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 2)) (then (local.set $a2 (global.get $missing)))))))) (return_call_ref $Prim03 (local.get $a0) (local.get $a1) (local.get $a2) (ref.cast (ref $Prim03) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [18 ; between 1 and 2 arguments `((if (i32.lt_u (local.get $argc) (i32.const 1)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 2)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim12) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 1)) (then (local.set $a1 (global.get $missing)))) (return_call_ref $Prim12 (local.get $a0) (local.get $a1) (ref.cast (ref $Prim12) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [19 ; between 1 and 3 arguments `((if (i32.lt_u (local.get $argc) (i32.const 1)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 3)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim13) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 1)) (then (local.set $a1 (global.get $missing)) (local.set $a2 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 2)) (then (local.set $a2 (global.get $missing)))))) (return_call_ref $Prim13 (local.get $a0) (local.get $a1) (local.get $a2) (ref.cast (ref $Prim13) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [20 ; between 1 and 4 arguments `((if (i32.lt_u (local.get $argc) (i32.const 1)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 4)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim14) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 1)) (then (local.set $a1 (global.get $missing)) (local.set $a2 (global.get $missing)) (local.set $a3 (global.get $missing)))) (if (i32.eq (local.get $argc) (i32.const 2)) (then (local.set $a2 (global.get $missing)) (local.set $a3 (global.get $missing)))) (if (i32.eq (local.get $argc) (i32.const 3)) (then (local.set $a3 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 4)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))))))) (return_call_ref $Prim14 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $a3) (ref.cast (ref $Prim14) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [21 ; between 1 and 5 arguments `((if (i32.lt_u (local.get $argc) (i32.const 1)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 5)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim15) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 1)) (then (local.set $a1 (global.get $missing)) (local.set $a2 (global.get $missing)) (local.set $a3 (global.get $missing)) (local.set $a4 (global.get $missing)))) (if (i32.eq (local.get $argc) (i32.const 2)) (then (local.set $a2 (global.get $missing)) (local.set $a3 (global.get $missing)) (local.set $a4 (global.get $missing)))) (if (i32.eq (local.get $argc) (i32.const 3)) (then (local.set $a3 (global.get $missing)) (local.set $a4 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 4)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))) (local.set $a4 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 5)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))) (local.set $a4 (array.get $Args (local.get $args) (i32.const 4))))))))) (return_call_ref $Prim15 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $a3) (local.get $a4) (ref.cast (ref $Prim15) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [22 ; between 2 and 4 arguments `((if (i32.lt_u (local.get $argc) (i32.const 2)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 4)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim24) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 2)) (then (local.set $a2 (global.get $missing)) (local.set $a3 (global.get $missing)))) (if (i32.eq (local.get $argc) (i32.const 3)) (then (local.set $a3 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 4)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))))))) (return_call_ref $Prim24 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $a3) (ref.cast (ref $Prim24) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [23 ; between 2 and 5 arguments `((if (i32.lt_u (local.get $argc) (i32.const 2)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 5)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim25) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 2)) (then (local.set $a2 (global.get $missing)) (local.set $a3 (global.get $missing)) (local.set $a4 (global.get $missing)))) (if (i32.eq (local.get $argc) (i32.const 3)) (then (local.set $a3 (global.get $missing)) (local.set $a4 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 4)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))) (local.set $a4 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 5)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))) (local.set $a4 (array.get $Args (local.get $args) (i32.const 4))))))))) (return_call_ref $Prim25 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $a3) (local.get $a4) (ref.cast (ref $Prim25) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [24 ; between 3 and 5 arguments `((if (i32.lt_u (local.get $argc) (i32.const 3)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 5)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim35) (local.get $code)) (then (if (i32.eq (local.get $argc) (i32.const 3)) (then (local.set $a3 (global.get $missing)) (local.set $a4 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 4)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))) (local.set $a4 (global.get $missing))) (else (if (i32.eq (local.get $argc) (i32.const 5)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))) (local.set $a4 (array.get $Args (local.get $args) (i32.const 4))))))))) (return_call_ref $Prim35 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $a3) (local.get $a4) (ref.cast (ref $Prim35) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [25 ; between 6 and 7 arguments `((if (i32.lt_u (local.get $argc) (i32.const 6)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (i32.gt_u (local.get $argc) (i32.const 7)) (then (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (if (ref.test (ref $Prim67) (local.get $code)) (then (local.set $a0 (array.get $Args (local.get $args) (i32.const 0))) (local.set $a1 (array.get $Args (local.get $args) (i32.const 1))) (local.set $a2 (array.get $Args (local.get $args) (i32.const 2))) (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))) (local.set $a4 (array.get $Args (local.get $args) (i32.const 4))) (local.set $a5 (array.get $Args (local.get $args) (i32.const 5))) (if (i32.eq (local.get $argc) (i32.const 6)) (then (local.set $a6 (global.get $missing))) (else (local.set $a6 (array.get $Args (local.get $args) (i32.const 6))))) (return_call_ref $Prim67 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $a3) (local.get $a4) (local.get $a5) (local.get $a6) (ref.cast (ref $Prim67) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (unreachable))] [26 ; exact 6 `((if (i32.eq (local.get $argc) (i32.const 6)) (then (if (ref.test (ref $Prim6) (local.get $code)) (then (return_call_ref $Prim6 (local.get $a0) (local.get $a1) (local.get $a2) (array.get $Args (local.get $args) (i32.const 3)) (array.get $Args (local.get $args) (i32.const 4)) (array.get $Args (local.get $args) (i32.const 5)) (ref.cast (ref $Prim6) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable))] [_ (error 'primitive-invoker-tail "unknown shape: ~a" shape)])) (define (primitive-invoker shape) `(func ,($ (shape->invoker shape)) (type $ProcedureInvoker) (param $proc (ref $Procedure)) (param $args (ref $Args)) (result (ref eq)) (local $pproc (ref $PrimitiveProcedure)) (local $code (ref null func)) (local $argc i32) (local $a0 (ref eq)) (local $a1 (ref eq)) (local $a2 (ref eq)) (local $a3 (ref eq)) ; ? needed - see primitive-invoker-tail above (local $a4 (ref eq)) ; ? needed (local $a5 (ref eq)) ; ? needed (local $a6 (ref eq)) ; ? needed (local $rest (ref eq)) ; todo: we should limit this to fewer locals (local.set $a0 (global.get $null)) (local.set $a1 (global.get $null)) (local.set $a2 (global.get $null)) (local.set $a3 (global.get $null)) (local.set $a4 (global.get $null)) ; todo remove these (local.set $a5 (global.get $null)) ; todo remove these (local.set $a6 (global.get $null)) ; todo remove these (local.set $rest (global.get $null)) (local.set $pproc (ref.cast (ref $PrimitiveProcedure) (local.get $proc))) (local.set $code (struct.get $PrimitiveProcedure $code (local.get $pproc))) (if (ref.is_null (local.get $code)) (then (return (call $raise-no-code (local.get $pproc))))) (local.set $argc (array.len (local.get $args))) (if (i32.gt_u (local.get $argc) (i32.const 0)) (then (local.set $a0 (array.get $Args (local.get $args) (i32.const 0))))) (if (i32.gt_u (local.get $argc) (i32.const 1)) (then (local.set $a1 (array.get $Args (local.get $args) (i32.const 1))))) (if (i32.gt_u (local.get $argc) (i32.const 2)) (then (local.set $a2 (array.get $Args (local.get $args) (i32.const 2))))) ,@(primitive-invoker-tail shape))) (define (min-arity a) (match a [(arity-at-least n) n] [(? number? n) n] [(? list? l) (apply min (map min-arity l))] [_ (error 'min-arity "got: ~a" a)])) ; If a primitive is handled in the inliner, but hasn't been ; implemented here in `runtime-wasm.rkt` then put the symbol here. (define todo-handle-later '()) (define (initialize-primitives-as-globals) (for/list ([pr (in-list (active-described-primitives-sorted))] #:do [(define desc (primitive->description pr))]) (define ar (primitive-description-arity desc)) (define shape (primitive->shape pr desc)) (define $name ($ (string->symbol (~a "symbol:" pr)))) `(global.set ,($ (prim: pr)) (struct.new $PrimitiveProcedure ; for $Procedure (i32.const 0) ; hash (global.get ,$name) ; name (used by object-name) ,(arity->procedure-field-expr ar) ; arity (global.get $the-racket/primitive-realm) ; realm #;(ref.func $primitive-invoke) (ref.func ,($ (shape->invoker shape))) (ref.func ,($ pr)) ,(Imm #f #;(arity->internal-representation (primitive-description-result-arity desc))))))) (define (initialize-ffi-primitives-as-globals) (for/list ([f (in-list active-ffi-foreigns)]) (define pr (foreign-racket-name f)) (define ar (length (foreign-argument-types f))) (define shape (arity->shape ar)) (define $name ($ (string->symbol (~a "symbol:" pr)))) `(global.set ,($ (prim: pr)) (struct.new $PrimitiveProcedure ; for $Procedure (i32.const 0) ; hash (global.get ,$name) ; name (used by object-name) ,(arity->procedure-field-expr ar) ; arity (global.get $the-racket/primitive-realm) ; realm (ref.func ,($ (shape->invoker shape))) (ref.func ,($ pr)) ,(Imm #f))))) ;; String constants used in the runtime ;; `string-constants holds the constants passed by `generate-code` in `compiler.rkt` (define runtime-string-constants string-constants) (define (add-runtime-string-constant name string) (set! runtime-string-constants (cons (list name string) runtime-string-constants))) (define (declare-runtime-string-constants) (append* (for/list ([ns (reverse runtime-string-constants)]) (define name (first ns)) (define string (second ns)) (define $string-data:name (string->symbol (~a "$" "string-data:" name))) (define $string:name (string->symbol (~a "$" "string:" name))) (list `(data ,$string-data:name ,(wasm-data (string->bytes/utf-8 string))) `(global ,$string:name (mut (ref eq)) ,(Imm #f)))))) (define (initialize-runtime-string-constants) (for/list ([ns (reverse runtime-string-constants)]) (define name (first ns)) (define string (second ns)) (define $string-data:name (string->symbol (~a "$" "string-data:" name))) (define $string:name (string->symbol (~a "$" "string:" name))) (define n (bytes-length (string->bytes/utf-8 string))) `(global.set ,$string:name (call $i8array->string (array.new_data $I8Array ,$string-data:name (i32.const 0) (i32.const ,n)))))) ;; Bytes constants used in the runtime ;; `bytes-constants holds the constants passed by `generate-code` in `compiler.rkt` (define runtime-bytes-constants bytes-constants) (define (add-runtime-bytes-constant name bytes) (set! runtime-bytes-constants (cons (list name bytes) runtime-bytes-constants))) (define (declare-runtime-bytes-constants) (append* (for/list ([ns (reverse runtime-bytes-constants)]) (define name (first ns)) (define bytes (second ns)) (define $bytes-data:name (string->symbol (~a "$" "bytes-data:" name))) (define $bytes:name (string->symbol (~a "$" "bytes:" name))) (list `(data ,$bytes-data:name ,(wasm-data bytes)) `(global ,$bytes:name (mut (ref eq)) ,(Imm #f)))))) (define (initialize-runtime-bytes-constants) (for/list ([ns (reverse runtime-bytes-constants)]) (define name (first ns)) (define bytes (second ns)) (define $bytes-data:name (string->symbol (~a "$" "bytes-data:" name))) (define $bytes:name (string->symbol (~a "$" "bytes:" name))) (define n (bytes-length bytes)) `(global.set ,$bytes:name (call $i8array->immutable-bytes (array.new_data $I8Array ,$bytes-data:name (i32.const 0) (i32.const ,n)))))) ;; Symbol constants used in the runtime ;; `runtime-symbol-constants` holds the constants passed by `generate-code` ;; in `compiler.rkt` (define runtime-symbol-constants symbol-constants) (define runtime-symbols-ht (make-hasheq)) (define (add-runtime-symbol-constant symbol) (cond [(hash-ref runtime-symbols-ht symbol #f) => values] [else (define name symbol) (hash-set! runtime-symbols-ht symbol #t) ; avoid duplicates (set! runtime-symbol-constants (cons (list name symbol) runtime-symbol-constants))])) (define (declare-runtime-symbol-constants) ; This file adds more symbols to `runtime-symbol-constants` so ; we need to wait removing duplicates until here. (set! runtime-symbol-constants (remove-duplicates runtime-symbol-constants eq? #:key car)) (append* (for/list ([ns (reverse runtime-symbol-constants)]) (define name (first ns)) (define symbol (second ns)) (define string (symbol->string symbol)) (define $symbol-data:name (string->symbol (~a "$" "symbol-data:" name))) (define $symbol:name (string->symbol (~a "$" "symbol:" name))) (list `(data ,$symbol-data:name ,string) `(global ,$symbol:name (mut (ref eq)) ,(Imm #f)))))) (define (initialize-runtime-symbol-constants) #;(set! runtime-symbol-constants (remove-duplicates runtime-symbol-constants eq? #:key car)) (for/list ([ns (reverse runtime-symbol-constants)]) (define name (first ns)) (define symbol (second ns)) (define string (symbol->string symbol)) (define $symbol-data:name (string->symbol (~a "$" "symbol-data:" name))) (define $symbol:name (string->symbol (~a "$" "symbol:" name))) (define n (bytes-length (string->bytes/utf-8 string))) `(global.set ,$symbol:name (call $string->symbol (call $i8array->string (array.new_data $I8Array ,$symbol-data:name (i32.const 0) (i32.const ,n))))))) ;; String and symbol constants used in the runtime ; Names of each primitive (for ([pr (in-list (active-described-primitives-sorted))]) (add-runtime-symbol-constant pr)) (define (add-ffi-symbol-constants) (for ([pr (in-list (active-ffi-primitive-names))]) (add-runtime-symbol-constant pr))) (add-ffi-symbol-constants) (define linklet-body-core-reserved-symbols '(lambda case-lambda let-values letrec-values if begin begin0 begin-unsafe set! quote with-continuation-mark #%variable-reference)) (for-each add-runtime-symbol-constant (remove-duplicates (append linklet-body-core-reserved-symbols all-primitives) eq?)) (define linklet-body-reserved-symbols (sort (remove-duplicates (append linklet-body-core-reserved-symbols all-primitives) eq?) symbolstring arity-at-least make-arity-at-least arity-at-least? arity-at-least-value correlated make-correlated correlated? correlated-source correlated-line correlated-column correlated-position correlated-span correlated-e correlated-props ; remove? datum->correlated correlated-property correlated-property-symbol-keys instance instance-name instance-data instance-variable-box instance-variable-names instance-variable-value instance-set-variable-value! instance-unset-variable! make-instance link linklet-body-reserved-symbol? linklet-bundle? hash->linklet-bundle linklet-bundle->hash linklet-directory? hash->linklet-directory linklet-directory->hash syntax make-syntax syntax? syntax-e syntax-scopes syntax-shifted-multi-scopes syntax-srcloc syntax-props empty-props syntax-source syntax-line syntax-column syntax-position syntax-span datum->syntax syntax->datum syntax->list identifier? struct->list struct->vector exn exn? exn-message exn-continuation-marks exn:fail exn:fail? make-exn make-exn:fail exn:fail:contract exn:fail:contract? make-exn:fail:contract exn:fail:contract:arity exn:fail:contract:arity? make-exn:fail:contract:arity exn:fail:contract:divide-by-zero exn:fail:contract:divide-by-zero? make-exn:fail:contract:divide-by-zero exn:fail:contract:non-fixnum-result exn:fail:contract:non-fixnum-result? make-exn:fail:contract:non-fixnum-result exn:fail:contract:variable exn:fail:contract:variable? make-exn:fail:contract:variable exn:fail:contract:variable-id exn:fail:read exn:fail:read? make-exn:fail:read exn:fail:read-srclocs exn:fail:read:eof exn:fail:read:eof? make-exn:fail:read:eof exn:fail:read:non-char exn:fail:read:non-char? make-exn:fail:read:non-char exn:fail:filesystem exn:fail:filesystem? make-exn:fail:filesystem exn:fail:syntax exn:fail:syntax? make-exn:fail:syntax exn:fail:syntax-exprs exn:fail:syntax:missing-module exn:fail:syntax:missing-module? make-exn:fail:syntax:missing-module exn:fail:syntax:missing-module-path exn:fail:syntax:unbound exn:fail:syntax:unbound? make-exn:fail:syntax:unbound mutator fixnum fx/ match error return-false skip ...)) (for-each add-runtime-symbol-constant symbols)) (for ([sym '(lu ll lt lm lo mn mc me nd nl no ps pe pi pf pd pc po sc sm sk so zs zp zl cc cf cs co cn)]) (add-runtime-symbol-constant sym)) ; struct:exn, struct:exn:fail, etc. (for-each (λ (binding) (add-runtime-symbol-constant (car binding))) exception-struct-type-bindings) (add-runtime-string-constant 'dash "-") (add-runtime-string-constant 'bang "!") (add-runtime-string-constant 'hash-variable-reference "#") (add-runtime-string-constant 'box-prefix "#&") (add-runtime-string-constant 'bytes-prefix "#\"") (add-runtime-string-constant 'backslash "\\") (add-runtime-string-constant 'backslash-x "\\x") (add-runtime-string-constant 'double-quote "\"") (add-runtime-string-constant 'hash-t "#t") (add-runtime-string-constant 'hash-f "#f") (add-runtime-string-constant 'hash-hash "#hash") (add-runtime-string-constant 'hash-hasheq "#hasheq") (add-runtime-string-constant 'hash-hasheqv "#hasheqv") (add-runtime-string-constant 'hash-hashalw "#hashalw") (add-runtime-string-constant 'null "()") (add-runtime-string-constant 'eof "#") (add-runtime-string-constant 'void "#") (add-runtime-string-constant 'undefined "#") (add-runtime-string-constant 'unsafe-undefined "#") (add-runtime-string-constant 'unspecified "#") (add-runtime-string-constant 'missing "#") (add-runtime-string-constant 'closure "#") (add-runtime-string-constant 'external "#") (add-runtime-string-constant 'external-null "#") (add-runtime-string-constant 'linklet "#") (add-runtime-string-constant 'instance "#") (add-runtime-string-constant 'namespace "#") (add-runtime-string-constant 'hash-less-namespace-colon "#") (add-runtime-string-constant 'struct-open "#(struct ") (add-runtime-string-constant 'struct? "struct?") (add-runtime-string-constant 'struct-type? "struct-type?") (add-runtime-string-constant 'custom-write? "custom-write?") (add-runtime-string-constant 'struct->list:on-opaque "one of 'error, 'return-false, or 'skip") (add-runtime-string-constant 'struct:prefix "struct:") (add-runtime-string-constant 'hash-colon "#:") (add-runtime-string-constant 'hash-backslash "#\\") (add-runtime-string-constant 'hash-backslash-u "#\\u") (add-runtime-string-constant 'hash-backslash-U "#\\U") (add-runtime-string-constant 'word-newline "newline") (add-runtime-string-constant 'word-tab "tab") (add-runtime-string-constant 'word-return "return") (add-runtime-string-constant 'word-backspace "backspace") (add-runtime-string-constant 'word-space "space") (add-runtime-string-constant 'word-rubout "rubout") (add-runtime-string-constant 'word-nul "nul") (add-runtime-string-constant 'unknown "unknown") (add-runtime-string-constant 'colon ":") (add-runtime-string-constant '-> ">") (add-runtime-string-constant 'set "set") (add-runtime-string-constant 'syntax-open "#") (add-runtime-string-constant 'hash-less-boxed-colon "#syntax-srcloc "(or/c #f syntax? srcloc?)") (add-runtime-string-constant 'hash? "hash?") (add-runtime-string-constant 'syntax? "syntax?") (add-runtime-string-constant 'correlated? "correlated?") (add-runtime-string-constant 'correlated-or-false "(or/c correlated? #f)") (add-runtime-symbol-constant 'correlated->datum) (add-runtime-symbol-constant 'datum->correlated) (add-runtime-symbol-constant 'correlated-property) (add-runtime-symbol-constant 'correlated-property-symbol-keys) (add-runtime-symbol-constant 'relative) (add-runtime-symbol-constant 'up) (add-runtime-symbol-constant 'same) (add-runtime-string-constant 'instance? "instance?") (add-runtime-string-constant 'missing-variable-value "missing variable value") (add-runtime-string-constant 'missing-binding "missing binding:") (add-runtime-string-constant 'instance-variable-not-found "instance variable not found:") (add-runtime-string-constant 'cannot-modify-constant "cannot modify a constant") (add-runtime-string-constant 'at-most-one-optional-argument "expected at most one optional argument") (add-runtime-string-constant 'linklet? "linklet?") (add-runtime-string-constant 'compiled-linklet? "compiled-linklet?") (add-runtime-string-constant 'linklet-bundle? "linklet-bundle?") (add-runtime-string-constant 'linklet-directory? "linklet-directory?") (add-runtime-string-constant 'mutable-hasheq? "mutable hasheq?") (add-runtime-string-constant 'linklet-bundle-key? "(or/c symbol? fixnum?)") (add-runtime-string-constant 'linklet-directory-key? "(or/c symbol? #f)") (add-runtime-string-constant 'listof-symbol? "(listof symbol?)") (add-runtime-string-constant 'listof-listof-symbol? "(listof (listof symbol?))") (add-runtime-string-constant 'listof-instance? "(listof instance?)") (add-runtime-string-constant 'distinct-listof-symbol? "(and/c (listof symbol?) distinct?)") (add-runtime-string-constant 'symbol-or-false "(or/c symbol? #f)") (add-runtime-string-constant 'instance-mode? "(or/c #f 'constant 'consistent)") (add-runtime-string-constant 'instance-or-false "(or/c instance? #f)") (add-runtime-symbol-constant 'constant) (add-runtime-symbol-constant 'consistent) (add-runtime-string-constant 'instantiate-linklet:import-count "the number of import instances does not match the expected number of imports") (add-runtime-string-constant 'datum->correlated-srcloc (string-append "(or/c correlated? #f (list/c any/c (or/c exact-positive-integer? #f) " "(or/c exact-nonnegative-integer? #f) (or/c exact-positive-integer? #f) " "(or/c exact-nonnegative-integer? #f)) " "(vector/c any/c (or/c exact-positive-integer? #f) " "(or/c exact-nonnegative-integer? #f) " "(or/c exact-positive-integer? #f) " "(or/c exact-nonnegative-integer? #f)))")) (add-runtime-string-constant 'procedure? "procedure?") (add-runtime-string-constant 'arity-error:start ": arity mismatch;\n the expected number of arguments does not match the given number\n expected: ") (add-runtime-string-constant 'arity-error:start/no-expected ": arity mismatch;\n the expected number of arguments does not match the given number\n given: ") (add-runtime-string-constant 'arity-error:given "\n given: ") (add-runtime-string-constant 'arity-error:at-least "at least ") (add-runtime-string-constant 'arity-error:or " or ") (add-runtime-string-constant 'contract-violation:prefix ": contract violation\n expected: ") (add-runtime-string-constant 'application:not-procedure "application: not a procedure;\n expected a procedure that can be applied to arguments\n given: ") (add-runtime-string-constant 'question "?") (add-runtime-string-constant 'accessor-suffix "-accessor") (add-runtime-string-constant 'fx-overflow:middle ": fixnum overflow with arguments ") (add-runtime-string-constant 'fx-overflow:and " and ") (add-runtime-string-constant 'expected-fixnum:got "expected fixnum, got: ") (add-runtime-string-constant 'path? "path?") (add-runtime-string-constant 'path-for-some-system? "path-for-some-system?") (add-runtime-string-constant 'path-element? "path-element?") (add-runtime-string-constant 'listof-path? "(listof path?)") ;; Byte Strings (add-runtime-bytes-constant 'empty #"") (add-runtime-bytes-constant 'non-empty #"_") (add-runtime-bytes-constant 'slash #"/") (add-runtime-bytes-constant 'backslash #"\\") (add-runtime-bytes-constant 'dot #".") (add-runtime-bytes-constant 'dot-dot #"..") (add-runtime-bytes-constant 'rkttmp-template #"rkttmp~a") (add-runtime-bytes-constant 'app-dir #"/app/") (add-runtime-bytes-constant 'vfs-root #"/") (add-runtime-bytes-constant 'vfs-home-dir #"/home/") (add-runtime-bytes-constant 'vfs-pref-dir #"/home/.config/racket/") (add-runtime-bytes-constant 'vfs-pref-file #"/home/.config/racket/racket-prefs.rktd") (add-runtime-bytes-constant 'vfs-temp-dir #"/tmp/") (add-runtime-bytes-constant 'vfs-init-file #"/home/.racketrc") (add-runtime-bytes-constant 'vfs-config-dir #"/app/etc/") (add-runtime-bytes-constant 'vfs-addon-dir #"/home/.local/share/racket/") (add-runtime-bytes-constant 'vfs-cache-dir #"/home/.cache/racket/") (add-runtime-bytes-constant 'vfs-exec-file #"/app/webracket") (add-runtime-bytes-constant 'vfs-run-file #"/app/main.rkt") (add-runtime-bytes-constant 'vfs-collects-dir #"/app/collects/") ;;; ;;; Predicate Generator ;;; ; Note: We could generate ; (func $predicate-name? (type $Prim1) ...) ;; too, but it is convenient to grep for function definitions ; using "func $name". (define (make-predicate-body ref-type) `((param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref ,ref-type) (local.get $v)) (then (global.get $true)) (else (global.get $false))))) ;;; ;;; WEB-ASSEMBLY ;;; (define (build-runtime-module) `(module ;;; ;;; Internal Types ;;; ; The following internal types are used to implement the various Racket data types. (type $Array (array (mut (ref eq)))) (type $I32Array (array (mut i32))) (type $I8Array (array (mut i8))) (type $GrowableArray (struct (field $arr (mut (ref $Array))) ;; underlying array (field $cap (mut i32)) ;; capacity (the size of the array) (field $i (mut i32)))) ;; the index of the next free slot in $arr (type $GrowableBytes (struct (field $arr (mut (ref $I8Array))) ;; underlying byte array (field $cap (mut i32)) ;; capacity of the array (field $i (mut i32)))) ;; next free index (type $I32GrowableArray (struct (field $arr (mut (ref $I32Array))) ;; underlying array (field $cap (mut i32)) ;; capacity (field $i (mut i32)))) ;; current size ; The type $Boxed is used for assignable variables. ; They are not exposed, so they do not need to carry a hash code. (type $Boxed (struct (field $v (mut (ref eq))))) ;;; ;;; Support ;;; (type $Values (array (mut (ref eq)))) ; for multiple values return (type $Args (array (mut (ref eq)))) ; holds arguments passed to a closure (type $Free (array (mut (ref eq)))) ; holds captured free variables ;;; ;;; Types: Heap allocated objects ;;; ; All heap allocated values carry a hash code. ; If the hash code is 0 it hash hasn't been computed yet. (rec (type $Heap (sub (struct (field $hash (mut i32))))) (type $Pair (sub $Heap (struct (field $hash (mut i32)) (field $a (mut (ref eq))) (field $d (mut (ref eq)))))) (type $MPair (sub $Heap (struct (field $hash (mut i32)) (field $a (mut (ref eq))) (field $d (mut (ref eq)))))) (type $Box (sub $Heap (struct (field $hash (mut i32)) (field $immutable i32) (field $v (mut (ref eq)))))) (type $Procedure (sub $Heap (struct (field $hash (mut i32)) (field $name (ref eq)) ;; $false or a $Symbol (field $arity (ref eq)) ;; fixnum (i31 with lsb=0) ; +n means precisely n ; 0 means precisely 0 ; -1 means at least 0 ; -2 means at least 1 ; -n means at least n-1 (field $realm (ref eq)) ;; $false or $Symbol (field $invoke (ref $ProcedureInvoker))))) (type $ProcedureInvoker (func (param $proc (ref $Procedure)) (param $args (ref $Args)) ; an array of (ref eq) (result (ref eq)))) ;; Raw primitive function types (type $Prim0 (func (result (ref eq)))) (type $Prim1 (func (param (ref eq)) (result (ref eq)))) (type $Prim2 (func (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim3 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim4 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim5 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim>=0 (func (param (ref eq)) ;; list of args (result (ref eq)))) (type $Prim>=1 (func (param (ref eq)) ;; first arg (param (ref eq)) ;; rest list (result (ref eq)))) (type $Prim>=2 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) ;; rest list (result (ref eq)))) (type $Prim>=3 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) ;; rest list (result (ref eq)))) (type $Prim>=4 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) ;; rest list (result (ref eq)))) (type $Prim01 (func (param (ref eq)) (result (ref eq)))) (type $Prim02 (func (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim03 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim12 (func (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim13 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim14 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim15 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) ; Instead of introducing an $Prim16 use $Prim>=1 to reduce the number of shapes. (type $Prim23 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim24 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim25 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim34 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim35 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim67 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $Prim6 (func (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $ClosureCode (func (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)))) (type $Closure (sub $Procedure (struct ; from $Procedure (field $hash (mut i32)) (field $name (ref eq)) ;; $false or a $Symbol (field $arity (ref eq)) ;; fixnum (i31 with lsb=0) or (arity-at-least n) (field $realm (ref eq)) ;; $false or $Symbol (field $invoke (ref $ProcedureInvoker)) ; from $Closure (field $debug-id (ref eq)) ;; $false or a $Symbol used for debug lookup (field $code (ref $ClosureCode)) (field $free (ref $Free))))) (type $CaseClosure ; for case-lambda (sub $Closure (struct ;; inherited $Procedure fields (field $hash (mut i32)) (field $name (ref eq)) (field $arity (ref eq)) ;; store (ref $I32Array) here for Racket-style arity (field $realm (ref eq)) (field $invoke (ref $ProcedureInvoker)) ;; inherited $Closure fields (field $debug-id (ref eq)) (field $code (ref $ClosureCode)) ;; dispatcher (field $free (ref $Free)) ;; can be an empty array ;; new, typed payload (field $arities (ref $I32Array)) ;; markers: m>=0 exact m; m<0 at least (-m-1) (field $arms (ref $Array))))) ;; (ref eq) array of arm closures (type $PrimitiveProcedure (sub $Procedure (struct ; From $Procedure (field $hash (mut i32)) (field $name (ref eq)) (field $arity (ref eq)) (field $realm (ref eq)) (field $invoke (ref $ProcedureInvoker)) ; Function pointer for primitive implementation (field $code (ref null func)) ; other fields (field $result-arity (ref eq))))) ;; fixnum like 1 for most (type $PrimitiveClosure (sub $PrimitiveProcedure (struct ;; Inherits all fields from $PrimitiveProcedure: (field $hash (mut i32)) (field $name (ref eq)) (field $arity (ref eq)) (field $realm (ref eq)) (field $invoke (ref $ProcedureInvoker)) (field $code (ref null func)) (field $result-arity (ref eq)) ; fixnum ;; Own fields ; ... ))) ; Some structs are applicable. ; In order to make function calls fast, we make structs a subtype of $Procedure. ; Non-applicable structs gets an $invoke function that signals an error. ; The down side is that we add 4 (four!) extra fields. (type $Struct (sub $Procedure (struct ; from the procedure super-type (field $hash (mut i32)) ;; Computed lazily, starts at 0 (field $name (ref eq)) ;; $false or a $Symbol (field $arity (ref eq)) ;; fixnum (i31 with lsb=0) or (arity-at-least n) (field $realm (ref eq)) ;; $false or $Symbol (field $invoke (ref $ProcedureInvoker)) ; fields for structs (field $type (ref $StructType)) ;; Pointer to struct type descriptor (field $fields (ref $Array))))) ;; Array of (ref eq), holds field values (type $StructType (sub $Heap (struct (field $hash (mut i32)) ;; Computed lazily, starts at 0 (field $name (ref $Symbol)) ;; Struct name (field $super (ref eq)) ;; Supertype or #f (use global $false) (field $field-count i32) ;; Total number of fields (field $init-indices (ref eq)) ;; List of init field indices ($Pair or $null) (field $auto-indices (ref eq)) ;; List of auto field indices (field $auto-values (ref eq)) ;; List of values for auto fields (field $properties (ref eq)) ;; Property table: hash table (field $inspector (ref eq)) ;; Inspector object or #f (field $immutables (ref eq)) ;; Immutables descriptor or #f (field $guard (ref eq)) ;; Guard procedure or #f (field $constructor-name (ref eq))))) ;; For error reporting / printing ($Symbol or #f) (type $StructConstructorProcedure ; current representation is a plain closure (sub $Closure (struct (field $hash (mut i32)) (field $name (ref eq)) ;; $false or a $Symbol (field $arity (ref eq)) ;; fixnum (i31 with lsb=0) or (arity-at-least n) (field $realm (ref eq)) ;; $false or $Symbol (field $invoke (ref $ProcedureInvoker)) (field $debug-id (ref eq)) (field $code (ref $ClosureCode)) (field $free (ref $Free))))) (type $StructPredicateProcedure ; current representation is a plain closure (sub $Closure (struct (field $hash (mut i32)) (field $name (ref eq)) ;; $false or a $Symbol (field $arity (ref eq)) ;; fixnum (i31 with lsb=0) or (arity-at-least n) (field $realm (ref eq)) ;; $false or $Symbol (field $invoke (ref $ProcedureInvoker)) (field $debug-id (ref eq)) (field $code (ref $ClosureCode)) (field $free (ref $Free))))) (type $StructAccessorProcedure ; current representation is a plain closure (sub $Closure (struct (field $hash (mut i32)) (field $name (ref eq)) ;; $false or a $Symbol (field $arity (ref eq)) ;; fixnum (i31 with lsb=0) or (arity-at-least n) (field $realm (ref eq)) ;; $false or $Symbol (field $invoke (ref $ProcedureInvoker)) (field $debug-id (ref eq)) (field $code (ref $ClosureCode)) (field $free (ref $Free))))) (type $StructMutatorProcedure ; current representation is a plain closure (sub $Closure (struct (field $hash (mut i32)) (field $name (ref eq)) ;; $false or a $Symbol (field $arity (ref eq)) ;; fixnum (i31 with lsb=0) or (arity-at-least n) (field $realm (ref eq)) ;; $false or $Symbol (field $invoke (ref $ProcedureInvoker)) (field $debug-id (ref eq)) (field $code (ref $ClosureCode)) (field $free (ref $Free))))) (type $StructTypeProperty (sub $Heap (struct (field $hash (mut i32)) ;; Cached hash, lazily computed (field $name (ref $Symbol)) ;; Property name (field $guard-info (ref eq)) ;; Guard metadata or #f (field $supers (ref eq)) ;; List of super property descriptors (field $can-impersonate (ref eq)) ;; Boolean indicating impersonation support (field $accessor-name-info (ref eq)) ;; Accessor naming hints or #f (field $predicate-cache (mut (ref eq))) ;; Cached predicate procedure or #f (field $accessor-cache (mut (ref eq)))))) ;; Cached accessor procedure or #f (type $Number (sub $Heap ; abstract super type for boxed numbers (struct (field $hash (mut i32))))) (type $Flonum (sub $Number ; double precision (struct (field $hash (mut i32)) ; sigh (field $v f64)))) (type $Vector (sub $Heap (struct (field $hash (mut i32)) (field $immutable i32) ;; 0 or 1 (field $arr (ref $Array))))) (type $String (sub $Heap (struct (field $hash (mut i32)) (field $immutable i32) ;; 0 or 1 (field $codepoints (mut (ref $I32Array)))))) ;; An array of Unicode code points (type $Bytes (sub $Heap (struct (field $hash (mut i32)) (field $immutable i32) ;; 0 or 1 (field $bs (mut (ref $I8Array)))))) ;; An array of bytes (type $Symbol (sub $Heap (struct (field $hash (mut i32)) ;; cached hash (field $name (ref $String)) ;; symbol name (string) (field $property-list (mut (ref eq)))))) ;; user-defined properties (type $Keyword (sub $Heap (struct (field $hash (mut i32)) (field $str (ref $String))))) ; string without #: (type $Location (sub $Heap ; If line counting is not enabled, the first two are #f (struct (field $hash (mut i32)) (field $line (mut (ref eq))) ; #f or line number (fixnum) (field $col (mut (ref eq))) ; #f or column number (field $pos (mut (ref eq)))))) ; #f or position (type $Path (sub $Heap (struct (field $hash (mut i32)) ;; cached hash (field $bytes (ref $Bytes)) ;; raw byte representation (bytes) (field $convention (ref eq))))) ;; 'unix or 'windows symbol (type $Port (sub $Heap (struct (field $hash (mut i32)) (field $name (mut (ref eq))) ; the port name (string) [the object-name] (field $closed (mut i32)) ; 0 = open, 1 = closed ; buffer (if used): (field $bytes (mut (ref $Bytes))) ; the byte string (bytes) (field $len (mut i32)) ; the length of the string (field $idx (mut i32)) ; the current index into the byte string ; location (field $loc (mut (ref $Location)))))) ; the current location (type $InputPort (sub $Port (struct (field $hash (mut i32)) (field $name (mut (ref eq))) ; the port name (string) [the object-name] (field $closed (mut i32)) ; 0 = open, 1 = closed (field $bytes (mut (ref $Bytes))) ; the byte string (bytes) (field $len (mut i32)) ; the length of the string (field $idx (mut i32)) ; the current index into the byte string (field $loc (mut (ref $Location)))))) ; the current location (type $OutputPort (sub $Port (struct (field $hash (mut i32)) (field $name (mut (ref eq))) ; the port name (string) [the object-name] (field $closed (mut i32)) ; 0 = open, 1 = closed (field $bytes (mut (ref $Bytes))) ; the byte string (bytes) (field $len (mut i32)) ; the length of the string (field $idx (mut i32)) ; the current index into the byte string (field $loc (mut (ref $Location)))))) ; the current location (type $InputStringPort (sub $InputPort (struct (field $hash (mut i32)) (field $name (mut (ref eq))) ; the port name (string) (field $closed (mut i32)) ; 0 = open, 1 = closed (field $bytes (mut (ref $Bytes))) ; the byte string (bytes) (field $len (mut i32)) ; the length of the string (field $idx (mut i32)) ; the current index into the byte string (field $loc (mut (ref $Location))) ; the current location ;; UTF-8 decoder state: (field $utf8-len (mut i32)) ;; 0 = idle, 1-4 = number of bytes expected (field $utf8-left (mut i32)) ;; number of continuation bytes still needed (field $utf8-bytes (mut i32))))) ;; current byte count seen (for column fix) (type $OutputStringPort (sub $OutputPort (struct (field $hash (mut i32)) (field $name (mut (ref eq))) ; the port name (string) (field $closed (mut i32)) ; 0 = open, 1 = closed (field $bytes (mut (ref $Bytes))) ; the byte string (bytes) (field $len (mut i32)) ; the length of the string (field $idx (mut i32)) ; the current index into the string (field $loc (mut (ref $Location))) ; the current location ;; UTF-8 decoder state: (field $utf8-len (mut i32)) ;; 0 = idle, 1-4 = number of bytes expected (field $utf8-left (mut i32)) ;; number of continuation bytes still needed (field $utf8-bytes (mut i32))))) ;; current byte count seen (for column fix) (type $OutputFilePort (sub $OutputStringPort (struct (field $hash (mut i32)) (field $name (mut (ref eq))) (field $closed (mut i32)) (field $bytes (mut (ref $Bytes))) (field $len (mut i32)) (field $idx (mut i32)) (field $loc (mut (ref $Location))) (field $utf8-len (mut i32)) (field $utf8-left (mut i32)) (field $utf8-bytes (mut i32)) (field $path (ref $Path))))) (type $CustomInputPort (sub $InputPort (struct (field $hash (mut i32)) (field $name (mut (ref eq))) ; the port name (any/c) (field $closed (mut i32)) ; 0 = open, 1 = closed (field $bytes (mut (ref $Bytes))) ; scratch buffer (bytes) (field $len (mut i32)) ; unused length placeholder (field $idx (mut i32)) ; unused index placeholder (field $loc (mut (ref $Location))) ; the current location (field $read-proc (mut (ref eq))) ; required read-in argument (field $peek-proc (mut (ref eq))) ; required peek argument (field $close-proc (mut (ref eq))) ; required close argument (field $get-progress-evt (mut (ref eq))) ; optional, default = #f (field $commit-proc (mut (ref eq))) ; optional, default = #f (field $get-location-proc (mut (ref eq))) ; optional, default = #f (field $count-lines-proc (mut (ref eq))) ; optional, default = void (field $init-position-arg (mut (ref eq))) ; optional, default = 1 (field $buffer-mode-arg (mut (ref eq)))))) ; optional, default = #f (type $Hash ; abstract super type for hashtables (sub $Heap (struct (field $hash (mut i32)) (field $mutable? (ref eq))))) ; boolean (immediate, i31 tagged) (type $HashEq (sub $Hash (struct (field $hash (mut i32)) (field $mutable? (ref eq))))) (type $HashEqv (sub $Hash (struct (field $hash (mut i32)) (field $mutable? (ref eq))))) (type $HashEqual (sub $Hash (struct (field $hash (mut i32)) (field $mutable? (ref eq))))) (type $HashEqualAlways (sub $Hash (struct (field $hash (mut i32)) (field $mutable? (ref eq))))) (type $HashEqMutable ; Mutable hash tables are implemented as an open-addressing hash table ; with linear probing. (sub $HashEq (struct (field $hash (mut i32)) (field $mutable? (ref eq)) (field $entries (mut (ref $Array))) ;; flat array: key0, val0, key1, val1, ... (field $count (mut i32))))) ;; number of key/value pairs currently stored (type $HashEqvMutable ; Mutable hash tables are implemented as an open-addressing hash table ; with linear probing. (sub $HashEqv (struct (field $hash (mut i32)) (field $mutable? (ref eq)) (field $entries (mut (ref $Array))) ;; flat array: key0, val0, key1, val1, ... (field $count (mut i32))))) ;; number of key/value pairs currently stored (type $HashEqualMutable ; Mutable hash tables are implemented as an open-addressing hash table ; with linear probing. (sub $HashEqual (struct (field $hash (mut i32)) (field $mutable? (ref eq)) (field $entries (mut (ref $Array))) ;; flat array: key0, val0, key1, val1, ... (field $count (mut i32))))) ;; number of key/value pairs currently stored (type $HashEqualAlwaysMutable ; Mutable hash tables are implemented as an open-addressing hash table ; with linear probing. (sub $HashEqualAlways (struct (field $hash (mut i32)) (field $mutable? (ref eq)) (field $entries (mut (ref $Array))) ;; flat array: key0, val0, key1, val1, ... (field $count (mut i32))))) ;; number of key/value pairs currently stored (type $VariableReference ; opaque value returned by #%variable-reference ; Minimal payload needed by variable-reference predicates. (sub $Heap (struct (field $hash (mut i32)) (field $constant? (ref eq)) (field $from-unsafe? (ref eq)) ))) (type $External (sub $Heap (struct (field $hash (mut i32)) (field $v (ref null extern))))) ;; (Placeholder) module registry (type $ModuleRegistry (sub $Heap (struct (field $hash (mut i32)) (field $table (mut (ref $Array)))))) ; A namespace has a module registry. ; The registry maps a module names to module declarations. ; The registry is shared by all phases. ; The namespace holds a distinct set of top-level variables for each phase. ; Module instances are (can be) distinct for each phase. ; Each namespace has a base phase. ; The base phase is used by `eval` and `dynamic-require`. ; The first step in evaluating any compiled expression is to ; link its top-level variable and module-level variable references to ; specific variables in the namespace. ; At all times during evaluation, some namespace is designated as the current namespace. ; How does provide and require work? ; A module body is executed only when the module is ; explicitly instantiated via require or dynamic-require. ; On invocation, imported modules are instantiated in the order in which ; they are required into the module (although earlier instantiations or ; transitive requires can trigger the instantiation of a module before ; its order within a given module). ; Then, expressions and definitions ; are evaluated in order as they appear within the module. ; - for each module we need an $module::instantiate ; - allocate a $ModuleInstance ; - require modules ; - setup module level variables ; - evaluate expressions and definitions ;; Namespace now maps Symbol -> Boxed via a single hasheq/mutable table (type $Namespace (sub $Heap (struct (field $hash (mut i32)) (field $name (ref eq)) ;; #f or $String (field $base-phase i32) (field $table (mut (ref $HashEqMutable))) ;; hasheq: Symbol → Boxed (field $modules (mut (ref $ModuleRegistry))) (field $protect (mut i32))))) ;; A builder that accumulates arguments to be serialized for the host. (type $FaslBuilder (sub $Heap (struct (field $hash (mut i32)) ;; lazy, start at 0 (field $strings (ref $GrowableArray)) ;; growable of (ref $String) (field $values (ref $GrowableArray))))) ;; growable of (ref eq) ;; Linklet instance structure (type $Instance (sub $Heap (struct (field $hash (mut i32)) (field $name (ref eq)) ;; any value for debugging (field $data (ref eq)) ;; any value (e.g., namespace) (field $variables (mut (ref $HashEqMutable))) ;; hasheq: Symbol → Box (field $constants (mut (ref $HashEqMutable)))))) ;; hasheq: Symbol → #t (type $Linklet (sub $Heap (struct (field $hash (mut i32)) (field $name (ref eq)) ; #f or a symbol (field $importss (ref eq)) ; (listof (listof symbol?)) (field $exports (ref eq)) ; (listof symbol?) ))) ; A compiled linklet is a procedure `proc` that as arguments ; take an self-instance and the import instances. ; The arity is the same as the length of `importss` plus one (due to the self-instance). ; Each sublist of `importss` is a list of symbols imported from that instance. ; The field `exports` is a list of symbols to be exported. ; Calling `proc` will run the body of the linklet. (type $CompiledLinklet (sub $Linklet (struct (field $hash (mut i32)) (field $name (ref eq)) ; #f or a symbol (field $importss (ref eq)) ; (listof (listof symbol?)) (field $exports (ref eq)) ; (listof symbol?) (field $proc (ref eq)) ; takes self instance plus instance arguments ))) ;; WebRacket currently stores mutable hasheq tables directly in ;; linklet bundle/directory wrappers. (type $LinkletBundle (sub $Heap (struct (field $hash (mut i32)) (field $content (ref $HashEqMutable))))) (type $LinkletDirectory (sub $Heap (struct (field $hash (mut i32)) (field $content (ref $HashEqMutable))))) (type $UnquotedPrintingString (sub $Heap (struct (field $hash (mut i32)) (field $value (ref eq))))) ) ; rec ;;; ;;; MEMORY ;;; (import "env" "memory" (memory $memory 1024)) ;;; ;;; IMPORTS ;;; ; Imported functions from the host (JavaScript). ; Note all imports must appear before other function definitions. (func $js_output (import "primitives" "js_output") (param i32)) (func $js_print_fasl (import "primitives" "js_print_fasl") (param i32) (param i32)) (func $js-vfs-stat-kind (import "primitives" "vfs_stat_kind") (param i32) (param i32) (result i32)) (func $js-vfs-file-size (import "primitives" "vfs_file_size") (param i32) (param i32) (result i32)) (func $js-vfs-stat (import "primitives" "vfs_stat") (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-read-file (import "primitives" "vfs_read_file") (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-write-file (import "primitives" "vfs_write_file") (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-delete-file (import "primitives" "vfs_delete_file") (param i32) (param i32) (result i32)) (func $js-vfs-delete-directory (import "primitives" "vfs_delete_directory") (param i32) (param i32) (result i32)) (func $js-vfs-delete-directory/files (import "primitives" "vfs_delete_directory_files") (param i32) (param i32) (result i32)) (func $js-vfs-make-directory (import "primitives" "vfs_make_directory") (param i32) (param i32) (result i32)) (func $js-vfs-make-directory* (import "primitives" "vfs_make_directory_star") (param i32) (param i32) (result i32)) (func $js-vfs-make-parent-directory* (import "primitives" "vfs_make_parent_directory_star") (param i32) (param i32) (result i32)) (func $js-vfs-list-directory (import "primitives" "vfs_list_directory") (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-root-list (import "primitives" "vfs_root_list") (param i32) (param i32) (result i32)) (func $js-vfs-rename (import "primitives" "vfs_rename") (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-copy-file (import "primitives" "vfs_copy_file") (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-copy-directory/files (import "primitives" "vfs_copy_directory_files") (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-modify-seconds (import "primitives" "vfs_modify_seconds") (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-permissions (import "primitives" "vfs_permissions") (param i32) (param i32) (param i32) (param i32) (result i32)) (func $js-vfs-identity (import "primitives" "vfs_identity") (param i32) (param i32) (result i32)) (func $char-upcase/ucs (import "primitives" "char_upcase") (param i32) (result i32)) (func $char-downcase/ucs (import "primitives" "char_downcase") (param i32) (result i32)) (func $char-titlecase/ucs (import "primitives" "char_titlecase") (param i32) (result i32)) (func $char-foldcase/ucs (import "primitives" "char_foldcase") (param i32) (result i32)) ;; Host provides an approximate grapheme break property mapping. ;; It currently covers only a subset of Unicode ranges. (func $char-grapheme-break-property/ucs (import "primitives" "char_grapheme_break_property") (param i32) (result i32)) (func $char-general-category/ucs (import "primitives" "char_general_category") (param i32) (result i32)) (func $char-alphabetic?/ucs (import "primitives" "char_alphabetic") (param i32) (result i32)) (func $char-lower-case?/ucs (import "primitives" "char_lower_case") (param i32) (result i32)) (func $char-upper-case?/ucs (import "primitives" "char_upper_case") (param i32) (result i32)) (func $char-title-case?/ucs (import "primitives" "char_title_case") (param i32) (result i32)) (func $char-numeric?/ucs (import "primitives" "char_numeric") (param i32) (result i32)) (func $char-symbolic?/ucs (import "primitives" "char_symbolic") (param i32) (result i32)) (func $char-punctuation?/ucs (import "primitives" "char_punctuation") (param i32) (result i32)) (func $char-graphic?/ucs (import "primitives" "char_graphic") (param i32) (result i32)) (func $char-extended-pictographic?/ucs (import "primitives" "char_extended_pictographic") (param i32) (result i32)) ;; Math functions (func $js-math-abs (import "math" "abs") (param f64) (result f64)) (func $js-math-acos (import "math" "acos") (param f64) (result f64)) (func $js-math-acosh (import "math" "acosh") (param f64) (result f64)) (func $js-math-asin (import "math" "asin") (param f64) (result f64)) (func $js-math-asinh (import "math" "asinh") (param f64) (result f64)) (func $js-math-atan (import "math" "atan") (param f64) (result f64)) (func $js-math-atan2 (import "math" "atan2") (param f64) (param f64) (result f64)) (func $js-math-atanh (import "math" "atanh") (param f64) (result f64)) (func $js-math-cbrt (import "math" "cbrt") (param f64) (result f64)) (func $js-math-ceil (import "math" "ceil") (param f64) (result f64)) (func $js-math-clz32 (import "math" "clz32") (param i32) (result i32)) (func $js-math-cos (import "math" "cos") (param f64) (result f64)) (func $js-math-cosh (import "math" "cosh") (param f64) (result f64)) (func $js-math-exp (import "math" "exp") (param f64) (result f64)) (func $js-math-expm1 (import "math" "expm1") (param f64) (result f64)) (func $js-math-floor (import "math" "floor") (param f64) (result f64)) (func $js-math-fround (import "math" "fround") (param f64) (result f64)) (func $js-math-hypot (import "math" "hypot") (param f64) (param f64) (result f64)) (func $js-math-imul (import "math" "imul") (param i32) (param i32) (result i32)) (func $js-math-log (import "math" "log") (param f64) (result f64)) (func $js-math-log10 (import "math" "log10") (param f64) (result f64)) (func $js-math-log1p (import "math" "log1p") (param f64) (result f64)) (func $js-math-log2 (import "math" "log2") (param f64) (result f64)) (func $js-math-max (import "math" "max") (param f64) (param f64) (result f64)) (func $js-math-min (import "math" "min") (param f64) (param f64) (result f64)) (func $js-math-pow (import "math" "pow") (param f64) (param f64) (result f64)) (func $js-math-random (import "math" "random") (result f64)) (func $js-math-round (import "math" "round") (param f64) (result f64)) (func $js-math-sign (import "math" "sign") (param f64) (result f64)) (func $js-math-sin (import "math" "sin") (param f64) (result f64)) (func $js-math-sinh (import "math" "sinh") (param f64) (result f64)) (func $js-math-sqrt (import "math" "sqrt") (param f64) (result f64)) (func $js-math-tan (import "math" "tan") (param f64) (result f64)) (func $js-math-tanh (import "math" "tanh") (param f64) (result f64)) (func $js-math-trunc (import "math" "trunc") (param f64) (result f64)) (func $js-make-callback (import "primitives" "make_callback") (param i32) (result (ref extern))) (func $js-register-external (import "primitives" "register_external") (param (ref extern)) (result i32)) (func $js-lookup-external (import "primitives" "lookup_external") (param i32) (result (ref extern))) (func $js-external-number->f64 (import "primitives" "external_number_to_f64") (param (ref extern)) (result f64)) (func $js-flonum->string (import "primitives" "flonum_to_string") (param f64) (result i32)) (func $js-external-string->string (import "primitives" "external_string_to_string") (param externref) (result i32)) ;; Predicates used by define-foreign return-type adapters. (func $js-external-nullish? (import "primitives" "external_nullish") (param externref) (result i32)) (func $js-external-undefined? (import "primitives" "external_undefined") (param externref) (result i32)) ;; FFI host exception tag imported from the host runtime. ;; Foreign imports can throw this tag to communicate host-language ;; errors that must become ordinary WebRacket exceptions. (tag $ffi-host-exn (import "primitives" "foreign_error_tag") (param externref)) ;; FFI related imports ,@active-ffi-imports-wat ; generated from "driver.rkt" in "define-foreign.rkt" ,@active-ffi-funcs-wat ; generated from "driver.rkt" in "define-foreign.rkt" ;;; ;;; Exceptions ;;; (func $raise-expected-string (unreachable)) (func $raise-unexpected-argument (unreachable)) (func $raise-wrong-number-of-values-received (unreachable)) (func $expect-zero-values (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Values) (local.get $v)) (then (if (i32.eqz (i32.eq (array.len (ref.cast (ref $Values) (local.get $v))) (i32.const 0))) (then (call $raise-wrong-number-of-values-received) (unreachable))) (global.get $void)) (else (call $raise-wrong-number-of-values-received) (unreachable)))) (func $expect-one-value (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Values) (local.get $v)) (then (if (i32.eqz (i32.eq (array.len (ref.cast (ref $Values) (local.get $v))) (i32.const 1))) (then (call $raise-wrong-number-of-values-received) (unreachable))) (array.get $Values (ref.cast (ref $Values) (local.get $v)) (i32.const 0))) (else (local.get $v)))) (func $expect-n-values (param $v (ref eq)) (param $n i32) (result (ref $Values)) (if (result (ref $Values)) (ref.test (ref $Values) (local.get $v)) (then (if (i32.eqz (i32.eq (array.len (ref.cast (ref $Values) (local.get $v))) (local.get $n))) (then (call $raise-wrong-number-of-values-received) (unreachable))) (ref.cast (ref $Values) (local.get $v))) (else (call $raise-wrong-number-of-values-received) (unreachable)))) ;; Convert a host exception caught at an FFI boundary into a ;; regular WebRacket exn:fail so `with-handlers` can catch it. (func $raise-ffi-host-exception (param $host externref) (local $message (ref eq)) (local.set $message (call $linear-memory->string (call $js-external-string->string (local.get $host)))) (drop (call $raise (call $make-exn:fail (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true))) (unreachable)) ;; raise-input-port-closed : string? -> none ;; Raise an exn:fail for an operation on a closed input port. (func $raise-input-port-closed (param $message (ref eq)) (drop (call $raise (call $make-exn:fail (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true))) (unreachable)) ;; raise-output-port-closed : string? -> none ;; Raise an exn:fail for an operation on a closed output port. (func $raise-output-port-closed (param $message (ref eq)) (drop (call $raise (call $make-exn:fail (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true))) (unreachable)) ;; raise-vfs-file-error : string? -> none ;; Raise a catchable exn:fail:filesystem for VFS filesystem errors. (func $raise-vfs-file-error (param $message (ref eq)) (drop (call $raise (call $make-exn:fail:filesystem (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true))) (unreachable)) (func $raise-argument-error1 (param $who (ref eq)) ;; symbol (param $expected (ref eq)) ;; expected description (param $got (ref eq)) ;; received value (local $out (ref $GrowableArray)) (local $message (ref $String)) (local.set $out (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $out) (call $format/display (local.get $who))) (call $growable-array-add! (local.get $out) (global.get $string:contract-violation:prefix)) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (local.get $expected))) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:given)) (call $growable-array-add! (local.get $out) (call $format/display (local.get $got))) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) ;; Unquoted printing strings wrap a string but should display without ;; additional quoting, matching the behavior of the underlying string. (func $unquoted-printing-string? (type $Prim1) ,@(make-predicate-body '$UnquotedPrintingString)) (func $unquoted-printing-string (type $Prim1) (param $s (ref eq)) ;; string? (result (ref eq)) ;; unquoted-printing-string? (local $str (ref $String)) (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-argument-error:string-expected (local.get $s)) (unreachable))) (local.set $str (ref.cast (ref $String) (local.get $s))) (struct.new $UnquotedPrintingString (i32.const 0) (local.get $str))) (func $raise-argument-error:unquoted-printing-string-expected (unreachable)) (func $unquoted-printing-string-value (type $Prim1) (param $ups (ref eq)) ;; unquoted-printing-string? (result (ref eq)) ;; string? (local $wrapped (ref $UnquotedPrintingString)) (if (i32.eqz (ref.test (ref $UnquotedPrintingString) (local.get $ups))) (then (call $raise-argument-error:unquoted-printing-string-expected (local.get $ups)) (unreachable))) (local.set $wrapped (ref.cast (ref $UnquotedPrintingString) (local.get $ups))) (struct.get $UnquotedPrintingString $value (local.get $wrapped))) ;;; ;;; Singletons ;;; (global $null (ref eq) ,(Imm '())) (global $undefined (ref eq) ,(Imm (undefined))) ; 79 (global $unsafe-undefined (ref eq) ,(Imm (unsafe-undefined))) ; 335 (global $void (ref eq) ,(Imm (void))) (global $false (ref eq) ,(Imm #f)) ; (ref.i31 (i32.const ?)) (global $true (ref eq) ,(Imm #t)) (global $eof (ref eq) ,(Imm eof)) (global $error (ref eq) ,(R 77)) (global $missing (ref eq) ,(R missing-value)) ; #x7fffffff (global $tombstone (ref eq) ,(R tombstone-value)) ; #x3fffffff" ;; Commonly used fixnums (global $zero (ref eq) ,(Imm 0)) (global $one (ref eq) ,(Imm 1)) (global $two (ref eq) ,(Imm 2)) (global $three (ref eq) ,(Imm 3)) ;; Commonly used flonums ;; - initialized in $entry (global $flzero (mut (ref eq)) ,(Undefined)) (global $flone (mut (ref eq)) ,(Undefined)) (global $fltwo (mut (ref eq)) ,(Undefined)) (global $flthree (mut (ref eq)) ,(Undefined)) ;; String constants used in the runtime ,@(declare-runtime-string-constants) ;; Bytes constants used in the runtime ,@(declare-runtime-bytes-constants) ;; Symbol constants used in the runtime ,@(declare-runtime-symbol-constants) ;; The function $char-general-category returns symbols from this array, ;; based on an index computed on the host. (global $char-general-category-symbols (mut (ref null $Array)) (ref.null $Array)) ;; Commonly used realms (global $the-racket-realm (mut (ref eq)) ,(Undefined)) ; the symbol 'racket (global $the-racket/primitive-realm (mut (ref eq)) ,(Undefined)) ; the symbol 'racket/primitive ;; Cached kernel exception struct type descriptors (global $exn-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:contract-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:contract:arity-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:contract:divide-by-zero-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:contract:non-fixnum-result-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:contract:variable-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:read-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:read:eof-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:read:non-char-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:filesystem-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:syntax-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:syntax:missing-module-type (mut (ref null $StructType)) (ref.null $StructType)) (global $exn:fail:syntax:unbound-type (mut (ref null $StructType)) (ref.null $StructType)) ;; Cached srcloc struct type descriptor (global $srcloc-type (mut (ref null $StructType)) (ref.null $StructType)) ;; Cached arity-at-least struct type descriptor (global $arity-at-least-type (mut (ref null $StructType)) (ref.null $StructType)) ;; Cached correlated struct type descriptor (global $correlated-type (mut (ref null $StructType)) (ref.null $StructType)) ;; Shared empty correlated properties table (global $correlated-empty-props (mut (ref eq)) ,(Undefined)) ;; Cached syntax struct type descriptor (global $syntax-type (mut (ref null $StructType)) (ref.null $StructType)) ;; Shared empty syntax properties table (global $syntax-empty-props (mut (ref eq)) ,(Undefined)) ;; Module Registry (global $empty-module-registry (ref $ModuleRegistry) (struct.new $ModuleRegistry (i32.const 0) (array.new $Array (global.get $null) (i32.const 0)))) ;; Namespaces (global $top-level-namespace (mut (ref null $Namespace)) (ref.null $Namespace)) ;; Callback registry (global $callback-registry (ref $GrowableArray) (struct.new $GrowableArray (array.new $Array (global.get $false) (i32.const 4)) (i32.const 4) (i32.const 0))) ;; Primitives (as values) ,@(declare-primitives-as-globals) ,@(declare-ffi-primitives-as-globals) ;; Closures ; Closures with no free variables can share an empty array (global $empty-free (ref $Free) (array.new $Free (global.get $null) (i32.const 0))) ;; Closure invocation - invoke #;(type $ProcedureInvoker (func (param $proc (ref $Procedure)) (param $args (ref $Args)) ; an array of (ref eq) (result (ref eq)))) (func $raise-arity-mismatch (unreachable)) (func $procedure-arity->expected-string (param $proc (ref $Procedure)) (result (ref $String)) (local $a (ref eq)) (local $arity/tag i32) (local $arity i32) (local.set $a (struct.get $Procedure $arity (local.get $proc))) (if (ref.test (ref i31) (local.get $a)) (then (local.set $arity/tag (i31.get_s (ref.cast (ref i31) (local.get $a)))) (local.set $arity (i32.shr_s (local.get $arity/tag) (i32.const 1))) (return (call $arity-i32->string (local.get $arity))))) (ref.cast (ref $String) (call $format/display (call $procedure-arity (local.get $proc))))) (func $raise-arity-mismatch/proc (param $proc (ref $Procedure)) (param $argc i32) (local $name (ref eq)) (local $expected-str (ref $String)) (local $received-str (ref $String)) (local $out (ref $GrowableArray)) (local $message (ref $String)) (local.set $name (struct.get $Procedure $name (local.get $proc))) (local.set $expected-str (call $procedure-arity->expected-string (local.get $proc))) (local.set $received-str (call $i32->string (local.get $argc))) (local.set $out (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $out) (call $format/display (local.get $name))) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:start)) (call $growable-array-add! (local.get $out) (local.get $expected-str)) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:given)) (call $growable-array-add! (local.get $out) (local.get $received-str)) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract:arity/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) (func $raise-arity-mismatch/name+given (param $name (ref eq)) (param $argc i32) (local $received-str (ref $String)) (local $out (ref $GrowableArray)) (local $message (ref $String)) ;; TODO: include "arguments..." block like Racket. (local.set $received-str (call $i32->string (local.get $argc))) (local.set $out (call $make-growable-array (i32.const 3))) (call $growable-array-add! (local.get $out) (call $format/display (local.get $name))) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:start/no-expected)) (call $growable-array-add! (local.get $out) (local.get $received-str)) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract:arity/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) (func $raise-application-not-procedure (param $v (ref eq)) (local $out (ref $GrowableArray)) (local $message (ref $String)) (local.set $out (call $make-growable-array (i32.const 2))) (call $growable-array-add! (local.get $out) (global.get $string:application:not-procedure)) (call $growable-array-add! (local.get $out) (call $format/display (local.get $v))) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) (func $raise-apply-final-arg-not-list (param $v (ref eq)) (local $out (ref $GrowableArray)) (local $message (ref $String)) ;; TODO: include "argument position: last" and "other arguments..." block like Racket. (local.set $out (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $out) (call $format/display (global.get $symbol:apply))) (call $growable-array-add! (local.get $out) (global.get $string:contract-violation:prefix)) (call $growable-array-add! (local.get $out) (global.get $string:list?)) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:given)) (call $growable-array-add! (local.get $out) (call $format/display (local.get $v))) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) (func $raise-struct-type-property-accessor-contract (param $prop (ref $StructTypeProperty)) (param $v (ref eq)) (local $accessor-name (ref $String)) (local $prop-name-str (ref $String)) (local $expected-str (ref $String)) (local $out (ref $GrowableArray)) (local $message (ref $String)) (local.set $prop-name-str (ref.cast (ref $String) (call $symbol->string (struct.get $StructTypeProperty $name (local.get $prop))))) (local.set $accessor-name (call $string-append/2 (local.get $prop-name-str) (global.get $string:accessor-suffix))) (local.set $expected-str (call $string-append/2 (local.get $prop-name-str) (global.get $string:question))) (local.set $out (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $out) (local.get $accessor-name)) (call $growable-array-add! (local.get $out) (global.get $string:contract-violation:prefix)) (call $growable-array-add! (local.get $out) (local.get $expected-str)) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:given)) (call $growable-array-add! (local.get $out) (call $format/display (local.get $v))) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) (func $invoke-closure (type $ProcedureInvoker) (param $proc (ref $Procedure)) (param $args (ref $Args)) ;; array of (ref eq) (result (ref eq)) (local $clos (ref $Closure)) (local $code (ref $ClosureCode)) (local $arity-i31 (ref i31)) (local $arity-i32 i32) (local $arg-count i32) (local $args-repacked (ref $Args)) ;; Step 1: cast proc to closure and extract code (local.set $clos (ref.cast (ref $Closure) (local.get $proc))) (local.set $code (struct.get $Closure $code (local.get $clos))) ;; Step 2: get arity as signed i32 (local.set $arity-i31 (ref.cast (ref i31) (struct.get $Procedure $arity (local.get $clos)))) (local.set $arity-i32 (i32.shr_s (i31.get_s (local.get $arity-i31)) (i32.const 1))) ;; Step 3: get argument count (local.set $arg-count (array.len (local.get $args))) ;; Debug: log argument count and expected arity ; (drop (call $js-log (local.get $proc))) ;; Step 4: check arity match (if (i32.eqz (call $procedure-arity-includes?/checked/i32 (local.get $clos) (local.get $arg-count))) (then (call $raise-arity-mismatch/proc (local.get $clos) (local.get $arg-count)))) ;; Step 5: repack arguments (if variadic) (local.set $args-repacked (call $repack-arguments (local.get $args) (local.get $arity-i32))) ;; Step 6: invoke (return_call_ref $ClosureCode (local.get $clos) (local.get $args-repacked) (local.get $code))) (func $invoke-reduced-procedure (type $ProcedureInvoker) (param $proc (ref $Procedure)) (param $args (ref $Args)) (result (ref eq)) (local $wrapper (ref $Closure)) (local $original (ref $Procedure)) (local $arg-count i32) (local.set $wrapper (ref.cast (ref $Closure) (local.get $proc))) (local.set $original (ref.cast (ref $Procedure) (array.get $Free (struct.get $Closure $free (local.get $wrapper)) (i32.const 0)))) (local.set $arg-count (array.len (local.get $args))) (if (i32.eqz (call $procedure-arity-includes?/checked/i32 (local.get $wrapper) (local.get $arg-count))) (then (call $raise-arity-mismatch/proc (local.get $wrapper) (local.get $arg-count)))) (return_call_ref $ProcedureInvoker (local.get $original) (local.get $args) (struct.get $Procedure $invoke (local.get $original)))) ;; invoke-composed-procedure : procedure? args-array? -> any ;; Invoke a compose/compose1 wrapper from right to left. (func $invoke-composed-procedure (type $ProcedureInvoker) (param $proc (ref $Procedure)) (param $args (ref $Args)) (result (ref eq)) (local $wrapper (ref $Closure)) (local $free (ref $Free)) (local $procs (ref $Array)) (local $single? (ref eq)) (local $p (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $result (ref eq)) (local $vals (ref $Values)) (local $next (ref $Args)) (local $i i32) (local $n i32) (local $m i32) (local.set $p (global.get $dummy-closure)) (local.set $inv (ref.func $invoke-closure)) (local.set $result (global.get $false)) (local.set $vals (array.new $Values (global.get $null) (i32.const 0))) (local.set $next (array.new $Args (global.get $null) (i32.const 0))) (local.set $wrapper (ref.cast (ref $Closure) (local.get $proc))) (local.set $free (struct.get $Closure $free (local.get $wrapper))) (local.set $procs (ref.cast (ref $Array) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $single? (array.get $Free (local.get $free) (i32.const 1))) (local.set $n (array.len (local.get $procs))) (if (i32.eqz (local.get $n)) (then (return_call $values (local.get $args)))) (local.set $i (i32.sub (local.get $n) (i32.const 1))) (local.set $p (ref.cast (ref $Procedure) (array.get $Array (local.get $procs) (local.get $i)))) (local.set $inv (struct.get $Procedure $invoke (local.get $p))) (local.set $result (call_ref $ProcedureInvoker (local.get $p) (local.get $args) (local.get $inv))) (block $done (loop $loop (br_if $done (i32.eqz (local.get $i))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (if (ref.eq (local.get $single?) (global.get $true)) (then (local.set $result (call $expect-one-value (local.get $result))) (local.set $next (array.new_fixed $Args 1 (local.get $result)))) (else (if (ref.test (ref $Values) (local.get $result)) (then (local.set $vals (ref.cast (ref $Values) (local.get $result))) (local.set $m (array.len (local.get $vals))) (local.set $next (array.new $Args (global.get $null) (local.get $m))) (array.copy $Args $Values (local.get $next) (i32.const 0) (local.get $vals) (i32.const 0) (local.get $m))) (else (local.set $next (array.new_fixed $Args 1 (local.get $result))))))) (local.set $p (ref.cast (ref $Procedure) (array.get $Array (local.get $procs) (local.get $i)))) (local.set $inv (struct.get $Procedure $invoke (local.get $p))) (local.set $result (call_ref $ProcedureInvoker (local.get $p) (local.get $next) (local.get $inv))) (br $loop))) (local.get $result)) ;; Invoker for case-lambda closures. ;; - $args is the vector of *user* arguments (no [closure, tail?] header). ;; - Arity checking + repacking are handled by the closure's code ;; ($code:case-lambda-dispatch), so we just tail-call it. ; Note: In principle we could use $invoke-closure, ; but that leads to checking the arity multiple times. (func $invoke-case-closure (type $ProcedureInvoker) (param $proc (ref $Procedure)) (param $args (ref $Args)) (result (ref eq)) (local $clos (ref $Closure)) (local $code (ref $ClosureCode)) ;; Cast and fetch code pointer (local.set $clos (ref.cast (ref $Closure) (local.get $proc))) (local.set $code (struct.get $Closure $code (local.get $clos))) ;; Tail-call the dispatcher (return_call_ref $ClosureCode (local.get $clos) (local.get $args) (local.get $code))) ;; Primitive procedure invocation helpers (func $raise-no-code (param $pproc (ref $PrimitiveProcedure)) (result (ref eq)) (unreachable)) (func $raise-code-type-mismatch (param $pproc (ref $PrimitiveProcedure)) (result (ref eq)) (call $js-log (call $format/display (local.get $pproc))) (unreachable)) (func $primitive-invoke:raise-arity-error (param $pproc (ref $PrimitiveProcedure)) (param $argc i32) (result (ref eq)) (local $name (ref eq)) (local $expected-str (ref $String)) (local $received-str (ref $String)) (local $out (ref $GrowableArray)) (local $message (ref $String)) (local.set $name (struct.get $Procedure $name (local.get $pproc))) (local.set $expected-str (call $procedure-arity->expected-string (local.get $pproc))) (local.set $received-str (call $i32->string (local.get $argc))) (local.set $out (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $out) (call $format/display (local.get $name))) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:start)) (call $growable-array-add! (local.get $out) (local.get $expected-str)) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:given)) (call $growable-array-add! (local.get $out) (local.get $received-str)) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract:arity/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) ;; Shape codes: ;; 0: exact 0 ;; 1: exact 1 ;; 2: exact 2 ;; 3: exact 3 ;; 4: exact 4 ;; 5: exact 5 ;; 6: at least 0 ;; 7: at least 1 ;; 8: at least 2 ;; 9: at least >=3 ;; 10-13: like 6-9 but pass rest arguments as $Args arrays ;; 14: between 2 and 3 arguments (3rd optional) ;; 15: between 3 and 4 arguments (4th optional) ;; 16: between 0 and 1 arguments ;; 17: between 0 and 2 arguments ;; 18: between 1 and 2 arguments ;; 19: between 1 and 3 arguments ;; 20: between 1 and 4 arguments ;; 21: between 1 and 5 arguments ;; 22: between 2 and 4 arguments ;; 23: between 2 and 5 arguments ;; 24: between 3 and 5 arguments ;; 25: between 6 and 7 arguments (func $raise-primitive-invoke:unhandled-shape (unreachable)) (func $primitive-invoke (type $ProcedureInvoker) (param $proc (ref $Procedure)) (param $args (ref $Args)) (result (ref eq)) (local $pproc (ref $PrimitiveProcedure)) (local $code (ref null func)) (local $arity/tag i32) (local $arity i32) (local $shape i32) (local $k i32) (local $argc i32) (local $a0 (ref eq)) (local $a1 (ref eq)) (local $a2 (ref eq)) (local $a3 (ref eq)) (local $a4 (ref eq)) (local $rest (ref eq)) (local.set $a0 (global.get $null)) (local.set $a1 (global.get $null)) (local.set $a2 (global.get $null)) ; (local.set $a3 (global.get $null)) ; (local.set $a4 (global.get $null)) (local.set $rest (global.get $null)) ;; Proc -> PrimitiveProcedure (local.set $pproc (ref.cast (ref $PrimitiveProcedure) (local.get $proc))) ;; Fetch code (local.set $code (struct.get $PrimitiveProcedure $code (local.get $pproc))) ;; Ensure non-null (if (ref.is_null (local.get $code)) (then (return (call $raise-no-code (local.get $pproc))))) ;; argc (local.set $argc (array.len (local.get $args))) ;; Decode arity fixnum: i31 -> i32, then >> 1 (local.set $arity/tag (i31.get_s ; signed due to negative arities (ref.cast (ref i31) (struct.get $Procedure $arity (local.get $pproc))))) (local.set $arity (i32.shr_s (local.get $arity/tag) (i32.const 1))) ;; Compute shape (and k for "at least k") (if (i32.ge_s (local.get $arity) (i32.const 0)) (then ;; exact: shape = min(arity, 5), except for 6 -> 26 (local.set $shape (local.get $arity)) (if (i32.gt_u (local.get $shape) (i32.const 5)) (then (if (i32.eq (local.get $shape) (i32.const 6)) (then (local.set $shape (i32.const 26))) (else (local.set $shape (i32.const 5))))))) (else ;; at least: k = -arity - 1; shape = 6 + min(k, 3) (local.set $k (i32.sub (i32.const -1) (local.get $arity))) (local.set $shape (local.get $k)) (if (i32.gt_u (local.get $shape) (i32.const 3)) (then (local.set $shape (i32.const 3)))) (local.set $shape (i32.add (i32.const 6) (local.get $shape))))) ;; Debug: log argc, arity, shape ;; (drop (call $js-log (call $i32->string (local.get $argc)))) ;; (drop (call $js-log (call $i32->string (local.get $arity)))) ;; (drop (call $js-log (call $i32->string (local.get $shape)))) ;; Preload first five args when available (if (i32.gt_u (local.get $argc) (i32.const 0)) (then (local.set $a0 (array.get $Args (local.get $args) (i32.const 0))))) (if (i32.gt_u (local.get $argc) (i32.const 1)) (then (local.set $a1 (array.get $Args (local.get $args) (i32.const 1))))) (if (i32.gt_u (local.get $argc) (i32.const 2)) (then (local.set $a2 (array.get $Args (local.get $args) (i32.const 2))))) #;(if (i32.gt_u (local.get $argc) (i32.const 3)) (then (local.set $a3 (array.get $Args (local.get $args) (i32.const 3))))) #;(if (i32.gt_u (local.get $argc) (i32.const 4)) (then (local.set $a4 (array.get $Args (local.get $args) (i32.const 4))))) ;; br_table dispatch by shape (block $default (block $L13 (block $L12 (block $L11 (block $L10 (block $L9 (block $L8 (block $L7 (block $L6 (block $L5 (block $L4 (block $L3 (block $L2 (block $L1 (block $L0 (br_table $L0 $L1 $L2 $L3 $L4 $L5 $L6 $L7 $L8 $L9 $L10 $L11 $L12 $L13 $default (local.get $shape)) ) ;; end $L0 ;; shape 0: exact 0 #;(drop (call $js-log (call $i32->string (i32.const 0)))) (if (i32.eqz (local.get $argc)) (then (if (ref.test (ref $Prim0) (local.get $code)) (then (return_call_ref $Prim0 (ref.cast (ref $Prim0) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) )) ;; end $L1 ;; shape 1: exact 1 #;(drop (call $js-log (call $i32->string (i32.const 1)))) (if (i32.eq (local.get $argc) (i32.const 1)) (then (if (ref.test (ref $Prim1) (local.get $code)) (then (return_call_ref $Prim1 (local.get $a0) (ref.cast (ref $Prim1) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) )) ;; end $L2 ;; shape 2: exact 2 #;(drop (call $js-log (call $i32->string (i32.const 2)))) (if (i32.eq (local.get $argc) (i32.const 2)) (then (if (ref.test (ref $Prim2) (local.get $code)) (then (return_call_ref $Prim2 (local.get $a0) (local.get $a1) (ref.cast (ref $Prim2) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) )) ;; end $L3 ;; shape 3: exact 3 #;(drop (call $js-log (call $i32->string (i32.const 3)))) (if (i32.eq (local.get $argc) (i32.const 3)) (then (if (ref.test (ref $Prim3) (local.get $code)) (then (return_call_ref $Prim3 (local.get $a0) (local.get $a1) (local.get $a2) (ref.cast (ref $Prim3) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) )) ;; end $L4 ;; shape 4: exact 4 #;(drop (call $js-log (call $i32->string (i32.const 4)))) (if (i32.eq (local.get $argc) (i32.const 4)) (then (if (ref.test (ref $Prim4) (local.get $code)) (then (return_call_ref $Prim4 (local.get $a0) (local.get $a1) (local.get $a2) (array.get $Args (local.get $args) (i32.const 3)) (ref.cast (ref $Prim4) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc)))) )) ;; end $L5 ;; shape 5: exact 5 #;(drop (call $js-log (call $i32->string (i32.const 5)))) (if (i32.eq (local.get $argc) (i32.const 5)) (then (if (ref.test (ref $Prim5) (local.get $code)) (then (return_call_ref $Prim5 (local.get $a0) (local.get $a1) (local.get $a2) (array.get $Args (local.get $args) (i32.const 3)) (array.get $Args (local.get $args) (i32.const 4)) (ref.cast (ref $Prim5) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc)))) )) ;; end $L6 ;; shape 6: at least 0 (rest list) (if (ref.test (ref $Prim>=0) (local.get $code)) (then (local.set $rest (call $rest-arguments->list (local.get $args) (i32.const 0))) (return_call_ref $Prim>=0 (local.get $rest) (ref.cast (ref $Prim>=0) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))) )) ;; end $L7 ;; shape 7: at least 1 (rest list) (if (i32.ge_u (local.get $argc) (i32.const 1)) (then (if (ref.test (ref $Prim>=1) (local.get $code)) (then (local.set $rest (call $rest-arguments->list (local.get $args) (i32.const 1)))) (else (return_call_ref $Prim>=1 (local.get $a0) (local.get $rest) (ref.cast (ref $Prim>=1) (local.get $code)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc)))) )) ;; end $L8 ;; shape 8: at least 2 (rest list) (if (i32.ge_u (local.get $argc) (i32.const 2)) (then (if (ref.test (ref $Prim>=2) (local.get $code)) (then (local.set $rest (call $rest-arguments->list (local.get $args) (i32.const 2)))) (else (return_call_ref $Prim>=2 (local.get $a0) (local.get $a1) (local.get $rest) (ref.cast (ref $Prim>=2) (local.get $code)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc)))) )) ;; end $L9 ;; shape 9: at least 3 (rest list) (if (i32.ge_u (local.get $argc) (i32.const 3)) (then (if (ref.test (ref $Prim>=3) (local.get $code)) (then (local.set $rest (call $rest-arguments->list (local.get $args) (i32.const 3)))) (else (return_call_ref $Prim>=3 (local.get $a0) (local.get $a1) (local.get $a2) (local.get $rest) (ref.cast (ref $Prim>=3) (local.get $code)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc)))) )) ;; end $L10 ;; shape 10: at least 0 (rest $Args) (if (ref.test (ref $Prim>=0) (local.get $code)) (then (return_call_ref $Prim>=0 (local.get $args) (ref.cast (ref $Prim>=0) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))) )) ;; end $L11 ;; shape 11: at least 1 (rest $Args) (if (i32.ge_u (local.get $argc) (i32.const 1)) (then (if (ref.test (ref $Prim>=1) (local.get $code)) (then (return_call_ref $Prim>=1 (local.get $a0) (call $rest-arguments->args (local.get $args) (i32.const 1)) (ref.cast (ref $Prim>=1) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc)))) )) ;; end $L12 ;; shape 12: at least 2 (rest $Args) (if (i32.ge_u (local.get $argc) (i32.const 2)) (then (if (ref.test (ref $Prim>=2) (local.get $code)) (then (return_call_ref $Prim>=2 (local.get $a0) (local.get $a1) (call $rest-arguments->args (local.get $args) (i32.const 2)) (ref.cast (ref $Prim>=2) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) )) ;; end $L13 ;; shape 13: at least 3 (rest $Args) (if (i32.ge_u (local.get $argc) (i32.const 3)) (then (if (ref.test (ref $Prim>=3) (local.get $code)) (then (return_call_ref $Prim>=3 (local.get $a0) (local.get $a1) (local.get $a2) (call $rest-arguments->args (local.get $args) (i32.const 3)) (ref.cast (ref $Prim>=3) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) )) ;; end $default (if (i32.eq (local.get $shape) (i32.const 26)) (then (if (i32.eq (local.get $argc) (i32.const 6)) (then (if (ref.test (ref $Prim6) (local.get $code)) (then (return_call_ref $Prim6 (local.get $a0) (local.get $a1) (local.get $a2) (array.get $Args (local.get $args) (i32.const 3)) (array.get $Args (local.get $args) (i32.const 4)) (array.get $Args (local.get $args) (i32.const 5)) (ref.cast (ref $Prim6) (local.get $code)))) (else (return (call $raise-code-type-mismatch (local.get $pproc)))))) (else (return (call $primitive-invoke:raise-arity-error (local.get $pproc) (local.get $argc))))) (unreachable)) (else (nop))) #;(drop (call $js-log (call $i32->string (i32.const 10)))) (call $raise-primitive-invoke:unhandled-shape) (unreachable)) ,@(for/list ([shape (in-list primitive-shapes)]) (primitive-invoker shape)) (func $repack-arguments ; Returns new $Args suitable for calling both fixed and variadic procedures. ; I.e. function converts the rest arguments to a list and stores them in the last slot. (param $args (ref $Args)) ;; full argument list (param $arity i32) ;; decoded arity (from fixnum) (result (ref $Args)) (local $arg-count i32) (local $rest-start i32) (local $rest (ref eq)) (local $args+rest (ref $Args)) ;; Step 1: Compute number of arguments (local.set $arg-count (array.len (local.get $args))) ;; Step 2: Check if arity is negative (variadic) (if (i32.lt_s (local.get $arity) (i32.const 0)) (then ;; Step 3: Compute number of fixed args = -1 - arity (local.set $rest-start (i32.sub (i32.const -1) (local.get $arity))) ;; Step 4: Extract rest arguments and turn into list (local.set $rest (call $rest-arguments->list (local.get $args) (local.get $rest-start))) ;; Step 5: Create new $Args array with fixed args + 1 (local.set $args+rest (array.new $Args (global.get $false) (i32.add (local.get $rest-start) (i32.const 1)))) ;; Step 6: Copy fixed arguments (array.copy $Args $Args (local.get $args+rest) (i32.const 0) (local.get $args) (i32.const 0) (local.get $rest-start)) ;; Step 7: Place rest list at final position (array.set $Args (local.get $args+rest) (local.get $rest-start) (local.get $rest)) ;; Step 8: Return modified array (return (local.get $args+rest))) (else ;; Step 9: Arity is non-negative — return original (return (local.get $args)))) (unreachable)) ;; Dispatcher for (case-lambda ...) using arities: ;; m >= 0 ⇒ exactly m args ;; m < 0 ⇒ at least (-m - 1) args ;; ;; $Free payload captured by the dispatcher closure: ;; index 0 : (ref $I32Array) ; arities per arm ;; index 1 : (ref $Array) ; arm closures (source order) (func $raise-arity-error/case-lambda/arities (param $proc (ref $Procedure)) (param $argc i32) (param $arities (ref $I32Array)) (result (ref eq)) (local $name (ref eq)) (local $expected-str (ref $String)) (local $received-str (ref $String)) (local $i i32) (local $n i32) (local $m i32) (local $ga (ref $GrowableArray)) (local $out (ref $GrowableArray)) (local $message (ref $String)) (local.set $name (struct.get $Procedure $name (local.get $proc))) (local.set $received-str (call $i32->string (local.get $argc))) (local.set $n (array.len (local.get $arities))) (local.set $ga (call $make-growable-array (local.get $n))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $n))) (local.set $m (array.get $I32Array (local.get $arities) (local.get $i))) (call $growable-array-add! (local.get $ga) (call $arity-i32->string (local.get $m))) (if (i32.lt_u (i32.add (local.get $i) (i32.const 1)) (local.get $n)) (then (call $growable-array-add! (local.get $ga) (global.get $string:arity-error:or)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.set $expected-str (call $growable-array-of-strings->string (local.get $ga))) (local.set $out (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $out) (call $format/display (local.get $name))) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:start)) (call $growable-array-add! (local.get $out) (local.get $expected-str)) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:given)) (call $growable-array-add! (local.get $out) (local.get $received-str)) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract:arity/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) ;; Dispatcher uses typed $CaseClosure fields: $arities and $arms. (func $code:case-lambda-dispatch (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $cclos (ref $CaseClosure)) (local $arities (ref $I32Array)) (local $arms (ref $Array)) (local $argc i32) (local $i i32) (local $m i32) (local $arm (ref $Closure)) (local $out (ref $Args)) (local $code (ref $ClosureCode)) ; Get arities and arms (closures) (local.set $cclos (ref.cast (ref $CaseClosure) (local.get $clos))) (local.set $arities (struct.get $CaseClosure $arities (local.get $cclos))) (local.set $arms (struct.get $CaseClosure $arms (local.get $cclos))) ; Argument count (local.set $argc (array.len (local.get $args))) (local.set $i (i32.const 0)) (loop $scan (if (i32.ge_u (local.get $i) (array.len (local.get $arms))) (then (drop (call $js-log (call $format/display (local.get $clos)))) (call $raise-arity-error/case-lambda/arities (local.get $clos) (local.get $argc) (local.get $arities)) (unreachable))) (local.set $m (array.get $I32Array (local.get $arities) (local.get $i))) (if (i32.or ;; fixed: argc == m (i32.and (i32.ge_s (local.get $m) (i32.const 0)) (i32.eq (local.get $argc) (local.get $m))) ;; rest: argc >= -m - 1 (i32.and (i32.lt_s (local.get $m) (i32.const 0)) (i32.ge_u (local.get $argc) (i32.sub (i32.sub (i32.const 0) (local.get $m)) (i32.const 1))))) (then ;; Match → repack once and tail-call arm's *code* (local.set $arm (ref.cast (ref $Closure) (array.get $Array (local.get $arms) (local.get $i)))) (local.set $out (call $repack-arguments (local.get $args) (local.get $m))) (local.set $code (struct.get $Closure $code (local.get $arm))) (return_call_ref $ClosureCode (local.get $arm) (local.get $out) (local.get $code))) (else (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $scan)))) (unreachable)) (func $invoke-struct (type $ProcedureInvoker) (param $proc (ref $Procedure)) ; type check: an $Struct is expected (param $args (ref $Args)) ; an array of (ref eq) (result (ref eq)) (local $struct (ref $Struct)) (local $type (ref $StructType)) (local $prop-desc (ref $StructTypeProperty)) (local $sentinel (ref eq)) (local $prop-val (ref eq)) (local $fields (ref $Array)) (local $idx i32) (local $super (ref eq)) (local $super-type (ref null $StructType)) (local $super-count i32) (local $abs-index i32) (local $target (ref eq)) (local $delegate (ref $Procedure)) (local $delegate-inv (ref $ProcedureInvoker)) (local $argc i32) (local $packed-args (ref $Args)) (local $i i32) (local $struct-name (ref eq)) ;; Validate and cast the incoming procedure reference. (if (i32.eqz (ref.test (ref $Struct) (local.get $proc))) (then (call $raise-application-not-procedure (local.get $proc)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $proc))) (local.set $argc (array.len (local.get $args))) ;; Look up the prop:procedure association for the structure type. (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $struct-name (struct.get $StructType $name (local.get $type))) (local.set $prop-desc (ref.cast (ref $StructTypeProperty) (global.get $prop:procedure))) (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $prop-val (call $struct-type-property-lookup (local.get $type) (local.get $prop-desc) (local.get $sentinel))) ;; Abort when the structure type is not applicable. (if (ref.eq (local.get $prop-val) (local.get $sentinel)) (then (call $raise-application-not-procedure (ref.cast (ref eq) (local.get $struct))) (unreachable))) ;; Case 1: property designates a structure field containing a procedure. (if (ref.test (ref i31) (local.get $prop-val)) (then (local.set $idx (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $prop-val))) (i32.const 1))) (local.set $super (struct.get $StructType $super (local.get $type))) (local.set $super-type (ref.null $StructType)) (local.set $super-count (i32.const 0)) (if (i32.eqz (ref.eq (local.get $super) (global.get $false))) (then (local.set $super-type (ref.cast (ref $StructType) (local.get $super))) (local.set $super-count (struct.get $StructType $field-count (local.get $super-type))))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (local.set $abs-index (i32.add (local.get $super-count) (local.get $idx))) (local.set $target (array.get $Array (local.get $fields) (local.get $abs-index))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $target))) (then (call $raise-arity-mismatch/name+given (local.get $struct-name) (local.get $argc)) (unreachable))) (local.set $delegate (ref.cast (ref $Procedure) (local.get $target))) (local.set $delegate-inv (struct.get $Procedure $invoke (local.get $delegate))) (return_call_ref $ProcedureInvoker (local.get $delegate) (local.get $args) (local.get $delegate-inv)))) ;; Case 2: property supplies a procedure to receive the structure + arguments. (if (i32.eqz (ref.test (ref $Procedure) (local.get $prop-val))) (then (call $raise-arity-mismatch/name+given (local.get $struct-name) (local.get $argc)) (unreachable))) (local.set $delegate (ref.cast (ref $Procedure) (local.get $prop-val))) (local.set $delegate-inv (struct.get $Procedure $invoke (local.get $delegate))) (local.set $packed-args (array.new $Args (global.get $null) (i32.add (local.get $argc) (i32.const 1)))) (array.set $Args (local.get $packed-args) (i32.const 0) (local.get $struct)) (local.set $i (i32.const 0)) (block $done (loop $copy (br_if $done (i32.ge_u (local.get $i) (local.get $argc))) (array.set $Args (local.get $packed-args) (i32.add (local.get $i) (i32.const 1)) (array.get $Args (local.get $args) (local.get $i))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $copy))) (return_call_ref $ProcedureInvoker (local.get $delegate) (local.get $packed-args) (local.get $delegate-inv))) ;; Unused stubs kept for now; safe to remove later. (func $raise-arity-error:exactly (unreachable)) (func $raise-arity-error:at-least (unreachable)) (func $rest-arguments->list (param $args (ref $Args)) (param $n i32) (result (ref eq)) (local $len i32) (local $i i32) (local $x (ref eq)) (local $xs (ref eq)) ;; Compute length of args (local.set $len (array.len (local.get $args))) ;; Return empty list if n >= len (if (i32.ge_u (local.get $n) (local.get $len)) (then (return (global.get $null)))) ;; Start with empty list (local.set $xs (global.get $null)) ;; Start loop from len - 1 down to n (inclusive) (local.set $i (i32.sub (local.get $len) (i32.const 1))) (block $done (loop $rev ;; x = args[i] (local.set $x (array.get $Args (local.get $args) (local.get $i))) ;; xs = (cons x xs) (local.set $xs (struct.new $Pair (i32.const 0) ;; dummy hash (local.get $x) ;; car (local.get $xs))) ;; cdr ;; Stop when i == n (br_if $done (i32.eq (local.get $i) (local.get $n))) ;; i-- (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $rev))) (local.get $xs)) (func $rest-arguments->args (param $args (ref $Args)) (param $n i32) (result (ref $Args)) (local $len i32) (local $count i32) (local $res (ref $Args)) (local.set $len (array.len (local.get $args))) (if (i32.ge_u (local.get $n) (local.get $len)) (then (return (array.new $Args (global.get $null) (i32.const 0))))) (local.set $count (i32.sub (local.get $len) (local.get $n))) (local.set $res (array.new $Args (global.get $null) (local.get $count))) (array.copy $Args $Args (local.get $res) (i32.const 0) (local.get $args) (local.get $n) (local.get $count)) (local.get $res)) ;; Variable used by `closedapp` to hold the closure during construction. (func $dummy-code (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) ,(Imm 0)) (global $dummy-closure (ref $Closure) (struct.new $Closure (i32.const 0) ; hash (global.get $false) ; name: #f or $String (global.get $zero) ; arity: todo (global.get $false) ; realm: #f or $Symbol (ref.func $invoke-closure) ; invoke (used by apply, map, etc.) (global.get $false) ; debug-id (ref.func $dummy-code) (array.new_fixed $Free 0))) (global $closedapp-clos (mut (ref $Closure)) (global.get $dummy-closure)) ;; Predefined exception struct type descriptors (as boxed top-level bindings) ;; struct:exn, struct:exn:fail, etc. ,@(for/list ([binding exception-struct-type-bindings]) (define name (car binding)) (define $name (string->symbol (~a "$" (symbol->string name)))) `(global ,$name (mut (ref eq)) (global.get $undefined))) ;; Return value (for a module) (global ,result (mut (ref eq)) (global.get $void)) ;; Variables defined at the top-level ,@top-level-variable-declarations ;; The symbol table from strings to symbols. ;; Used to intern symbols. (global $the-symbol-table (mut (ref null $SymbolTable)) (ref.null $SymbolTable)) ;; The keyword table from strings to keywords. ;; Used to intern keywords. (global $the-keyword-table (mut (ref null $SymbolTable)) (ref.null $SymbolTable)) (func $initialize-the-symbol-table (if (ref.is_null (global.get $the-symbol-table)) (then (global.set $the-symbol-table (call $make-symbol-table))))) (func $initialize-the-keyword-table (if (ref.is_null (global.get $the-keyword-table)) (then (global.set $the-keyword-table (call $make-symbol-table))))) ;;; ;;; Arrays ;;; ;; Arrays are "vectors" of (ref eq) values. ;; They are used internally to implement various Racket data structures. ;; Define a mutable array type of (ref eq) ; (type $Array (array (mut (ref eq)))) ;; make-array : i32 (ref eq) -> $Array ;; (make-array size v) -> $Array (func $make-array (param $size i32) (param $v (ref eq)) (result (ref $Array)) (local $arr (ref $Array)) (array.new $Array (local.get $v) (local.get $size))) ;; array-length : $Array -> i32 ;; (array-length arr) -> i32 ;; Note: Could just use `array.len` directly. (func $array-length (export "array-length") (param $arr (ref $Array)) (result i32) (array.len (local.get $arr))) ;; array-ref : $Array i32 -> (ref eq) ;; (array-ref arr pos) -> (ref eq) ;; No bounds check. (func $array-ref (export "array-ref") (param $arr (ref $Array)) (param $pos i32) (result (ref eq)) (array.get $Array (local.get $arr) (local.get $pos))) ;; array-set! : $Array i32 (ref eq) -> ;; (array-set! arr pos v) -> void (func $array-set! (export "array-set!") (param $arr (ref $Array)) (param $pos i32) (param $v (ref eq)) (array.set $Array (local.get $arr) (local.get $pos) (local.get $v))) ;; array-fill! : $Array (ref eq) -> ;; (array-fill! arr v) -> void (func $array-fill! (export "array-fill!") (param $arr (ref $Array)) (param $v (ref eq)) (local $i i32) (local.set $i (i32.const 0)) (block $exit (loop $fill (br_if $exit (i32.ge_u (local.get $i) (array.len (local.get $arr)))) (array.set $Array (local.get $arr) (local.get $i) (local.get $v)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $fill)))) ;; array-copy! : $Array i32 $Array i32 i32 -> ;; (array-copy! dest dest-start src src-start src-end) -> ;; Traps on error (func $array-copy! (export "array-copy!") (param $dest (ref $Array)) (param $dest-start i32) (param $src (ref $Array)) (param $src-start i32) (param $src-end i32) (local $src-len i32) (local $dest-len i32) (local $len i32) ;; Bounds checks (local.set $src-len (array.len (local.get $src))) (local.set $dest-len (array.len (local.get $dest))) (if (i32.or (i32.or (i32.lt_u (local.get $src-start) (i32.const 0)) (i32.gt_u (local.get $src-end) (local.get $src-len))) (i32.gt_u (i32.add (local.get $dest-start) (i32.sub (local.get $src-end) (local.get $src-start))) (local.get $dest-len))) (then (unreachable))) (local.set $len (i32.sub (local.get $src-end) (local.get $src-start))) (array.copy $Array $Array (local.get $dest) (local.get $dest-start) (local.get $src) (local.get $src-start) (local.get $len))) ;; array-copy!/error : $Array i32 $Array i32 i32 -> i32 ;; (array-copy!/error dest dest-start src src-start src-end) -> i32 ;; Returns 1 on success, 0 on error. (func $array-copy!/error (export "array-copy!/error") (param $dest (ref $Array)) (param $dest-start i32) (param $src (ref $Array)) (param $src-start i32) (param $src-end i32) (result i32) (local $src-len i32) (local $dest-len i32) (local $len i32) ;; Bounds checks (local.set $src-len (array.len (local.get $src))) (local.set $dest-len (array.len (local.get $dest))) (if (i32.or (i32.or (i32.lt_u (local.get $src-start) (i32.const 0)) (i32.gt_u (local.get $src-end) (local.get $src-len))) (i32.gt_u (i32.add (local.get $dest-start) (i32.sub (local.get $src-end) (local.get $src-start))) (local.get $dest-len))) (then (return (i32.const 0)))) (local.set $len (i32.sub (local.get $src-end) (local.get $src-start))) (array.copy $Array $Array (local.get $dest) (local.get $dest-start) (local.get $src) (local.get $src-start) (local.get $len)) (i32.const 1)) ; array-append : $Array $Array -> $Array ; Append the arrays. (func $array-append (param $a0 (ref $Array)) (param $a1 (ref $Array)) (result (ref $Array)) (local $len0 i32) (local $len1 i32) (local $total i32) (local $arr (ref $Array)) (local.set $len0 (array.len (local.get $a0))) (local.set $len1 (array.len (local.get $a1))) (local.set $total (i32.add (local.get $len0) (local.get $len1))) (local.set $arr (call $make-array (local.get $total) (global.get $false))) (call $array-copy! (local.get $arr) (i32.const 0) (local.get $a0) (i32.const 0) (local.get $len0)) (call $array-copy! (local.get $arr) (local.get $len0) (local.get $a1) (i32.const 0) (local.get $len1)) (local.get $arr)) ; array-append-all : (array-of $Array) -> $Array ; Given an array of arrays. Make a new array. (func $array-append-all (param $arrs (ref $Array)) (result (ref $Array)) (local $n i32) (local $i i32) (local $total i32) (local $len i32) (local $dst i32) (local $tmp (ref $Array)) (local $result (ref $Array)) (local.set $n (array.len (local.get $arrs))) (local.set $i (i32.const 0)) (local.set $total (i32.const 0)) (block $exit (loop $count (br_if $exit (i32.ge_u (local.get $i) (local.get $n))) (local.set $tmp (ref.cast (ref $Array) (array.get $Array (local.get $arrs) (local.get $i)))) (local.set $len (array.len (local.get $tmp))) (local.set $total (i32.add (local.get $total) (local.get $len))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $count))) (local.set $result (call $make-array (local.get $total) (global.get $false))) (local.set $i (i32.const 0)) (local.set $dst (i32.const 0)) (block $done (loop $copy (br_if $done (i32.ge_u (local.get $i) (local.get $n))) (local.set $tmp (ref.cast (ref $Array) (array.get $Array (local.get $arrs) (local.get $i)))) (local.set $len (array.len (local.get $tmp))) (call $array-copy! (local.get $result) (local.get $dst) (local.get $tmp) (i32.const 0) (local.get $len)) (local.set $dst (i32.add (local.get $dst) (local.get $len))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $copy))) (local.get $result)) ; array-take : $Array i32 -> $Array (func $array-take (param $arr (ref $Array)) (param $pos i32) (result (ref $Array)) (call $array-copy (local.get $arr) (i32.const 0) (local.get $pos))) ; array-take-right : $Array i32 -> $Array (func $array-take-right (param $arr (ref $Array)) (param $pos i32) (result (ref $Array)) (local $len i32) (local.set $len (array.len (local.get $arr))) (call $array-copy (local.get $arr) (i32.sub (local.get $len) (local.get $pos)) (local.get $len))) ; array-drop : $Array i32 -> $Array (func $array-drop (param $arr (ref $Array)) (param $pos i32) (result (ref $Array)) (local $len i32) (local.set $len (array.len (local.get $arr))) (call $array-copy (local.get $arr) (local.get $pos) (local.get $len))) ; array-drop-right : $Array i32 -> $Array (func $array-drop-right (param $arr (ref $Array)) (param $pos i32) (result (ref $Array)) (local $len i32) (local.set $len (array.len (local.get $arr))) (call $array-copy (local.get $arr) (i32.const 0) (i32.sub (local.get $len) (local.get $pos)))) ; array-split-at : $Array i32 -> (array $Array $Array) (func $array-split-at (param $arr (ref $Array)) (param $pos i32) (result (ref $Array)) (local $a (ref $Array)) (local $b (ref $Array)) (local $res (ref $Array)) (local.set $a (call $array-take (local.get $arr) (local.get $pos))) (local.set $b (call $array-drop (local.get $arr) (local.get $pos))) (local.set $res (call $make-array (i32.const 2) (global.get $false))) (call $array-set! (local.get $res) (i32.const 0) (local.get $a)) (call $array-set! (local.get $res) (i32.const 1) (local.get $b)) (local.get $res)) ; $array-copy : $Array i32 i32 -> $Array ; (array-copy arr start end) (func $array-copy (param $arr (ref $Array)) (param $start i32) (param $end i32) (result (ref $Array)) (local $res (ref $Array)) (local.set $res (call $make-array (i32.sub (local.get $end) (local.get $start)) (global.get $false))) (call $array-copy! (local.get $res) (i32.const 0) (local.get $arr) (local.get $start) (local.get $end)) (local.get $res)) (func $array-set/copy (param $arr (ref $Array)) (param $pos i32) (param $val (ref eq)) (result (ref $Array)) (local $len i32) (local $res (ref $Array)) (local.set $len (array.len (local.get $arr))) (local.set $res (call $make-array (local.get $len) (global.get $false))) (call $array-copy! (local.get $res) (i32.const 0) (local.get $arr) (i32.const 0) (local.get $len)) (call $array-set! (local.get $res) (local.get $pos) (local.get $val)) (local.get $res)) (func $array-extend (param $arr (ref $Array)) (param $new-size i32) (param $val (ref eq)) (result (ref $Array)) (local $old-size i32) (local $res (ref $Array)) (local.set $old-size (array.len (local.get $arr))) (local.set $res (call $make-array (local.get $new-size) (local.get $val))) (call $array-copy! (local.get $res) (i32.const 0) (local.get $arr) (i32.const 0) (local.get $old-size)) (local.get $res)) (func $list->array (param $xs (ref eq)) (result (ref $Array)) (local $len i32) (local $arr (ref $Array)) (local $idx i32) (local $x (ref eq)) (local $node (ref eq)) ;; Step 1: compute length of list (local.set $len (call $length/i32 (local.get $xs))) ;; Step 2: allocate array of given length (local.set $arr (call $make-array (local.get $len) (global.get $null))) ;; Step 3: initialize traversal variables (local.set $node (local.get $xs)) (local.set $idx (i32.const 0)) ;; Step 4: fill array (block $done (loop $fill ;; Stop at null (br_if $done (ref.eq (local.get $node) (global.get $null))) ;; Check that it's a pair (if (ref.test (ref $Pair) (local.get $node)) (then (local.set $x (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $node)))) (call $array-set! (local.get $arr) (local.get $idx) (local.get $x)) (local.set $idx (i32.add (local.get $idx) (i32.const 1))) (local.set $node (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $node)))) (br $fill)) (else (call $raise-pair-expected (local.get $node)) (unreachable))))) ;; Return the filled array (local.get $arr)) ;;; ;;; Growable Arrays ;;; ;; A growable array is like an array, but the length can change over time. ;; They are modelled over "growable vectors" from `racket/data`. (func $make-growable-array (param $cap i32) (result (ref $GrowableArray)) (local $initial-cap i32) (local.set $initial-cap (if (result i32) (i32.eqz (local.get $cap)) (then (i32.const 16)) (else (local.get $cap)))) (struct.new $GrowableArray (call $make-array (local.get $initial-cap) (global.get $false)) (local.get $initial-cap) (i32.const 0))) (func $growable-array?? (param $x (ref eq)) (result i32) ; 0 = false, 1 = true (ref.test (ref $GrowableArray) (local.get $x))) (func $growable-array? (param $x (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $GrowableArray) (local.get $x)) (then (global.get $true)) (else (global.get $false)))) (func $growable-array-ref (param $g (ref $GrowableArray)) (param $index i32) (result (ref eq)) (local $i i32) (local.set $i (struct.get $GrowableArray $i (local.get $g))) (if (result (ref eq)) (i32.lt_u (local.get $index) (local.get $i)) (then (array.get $Array (struct.get $GrowableArray $arr (local.get $g)) (local.get $index))) (else (global.get $false)))) (func $growable-array-ref/default (param $g (ref $GrowableArray)) (param $index i32) (param $default (ref eq)) (result (ref eq)) (local $i i32) (local.set $i (struct.get $GrowableArray $i (local.get $g))) (if (result (ref eq)) (i32.lt_u (local.get $index) (local.get $i)) (then (array.get $Array (struct.get $GrowableArray $arr (local.get $g)) (local.get $index))) (else (local.get $default)))) (func $growable-array-add! (param $g (ref $GrowableArray)) (param $v (ref eq)) (local $i i32) (local $cap i32) (local $arr (ref $Array)) (local $new-cap i32) (local $new-arr (ref $Array)) (local.set $i (struct.get $GrowableArray $i (local.get $g))) (local.set $cap (struct.get $GrowableArray $cap (local.get $g))) (local.set $arr (struct.get $GrowableArray $arr (local.get $g))) (if (i32.eq (local.get $i) (local.get $cap)) (then (local.set $new-cap (i32.shl (local.get $cap) (i32.const 1))) ;; new-cap = cap * 2 (local.set $new-arr (call $array-extend (local.get $arr) (local.get $new-cap) (global.get $false))) (struct.set $GrowableArray $arr (local.get $g) (local.get $new-arr)) (struct.set $GrowableArray $cap (local.get $g) (local.get $new-cap)) (local.set $arr (local.get $new-arr)))) (array.set $Array (local.get $arr) (local.get $i) (local.get $v)) (struct.set $GrowableArray $i (local.get $g) (i32.add (local.get $i) (i32.const 1)))) (func $growable-array-insert! (param $g (ref $GrowableArray)) (param $index i32) (param $value (ref eq)) (local $i i32) (local $arr (ref $Array)) (local.set $i (struct.get $GrowableArray $i (local.get $g))) (if (i32.eq (local.get $index) (local.get $i)) (then (call $growable-array-add! (local.get $g) (local.get $value))) (else (local.set $arr (struct.get $GrowableArray $arr (local.get $g))) (call $growable-array-add! (local.get $g) (global.get $false)) (call $array-copy! ; dest dest-start src src-start src-end (local.get $arr) (i32.add (local.get $index) (i32.const 1)) (local.get $arr) (local.get $index) (local.get $i)) (array.set $Array (local.get $arr) (local.get $index) (local.get $value))))) (func $growable-array-set! (param $g (ref $GrowableArray)) (param $index i32) (param $value (ref eq)) (local $i i32) (local.set $i (struct.get $GrowableArray $i (local.get $g))) (if (i32.eq (local.get $index) (local.get $i)) (then (call $growable-array-add! (local.get $g) (local.get $value))) (else (array.set $Array (struct.get $GrowableArray $arr (local.get $g)) (local.get $index) (local.get $value))))) (func $growable-array-remove! (param $g (ref $GrowableArray)) (param $index i32) (local $i i32) (local.set $i (struct.get $GrowableArray $i (local.get $g))) (call $array-copy! (struct.get $GrowableArray $arr (local.get $g)) (local.get $index) (struct.get $GrowableArray $arr (local.get $g)) (i32.add (local.get $index) (i32.const 1)) (local.get $i)) (struct.set $GrowableArray $i (local.get $g) (i32.sub (local.get $i) (i32.const 1)))) (func $growable-array-remove-last! (param $g (ref $GrowableArray)) (result (ref eq)) (local $i i32) (local.set $i (struct.get $GrowableArray $i (local.get $g))) (struct.set $GrowableArray $i (local.get $g) (i32.sub (local.get $i) (i32.const 1))) (array.get $Array (struct.get $GrowableArray $arr (local.get $g)) (i32.sub (local.get $i) (i32.const 1)))) (func $growable-array-count (param $g (ref $GrowableArray)) (result i32) (struct.get $GrowableArray $i (local.get $g))) (func $growable-array->array (param $g (ref $GrowableArray)) (result (ref $Array)) (local $count i32) (local.set $count (struct.get $GrowableArray $i (local.get $g))) (call $array-copy (struct.get $GrowableArray $arr (local.get $g)) (i32.const 0) (local.get $count))) (func $array->growable-array (param $a (ref $Array)) (result (ref $GrowableArray)) ; Note: This wraps the array in a growable array. It does not make a copy. (local $n i32) (local.set $n (array.len (local.get $a))) (struct.new $GrowableArray (local.get $a) (local.get $n) (local.get $n))) ;;; ;;; Growable Arrays of Bytes ;;; (func $make-growable-bytes (param $capacity i32) (result (ref $GrowableBytes)) (struct.new $GrowableBytes (call $i8make-array (local.get $capacity) (i32.const 0)) (local.get $capacity) (i32.const 0))) (func $growable-bytes-add! (param $g (ref $GrowableBytes)) (param $b i32) (local $i i32) (local $cap i32) (local $arr (ref $I8Array)) (local.set $i (struct.get $GrowableBytes $i (local.get $g))) (local.set $cap (struct.get $GrowableBytes $cap (local.get $g))) (local.set $arr (struct.get $GrowableBytes $arr (local.get $g))) ;; Grow if necessary (if (i32.eq (local.get $i) (local.get $cap)) (then (local.set $cap (i32.shl (local.get $cap) (i32.const 1))) ; double (local.set $arr (call $i8array-extend (local.get $arr) (local.get $cap) (i32.const 0))) (struct.set $GrowableBytes $arr (local.get $g) (local.get $arr)) (struct.set $GrowableBytes $cap (local.get $g) (local.get $cap)))) ;; Set byte (array.set $I8Array (local.get $arr) (local.get $i) (local.get $b)) (struct.set $GrowableBytes $i (local.get $g) (i32.add (local.get $i) (i32.const 1)))) (func $growable-bytes->bytes (param $g (ref $GrowableBytes)) (result (ref $Bytes)) (local $src (ref $I8Array)) (local $n i32) (local $dst (ref $I8Array)) ;; Extract fields (local.set $src (struct.get $GrowableBytes $arr (local.get $g))) (local.set $n (struct.get $GrowableBytes $i (local.get $g))) ;; Allocate new array of length $n (local.set $dst (array.new_default $I8Array (local.get $n))) ;; Copy from $src to $dst using array.copy (array.copy $I8Array $I8Array (local.get $dst) ;; dst array (i32.const 0) ;; dst offset (local.get $src) ;; src array (i32.const 0) ;; src offset (local.get $n)) ;; length ;; Construct and return new Bytes struct (struct.new $Bytes (i32.const 0) ;; hash = 0 (local.get $n) ;; length (local.get $dst))) ;; copied byte array ;;; ;;; Growable Arrays of I32 ;;; #;(type $I32Array (array (mut i32))) #;(type $I32GrowableArray (struct (field $arr (mut (ref $I32Array))) ;; underlying array (field $cap (mut i32)) ;; capacity (field $i (mut i32)))) ;; current size (func $make-i32growable-array (param $cap i32) (result (ref $I32GrowableArray)) (local $initial-cap i32) (local.set $initial-cap (if (result i32) (i32.eqz (local.get $cap)) (then (i32.const 16)) (else (local.get $cap)))) (struct.new $I32GrowableArray (array.new_default $I32Array (local.get $initial-cap)) (local.get $initial-cap) (i32.const 0))) (func $i32growable-array-add! (param $g (ref $I32GrowableArray)) (param $v i32) (local $i i32) (local $cap i32) (local $arr (ref $I32Array)) (local $new-cap i32) (local $new-arr (ref $I32Array)) (local.set $i (struct.get $I32GrowableArray $i (local.get $g))) (local.set $cap (struct.get $I32GrowableArray $cap (local.get $g))) (local.set $arr (struct.get $I32GrowableArray $arr (local.get $g))) (if (i32.eq (local.get $i) (local.get $cap)) (then (local.set $new-cap (i32.shl (local.get $cap) (i32.const 1))) (local.set $new-arr (call $i32array-extend (local.get $arr) (local.get $new-cap) (i32.const 0))) (struct.set $I32GrowableArray $arr (local.get $g) (local.get $new-arr)) (struct.set $I32GrowableArray $cap (local.get $g) (local.get $new-cap)) (local.set $arr (local.get $new-arr)))) (array.set $I32Array (local.get $arr) (local.get $i) (local.get $v)) (struct.set $I32GrowableArray $i (local.get $g) (i32.add (local.get $i) (i32.const 1)))) (func $i32growable-array-ref (param $g (ref $I32GrowableArray)) (param $index i32) (result i32) (local $i i32) (local.set $i (struct.get $I32GrowableArray $i (local.get $g))) (if (result i32) (i32.lt_u (local.get $index) (local.get $i)) (then (array.get $I32Array (struct.get $I32GrowableArray $arr (local.get $g)) (local.get $index))) (else (i32.const 0)))) ;; You might want to raise instead (func $i32growable-array-count (param $g (ref $I32GrowableArray)) (result i32) (struct.get $I32GrowableArray $i (local.get $g))) (func $i32growable-array->array (param $g (ref $I32GrowableArray)) (result (ref $I32Array)) (local $count i32) (local.set $count (struct.get $I32GrowableArray $i (local.get $g))) (call $i32array-copy (struct.get $I32GrowableArray $arr (local.get $g)) (i32.const 0) (local.get $count))) (func $i32array->growable-array (param $a (ref $I32Array)) (result (ref $I32GrowableArray)) (local $n i32) (local.set $n (array.len (local.get $a))) (struct.new $I32GrowableArray (local.get $a) (local.get $n) (local.get $n))) ;;; ;;; I32Array - Arrays of i32 ;;; ; (type $I32Array (array (mut i32))) (func $i32array-make (param $size i32) (param $v i32) (result (ref $I32Array)) (array.new $I32Array (local.get $v) (local.get $size))) (func $i32array-length (param $arr (ref $I32Array)) (result i32) (array.len (local.get $arr))) (func $i32array-ref (param $arr (ref $I32Array)) (param $pos i32) (result i32) (array.get $I32Array (local.get $arr) (local.get $pos))) (func $i32array-set! (param $arr (ref $I32Array)) (param $pos i32) (param $v i32) (array.set $I32Array (local.get $arr) (local.get $pos) (local.get $v))) (func $i32array-equal? (param $a (ref $I32Array)) (param $b (ref $I32Array)) (result i32) (local $len i32) (local $i i32) ;; Compare lengths (if (i32.ne (array.len (local.get $a)) (array.len (local.get $b))) (then (return (i32.const 0)))) ;; Set up loop (local.set $len (array.len (local.get $a))) (local.set $i (i32.const 0)) (block $exit (loop $loop (br_if $exit (i32.ge_u (local.get $i) (local.get $len))) (if (i32.ne (array.get $I32Array (local.get $a) (local.get $i)) (array.get $I32Array (local.get $b) (local.get $i))) (then (return (i32.const 0)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (i32.const 1)) (func $i32array-fill! (param $arr (ref $I32Array)) (param $v i32) (local $i i32) (local.set $i (i32.const 0)) (block $exit (loop $fill (br_if $exit (i32.ge_u (local.get $i) (array.len (local.get $arr)))) (array.set $I32Array (local.get $arr) (local.get $i) (local.get $v)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $fill)))) (func $i32array-copy! (param $dest (ref $I32Array)) (param $dest-start i32) (param $src (ref $I32Array)) (param $src-start i32) (param $src-end i32) (local $len i32) (local.set $len (i32.sub (local.get $src-end) (local.get $src-start))) (array.copy $I32Array $I32Array (local.get $dest) (local.get $dest-start) (local.get $src) (local.get $src-start) (local.get $len))) (func $i32array-copy!/error (param $dest (ref $I32Array)) (param $dest-start i32) (param $src (ref $I32Array)) (param $src-start i32) (param $src-end i32) (result i32) (local $src-len i32) (local $dest-len i32) (local $len i32) (local.set $src-len (array.len (local.get $src))) (local.set $dest-len (array.len (local.get $dest))) (if (i32.or (i32.or (i32.lt_u (local.get $src-start) (i32.const 0)) (i32.gt_u (local.get $src-end) (local.get $src-len))) (i32.gt_u (i32.add (local.get $dest-start) (i32.sub (local.get $src-end) (local.get $src-start))) (local.get $dest-len))) (then (return (i32.const 0)))) (local.set $len (i32.sub (local.get $src-end) (local.get $src-start))) (array.copy $I32Array $I32Array (local.get $dest) (local.get $dest-start) (local.get $src) (local.get $src-start) (local.get $len)) (i32.const 1)) (func $i32array-copy (param $arr (ref $I32Array)) (param $start i32) (param $end i32) (result (ref $I32Array)) (local $res (ref $I32Array)) (local.set $res (call $i32array-make (i32.sub (local.get $end) (local.get $start)) (i32.const 0))) (call $i32array-copy! (local.get $res) (i32.const 0) (local.get $arr) (local.get $start) (local.get $end)) (local.get $res)) (func $i32array-append (param $a0 (ref $I32Array)) (param $a1 (ref $I32Array)) (result (ref $I32Array)) (local $len0 i32) (local $len1 i32) (local $total i32) (local $arr (ref $I32Array)) (local.set $len0 (array.len (local.get $a0))) (local.set $len1 (array.len (local.get $a1))) (local.set $total (i32.add (local.get $len0) (local.get $len1))) (local.set $arr (call $i32array-make (local.get $total) (i32.const 0))) (call $i32array-copy! (local.get $arr) (i32.const 0) (local.get $a0) (i32.const 0) (local.get $len0)) (call $i32array-copy! (local.get $arr) (local.get $len0) (local.get $a1) (i32.const 0) (local.get $len1)) (local.get $arr)) (func $i32array-take (param $arr (ref $I32Array)) (param $pos i32) (result (ref $I32Array)) (call $i32array-copy (local.get $arr) (i32.const 0) (local.get $pos))) (func $i32array-take-right (param $arr (ref $I32Array)) (param $pos i32) (result (ref $I32Array)) (local $len i32) (local.set $len (array.len (local.get $arr))) (call $i32array-copy (local.get $arr) (i32.sub (local.get $len) (local.get $pos)) (local.get $len))) (func $i32array-drop (param $arr (ref $I32Array)) (param $pos i32) (result (ref $I32Array)) (local $len i32) (local.set $len (array.len (local.get $arr))) (call $i32array-copy (local.get $arr) (local.get $pos) (local.get $len))) (func $i32array-drop-right (param $arr (ref $I32Array)) (param $pos i32) (result (ref $I32Array)) (local $len i32) (local.set $len (array.len (local.get $arr))) (call $i32array-copy (local.get $arr) (i32.const 0) (i32.sub (local.get $len) (local.get $pos)))) (func $i32array-set/copy (param $arr (ref $I32Array)) (param $pos i32) (param $val i32) (result (ref $I32Array)) (local $len i32) (local $res (ref $I32Array)) (local.set $len (array.len (local.get $arr))) (local.set $res (call $i32array-make (local.get $len) (i32.const 0))) (call $i32array-copy! (local.get $res) (i32.const 0) (local.get $arr) (i32.const 0) (local.get $len)) (call $i32array-set! (local.get $res) (local.get $pos) (local.get $val)) (local.get $res)) (func $i32array-extend (param $arr (ref $I32Array)) (param $new-size i32) (param $val i32) (result (ref $I32Array)) (local $old-size i32) (local $res (ref $I32Array)) (local.set $old-size (array.len (local.get $arr))) (local.set $res (call $i32array-make (local.get $new-size) (local.get $val))) (call $i32array-copy! (local.get $res) (i32.const 0) (local.get $arr) (i32.const 0) (local.get $old-size)) (local.get $res)) (func $list->i32array (param $xs (ref eq)) (result (ref $I32Array)) (local $len i32) (local $arr (ref $I32Array)) (local $idx i32) (local $x (ref eq)) (local $v i32) (local $node (ref eq)) ;; Step 1: compute length of list (local.set $len (call $length/i32 (local.get $xs))) ;; Step 2: allocate array of given length (local.set $arr (call $i32array-make (local.get $len) (i32.const 0))) ;; Step 3: initialize traversal variables (local.set $node (local.get $xs)) (local.set $idx (i32.const 0)) ;; Step 4: fill array (block $done (loop $fill ;; Stop at null (br_if $done (ref.eq (local.get $node) (global.get $null))) ;; Check that it's a pair (if (ref.test (ref $Pair) (local.get $node)) (then ;; Get the car (local.set $x (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $node)))) ;; Check that car is a fixnum (i31 with LSB 0) (if (ref.test (ref i31) (local.get $x)) (then (local.set $v (i31.get_u (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $v) (i32.const 1))) (then ;; Decode fixnum: shift right 1 and store (call $i32array-set! (local.get $arr) (local.get $idx) (i32.shr_u (local.get $v) (i32.const 1))) ;; Advance (local.set $idx (i32.add (local.get $idx) (i32.const 1))) (local.set $node (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $node)))) (br $fill)) (else (call $raise-check-fixnum (local.get $x)) (unreachable)))) (else (call $raise-check-fixnum (local.get $x)) (unreachable)))) (else (call $raise-pair-expected (local.get $node)) (unreachable))))) ;; Return the filled array (local.get $arr)) ;;; ;;; I8Array ;;; ; (type $I8Array (array (mut i8))) (func $raise-bad-byte (param $x (ref eq)) (unreachable)) (func $i8make-array (param $size i32) (param $v i32) (result (ref $I8Array)) (array.new $I8Array (local.get $v) (local.get $size))) (func $i8array-length (export "i8array-length") (param $arr (ref $I8Array)) (result i32) (array.len (local.get $arr))) (func $i8array-ref (export "i8array-ref") (param $arr (ref $I8Array)) (param $pos i32) (result i32) (array.get_u $I8Array (local.get $arr) (local.get $pos))) (func $i8array-set! (export "i8array-set!") (param $arr (ref $I8Array)) (param $pos i32) (param $v i32) (array.set $I8Array (local.get $arr) (local.get $pos) (local.get $v))) (func $i8array-fill! (export "i8array-fill!") (param $arr (ref $I8Array)) (param $v i32) (local $i i32) (local.set $i (i32.const 0)) (block $exit (loop $fill (br_if $exit (i32.ge_u (local.get $i) (array.len (local.get $arr)))) (array.set $I8Array (local.get $arr) (local.get $i) (local.get $v)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $fill)))) (func $i8array-copy! (export "i8array-copy!") (param $dest (ref $I8Array)) (param $dest-start i32) (param $src (ref $I8Array)) (param $src-start i32) (param $src-end i32) (local $src-len i32) (local $dest-len i32) (local $len i32) (local.set $src-len (array.len (local.get $src))) (local.set $dest-len (array.len (local.get $dest))) (if (i32.or (i32.or (i32.lt_u (local.get $src-start) (i32.const 0)) (i32.gt_u (local.get $src-end) (local.get $src-len))) (i32.gt_u (i32.add (local.get $dest-start) (i32.sub (local.get $src-end) (local.get $src-start))) (local.get $dest-len))) (then (unreachable))) (local.set $len (i32.sub (local.get $src-end) (local.get $src-start))) (array.copy $I8Array $I8Array (local.get $dest) (local.get $dest-start) (local.get $src) (local.get $src-start) (local.get $len))) (func $i8array-copy!/error (export "i8array-copy!/error") (param $dest (ref $I8Array)) (param $dest-start i32) (param $src (ref $I8Array)) (param $src-start i32) (param $src-end i32) (result i32) (local $src-len i32) (local $dest-len i32) (local $len i32) (local.set $src-len (array.len (local.get $src))) (local.set $dest-len (array.len (local.get $dest))) (if (i32.or (i32.or (i32.lt_u (local.get $src-start) (i32.const 0)) (i32.gt_u (local.get $src-end) (local.get $src-len))) (i32.gt_u (i32.add (local.get $dest-start) (i32.sub (local.get $src-end) (local.get $src-start))) (local.get $dest-len))) (then (return (i32.const 0)))) (local.set $len (i32.sub (local.get $src-end) (local.get $src-start))) (array.copy $I8Array $I8Array (local.get $dest) (local.get $dest-start) (local.get $src) (local.get $src-start) (local.get $len)) (i32.const 1)) (func $i8array-copy (param $arr (ref $I8Array)) (param $start i32) (param $end i32) (result (ref $I8Array)) (local $res (ref $I8Array)) (local.set $res (call $i8make-array (i32.sub (local.get $end) (local.get $start)) (i32.const 0))) (call $i8array-copy! (local.get $res) (i32.const 0) (local.get $arr) (local.get $start) (local.get $end)) (local.get $res)) (func $i8array-set/copy (param $arr (ref $I8Array)) (param $pos i32) (param $val i32) (result (ref $I8Array)) (local $len i32) (local $res (ref $I8Array)) (local.set $len (array.len (local.get $arr))) (local.set $res (call $i8make-array (local.get $len) (i32.const 0))) (call $i8array-copy! (local.get $res) (i32.const 0) (local.get $arr) (i32.const 0) (local.get $len)) (call $i8array-set! (local.get $res) (local.get $pos) (local.get $val)) (local.get $res)) (func $i8array-extend (param $arr (ref $I8Array)) (param $new-size i32) (param $val i32) (result (ref $I8Array)) (local $old-size i32) (local $res (ref $I8Array)) (local.set $old-size (array.len (local.get $arr))) (local.set $res (call $i8make-array (local.get $new-size) (local.get $val))) (call $i8array-copy! (local.get $res) (i32.const 0) (local.get $arr) (i32.const 0) (local.get $old-size)) (local.get $res)) (func $i8array-append (param $a0 (ref $I8Array)) (param $a1 (ref $I8Array)) (result (ref $I8Array)) (local $n0 i32) (local $n1 i32) (local $res (ref $I8Array)) (local.set $n0 (array.len (local.get $a0))) (local.set $n1 (array.len (local.get $a1))) (local.set $res (array.new $I8Array (i32.const 0) (i32.add (local.get $n0) (local.get $n1)))) (call $i8array-copy! (local.get $res) (i32.const 0) (local.get $a0) (i32.const 0) (local.get $n0)) (call $i8array-copy! (local.get $res) (local.get $n0) (local.get $a1) (i32.const 0) (local.get $n1)) (local.get $res)) (func $list->i8array (param $xs (ref eq)) (result (ref $I8Array)) (local $len i32) (local $arr (ref $I8Array)) (local $idx i32) (local $x (ref eq)) (local $v i32) (local $node (ref eq)) ;; Step 1: compute list length (local.set $len (call $length/i32 (local.get $xs))) ;; Step 2: allocate array of that length (local.set $arr (array.new $I8Array (i32.const 0) (local.get $len))) ;; Step 3: initialize index and node (local.set $idx (i32.const 0)) (local.set $node (local.get $xs)) ;; Step 4: fill loop (block $done (loop $fill ;; If we hit null, stop (br_if $done (ref.eq (local.get $node) (global.get $null))) ;; Must be a pair (if (ref.test (ref $Pair) (local.get $node)) (then ;; Extract the car (local.set $x (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $node)))) ;; Check that car is a fixnum (if (ref.test (ref i31) (local.get $x)) (then (local.set $v (i31.get_u (ref.cast (ref i31) (local.get $x)))) ;; Check fixnum has LSB = 0 (if (i32.eqz (i32.and (local.get $v) (i32.const 1))) (then ;; Shift right to get raw i32 (local.set $v (i32.shr_u (local.get $v) (i32.const 1))) ;; Check range 0–255 (if (i32.le_u (local.get $v) (i32.const 255)) (then ;; Store into array (array.set $I8Array (local.get $arr) (local.get $idx) (local.get $v)) ;; Advance (local.set $idx (i32.add (local.get $idx) (i32.const 1))) (local.set $node (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $node)))) (br $fill)) (else (call $raise-bad-byte (local.get $x)) (unreachable)))) (else (call $raise-check-fixnum (local.get $x)) (unreachable)))) (else (call $raise-check-fixnum (local.get $x)) (unreachable)))) (else (call $raise-pair-expected (local.get $node)) (unreachable))))) ;; Return array (local.get $arr)) (func $i8array->bytes (param $arr (ref $I8Array)) (result (ref $Bytes)) ;; Constructs a mutable $Bytes object (immutable = 0, hash = 0). (struct.new $Bytes (i32.const 0) ;; hash = 0 (i32.const 0) ;; immutable = false (local.get $arr))) ;; the backing I8Array (func $i8array->immutable-bytes (param $arr (ref $I8Array)) (result (ref $Bytes)) ;; Constructs an immutable $Bytes object (immutable = 1, hash = 0). (struct.new $Bytes (i32.const 0) ;; hash = 0 (i32.const 1) ;; immutable = true (local.get $arr))) ;; the backing I8Array ;;; ;;; Exceptions ;;; (tag $exn (param (ref eq))) ; an exception that that carries an value ; Note: ; The WebRacket version of `raise` ignores the `barrier?` argument. (func $raise (type $Prim12) (param $v (ref eq)) ; any/c (param $barrier? (ref eq)) ; any/c, optional with default #t (result (ref eq)) ; Handle optional barrier? with default $t #;(if (ref.eq (local.get $barrier?) (global.get $missing)) (then (local.set $barrier? (global.get $true)))) #;(call $js-log (global.get $string:uncaught-exception)) #;(call $js-log (local.get $v)) (throw $exn (local.get $v))) (func $call-with-exception-handler:procedure-expected-as-handler (unreachable)) (func $call-with-exception-handler:procedure-expected-as-thunk (unreachable)) (func $call-with-exception-handler (type $Prim2) (param $handler (ref eq)) ;; procedure? (param $thunk (ref eq)) ;; (-> any) (result (ref eq)) (local $handler-proc (ref $Procedure)) (local $handler-inv (ref $ProcedureInvoker)) (local $handler-args (ref $Args)) (local $thunk-proc (ref $Procedure)) (local $thunk-inv (ref $ProcedureInvoker)) (local $thunk-args (ref $Args)) (local $exn-val (ref eq)) (local $handler-result (ref eq)) ;; Validate handler argument. (if (i32.eqz (ref.test (ref $Procedure) (local.get $handler))) (then (call $call-with-exception-handler:procedure-expected-as-handler (local.get $handler)) (unreachable))) ;; Validate thunk argument. (if (i32.eqz (ref.test (ref $Procedure) (local.get $thunk))) (then (call $call-with-exception-handler:procedure-expected-as-thunk (local.get $thunk)) (unreachable))) (local.set $handler-proc (ref.cast (ref $Procedure) (local.get $handler))) (local.set $handler-inv (struct.get $Procedure $invoke (local.get $handler-proc))) (local.set $thunk-proc (ref.cast (ref $Procedure) (local.get $thunk))) (local.set $thunk-inv (struct.get $Procedure $invoke (local.get $thunk-proc))) (local.set $handler-args (array.new $Args (global.get $null) (i32.const 1))) (local.set $thunk-args (array.new $Args (global.get $null) (i32.const 0))) (block $done (result (ref eq)) (block $handler-block (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler-block) (call_ref $ProcedureInvoker (local.get $thunk-proc) (local.get $thunk-args) (local.get $thunk-inv)) (br $done))) (local.set $exn-val) (array.set $Args (local.get $handler-args) (i32.const 0) (local.get $exn-val)) ;; WebRacket currently ignores breaks, so this handler runs without break parameterization. (local.set $handler-result (call_ref $ProcedureInvoker (local.get $handler-proc) (local.get $handler-args) (local.get $handler-inv))) ;; Returning from the handler propagates the exception to enclosing handlers. (throw $exn (local.get $handler-result)) (unreachable))) (func $catch:procedure-expected-for-predicate (unreachable)) (func $catch:procedure-expected-for-handler (unreachable)) (func $catch:procedure-expected-for-thunk (unreachable)) (func $catch (type $Prim3) (param $pred (ref eq)) (param $handler (ref eq)) (param $thunk (ref eq)) (result (ref eq)) (local $pred-proc (ref $Procedure)) (local $handler-proc (ref $Procedure)) (local $thunk-proc (ref $Procedure)) (local $pred-inv (ref $ProcedureInvoker)) (local $handler-inv (ref $ProcedureInvoker)) (local $thunk-inv (ref $ProcedureInvoker)) (local $pred-args (ref $Args)) (local $handler-args (ref $Args)) (local $thunk-args (ref $Args)) (local $pred-result (ref eq)) (local $exn-val (ref eq)) ;; Validate the predicate, handler, and thunk arguments. (if (i32.eqz (ref.test (ref $Procedure) (local.get $pred))) (then (call $catch:procedure-expected-for-predicate (local.get $pred)) (unreachable))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $handler))) (then (call $catch:procedure-expected-for-handler (local.get $handler)) (unreachable))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $thunk))) (then (call $catch:procedure-expected-for-thunk (local.get $thunk)) (unreachable))) ;; Extract procedures and their invokers. (local.set $pred-proc (ref.cast (ref $Procedure) (local.get $pred))) (local.set $pred-inv (struct.get $Procedure $invoke (local.get $pred-proc))) (local.set $handler-proc (ref.cast (ref $Procedure) (local.get $handler))) (local.set $handler-inv (struct.get $Procedure $invoke (local.get $handler-proc))) (local.set $thunk-proc (ref.cast (ref $Procedure) (local.get $thunk))) (local.set $thunk-inv (struct.get $Procedure $invoke (local.get $thunk-proc))) ;; Preallocate argument arrays. (local.set $pred-args (array.new $Args (global.get $null) (i32.const 1))) (local.set $handler-args (array.new $Args (global.get $null) (i32.const 1))) (local.set $thunk-args (array.new $Args (global.get $null) (i32.const 0))) (block $done (result (ref eq)) (block $handler-block (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler-block) (call_ref $ProcedureInvoker (local.get $thunk-proc) (local.get $thunk-args) (local.get $thunk-inv)) (br $done))) ; handler block (local.set $exn-val) ; gets value from stack (array.set $Args (local.get $pred-args) (i32.const 0) (local.get $exn-val)) (local.set $pred-result (call_ref $ProcedureInvoker (local.get $pred-proc) (local.get $pred-args) (local.get $pred-inv))) (if (ref.eq (local.get $pred-result) (global.get $false)) (then (throw $exn (local.get $exn-val)))) (array.set $Args (local.get $handler-args) (i32.const 0) (local.get $exn-val)) (br $done (return_call_ref $ProcedureInvoker (local.get $handler-proc) (local.get $handler-args) (local.get $handler-inv)))) ; done block ; - fall through to return the value ) (func $catch*:procedure-expected-for-predicate (unreachable)) (func $catch*:procedure-expected-for-handler (unreachable)) (func $catch*:procedure-expected-for-thunk (unreachable)) (func $catch* (type $Prim3) (param $preds (ref eq)) (param $handlers (ref eq)) (param $thunk (ref eq)) (result (ref eq)) (local $thunk-proc (ref $Procedure)) (local $thunk-inv (ref $ProcedureInvoker)) (local $thunk-args (ref $Args)) (local $pred-args (ref $Args)) (local $handler-args (ref $Args)) (local $pred-node (ref eq)) (local $handler-node (ref eq)) (local $pred-pair (ref $Pair)) (local $handler-pair (ref $Pair)) (local $pred-val (ref eq)) (local $handler-val (ref eq)) (local $pred-tail (ref eq)) (local $handler-tail (ref eq)) (local $pred-proc (ref $Procedure)) (local $handler-proc (ref $Procedure)) (local $pred-inv (ref $ProcedureInvoker)) (local $handler-inv (ref $ProcedureInvoker)) (local $pred-result (ref eq)) (local $exn-val (ref eq)) ;; Validate thunk argument. (if (i32.eqz (ref.test (ref $Procedure) (local.get $thunk))) (then (call $catch*:procedure-expected-for-thunk #;(local.get $thunk)) (unreachable))) ;; Validate predicate and handler lists. (local.set $pred-node (local.get $preds)) (local.set $handler-node (local.get $handlers)) (block $validated (loop $validate (if (ref.eq (local.get $pred-node) (global.get $null)) (then (if (ref.eq (local.get $handler-node) (global.get $null)) (then (br $validated)) (else (call $raise-argument-error1 (global.get $symbol:catch*) (global.get $string:catch*-matching-lengths) (local.get $handlers)) (unreachable))))) (if (ref.eq (local.get $handler-node) (global.get $null)) (then (call $raise-argument-error1 (global.get $symbol:catch*) (global.get $string:catch*-matching-lengths) (local.get $handlers)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $pred-node))) (then (call $raise-pair-expected (local.get $pred-node)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $handler-node))) (then (call $raise-pair-expected (local.get $handler-node)) (unreachable))) (local.set $pred-pair (ref.cast (ref $Pair) (local.get $pred-node))) (local.set $handler-pair (ref.cast (ref $Pair) (local.get $handler-node))) (local.set $pred-val (struct.get $Pair $a (local.get $pred-pair))) (local.set $handler-val (struct.get $Pair $a (local.get $handler-pair))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $pred-val))) (then (call $catch*:procedure-expected-for-predicate #;(local.get $pred-val)) (unreachable))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $handler-val))) (then (call $catch*:procedure-expected-for-handler #;(local.get $handler-val)) (unreachable))) (local.set $pred-node (struct.get $Pair $d (local.get $pred-pair))) (local.set $handler-node (struct.get $Pair $d (local.get $handler-pair))) (br $validate))) ;; Prepare invokers and argument arrays. (local.set $thunk-proc (ref.cast (ref $Procedure) (local.get $thunk))) (local.set $thunk-inv (struct.get $Procedure $invoke (local.get $thunk-proc))) (local.set $thunk-args (array.new $Args (global.get $null) (i32.const 0))) (local.set $pred-args (array.new $Args (global.get $null) (i32.const 1))) (local.set $handler-args (array.new $Args (global.get $null) (i32.const 1))) (block $done (result (ref eq)) (block $handler (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler) (call_ref $ProcedureInvoker (local.get $thunk-proc) (local.get $thunk-args) (local.get $thunk-inv)) (br $done))) ;; handler block ;; - find predicate and handler to use (local.set $exn-val) ; uses value on stack (local.set $pred-node (local.get $preds)) (local.set $handler-node (local.get $handlers)) (loop $search ; rethrow if no predicates return true (if (ref.eq (local.get $pred-node) (global.get $null)) (then (throw $exn (local.get $exn-val)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $pred-node))) (then (call $raise-pair-expected (local.get $pred-node)) (unreachable))) (if (ref.eq (local.get $handler-node) (global.get $null)) (then (call $raise-argument-error1 (global.get $symbol:catch*) (global.get $string:catch*-matching-lengths) (local.get $handlers)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $handler-node))) (then (call $raise-pair-expected (local.get $handler-node)) (unreachable))) (local.set $pred-pair (ref.cast (ref $Pair) (local.get $pred-node))) (local.set $handler-pair (ref.cast (ref $Pair) (local.get $handler-node))) (local.set $pred-val (struct.get $Pair $a (local.get $pred-pair))) (local.set $handler-val (struct.get $Pair $a (local.get $handler-pair))) (local.set $pred-tail (struct.get $Pair $d (local.get $pred-pair))) (local.set $handler-tail (struct.get $Pair $d (local.get $handler-pair))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $pred-val))) (then (call $raise-argument-error:procedure-expected (local.get $pred-val)) (unreachable))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $handler-val))) (then (call $raise-argument-error:procedure-expected (local.get $handler-val)) (unreachable))) (local.set $pred-proc (ref.cast (ref $Procedure) (local.get $pred-val))) (local.set $pred-inv (struct.get $Procedure $invoke (local.get $pred-proc))) (array.set $Args (local.get $pred-args) (i32.const 0) (local.get $exn-val)) (local.set $pred-result (call_ref $ProcedureInvoker (local.get $pred-proc) (local.get $pred-args) (local.get $pred-inv))) (if (ref.eq (local.get $pred-result) (global.get $false)) (then (local.set $pred-node (local.get $pred-tail)) (local.set $handler-node (local.get $handler-tail)) (br $search)) (else (local.set $pred-node (local.get $pred-tail)) (local.set $handler-node (local.get $handler-tail)) (local.set $handler-proc (ref.cast (ref $Procedure) (local.get $handler-val))) (local.set $handler-inv (struct.get $Procedure $invoke (local.get $handler-proc))) (array.set $Args (local.get $handler-args) (i32.const 0) (local.get $exn-val)) (return_call_ref $ProcedureInvoker (local.get $handler-proc) (local.get $handler-args) (local.get $handler-inv))))) (unreachable))) ;;; ;;; Exception Structires ;;; ;; Exception base struct type descriptor cache (func $ensure-exn-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $indices (ref eq)) (local.set $existing (global.get $exn-type)) (if (ref.is_null (local.get $existing)) (then (local.set $indices (call $list-from-range/checked (i32.const 0) (i32.const 2))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn)) (global.get $false) (i32.const 2) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $indices) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn)))) (global.set $exn-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail struct type descriptor cache (func $ensure-exn:fail-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail)))) (global.set $exn:fail-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Ensure message argument is a string (func $exn-ensure-message (param $who (ref eq)) ; symbol (param $message (ref eq)) ; any/c (result (ref eq)) (if (i32.eqz (ref.test (ref $String) (local.get $message))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:string?) (local.get $message)) (unreachable))) (local.get $message)) ;; Validate that a value is a kernel exception (func $exn-ensure (param $who (ref eq)) ; symbol (param $v (ref eq)) ; any/c (result (ref $Struct)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.get $struct)) ;; Construct a kernel exception instance (func $exn/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn-type)) (local.set $fields (array.new_fixed $Array 2 (local.get $message) (local.get $marks))) (struct.new $Struct (i32.const 0) (global.get $false) ; <-- a name symbol !? (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail instance (func $exn:fail/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail-type)) (local.set $fields (array.new_fixed $Array 2 (local.get $message) (local.get $marks))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; exn : string? continuation-mark-set? -> exn (func $exn (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn/make (call $exn-ensure-message (global.get $symbol:exn) (local.get $message)) (local.get $marks)))) ;; make-exn : string? continuation-mark-set? -> exn (func $make-exn (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn/make (call $exn-ensure-message (global.get $symbol:make-exn) (local.get $message)) (local.get $marks)))) ;; exn? : any/c -> boolean? (func $exn? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn-message : exn -> string? (func $exn-message (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $struct (ref $Struct)) (local $fields (ref $Array)) (local.set $struct (call $exn-ensure (global.get $symbol:exn-message) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.get $Array (local.get $fields) (i32.const 0))) ;; exn-continuation-marks : exn -> continuation-mark-set? (func $exn-continuation-marks (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $struct (ref $Struct)) (local $fields (ref $Array)) (local.set $struct (call $exn-ensure (global.get $symbol:exn-continuation-marks) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.get $Array (local.get $fields) (i32.const 1))) ;; --- ;; exn:fail : string? continuation-mark-set? -> exn:fail (func $exn:fail (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail/make (call $exn-ensure-message (global.get $symbol:exn:fail) (local.get $message)) (local.get $marks)))) ;; make-exn:fail : string? continuation-mark-set? -> exn:fail (func $make-exn:fail (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail/make (call $exn-ensure-message (global.get $symbol:make-exn:fail) (local.get $message)) (local.get $marks)))) ;; exn:fail? : any/c -> boolean? (func $exn:fail? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ; Kernel exception fail:contract struct type descriptor cache (func $ensure-exn:fail:contract-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail:contract-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract)))) (global.set $exn:fail:contract-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail:contract:arity struct type descriptor cache (func $ensure-exn:fail:contract:arity-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail:contract:arity-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail:contract-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract:arity)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract:arity)))) (global.set $exn:fail:contract:arity-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail:contract:divide-by-zero struct type descriptor cache (func $ensure-exn:fail:contract:divide-by-zero-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail:contract:divide-by-zero-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail:contract-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract:divide-by-zero)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract:divide-by-zero)))) (global.set $exn:fail:contract:divide-by-zero-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail:contract:non-fixnum-result struct type descriptor cache (func $ensure-exn:fail:contract:non-fixnum-result-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail:contract:non-fixnum-result-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail:contract-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract:non-fixnum-result)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract:non-fixnum-result)))) (global.set $exn:fail:contract:non-fixnum-result-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail:contract:variable struct type descriptor cache (func $ensure-exn:fail:contract:variable-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local $super-count i32) (local $new-immut (ref eq)) (local.set $existing (global.get $exn:fail:contract:variable-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail:contract-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $super-count (struct.get $StructType $field-count (local.get $super))) (local.set $new-immut (call $append/2 (local.get $immut) (call $list-from-range/checked (local.get $super-count) (i32.add (local.get $super-count) (i32.const 1))))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract:variable)) (ref.cast (ref eq) (local.get $super)) (i32.const 1) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $new-immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:contract:variable)))) (global.set $exn:fail:contract:variable-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Validate that a value is a kernel exception:fail:contract (func $exn:fail:contract-ensure (param $who (ref eq)) ; symbol (param $v (ref eq)) ; any/c (result (ref $Struct)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:contract-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.get $struct)) ;; Validate that a value is a kernel exception:fail:contract:variable (func $exn:fail:contract:variable-ensure (param $who (ref eq)) ; symbol (param $v (ref eq)) ; any/c (result (ref $Struct)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:contract:variable-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.get $struct)) ;; Validate that the id argument is a symbol (func $exn:fail:contract:variable-ensure-id (param $who (ref eq)) ; symbol (param $id (ref eq)) ; any/c (result (ref eq)) (if (i32.eqz (ref.test (ref $Symbol) (local.get $id))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:symbol?) (local.get $id)) (unreachable))) (local.get $id)) ;; Construct a kernel exception:fail:contract instance (func $exn:fail:contract/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:contract-type)) (local.set $fields (array.new_fixed $Array 2 (local.get $message) (local.get $marks))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail:contract:arity instance (func $exn:fail:contract:arity/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:contract:arity-type)) (local.set $fields (array.new_fixed $Array 2 (local.get $message) (local.get $marks))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail:contract:divide-by-zero instance (func $exn:fail:contract:divide-by-zero/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:contract:divide-by-zero-type)) (local.set $fields (array.new_fixed $Array 2 (local.get $message) (local.get $marks))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail:contract:non-fixnum-result instance (func $exn:fail:contract:non-fixnum-result/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:contract:non-fixnum-result-type)) (local.set $fields (array.new_fixed $Array 2 (local.get $message) (local.get $marks))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail:contract:variable instance (func $exn:fail:contract:variable/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $id (ref eq)) ; symbol (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:contract:variable-type)) (local.set $fields (array.new_fixed $Array 3 (local.get $message) (local.get $marks) (local.get $id))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; exn:fail:contract : string? continuation-mark-set? -> exn:fail:contract (func $exn:fail:contract (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract/make (call $exn-ensure-message (global.get $symbol:exn:fail:contract) (local.get $message)) (local.get $marks)))) ;; make-exn:fail:contract : string? continuation-mark-set? -> exn:fail:contract (func $make-exn:fail:contract (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:contract) (local.get $message)) (local.get $marks)))) ;; exn:fail:contract? : any/c -> boolean? (func $exn:fail:contract? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:contract-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:contract:arity : string? continuation-mark-set? -> exn:fail:contract:arity (func $exn:fail:contract:arity (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract:arity/make (call $exn-ensure-message (global.get $symbol:exn:fail:contract:arity) (local.get $message)) (local.get $marks)))) ;; make-exn:fail:contract:arity : string? continuation-mark-set? -> exn:fail:contract:arity (func $make-exn:fail:contract:arity (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract:arity/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:contract:arity) (local.get $message)) (local.get $marks)))) ;; exn:fail:contract:arity? : any/c -> boolean? (func $exn:fail:contract:arity? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:contract:arity-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:contract:divide-by-zero : string? continuation-mark-set? -> exn:fail:contract:divide-by-zero (func $exn:fail:contract:divide-by-zero (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract:divide-by-zero/make (call $exn-ensure-message (global.get $symbol:exn:fail:contract:divide-by-zero) (local.get $message)) (local.get $marks)))) ;; make-exn:fail:contract:divide-by-zero : string? continuation-mark-set? -> exn:fail:contract:divide-by-zero (func $make-exn:fail:contract:divide-by-zero (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract:divide-by-zero/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:contract:divide-by-zero) (local.get $message)) (local.get $marks)))) ;; exn:fail:contract:divide-by-zero? : any/c -> boolean? (func $exn:fail:contract:divide-by-zero? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:contract:divide-by-zero-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:contract:non-fixnum-result : string? continuation-mark-set? -> exn:fail:contract:non-fixnum-result (func $exn:fail:contract:non-fixnum-result (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract:non-fixnum-result/make (call $exn-ensure-message (global.get $symbol:exn:fail:contract:non-fixnum-result) (local.get $message)) (local.get $marks)))) ;; make-exn:fail:contract:non-fixnum-result : string? continuation-mark-set? -> exn:fail:contract:non-fixnum-result (func $make-exn:fail:contract:non-fixnum-result (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract:non-fixnum-result/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:contract:non-fixnum-result) (local.get $message)) (local.get $marks)))) ;; exn:fail:contract:non-fixnum-result? : any/c -> boolean? (func $exn:fail:contract:non-fixnum-result? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:contract:non-fixnum-result-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:contract:variable : string? continuation-mark-set? symbol? -> exn:fail:contract:variable (func $exn:fail:contract:variable (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $id (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract:variable/make (call $exn-ensure-message (global.get $symbol:exn:fail:contract:variable) (local.get $message)) (local.get $marks) (call $exn:fail:contract:variable-ensure-id (global.get $symbol:exn:fail:contract:variable) (local.get $id))))) ;; make-exn:fail:contract:variable : string? continuation-mark-set? symbol? -> exn:fail:contract:variable (func $make-exn:fail:contract:variable (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $id (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:contract:variable/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:contract:variable) (local.get $message)) (local.get $marks) (call $exn:fail:contract:variable-ensure-id (global.get $symbol:make-exn:fail:contract:variable) (local.get $id))))) ;; exn:fail:contract:variable? : any/c -> boolean? (func $exn:fail:contract:variable? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:contract:variable-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:contract:variable-id : exn:fail:contract:variable -> symbol? (func $exn:fail:contract:variable-id (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $struct (ref $Struct)) (local $fields (ref $Array)) (local.set $struct (call $exn:fail:contract:variable-ensure (global.get $symbol:exn:fail:contract:variable-id) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.get $Array (local.get $fields) (i32.const 2))) ;; Kernel exception fail:read struct type descriptor cache (func $ensure-exn:fail:read-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local $super-count i32) (local $new-immut (ref eq)) (local.set $existing (global.get $exn:fail:read-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $super-count (struct.get $StructType $field-count (local.get $super))) (local.set $new-immut (call $append/2 (local.get $immut) (call $list-from-range/checked (local.get $super-count) (i32.add (local.get $super-count) (i32.const 1))))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:read)) (ref.cast (ref eq) (local.get $super)) (i32.const 1) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $new-immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:read)))) (global.set $exn:fail:read-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail:read:eof struct type descriptor cache (func $ensure-exn:fail:read:eof-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail:read:eof-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail:read-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:read:eof)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:read:eof)))) (global.set $exn:fail:read:eof-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail:read:non-char struct type descriptor cache (func $ensure-exn:fail:read:non-char-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail:read:non-char-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail:read-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:read:non-char)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:read:non-char)))) (global.set $exn:fail:read:non-char-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Validate that srclocs is a (listof srcloc?) (func $exn:fail:read-ensure-srclocs (param $who (ref eq)) ; symbol (param $srclocs (ref eq)) ; any/c (result (ref eq)) (local $rest (ref eq)) (local $pair (ref $Pair)) (local $car (ref eq)) (local.set $rest (local.get $srclocs)) (block $done (loop $loop (if (ref.eq (local.get $rest) (global.get $null)) (then (br $done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $rest))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:listof-srcloc?) (local.get $srclocs)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $rest))) (local.set $car (struct.get $Pair $a (local.get $pair))) (if (ref.eq (call $srcloc? (local.get $car)) (global.get $true)) (then (nop)) (else (call $raise-argument-error1 (local.get $who) (global.get $string:listof-srcloc?) (local.get $srclocs)) (unreachable))) (local.set $rest (struct.get $Pair $d (local.get $pair))) (br $loop))) (local.get $srclocs)) ;; Validate that a value is a kernel exception:fail:read (func $exn:fail:read-ensure (param $who (ref eq)) ; symbol (param $v (ref eq)) ; any/c (result (ref $Struct)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:read-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.get $struct)) ;; Construct a kernel exception:fail:read instance (func $exn:fail:read/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; (listof srcloc?) (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:read-type)) (local.set $fields (array.new_fixed $Array 3 (local.get $message) (local.get $marks) (local.get $srclocs))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail:read:eof instance (func $exn:fail:read:eof/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; (listof srcloc?) (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:read:eof-type)) (local.set $fields (array.new_fixed $Array 3 (local.get $message) (local.get $marks) (local.get $srclocs))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail:read:non-char instance (func $exn:fail:read:non-char/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; (listof srcloc?) (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:read:non-char-type)) (local.set $fields (array.new_fixed $Array 3 (local.get $message) (local.get $marks) (local.get $srclocs))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; exn:fail:read : string? continuation-mark-set? (listof srcloc?) -> exn:fail:read (func $exn:fail:read (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:read/make (call $exn-ensure-message (global.get $symbol:exn:fail:read) (local.get $message)) (local.get $marks) (call $exn:fail:read-ensure-srclocs (global.get $symbol:exn:fail:read) (local.get $srclocs))))) ;; make-exn:fail:read : string? continuation-mark-set? (listof srcloc?) -> exn:fail:read (func $make-exn:fail:read (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:read/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:read) (local.get $message)) (local.get $marks) (call $exn:fail:read-ensure-srclocs (global.get $symbol:make-exn:fail:read) (local.get $srclocs))))) ;; exn:fail:read? : any/c -> boolean? (func $exn:fail:read? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:read-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:read-srclocs : exn:fail:read -> (listof srcloc?) (func $exn:fail:read-srclocs (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $struct (ref $Struct)) (local $fields (ref $Array)) (local.set $struct (call $exn:fail:read-ensure (global.get $symbol:exn:fail:read-srclocs) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.get $Array (local.get $fields) (i32.const 2))) ;; exn:fail:read:eof : string? continuation-mark-set? (listof srcloc?) -> exn:fail:read:eof (func $exn:fail:read:eof (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:read:eof/make (call $exn-ensure-message (global.get $symbol:exn:fail:read:eof) (local.get $message)) (local.get $marks) (call $exn:fail:read-ensure-srclocs (global.get $symbol:exn:fail:read:eof) (local.get $srclocs))))) ;; make-exn:fail:read:eof : string? continuation-mark-set? (listof srcloc?) -> exn:fail:read:eof (func $make-exn:fail:read:eof (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:read:eof/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:read:eof) (local.get $message)) (local.get $marks) (call $exn:fail:read-ensure-srclocs (global.get $symbol:make-exn:fail:read:eof) (local.get $srclocs))))) ;; exn:fail:read:eof? : any/c -> boolean? (func $exn:fail:read:eof? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:read:eof-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:read:non-char : string? continuation-mark-set? (listof srcloc?) -> exn:fail:read:non-char (func $exn:fail:read:non-char (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:read:non-char/make (call $exn-ensure-message (global.get $symbol:exn:fail:read:non-char) (local.get $message)) (local.get $marks) (call $exn:fail:read-ensure-srclocs (global.get $symbol:exn:fail:read:non-char) (local.get $srclocs))))) ;; make-exn:fail:read:non-char : string? continuation-mark-set? (listof srcloc?) -> exn:fail:read:non-char (func $make-exn:fail:read:non-char (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $srclocs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:read:non-char/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:read:non-char) (local.get $message)) (local.get $marks) (call $exn:fail:read-ensure-srclocs (global.get $symbol:make-exn:fail:read:non-char) (local.get $srclocs))))) ;; exn:fail:read:non-char? : any/c -> boolean? (func $exn:fail:read:non-char? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:read:non-char-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; Kernel exception fail:filesystem struct type descriptor cache (func $ensure-exn:fail:filesystem-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail:filesystem-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:filesystem)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:filesystem)))) (global.set $exn:fail:filesystem-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Construct a kernel exception:fail:filesystem instance (func $exn:fail:filesystem/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:filesystem-type)) (local.set $fields (array.new_fixed $Array 2 (local.get $message) (local.get $marks))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; exn:fail:filesystem : string? continuation-mark-set? -> exn:fail:filesystem (func $exn:fail:filesystem (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:filesystem/make (call $exn-ensure-message (global.get $symbol:exn:fail:filesystem) (local.get $message)) (local.get $marks)))) ;; make-exn:fail:filesystem : string? continuation-mark-set? -> exn:fail:filesystem (func $make-exn:fail:filesystem (type $Prim2) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:filesystem/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:filesystem) (local.get $message)) (local.get $marks)))) ;; exn:fail:filesystem? : any/c -> boolean? (func $exn:fail:filesystem? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:filesystem-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; Kernel exception fail:syntax struct type descriptor cache (func $ensure-exn:fail:syntax-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local $super-count i32) (local $new-immut (ref eq)) (local.set $existing (global.get $exn:fail:syntax-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $super-count (struct.get $StructType $field-count (local.get $super))) (local.set $new-immut (call $append/2 (local.get $immut) (call $list-from-range/checked (local.get $super-count) (i32.add (local.get $super-count) (i32.const 1))))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:syntax)) (ref.cast (ref eq) (local.get $super)) (i32.const 1) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $new-immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:syntax)))) (global.set $exn:fail:syntax-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail:syntax:missing-module struct type descriptor cache (func $ensure-exn:fail:syntax:missing-module-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local $super-count i32) (local $new-immut (ref eq)) (local.set $existing (global.get $exn:fail:syntax:missing-module-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail:syntax-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $super-count (struct.get $StructType $field-count (local.get $super))) (local.set $new-immut (call $append/2 (local.get $immut) (call $list-from-range/checked (local.get $super-count) (i32.add (local.get $super-count) (i32.const 1))))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:syntax:missing-module)) (ref.cast (ref eq) (local.get $super)) (i32.const 1) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $new-immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:syntax:missing-module)))) (global.set $exn:fail:syntax:missing-module-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Kernel exception fail:syntax:unbound struct type descriptor cache (func $ensure-exn:fail:syntax:unbound-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $super (ref $StructType)) (local $immut (ref eq)) (local.set $existing (global.get $exn:fail:syntax:unbound-type)) (if (ref.is_null (local.get $existing)) (then (local.set $super (call $ensure-exn:fail:syntax-type)) (local.set $immut (struct.get $StructType $immutables (local.get $super))) (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:syntax:unbound)) (ref.cast (ref eq) (local.get $super)) (i32.const 0) (i32.const 0) (global.get $false) (global.get $null) (global.get $false) (global.get $false) (local.get $immut) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:exn:fail:syntax:unbound)))) (global.set $exn:fail:syntax:unbound-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) ;; Validate that exprs is a (listof syntax?) (func $exn:fail:syntax-ensure-exprs (param $who (ref eq)) ; symbol (param $exprs (ref eq)) ; any/c (result (ref eq)) (local $rest (ref eq)) (local $pair (ref $Pair)) (local $car (ref eq)) (local.set $rest (local.get $exprs)) (block $done (loop $loop (if (ref.eq (local.get $rest) (global.get $null)) (then (br $done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $rest))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:listof-syntax?) (local.get $exprs)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $rest))) (local.set $car (struct.get $Pair $a (local.get $pair))) (if (ref.eq (call $syntax? (local.get $car)) (global.get $true)) (then (nop)) (else (call $raise-argument-error1 (local.get $who) (global.get $string:listof-syntax?) (local.get $exprs)) (unreachable))) (local.set $rest (struct.get $Pair $d (local.get $pair))) (br $loop))) (local.get $exprs)) ;; Validate that a value is a kernel exception:fail:syntax (func $exn:fail:syntax-ensure (param $who (ref eq)) ; symbol (param $v (ref eq)) ; any/c (result (ref $Struct)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:syntax-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.get $struct)) ;; Validate that a value is a kernel exception:fail:syntax:missing-module (func $exn:fail:syntax:missing-module-ensure (param $who (ref eq)) ; symbol (param $v (ref eq)) ; any/c (result (ref $Struct)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:syntax:missing-module-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:struct?) (local.get $v)) (unreachable))) (local.get $struct)) ;; Construct a kernel exception:fail:syntax instance (func $exn:fail:syntax/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; (listof syntax?) (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:syntax-type)) (local.set $fields (array.new_fixed $Array 3 (local.get $message) (local.get $marks) (local.get $exprs))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail:syntax:missing-module instance (func $exn:fail:syntax:missing-module/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; (listof syntax?) (param $path (ref eq)) ; module-path? (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:syntax:missing-module-type)) (local.set $fields (array.new_fixed $Array 4 (local.get $message) (local.get $marks) (local.get $exprs) (local.get $path))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; Construct a kernel exception:fail:syntax:unbound instance (func $exn:fail:syntax:unbound/make (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; (listof syntax?) (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-exn:fail:syntax:unbound-type)) (local.set $fields (array.new_fixed $Array 3 (local.get $message) (local.get $marks) (local.get $exprs))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) ;; exn:fail:syntax : string? continuation-mark-set? (listof syntax?) -> exn:fail:syntax (func $exn:fail:syntax (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:syntax/make (call $exn-ensure-message (global.get $symbol:exn:fail:syntax) (local.get $message)) (local.get $marks) (call $exn:fail:syntax-ensure-exprs (global.get $symbol:exn:fail:syntax) (local.get $exprs))))) ;; make-exn:fail:syntax : string? continuation-mark-set? (listof syntax?) -> exn:fail:syntax (func $make-exn:fail:syntax (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:syntax/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:syntax) (local.get $message)) (local.get $marks) (call $exn:fail:syntax-ensure-exprs (global.get $symbol:make-exn:fail:syntax) (local.get $exprs))))) ;; exn:fail:syntax? : any/c -> boolean? (func $exn:fail:syntax? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:syntax-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:syntax-exprs : exn:fail:syntax -> (listof syntax?) (func $exn:fail:syntax-exprs (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $struct (ref $Struct)) (local $fields (ref $Array)) (local.set $struct (call $exn:fail:syntax-ensure (global.get $symbol:exn:fail:syntax-exprs) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.get $Array (local.get $fields) (i32.const 2))) ;; exn:fail:syntax:missing-module : string? continuation-mark-set? (listof syntax?) module-path? -> exn:fail:syntax:missing-module (func $exn:fail:syntax:missing-module (type $Prim4) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; any/c (param $path (ref eq)) ; module-path? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:syntax:missing-module/make (call $exn-ensure-message (global.get $symbol:exn:fail:syntax:missing-module) (local.get $message)) (local.get $marks) (call $exn:fail:syntax-ensure-exprs (global.get $symbol:exn:fail:syntax:missing-module) (local.get $exprs)) (local.get $path)))) ;; make-exn:fail:syntax:missing-module : string? continuation-mark-set? (listof syntax?) module-path? -> exn:fail:syntax:missing-module (func $make-exn:fail:syntax:missing-module (type $Prim4) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; any/c (param $path (ref eq)) ; module-path? (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:syntax:missing-module/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:syntax:missing-module) (local.get $message)) (local.get $marks) (call $exn:fail:syntax-ensure-exprs (global.get $symbol:make-exn:fail:syntax:missing-module) (local.get $exprs)) (local.get $path)))) ;; exn:fail:syntax:missing-module? : any/c -> boolean? (func $exn:fail:syntax:missing-module? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:syntax:missing-module-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; exn:fail:syntax:missing-module-path : exn:fail:syntax:missing-module -> module-path? (func $exn:fail:syntax:missing-module-path (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $struct (ref $Struct)) (local $fields (ref $Array)) (local.set $struct (call $exn:fail:syntax:missing-module-ensure (global.get $symbol:exn:fail:syntax:missing-module-path) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.get $Array (local.get $fields) (i32.const 3))) ;; exn:fail:syntax:unbound : string? continuation-mark-set? (listof syntax?) -> exn:fail:syntax:unbound (func $exn:fail:syntax:unbound (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:syntax:unbound/make (call $exn-ensure-message (global.get $symbol:exn:fail:syntax:unbound) (local.get $message)) (local.get $marks) (call $exn:fail:syntax-ensure-exprs (global.get $symbol:exn:fail:syntax:unbound) (local.get $exprs))))) ;; make-exn:fail:syntax:unbound : string? continuation-mark-set? (listof syntax?) -> exn:fail:syntax:unbound (func $make-exn:fail:syntax:unbound (type $Prim3) (param $message (ref eq)) ; string (param $marks (ref eq)) ; continuation-mark-set? (param $exprs (ref eq)) ; any/c (result (ref eq)) (ref.cast (ref eq) (call $exn:fail:syntax:unbound/make (call $exn-ensure-message (global.get $symbol:make-exn:fail:syntax:unbound) (local.get $message)) (local.get $marks) (call $exn:fail:syntax-ensure-exprs (global.get $symbol:make-exn:fail:syntax:unbound) (local.get $exprs))))) ;; exn:fail:syntax:unbound? : any/c -> boolean? (func $exn:fail:syntax:unbound? (type $Prim1) (param $v (ref eq)) ; any/c (result (ref eq)) (local $std (ref $StructType)) (local $struct (ref $Struct)) (local $ok i32) (local.set $std (call $ensure-exn:fail:syntax:unbound-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $ok (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; raise-read-error : string? any/c (or/c exact-positive-integer? #f) ;; (or/c exact-nonnegative-integer? #f) ;; (or/c exact-positive-integer? #f) ;; (or/c exact-nonnegative-integer? #f) ;; [#:extra-srclocs (listof srcloc?)] -> none (func $raise-read-error (type $Prim67) (param $message (ref eq)) ; string (param $source (ref eq)) ; (or/c path-string? symbol? #f) (param $line (ref eq)) ; (or/c exact-positive-integer? #f) (param $column (ref eq)) ; (or/c exact-nonnegative-integer? #f) (param $position (ref eq)) ; (or/c exact-positive-integer? #f) (param $span (ref eq)) ; (or/c exact-nonnegative-integer? #f) (param $extra (ref eq)) ; optional (listof srcloc?), default = '() (result (ref eq)) (local $who (ref eq)) (local $message-checked (ref eq)) (local $line-checked (ref eq)) (local $column-checked (ref eq)) (local $position-checked (ref eq)) (local $span-checked (ref eq)) (local $extra-checked (ref eq)) (local $srcloc (ref eq)) (local $srclocs (ref eq)) (local.set $who (global.get $symbol:raise-read-error)) (local.set $message-checked (call $exn-ensure-message (local.get $who) (local.get $message))) (local.set $line-checked (call $srcloc-check-positive (local.get $who) (local.get $line))) (local.set $column-checked (call $srcloc-check-nonnegative (local.get $who) (local.get $column))) (local.set $position-checked (call $srcloc-check-positive (local.get $who) (local.get $position))) (local.set $span-checked (call $srcloc-check-nonnegative (local.get $who) (local.get $span))) (local.set $extra-checked (if (result (ref eq)) (ref.eq (local.get $extra) (global.get $missing)) (then (global.get $null)) (else (call $exn:fail:read-ensure-srclocs (local.get $who) (local.get $extra))))) (local.set $srcloc (call $make-srcloc (local.get $source) (local.get $line-checked) (local.get $column-checked) (local.get $position-checked) (local.get $span-checked))) (local.set $srclocs (call $cons (local.get $srcloc) (local.get $extra-checked))) (call $raise (call $exn:fail:read (local.get $message-checked) (global.get $false) (local.get $srclocs)) (global.get $true)) (unreachable)) ;; raise-read-eof-error : string? any/c (or/c exact-positive-integer? #f) ;; (or/c exact-nonnegative-integer? #f) ;; (or/c exact-positive-integer? #f) ;; (or/c exact-nonnegative-integer? #f) ;; [#:extra-srclocs (listof srcloc?)] -> none (func $raise-read-eof-error (type $Prim67) (param $message (ref eq)) ; string (param $source (ref eq)) ; (or/c path-string? symbol? #f) (param $line (ref eq)) ; (or/c exact-positive-integer? #f) (param $column (ref eq)) ; (or/c exact-nonnegative-integer? #f) (param $position (ref eq)) ; (or/c exact-positive-integer? #f) (param $span (ref eq)) ; (or/c exact-nonnegative-integer? #f) (param $extra (ref eq)) ; optional (listof srcloc?), default = '() (result (ref eq)) (local $who (ref eq)) (local $message-checked (ref eq)) (local $line-checked (ref eq)) (local $column-checked (ref eq)) (local $position-checked (ref eq)) (local $span-checked (ref eq)) (local $extra-checked (ref eq)) (local $srclocs (ref eq)) (local.set $who (global.get $symbol:raise-read-eof-error)) (local.set $message-checked (call $exn-ensure-message (local.get $who) (local.get $message))) (local.set $line-checked (call $srcloc-check-positive (local.get $who) (local.get $line))) (local.set $column-checked (call $srcloc-check-nonnegative (local.get $who) (local.get $column))) (local.set $position-checked (call $srcloc-check-positive (local.get $who) (local.get $position))) (local.set $span-checked (call $srcloc-check-nonnegative (local.get $who) (local.get $span))) (local.set $extra-checked (if (result (ref eq)) (ref.eq (local.get $extra) (global.get $missing)) (then (global.get $null)) (else (call $exn:fail:read-ensure-srclocs (local.get $who) (local.get $extra))))) (local.set $srclocs (call $cons (call $srcloc (local.get $source) (local.get $line-checked) (local.get $column-checked) (local.get $position-checked) (local.get $span-checked)) (local.get $extra-checked))) (call $raise (call $exn:fail:read:eof (local.get $message-checked) (global.get $false) (local.get $srclocs)) (global.get $true)) (unreachable)) ;;; ;;; RUNTIME SUPPORT FOR MATCH ;;; (func $match:error (type $Prim3) (param $val (ref eq)) (param $srclocs (ref eq)) (param $form-name (ref eq)) (result (ref eq)) (local $name-str (ref eq)) (local $prefix (ref eq)) (local $val-str (ref eq)) (local.set $name-str (block $done (result (ref eq)) (if (ref.eq (call $symbol? (local.get $form-name)) (global.get $true)) (then (br $done (call $symbol->immutable-string (local.get $form-name))))) (if (ref.eq (local.get $form-name) (global.get $false)) (then (br $done (call $symbol->immutable-string (global.get $symbol:match))))) (if (ref.eq (call $string? (local.get $form-name)) (global.get $true)) (then (br $done (local.get $form-name)))) (br $done (call $format/display (local.get $form-name))))) (local.set $prefix (call $string-append/2 (local.get $name-str) (global.get $string:match-error:prefix))) (local.set $val-str (call $format/display (local.get $val))) (call $raise (call $string-append/2 (local.get $prefix) (local.get $val-str)) (global.get $missing)) (unreachable)) ;;; ;;; Checkers ;;; (func $check-list (type $Prim1) (param $l (ref eq)) ;; list (result (ref eq)) (if (result (ref eq)) (ref.eq (call $list? (local.get $l)) (global.get $true)) (then (global.get $void)) (else (call $raise-argument-error1 (global.get $symbol:in-list) (global.get $string:list?) (local.get $l)) (unreachable)))) (func $check-vector (type $Prim1) (param $v (ref eq)) ;; vector (result (ref eq)) (if (result (ref eq)) (ref.eq (call $vector? (local.get $v)) (global.get $true)) (then (global.get $void)) (else (call $raise-argument-error1 (global.get $symbol:in-vector) (global.get $string:vector?) (local.get $v)) (unreachable)))) (func $check-string (type $Prim1) (param $s (ref eq)) ;; string (result (ref eq)) (if (result (ref eq)) (ref.eq (call $string? (local.get $s)) (global.get $true)) (then (global.get $void)) (else (call $raise-argument-error1 (global.get $symbol:in-string) (global.get $string:string?) (local.get $s)) (unreachable)))) ;; Racket's check-mlist accepts only mutable pairs; mpairs are ;; currently unsupported, so this version accepts any pair. (func $check-mlist (type $Prim1) (param $l (ref eq)) ;; mutable list (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $l) (global.get $null)) (then (global.get $void)) (else (if (result (ref eq)) (ref.eq (call $pair? (local.get $l)) (global.get $true)) (then (global.get $void)) (else (call $raise-argument-error1 (global.get $symbol:in-mlist) (global.get $string:mpair-or-null) (local.get $l)) (unreachable)))))) (func $check-range (type $Prim3) (param $a (ref eq)) ;; start (param $b (ref eq)) ;; end (param $step (ref eq)) ;; step (result (ref eq)) (call $check-range-generic (global.get $symbol:in-range) (local.get $a) (local.get $b) (local.get $step))) (func $check-range-generic (param $who (ref eq)) ;; symbol (param $a (ref eq)) ;; start (param $b (ref eq)) ;; end (param $step (ref eq)) ;; step (result (ref eq)) (if (result (ref eq)) (ref.eq (call $real? (local.get $a)) (global.get $true)) (then (if (result (ref eq)) (ref.eq (call $real? (local.get $b)) (global.get $true)) (then (if (result (ref eq)) (ref.eq (call $real? (local.get $step)) (global.get $true)) (then (global.get $void)) (else (call $raise-argument-error1 (local.get $who) (global.get $string:real?) (local.get $step)) (unreachable)))) (else (call $raise-argument-error1 (local.get $who) (global.get $string:real?) (local.get $b)) (unreachable)))) (else (call $raise-argument-error1 (local.get $who) (global.get $string:real?) (local.get $a)) (unreachable)))) (func $check-naturals (type $Prim1) (param $n (ref eq)) ;; n (result (ref eq)) (if (result (ref eq)) (ref.eq (call $integer? (local.get $n)) (global.get $true)) (then (if (result (ref eq)) (ref.eq (call $exact? (local.get $n)) (global.get $true)) (then (if (result (ref eq)) (ref.test (ref i31) (local.get $n)) (then (if (result (ref eq)) (i32.ge_s (i31.get_s (ref.cast (ref i31) (local.get $n))) (i32.const 0)) (then (global.get $void)) (else (call $raise-argument-error1 (global.get $symbol:in-naturals) (global.get $string:exact-nonnegative-integer?) (local.get $n)) (unreachable)))) (else (call $raise-argument-error1 (global.get $symbol:in-naturals) (global.get $string:exact-nonnegative-integer?) (local.get $n)) (unreachable)))) (else (call $raise-argument-error1 (global.get $symbol:in-naturals) (global.get $string:exact-nonnegative-integer?) (local.get $n)) (unreachable)))) (else (call $raise-argument-error1 (global.get $symbol:in-naturals) (global.get $string:exact-nonnegative-integer?) (local.get $n)) (unreachable)))) ;;; ;;; DATATYPES ;;; ;; https://docs.racket-lang.org/reference/data.html ;;; ;;; 4.1 Equality ;;; ;; https://docs.racket-lang.org/reference/Equality.html (func $eq? (type $Prim2) (param $v1 (ref eq)) (param $v2 (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v1) (local.get $v2)) (then (global.get $true)) (else (global.get $false)))) (func $eqv? (type $Prim2) ; Except for numbers and characters, `eqv?` works like `eq?`. (param $v1 (ref eq)) (param $v2 (ref eq)) (result (ref eq)) (local $i1 i32) (local $i2 i32) (local $f1 (ref $Flonum)) (local $f2 (ref $Flonum)) ;; --- Fast path: ref.eq --- (if (ref.eq (local.get $v1) (local.get $v2)) (then (return (global.get $true)))) ;; --- Case: both flonums --- (if (i32.and (ref.test (ref $Flonum) (local.get $v1)) (ref.test (ref $Flonum) (local.get $v2))) (then (local.set $f1 (ref.cast (ref $Flonum) (local.get $v1))) (local.set $f2 (ref.cast (ref $Flonum) (local.get $v2))) (if (f64.eq (struct.get $Flonum $v (local.get $f1)) (struct.get $Flonum $v (local.get $f2))) (then (return (global.get $true))) (else (return (global.get $false)))))) ;; --- Characters --- ; With our implementation, we `eqv` is the same as `eq` on characters. ;; --- Fallback: ref.eq --- (if (ref.eq (local.get $v1) (local.get $v2)) (then (return (global.get $true))) (else (return (global.get $false)))) (unreachable)) ;;; equal? ;; Top-level equal? (func $equal? (type $Prim2) (param $v1 (ref eq)) (param $v2 (ref eq)) (result (ref eq)) ;; Fast path: ref.eq (if (ref.eq (local.get $v1) (local.get $v2)) (then (return (global.get $true)))) ;; Fallback to eqv? (if (ref.eq (call $eqv? (local.get $v1) (local.get $v2)) (global.get $true)) (then (return (global.get $true)))) ;; Slower recursive structural equality (return (call $equal?/slow (local.get $v1) (local.get $v2)))) ;; Slow structural equality (recursive) (func $equal?/slow (param $v1 (ref eq)) (param $v2 (ref eq)) (result (ref eq)) ;; --- Pair --- (if (i32.and (ref.test (ref $Pair) (local.get $v1)) (ref.test (ref $Pair) (local.get $v2))) (then (return_call $equal?/pair (ref.cast (ref $Pair) (local.get $v1)) (ref.cast (ref $Pair) (local.get $v2))))) ;; --- Box --- (if (i32.and (ref.test (ref $Box) (local.get $v1)) (ref.test (ref $Box) (local.get $v2))) (then (return_call $equal?/box (ref.cast (ref $Box) (local.get $v1)) (ref.cast (ref $Box) (local.get $v2))))) ;; --- Vector --- (if (i32.and (ref.test (ref $Vector) (local.get $v1)) (ref.test (ref $Vector) (local.get $v2))) (then (return_call $equal?/vector (ref.cast (ref $Vector) (local.get $v1)) (ref.cast (ref $Vector) (local.get $v2))))) ;; --- String --- (if (i32.and (ref.test (ref $String) (local.get $v1)) (ref.test (ref $String) (local.get $v2))) (then (return_call $string=?/2 (ref.cast (ref $String) (local.get $v1)) (ref.cast (ref $String) (local.get $v2))))) ;; --- Bytes --- (if (i32.and (ref.test (ref $Bytes) (local.get $v1)) (ref.test (ref $Bytes) (local.get $v2))) (then (return_call $bytes=?/2/checked (ref.cast (ref $Bytes) (local.get $v1)) (ref.cast (ref $Bytes) (local.get $v2))))) ;; --- Struct --- (fieldwise comparison) (if (i32.and (ref.test (ref $Struct) (local.get $v1)) (ref.test (ref $Struct) (local.get $v2))) (then (return_call $equal?/struct (ref.cast (ref $Struct) (local.get $v1)) (ref.cast (ref $Struct) (local.get $v2))))) ;; --- Fallback --- (return (global.get $false))) ;; Compare pairs (func $equal?/pair (param $p1 (ref $Pair)) (param $p2 (ref $Pair)) (result (ref eq)) (if (ref.eq (call $equal? (struct.get $Pair $a (local.get $p1)) (struct.get $Pair $a (local.get $p2))) (global.get $true)) (then (return_call $equal? (struct.get $Pair $d (local.get $p1)) (struct.get $Pair $d (local.get $p2)))) (else (return (global.get $false)))) (unreachable)) ;; Compare boxes (func $equal?/box (param $b1 (ref $Box)) (param $b2 (ref $Box)) (result (ref eq)) (return_call $equal? (struct.get $Box $v (local.get $b1)) (struct.get $Box $v (local.get $b2)))) ;; Compare vectors (func $equal?/vector (param $v1 (ref $Vector)) (param $v2 (ref $Vector)) (result (ref eq)) (local $a1 (ref $Array)) (local $a2 (ref $Array)) (local $len i32) (local $i i32) (local $x1 (ref eq)) (local $x2 (ref eq)) (local.set $a1 (struct.get $Vector $arr (local.get $v1))) (local.set $a2 (struct.get $Vector $arr (local.get $v2))) (local.set $len (array.len (local.get $a1))) (if (i32.ne (local.get $len) (array.len (local.get $a2))) (then (return (global.get $false)))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $x1 (array.get $Array (local.get $a1) (local.get $i))) (local.set $x2 (array.get $Array (local.get $a2) (local.get $i))) (if (ref.eq (call $equal? (local.get $x1) (local.get $x2)) (global.get $false)) (then (return (global.get $false)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (return (global.get $true))) ;; Compare structs fieldwise (func $equal?/struct (param $s1 (ref $Struct)) (param $s2 (ref $Struct)) (result (ref eq)) (local $t1 (ref $StructType)) (local $t2 (ref $StructType)) (local $a1 (ref $Array)) (local $a2 (ref $Array)) (local $len i32) (local $i i32) (local $x1 (ref eq)) (local $x2 (ref eq)) (local $prop-name (ref $Symbol)) (local $sentinel (ref eq)) (local $prop-val (ref eq)) (local $prop-info (ref $Array)) (local.set $t1 (struct.get $Struct $type (local.get $s1))) (local.set $t2 (struct.get $Struct $type (local.get $s2))) (local.set $prop-name (ref.cast (ref $Symbol) (global.get $symbol:prop:equal+hash))) (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $prop-val (call $struct-type-property-lookup-by-name (local.get $t1) (local.get $prop-name) (local.get $sentinel))) (if (i32.eqz (ref.eq (local.get $prop-val) (local.get $sentinel))) (then (local.set $prop-info (ref.cast (ref $Array) (local.get $prop-val))) (return (call $struct-equal+hash-apply (local.get $prop-info) (ref.cast (ref eq) (local.get $s1)) (ref.cast (ref eq) (local.get $s2)) (i32.const 1))))) (local.set $prop-val (call $struct-type-property-lookup-by-name (local.get $t2) (local.get $prop-name) (local.get $sentinel))) (if (i32.eqz (ref.eq (local.get $prop-val) (local.get $sentinel))) (then (local.set $prop-info (ref.cast (ref $Array) (local.get $prop-val))) (return (call $struct-equal+hash-apply (local.get $prop-info) (ref.cast (ref eq) (local.get $s2)) (ref.cast (ref eq) (local.get $s1)) (i32.const 1))))) (if (ref.eq (local.get $t1) (local.get $t2)) (then (local.set $a1 (struct.get $Struct $fields (local.get $s1))) (local.set $a2 (struct.get $Struct $fields (local.get $s2))) (local.set $len (array.len (local.get $a1))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $x1 (array.get $Array (local.get $a1) (local.get $i))) (local.set $x2 (array.get $Array (local.get $a2) (local.get $i))) (if (ref.eq (call $equal? (local.get $x1) (local.get $x2)) (global.get $false)) (then (return (global.get $false)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (return (global.get $true)))) (global.get $false)) (func $equal+hash-recur/equal (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $argc i32) (local $a (ref eq)) (local $b (ref eq)) (local.set $argc (array.len (local.get $args))) (if (i32.ne (local.get $argc) (i32.const 2)) (then (call $raise-arity-mismatch/proc (local.get $clos) (local.get $argc)) (unreachable))) (local.set $a (array.get $Args (local.get $args) (i32.const 0))) (local.set $b (array.get $Args (local.get $args) (i32.const 1))) (call $equal? (local.get $a) (local.get $b))) (func $equal+hash-recur/equal-always (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $argc i32) (local $a (ref eq)) (local $b (ref eq)) (local.set $argc (array.len (local.get $args))) (if (i32.ne (local.get $argc) (i32.const 2)) (then (call $raise-arity-mismatch/proc (local.get $clos) (local.get $argc)) (unreachable))) (local.set $a (array.get $Args (local.get $args) (i32.const 0))) (local.set $b (array.get $Args (local.get $args) (i32.const 1))) (call $equal-always? (local.get $a) (local.get $b))) (func $equal+hash-recur/hash (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $argc i32) (local $v (ref eq)) (local.set $argc (array.len (local.get $args))) (if (i32.ne (local.get $argc) (i32.const 1)) (then (call $raise-arity-mismatch/proc (local.get $clos) (local.get $argc)) (unreachable))) (local.set $v (array.get $Args (local.get $args) (i32.const 0))) (call $equal-hash-code (local.get $v))) (func $struct-equal+hash-apply (param $info (ref $Array)) (param $self (ref eq)) (param $other (ref eq)) (param $mode i32) (result (ref eq)) (local $tag (ref i31)) (local $variant i32) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $recur (ref eq)) (local $mode-val (ref eq)) ;; Initialize non-defaultable locals (local.set $recur (global.get $equal+hash-recur/equal)) (local.set $mode-val (global.get $false)) (local.set $tag (ref.cast (ref i31) (array.get $Array (local.get $info) (i32.const 0)))) (local.set $variant (i32.shr_u (i31.get_u (local.get $tag)) (i32.const 1))) (if (i32.eq (local.get $mode) (i32.const 0)) (then (local.set $recur (global.get $equal+hash-recur/equal-always)) (local.set $mode-val (global.get $false))) (else (local.set $recur (global.get $equal+hash-recur/equal)) (local.set $mode-val (global.get $true)))) (if (i32.eq (local.get $variant) (i32.const 3)) (then (local.set $proc (ref.cast (ref $Procedure) (array.get $Array (local.get $info) (i32.const 1)))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.const 3))) (array.set $Args (local.get $args) (i32.const 0) (local.get $self)) (array.set $Args (local.get $args) (i32.const 1) (local.get $other)) (array.set $Args (local.get $args) (i32.const 2) (local.get $recur)) (return_call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv)))) (local.set $proc (ref.cast (ref $Procedure) (array.get $Array (local.get $info) (i32.const 1)))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.const 4))) (array.set $Args (local.get $args) (i32.const 0) (local.get $self)) (array.set $Args (local.get $args) (i32.const 1) (local.get $other)) (array.set $Args (local.get $args) (i32.const 2) (local.get $recur)) (array.set $Args (local.get $args) (i32.const 3) (local.get $mode-val)) (return_call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv))) (func $struct-equal+hash-hash (param $info (ref $Array)) (param $self (ref $Struct)) (param $mode i32) (result i32) (local $tag (ref i31)) (local $variant i32) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $mode-val (ref eq)) (local $result (ref eq)) (local $hash (ref i31)) (local $value i32) ;; Initialize non-defaultable locals (local.set $mode-val (global.get $false)) (local.set $result (global.get $false)) ;; Decode arguments (local.set $tag (ref.cast (ref i31) (array.get $Array (local.get $info) (i32.const 0)))) (local.set $variant (i32.shr_u (i31.get_u (local.get $tag)) (i32.const 1))) (if (i32.eq (local.get $mode) (i32.const 0)) (then (local.set $mode-val (global.get $false))) (else (local.set $mode-val (global.get $true)))) (if (i32.eq (local.get $variant) (i32.const 3)) (then (local.set $proc (ref.cast (ref $Procedure) (array.get $Array (local.get $info) (i32.const 2)))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (array.set $Args (local.get $args) (i32.const 0) (ref.cast (ref eq) (local.get $self))) (array.set $Args (local.get $args) (i32.const 1) (global.get $equal+hash-recur/hash)) (local.set $result (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv)))) (else (local.set $proc (ref.cast (ref $Procedure) (array.get $Array (local.get $info) (i32.const 2)))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.const 3))) (array.set $Args (local.get $args) (i32.const 0) (ref.cast (ref eq) (local.get $self))) (array.set $Args (local.get $args) (i32.const 1) (global.get $equal+hash-recur/hash)) (array.set $Args (local.get $args) (i32.const 2) (local.get $mode-val)) (local.set $result (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv))))) (local.set $hash (ref.cast (ref i31) (local.get $result))) (local.set $value (i32.shr_s (i31.get_s (local.get $hash)) (i32.const 1))) (local.get $value)) (func $equal-always?/struct (param $s1 (ref $Struct)) (param $s2 (ref $Struct)) (result (ref eq)) (local $t1 (ref $StructType)) (local $t2 (ref $StructType)) (local $prop-name (ref $Symbol)) (local $sentinel (ref eq)) (local $prop-val (ref eq)) (local $prop-info (ref $Array)) (local.set $t1 (struct.get $Struct $type (local.get $s1))) (local.set $t2 (struct.get $Struct $type (local.get $s2))) (local.set $prop-name (ref.cast (ref $Symbol) (global.get $symbol:prop:equal+hash))) (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $prop-val (call $struct-type-property-lookup-by-name (local.get $t1) (local.get $prop-name) (local.get $sentinel))) (if (i32.eqz (ref.eq (local.get $prop-val) (local.get $sentinel))) (then (local.set $prop-info (ref.cast (ref $Array) (local.get $prop-val))) (return (call $struct-equal+hash-apply (local.get $prop-info) (ref.cast (ref eq) (local.get $s1)) (ref.cast (ref eq) (local.get $s2)) (i32.const 0))))) (local.set $prop-val (call $struct-type-property-lookup-by-name (local.get $t2) (local.get $prop-name) (local.get $sentinel))) (if (i32.eqz (ref.eq (local.get $prop-val) (local.get $sentinel))) (then (local.set $prop-info (ref.cast (ref $Array) (local.get $prop-val))) (return (call $struct-equal+hash-apply (local.get $prop-info) (ref.cast (ref eq) (local.get $s2)) (ref.cast (ref eq) (local.get $s1)) (i32.const 0))))) (global.get $false)) ;; equal-always? (func $equal-always? (type $Prim2) (param $v1 (ref eq)) (param $v2 (ref eq)) (result (ref eq)) ;; Fast path: identical references (if (ref.eq (local.get $v1) (local.get $v2)) (then (return (global.get $true)))) ;; Numbers, characters, etc. (if (ref.eq (call $eqv? (local.get $v1) (local.get $v2)) (global.get $true)) (then (return (global.get $true)))) ;; Structural comparison that enforces immutability contracts (return (call $equal-always?/slow (local.get $v1) (local.get $v2)))) (func $equal-always?/slow (param $v1 (ref eq)) (param $v2 (ref eq)) (result (ref eq)) ;; Mutable pairs must be eq? (if (i32.or (ref.test (ref $MPair) (local.get $v1)) (ref.test (ref $MPair) (local.get $v2))) (then (return (global.get $false)))) ;; --- Pair --- (if (i32.and (ref.test (ref $Pair) (local.get $v1)) (ref.test (ref $Pair) (local.get $v2))) (then (return_call $equal-always?/pair (ref.cast (ref $Pair) (local.get $v1)) (ref.cast (ref $Pair) (local.get $v2))))) ;; --- Box --- (if (i32.and (ref.test (ref $Box) (local.get $v1)) (ref.test (ref $Box) (local.get $v2))) (then (return_call $equal-always?/box (ref.cast (ref $Box) (local.get $v1)) (ref.cast (ref $Box) (local.get $v2))))) ;; --- Vector --- (if (i32.and (ref.test (ref $Vector) (local.get $v1)) (ref.test (ref $Vector) (local.get $v2))) (then (return_call $equal-always?/vector (ref.cast (ref $Vector) (local.get $v1)) (ref.cast (ref $Vector) (local.get $v2))))) ;; --- Struct --- (if (i32.and (ref.test (ref $Struct) (local.get $v1)) (ref.test (ref $Struct) (local.get $v2))) (then (return_call $equal-always?/struct (ref.cast (ref $Struct) (local.get $v1)) (ref.cast (ref $Struct) (local.get $v2))))) ;; --- String --- (if (i32.and (ref.test (ref $String) (local.get $v1)) (ref.test (ref $String) (local.get $v2))) (then (return_call $equal-always?/string (ref.cast (ref $String) (local.get $v1)) (ref.cast (ref $String) (local.get $v2))))) ;; --- Bytes --- (if (i32.and (ref.test (ref $Bytes) (local.get $v1)) (ref.test (ref $Bytes) (local.get $v2))) (then (return_call $equal-always?/bytes (ref.cast (ref $Bytes) (local.get $v1)) (ref.cast (ref $Bytes) (local.get $v2))))) ;; Fallback: not equal-always (return (global.get $false))) ;; Compare pairs with equal-always? (func $equal-always?/pair (param $p1 (ref $Pair)) (param $p2 (ref $Pair)) (result (ref eq)) (if (ref.eq (call $equal-always? (struct.get $Pair $a (local.get $p1)) (struct.get $Pair $a (local.get $p2))) (global.get $true)) (then (return_call $equal-always? (struct.get $Pair $d (local.get $p1)) (struct.get $Pair $d (local.get $p2)))) (else (return (global.get $false)))) (unreachable)) ;; Compare boxes (immutable only) (func $equal-always?/box (param $b1 (ref $Box)) (param $b2 (ref $Box)) (result (ref eq)) (if (i32.eq (struct.get $Box $immutable (local.get $b1)) (i32.const 1)) (then (if (i32.eq (struct.get $Box $immutable (local.get $b2)) (i32.const 1)) (then (return_call $equal-always? (struct.get $Box $v (local.get $b1)) (struct.get $Box $v (local.get $b2))))))) (return (global.get $false))) ;; Compare vectors (immutable only) (func $equal-always?/vector (param $v1 (ref $Vector)) (param $v2 (ref $Vector)) (result (ref eq)) (local $a1 (ref $Array)) (local $a2 (ref $Array)) (local $len i32) (local $i i32) (local $x1 (ref eq)) (local $x2 (ref eq)) (if (i32.ne (struct.get $Vector $immutable (local.get $v1)) (i32.const 1)) (then (return (global.get $false)))) (if (i32.ne (struct.get $Vector $immutable (local.get $v2)) (i32.const 1)) (then (return (global.get $false)))) (local.set $a1 (struct.get $Vector $arr (local.get $v1))) (local.set $a2 (struct.get $Vector $arr (local.get $v2))) (local.set $len (array.len (local.get $a1))) (if (i32.ne (local.get $len) (array.len (local.get $a2))) (then (return (global.get $false)))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $x1 (array.get $Array (local.get $a1) (local.get $i))) (local.set $x2 (array.get $Array (local.get $a2) (local.get $i))) (if (ref.eq (call $equal-always? (local.get $x1) (local.get $x2)) (global.get $false)) (then (return (global.get $false)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (return (global.get $true))) ;; Compare strings (immutable only) (func $equal-always?/string (param $s1 (ref $String)) (param $s2 (ref $String)) (result (ref eq)) (if (i32.eq (struct.get $String $immutable (local.get $s1)) (i32.const 1)) (then (if (i32.eq (struct.get $String $immutable (local.get $s2)) (i32.const 1)) (then (return_call $string=?/2 (local.get $s1) (local.get $s2)))))) (return (global.get $false))) ;; Compare byte strings (immutable only) (func $equal-always?/bytes (param $b1 (ref $Bytes)) (param $b2 (ref $Bytes)) (result (ref eq)) (if (i32.eq (struct.get $Bytes $immutable (local.get $b1)) (i32.const 1)) (then (if (i32.eq (struct.get $Bytes $immutable (local.get $b2)) (i32.const 1)) (then (return_call $bytes=?/2/checked (local.get $b1) (local.get $b2)))))) (return (global.get $false))) ;;; ;;; 4.2 Booleans ;;; ;; https://docs.racket-lang.org/reference/booleans.html (func $mutable-string? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $String) (local.get $v)) (then (if (result (ref eq)) (i32.eq (struct.get $String $immutable (ref.cast (ref $String) (local.get $v))) (i32.const 0)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $immutable-string? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $String) (local.get $v)) (then (if (result (ref eq)) (i32.eq (struct.get $String $immutable (ref.cast (ref $String) (local.get $v))) (i32.const 1)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $mutable-bytes? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Bytes) (local.get $v)) (then (if (result (ref eq)) (i32.eq (struct.get $Bytes $immutable (ref.cast (ref $Bytes) (local.get $v))) (i32.const 0)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $immutable-bytes? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Bytes) (local.get $v)) (then (if (result (ref eq)) (i32.eq (struct.get $Bytes $immutable (ref.cast (ref $Bytes) (local.get $v))) (i32.const 1)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $mutable-vector? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Vector) (local.get $v)) (then (if (result (ref eq)) (i32.eq (struct.get $Vector $immutable (ref.cast (ref $Vector) (local.get $v))) (i32.const 0)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $immutable-vector? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Vector) (local.get $v)) (then (if (result (ref eq)) (i32.eq (struct.get $Vector $immutable (ref.cast (ref $Vector) (local.get $v))) (i32.const 1)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $mutable-box? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Box) (local.get $v)) (then (if (result (ref eq)) (i32.eq (struct.get $Box $immutable (ref.cast (ref $Box) (local.get $v))) (i32.const 0)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $immutable-box? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Box) (local.get $v)) (then (if (result (ref eq)) (i32.eq (struct.get $Box $immutable (ref.cast (ref $Box) (local.get $v))) (i32.const 1)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $mutable-hash? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Hash) (local.get $v)) (then (if (result (ref eq)) (ref.eq (struct.get $Hash $mutable? (ref.cast (ref $Hash) (local.get $v))) (global.get $true)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $immutable-hash? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Hash) (local.get $v)) (then (if (result (ref eq)) (ref.eq (struct.get $Hash $mutable? (ref.cast (ref $Hash) (local.get $v))) (global.get $false)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ; todo: Benchmark the two implementations of $boolean? below (func $boolean? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v) (global.get $true)) (then (global.get $true)) (else (if (result (ref eq)) (ref.eq (local.get $v) (global.get $false)) (then (global.get $true)) (else (global.get $false)))))) ; see comment above #;(func $boolean? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test i31ref (local.get $v)) (then (if (result (ref eq)) (i32.eq (i32.and (i31.get_s (ref.cast i31ref (local.get $v))) (i32.const ,boolean-mask)) (i32.const ,boolean-tag)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $boolean=? (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) ;; Type checks (if (ref.eq (call $boolean? (local.get $x)) (global.get $false)) (then (call $raise-argument-error (local.get $x)) (unreachable))) (if (ref.eq (call $boolean? (local.get $y)) (global.get $false)) (then (call $raise-argument-error (local.get $y)) (unreachable))) ;; Compare booleans (if (result (ref eq)) (ref.eq (local.get $x) (local.get $y)) (then (global.get $true)) (else (global.get $false)))) (func $false? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v) (global.get $false)) (then (global.get $true)) (else (global.get $false)))) (func $not (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v) (global.get $false)) (then (global.get $true)) (else (global.get $false)))) (func $xor (type $Prim2) (param $b1 (ref eq)) (param $b2 (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $b1) (global.get $false)) (then (if (result (ref eq)) (ref.eq (local.get $b2) (global.get $false)) (then (global.get $false)) (else (local.get $b2)))) (else (if (result (ref eq)) (ref.eq (local.get $b2) (global.get $false)) (then (local.get $b1)) (else (global.get $false)))))) (func $immutable? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $s (ref $String)) (local $bs (ref $Bytes)) (local $vec (ref $Vector)) (local $hash (ref $Hash)) (local $box (ref $Box)) (if (ref.test (ref $String) (local.get $v)) (then (local.set $s (ref.cast (ref $String) (local.get $v))) (if (i32.eq (struct.get $String $immutable (local.get $s)) (i32.const 1)) (then (return (global.get $true))) (else (return (global.get $false)))))) (if (ref.test (ref $Bytes) (local.get $v)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $v))) (if (i32.eq (struct.get $Bytes $immutable (local.get $bs)) (i32.const 1)) (then (return (global.get $true))) (else (return (global.get $false)))))) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (if (i32.eq (struct.get $Vector $immutable (local.get $vec)) (i32.const 1)) (then (return (global.get $true))) (else (return (global.get $false)))))) (if (ref.test (ref $Hash) (local.get $v)) (then (local.set $hash (ref.cast (ref $Hash) (local.get $v))) (if (ref.eq (struct.get $Hash $mutable? (local.get $hash)) (global.get $false)) (then (return (global.get $true))) (else (return (global.get $false)))))) (if (ref.test (ref $Box) (local.get $v)) (then (local.set $box (ref.cast (ref $Box) (local.get $v))) (if (i32.eq (struct.get $Box $immutable (local.get $box)) (i32.const 1)) (then (return (global.get $true))) (else (return (global.get $false)))))) (global.get $false)) ;;; ;;; 4.3 Numbers ;;; ;; https://docs.racket-lang.org/reference/numbers.html ;;; 4.3.1 Number Types ;; https://docs.racket-lang.org/reference/number-types.html ;; [x] number? ;; [ ] complex? ;; [x] real? ;; [ ] rational? ;; [x] integer? ;; [x] exact-integer? ;; [x] exact-nonnegative-integer? ;; [x] exact-positive-integer? ;; [x] positive-integer? ;; [x] negative-integer? ;; [x] nonpositive-integer? ;; [x] nonnegative-integer? ;; [x] natural? ;; [x] nan? ;; [x] infinite? ;; [x] inexact-real? ;; [x] fixnum? ;; [x] flonum? ;; [x] double-flonum? ;; [x] single-flonum? ;; [x] single-flonum-available? ;; [x] zero? ;; [x] positive? ;; [x] negative? ;; [x] even? ;; [x] odd? ;; [x] exact? ;; [x] inexact? ;; [x] inexact->exact ;; [x] exact->inexact ;; [ ] real->single-flonum ;; [x] real->double-flonum (func $number? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test i31ref (local.get $v)) (then (if (result (ref eq)) (i32.eqz (i32.and (i31.get_s (ref.cast i31ref (local.get $v))) (i32.const ,fixnum-mask))) (then (global.get $true)) (else (global.get $false)))) (else (if (result (ref eq)) (ref.test (ref $Flonum) (local.get $v)) (then (global.get $true)) (else (global.get $false)))))) (func $real? (type $Prim1) ;; WebRacket currently has no complex numbers, so real? is ;; equivalent to number? (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test i31ref (local.get $v)) (then (if (result (ref eq)) (i32.eqz (i32.and (i31.get_s (ref.cast i31ref (local.get $v))) (i32.const ,fixnum-mask))) (then (global.get $true)) (else (global.get $false)))) (else (if (result (ref eq)) (ref.test (ref $Flonum) (local.get $v)) (then (global.get $true)) (else (global.get $false)))))) (func $exact? (type $Prim1) ;; A number is exact if it's a fixnum: a ref i31 with LSB = 0 (param $z (ref eq)) (result (ref eq)) (local $bits i32) (if (result (ref eq)) (ref.test (ref i31) (local.get $z)) (then (local.set $bits (i31.get_u (ref.cast (ref i31) (local.get $z)))) (if (result (ref eq)) (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $inexact? (type $Prim1) ;; A number is inexact if it's a flonum (param $z (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Flonum) (local.get $z)) (then (global.get $true)) (else (global.get $false)))) (func $single-flonum? (type $Prim1) ;; single-flonums are not supported (param $v (ref eq)) (result (ref eq)) (global.get $false)) (func $single-flonum-available? (type $Prim0) ;; single-flonums are not supported (result (ref eq)) (global.get $false)) (func $inexact-real? (type $Prim1) ;; Returns #t for inexact real numbers (flonums that are not NaN) (param $z (ref eq)) (result (ref eq)) (local $fl (ref $Flonum)) (local $f64 f64) ;; Check: is z a flonum? (if (result (ref eq)) (ref.test (ref $Flonum) (local.get $z)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $z))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (if (result (ref eq)) (f64.eq (local.get $f64) (local.get $f64)) ; check not NaN (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $exact-integer? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $bits i32) (if (result (ref eq)) (ref.test (ref i31) (local.get $v)) (then (local.set $bits (i31.get_u (ref.cast (ref i31) (local.get $v)))) (if (result (ref eq)) (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $exact-nonnegative-integer? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $n i32) ;; Check: is v a fixnum with LSB = 0 (if (ref.test (ref i31) (local.get $v)) (then (local.set $n (i31.get_s (ref.cast (ref i31) (local.get $v)))) (if (i32.eqz (i32.and (local.get $n) (i32.const 1))) ;; LSB = 0? (then (if (i32.ge_s (i32.shr_s (local.get $n) (i32.const 1)) (i32.const 0)) (then (return (global.get $true))) (else (return (global.get $false)))))) (return (global.get $false)))) ;; Not a fixnum (return (global.get $false))) (func $exact-positive-integer? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $n i32) ;; Check if it's a fixnum (if (ref.test (ref i31) (local.get $v)) (then ;; Extract the signed value (local.set $n (i31.get_s (ref.cast (ref i31) (local.get $v)))) ;; Check LSB = 0 (exact) and value > 0 (if (i32.eqz (i32.and (local.get $n) (i32.const 1))) (then (if (i32.gt_s (i32.shr_s (local.get $n) (i32.const 1)) (i32.const 0)) (then (return (global.get $true))) (else (return (global.get $false)))))) ;; LSB ≠ 0 → not an exact integer (return (global.get $false)))) ;; Not a fixnum → not an exact integer (return (global.get $false))) (func $inexact->exact (type $Prim1) (param $z (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) (local $i32 i32) ;; If z is a fixnum, ensure LSB = 0 and return it (if (ref.test (ref i31) (local.get $z)) (then (local.set $bits (i31.get_u (ref.cast (ref i31) (local.get $z)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (return (local.get $z)))))) ;; If z is a flonum, convert to fixnum if finite and integral (if (ref.test (ref $Flonum) (local.get $z)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $z))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (if (f64.ne (local.get $f64) (local.get $f64)) (then (call $raise-expected-number (local.get $z)) (unreachable))) (if (f64.eq (local.get $f64) (f64.const +inf)) (then (call $raise-expected-number (local.get $z)) (unreachable))) (if (f64.eq (local.get $f64) (f64.const -inf)) (then (call $raise-expected-number (local.get $z)) (unreachable))) (if (f64.eq (f64.floor (local.get $f64)) (local.get $f64)) (then (local.set $i32 (i32.trunc_f64_s (local.get $f64))) (return (ref.i31 (i32.shl (local.get $i32) (i32.const 1))))) (else (call $raise-expected-number (local.get $z)) (unreachable))))) ;; Not a number (call $raise-expected-number (local.get $z)) (unreachable)) (func $exact->inexact (type $Prim1) (param $z (ref eq)) (result (ref eq)) (local $bits i32) ;; If z is already a flonum, return it (if (ref.test (ref $Flonum) (local.get $z)) (then (return (local.get $z)))) ;; If z is a fixnum, ensure LSB = 0 and convert (if (ref.test (ref i31) (local.get $z)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $z)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (return (struct.new $Flonum (i32.const 0) (f64.convert_i32_s (i32.shr_s (local.get $bits) (i32.const 1))))))))) ;; Not a number (call $raise-expected-real (global.get $symbol:exact->inexact) (local.get $z)) (unreachable)) (func $real->double-flonum (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) ;; If x is already a flonum, return it (if (ref.test (ref $Flonum) (local.get $x)) (then (return (local.get $x)))) ;; If x is a fixnum, ensure LSB = 0 and convert (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (return (struct.new $Flonum (i32.const 0) (f64.convert_i32_s (i32.shr_s (local.get $bits) (i32.const 1))))))))) ;; Not a number (call $raise-expected-real (global.get $symbol:real->double-flonum) (local.get $x)) (unreachable)) (func $zero? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $x/fx i32) (local $x/fl (ref $Flonum)) ;; If x is a fixnum, check if it's 0 (if (ref.test (ref i31) (local.get $x)) (then (local.set $x/fx (i31.get_u (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $x/fx) (i32.const 1))) ;; lsb = 0? (then (if (i32.eqz (i32.shr_u (local.get $x/fx) (i32.const 1))) ;; value == 0? (then (return (global.get $true))) (else (return (global.get $false))))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) ;; If x is a flonum, check if it's 0.0 (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $x/fl (ref.cast (ref $Flonum) (local.get $x))) (if (f64.eq (struct.get $Flonum $v (local.get $x/fl)) (f64.const 0.0)) (then (return (global.get $true))) (else (return (global.get $false)))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) (func $integer-sqrt (type $Prim1) (param $n (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) (local $i32 i32) ;; Fixnum case (if (ref.test (ref i31) (local.get $n)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $n)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $i32 (i32.shr_s (local.get $bits) (i32.const 1))) (local.set $f64 (f64.floor (call $js-math-sqrt (f64.convert_i32_s (local.get $i32))))) (if (i32.ge_s (local.get $i32) (i32.const 0)) (then (local.set $i32 (i32.trunc_f64_s (local.get $f64))) (return (ref.i31 (i32.shl (local.get $i32) (i32.const 1))))) (else (return (struct.new $Flonum (i32.const 0) (local.get $f64)))))) (else (call $raise-expected-number (local.get $n)) (unreachable))))) ;; Flonum case (if (ref.test (ref $Flonum) (local.get $n)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $n))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (if (f64.eq (f64.floor (local.get $f64)) (local.get $f64)) (then (return (struct.new $Flonum (i32.const 0) (f64.floor (call $js-math-sqrt (local.get $f64)))))) (else (call $raise-expected-number (local.get $n)) (unreachable))))) ;; Not a number (call $raise-expected-number (local.get $n)) (unreachable)) (func $integer-sqrt/remainder (type $Prim1) (param $n (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) (local $i32 i32) (local $sqrtf f64) (local $remf f64) (local $sq i32) (local $rem i32) ;; Fixnum case (if (ref.test (ref i31) (local.get $n)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $n)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $i32 (i32.shr_s (local.get $bits) (i32.const 1))) (local.set $sqrtf (f64.floor (call $js-math-sqrt (f64.convert_i32_s (local.get $i32))))) (if (i32.ge_s (local.get $i32) (i32.const 0)) (then (local.set $sq (i32.trunc_f64_s (local.get $sqrtf))) (local.set $rem (i32.sub (local.get $i32) (i32.mul (local.get $sq) (local.get $sq)))) (return (array.new_fixed $Values 2 (ref.i31 (i32.shl (local.get $sq) (i32.const 1))) (ref.i31 (i32.shl (local.get $rem) (i32.const 1)))))) (else (local.set $remf (f64.sub (f64.convert_i32_s (local.get $i32)) (f64.mul (local.get $sqrtf) (local.get $sqrtf)))) (return (array.new_fixed $Values 2 (struct.new $Flonum (i32.const 0) (local.get $sqrtf)) (struct.new $Flonum (i32.const 0) (local.get $remf)))))) (else (call $raise-expected-number (local.get $n)) (unreachable)))))) ;; Flonum case (if (ref.test (ref $Flonum) (local.get $n)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $n))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (if (f64.eq (f64.floor (local.get $f64)) (local.get $f64)) (then (local.set $sqrtf (f64.floor (call $js-math-sqrt (local.get $f64)))) (local.set $remf (f64.sub (local.get $f64) (f64.mul (local.get $sqrtf) (local.get $sqrtf)))) (return (array.new_fixed $Values 2 (struct.new $Flonum (i32.const 0) (local.get $sqrtf)) (struct.new $Flonum (i32.const 0) (local.get $remf))))) (else (call $raise-expected-number (local.get $n)) (unreachable))))) ;; Not a number (call $raise-expected-number (local.get $n)) (unreachable)) ; Note: ; The JavaScript math.pow function handles pow(-1,inf.0) does not follow IEEE 704, ; which says 1 is the result (in order to preserve a correct magnitude). (func $pow-special (param $base f64) (param $exp f64) (result f64) (local $abs-base f64) (local.set $abs-base (f64.abs (local.get $base))) (if (f64.eq (local.get $abs-base) (f64.const 1.0)) (then (if (f64.eq (local.get $exp) (f64.const inf)) (then (return (f64.const 1.0)))) (if (f64.eq (local.get $exp) (f64.const -inf)) (then (return (f64.const 1.0)))))) ; Handle 1.0^w according to IEEE 704-2019. (if (f64.eq (local.get $base) (f64.const 1.0)) (then (return (f64.const 1.0)))) (call $js-math-pow (local.get $base) (local.get $exp))) (func $expt (type $Prim2) (param $z (ref eq)) (param $w (ref eq)) (result (ref eq)) (local $zbits i32) (local $wbits i32) (local $zf64 f64) (local $wf64 f64) (local $res f64) (local $exactz i32) (local $exactw i32) ;; Decode z (if (ref.test (ref i31) (local.get $z)) (then (local.set $zbits (i31.get_s (ref.cast (ref i31) (local.get $z)))) (if (i32.eqz (i32.and (local.get $zbits) (i32.const 1))) (then (local.set $zf64 (f64.convert_i32_s (i32.shr_s (local.get $zbits) (i32.const 1)))) (local.set $exactz (i32.const 1))) (else (call $raise-expected-number (local.get $z)) (unreachable)))) (else (if (ref.test (ref $Flonum) (local.get $z)) (then (local.set $zf64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $z)))) (local.set $exactz (i32.const 0))) (else (call $raise-expected-number (local.get $z)) (unreachable))))) ;; Decode w (if (ref.test (ref i31) (local.get $w)) (then (local.set $wbits (i31.get_s (ref.cast (ref i31) (local.get $w)))) (if (i32.eqz (i32.and (local.get $wbits) (i32.const 1))) (then (local.set $wf64 (f64.convert_i32_s (i32.shr_s (local.get $wbits) (i32.const 1)))) (local.set $exactw (i32.const 1))) (else (call $raise-expected-number (local.get $w)) (unreachable)))) (else (if (ref.test (ref $Flonum) (local.get $w)) (then (local.set $wf64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $w)))) (local.set $exactw (i32.const 0))) (else (call $raise-expected-number (local.get $w)) (unreachable))))) ;; Special cases for exact results (if (i32.and (local.get $exactw) (i32.eqz (i32.shr_s (local.get $wbits) (i32.const 1)))) (then (return (ref.i31 (i32.const 2))))) (if (i32.and (local.get $exactz) (i32.eq (i32.shr_s (local.get $zbits) (i32.const 1)) (i32.const 1))) (then (return (ref.i31 (i32.const 2))))) ;; Handle the 1.0^w case explicitly because JavaScript's Math.pow ;; produces +nan.0 when w is +nan.0, while Racket (IEEE 704) specifies 1.0. (if (f64.eq (local.get $zf64) (f64.const 1.0)) (then (return (struct.new $Flonum (i32.const 0) (f64.const 1.0))))) ;; Compute using JS pow (local.set $res (call $pow-special (local.get $zf64) (local.get $wf64))) ;; If both operands exact and result integral within range, return fixnum (if (i32.and (local.get $exactz) (local.get $exactw)) (then (if (f64.eq (f64.floor (local.get $res)) (local.get $res)) (then ;; 536870911.0 = 2^29-1 and -536870912.0 = -2^29 ;; ensure result fits in fixnum range before converting (if (f64.le (local.get $res) (f64.const 536870911.0)) (then (if (f64.ge (local.get $res) (f64.const -536870912.0)) (then (return (ref.i31 (i32.shl (i32.trunc_f64_s (local.get $res)) (i32.const 1)))))))))))) (struct.new $Flonum (i32.const 0) (local.get $res))) (func $exp (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) ;; Fixnum case (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $bits (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.eqz (local.get $bits)) (then (return (ref.i31 (i32.const 2)))) (else (local.set $f64 (call $js-math-exp (f64.convert_i32_s (local.get $bits)))) (return (struct.new $Flonum (i32.const 0) (local.get $f64)))))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) ;; Flonum case (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (return (struct.new $Flonum (i32.const 0) (call $js-math-exp (local.get $f64)))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) (func $log (type $Prim12) (param $z (ref eq)) (param $b (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $zf64 f64) (local $bf64 f64) (local $res f64) ;; Natural log when base is missing (if (ref.eq (local.get $b) (global.get $missing)) (then (if (ref.test (ref i31) (local.get $z)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $z)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $bits (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.eqz (local.get $bits)) (then (call $raise-division-by-zero) (unreachable))) (if (i32.eq (local.get $bits) (i32.const 1)) (then (return (ref.i31 (i32.const 0))))) (local.set $res (call $js-math-log (f64.convert_i32_s (local.get $bits)))) (return (struct.new $Flonum (i32.const 0) (local.get $res)))) (else (call $raise-expected-number (local.get $z)) (unreachable))))) (if (ref.test (ref $Flonum) (local.get $z)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $z))) (local.set $zf64 (struct.get $Flonum $v (local.get $fl))) (return (struct.new $Flonum (i32.const 0) (call $js-math-log (local.get $zf64)))))) (call $raise-expected-number (local.get $z)) (unreachable)) (else ;; Log with base (if (ref.test (ref i31) (local.get $z)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $z)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $bits (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.eqz (local.get $bits)) (then (call $raise-division-by-zero) (unreachable))) (local.set $zf64 (f64.convert_i32_s (local.get $bits)))) (else (call $raise-expected-number (local.get $z)) (unreachable)))) (else (if (ref.test (ref $Flonum) (local.get $z)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $z))) (local.set $zf64 (struct.get $Flonum $v (local.get $fl)))) (else (call $raise-expected-number (local.get $z)) (unreachable))))) (if (ref.test (ref i31) (local.get $b)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $b)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $bits (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.eq (local.get $bits) (i32.const 1)) (then (call $raise-division-by-zero) (unreachable))) (local.set $bf64 (f64.convert_i32_s (local.get $bits)))) (else (call $raise-expected-number (local.get $b)) (unreachable)))) (else (if (ref.test (ref $Flonum) (local.get $b)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $b))) (local.set $bf64 (struct.get $Flonum $v (local.get $fl)))) (else (call $raise-expected-number (local.get $b)) (unreachable))))) (local.set $res (f64.div (call $js-math-log (local.get $zf64)) (call $js-math-log (local.get $bf64)))) (return (struct.new $Flonum (i32.const 0) (local.get $res))))) ;; Ensure static fallthrough is unreachable for the validator. (unreachable)) ;; Generic numeric unary functions (func $positive? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $x/fx i32) (local $x/fl (ref $Flonum)) ;; If x is a fixnum, check if it's > 0 (if (ref.test (ref i31) (local.get $x)) (then (local.set $x/fx (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $x/fx) (i32.const 1))) ;; lsb = 0? (then (if (i32.gt_s (i32.shr_s (local.get $x/fx) (i32.const 1)) (i32.const 0)) (then (return (global.get $true))) (else (return (global.get $false))))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) ;; If x is a flonum, check if it's > 0.0 (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $x/fl (ref.cast (ref $Flonum) (local.get $x))) (if (f64.gt (struct.get $Flonum $v (local.get $x/fl)) (f64.const 0.0)) (then (return (global.get $true))) (else (return (global.get $false)))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) (func $negative? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $x/fx i32) (local $x/fl (ref $Flonum)) ;; If x is a fixnum, check if it's < 0 (if (ref.test (ref i31) (local.get $x)) (then (local.set $x/fx (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $x/fx) (i32.const 1))) ;; lsb = 0? (then (if (i32.lt_s (i32.shr_s (local.get $x/fx) (i32.const 1)) (i32.const 0)) (then (return (global.get $true))) (else (return (global.get $false))))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) ;; If x is a flonum, check if it's < 0.0 (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $x/fl (ref.cast (ref $Flonum) (local.get $x))) (if (f64.lt (struct.get $Flonum $v (local.get $x/fl)) (f64.const 0.0)) (then (return (global.get $true))) (else (return (global.get $false)))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) (func $even? (type $Prim1) (param $n (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) (local $i32 i32) ;; Fixnum case (if (ref.test (ref i31) (local.get $n)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $n)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $i32 (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.eqz (i32.and (local.get $i32) (i32.const 1))) (then (return (global.get $true))) (else (return (global.get $false))))) (else (call $raise-expected-number (local.get $n)) (unreachable))))) ;; Flonum case (if (ref.test (ref $Flonum) (local.get $n)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $n))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (if (f64.ne (local.get $f64) (local.get $f64)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (local.get $f64) (f64.const +inf)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (local.get $f64) (f64.const -inf)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (f64.floor (local.get $f64)) (local.get $f64)) (then (local.set $i32 (i32.trunc_f64_s (local.get $f64))) (if (i32.eqz (i32.and (local.get $i32) (i32.const 1))) (then (return (global.get $true))) (else (return (global.get $false))))) (else (call $raise-expected-number (local.get $n)) (unreachable))))) ;; Not an integer (call $raise-expected-number (local.get $n)) (unreachable)) (func $odd? (type $Prim1) (param $n (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (call $even? (local.get $n)) (global.get $true)) (then (global.get $false)) (else (global.get $true)))) (func $integer? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $v/fx i32) (local $v/fl (ref $Flonum)) (local $f64v f64) ;; Case 1: fixnum (if (ref.test (ref i31) (local.get $v)) (then (local.set $v/fx (i31.get_u (ref.cast (ref i31) (local.get $v)))) (if (i32.eqz (i32.and (local.get $v/fx) (i32.const 1))) (then (return (global.get $true))) (else (return (global.get $false)))))) ;; Case 2: flonum (if (ref.test (ref $Flonum) (local.get $v)) (then (local.set $v/fl (ref.cast (ref $Flonum) (local.get $v))) (local.set $f64v (struct.get $Flonum $v (local.get $v/fl))) ;; Check: finite && round (if (f64.ne (local.get $f64v) (local.get $f64v)) ;; NaN (then (return (global.get $false)))) (if (f64.eq (local.get $f64v) (f64.const +inf)) ;; +inf.0 (then (return (global.get $false)))) (if (f64.eq (local.get $f64v) (f64.const -inf)) ;; -inf.0 (then (return (global.get $false)))) (if (f64.eq (f64.floor (local.get $f64v)) (local.get $f64v)) (then (return (global.get $true))) (else (return (global.get $false)))))) ;; Not a number (return (global.get $false))) (func $nan? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $x/fl (ref $Flonum)) (local $f64 f64) ;; Fixnum -> #f (if (ref.test (ref i31) (local.get $x)) (then (return (global.get $false)))) ;; Flonum -> check NaN (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $x/fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $x/fl))) (if (f64.ne (local.get $f64) (local.get $f64)) (then (return (global.get $true))) (else (return (global.get $false)))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) (func $infinite? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $x/fl (ref $Flonum)) (local $f64 f64) ;; Fixnum -> #f (if (ref.test (ref i31) (local.get $x)) (then (return (global.get $false)))) ;; Flonum -> check ±inf (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $x/fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $x/fl))) (if (f64.eq (local.get $f64) (f64.const +inf)) (then (return (global.get $true)))) (if (f64.eq (local.get $f64) (f64.const -inf)) (then (return (global.get $true)))) (return (global.get $false)))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) (func $positive-integer? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (call $integer? (local.get $x)) (global.get $true)) (then (call $positive? (local.get $x))) (else (global.get $false)))) (func $negative-integer? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (call $integer? (local.get $x)) (global.get $true)) (then (call $negative? (local.get $x))) (else (global.get $false)))) (func $nonpositive-integer? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (call $integer? (local.get $x)) (global.get $true)) (then (if (result (ref eq)) (ref.eq (call $positive? (local.get $x)) (global.get $true)) (then (global.get $false)) (else (global.get $true)))) (else (global.get $false)))) (func $nonnegative-integer? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (call $integer? (local.get $x)) (global.get $true)) (then (if (result (ref eq)) (ref.eq (call $negative? (local.get $x)) (global.get $true)) (then (global.get $false)) (else (global.get $true)))) (else (global.get $false)))) (func $natural? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (call $exact-nonnegative-integer? (local.get $x))) ;; 4.3.2 Generic Numerics ;; https://docs.racket-lang.org/reference/generic-numbers.html ;; 4.3.2.1 Arithmetic ;; https://docs.racket-lang.org/reference/generic-numbers.html#%28part._.Arithmetic%29 (func $quotient (type $Prim2) (param $n (ref eq)) (param $m (ref eq)) (result (ref eq)) (local $nu i32) (local $mu i32) (local $fl (ref $Flonum)) (local $nf f64) (local $mf f64) (local $qf f64) ;; Case 1: both fixnums (if (ref.test (ref i31) (local.get $n)) (then (local.set $nu (i31.get_s (ref.cast i31ref (local.get $n)))) (if (i32.eqz (i32.and (local.get $nu) (i32.const 1))) (then (if (ref.test (ref i31) (local.get $m)) (then (local.set $mu (i31.get_s (ref.cast i31ref (local.get $m)))) (if (i32.eqz (i32.and (local.get $mu) (i32.const 1))) (then (return (call $fxquotient (local.get $n) (local.get $m))))))))))) ;; Case 2: flonum/inexact ;; convert n to f64 (if (ref.test (ref $Flonum) (local.get $n)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $n))) (local.set $nf (struct.get $Flonum $v (local.get $fl)))) (else (if (ref.test (ref i31) (local.get $n)) (then (local.set $nu (i31.get_s (ref.cast i31ref (local.get $n)))) (if (i32.eqz (i32.and (local.get $nu) (i32.const 1))) (then (local.set $nf (f64.convert_i32_s (i32.shr_s (local.get $nu) (i32.const 1))))) (else (call $raise-expected-number (local.get $n)) (unreachable)))) (else (call $raise-expected-number (local.get $n)) (unreachable))))) ;; ensure n is a finite integer (if (f64.eq (local.get $nf) (f64.const inf)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (local.get $nf) (f64.const -inf)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (f64.floor (local.get $nf)) (local.get $nf)) (then) (else (call $raise-expected-number (local.get $n)) (unreachable))) ;; convert m to f64 (if (ref.test (ref $Flonum) (local.get $m)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $m))) (local.set $mf (struct.get $Flonum $v (local.get $fl)))) (else (if (ref.test (ref i31) (local.get $m)) (then (local.set $mu (i31.get_s (ref.cast i31ref (local.get $m)))) (if (i32.eqz (i32.and (local.get $mu) (i32.const 1))) (then (local.set $mf (f64.convert_i32_s (i32.shr_s (local.get $mu) (i32.const 1))))) (else (call $raise-expected-number (local.get $m)) (unreachable)))) (else (call $raise-expected-number (local.get $m)) (unreachable))))) ;; ensure m is a finite integer (if (f64.eq (local.get $mf) (f64.const inf)) (then (call $raise-expected-number (local.get $m)) (unreachable))) (if (f64.eq (local.get $mf) (f64.const -inf)) (then (call $raise-expected-number (local.get $m)) (unreachable))) (if (f64.eq (f64.floor (local.get $mf)) (local.get $mf)) (then) (else (call $raise-expected-number (local.get $m)) (unreachable))) ;; divide by zero? (if (f64.eq (local.get $mf) (f64.const 0.0)) (then (call $raise-division-by-zero) (unreachable))) ;; compute quotient in flonum (local.set $qf (f64.trunc (f64.div (local.get $nf) (local.get $mf)))) (return (struct.new $Flonum (i32.const 0) (local.get $qf)))) (func $remainder (type $Prim2) (param $n (ref eq)) (param $m (ref eq)) (result (ref eq)) (local $nu i32) (local $mu i32) (local $fl (ref $Flonum)) (local $nf f64) (local $mf f64) (local $rf f64) ;; Case 1: both fixnums (if (ref.test (ref i31) (local.get $n)) (then (local.set $nu (i31.get_s (ref.cast i31ref (local.get $n)))) (if (i32.eqz (i32.and (local.get $nu) (i32.const 1))) (then (if (ref.test (ref i31) (local.get $m)) (then (local.set $mu (i31.get_s (ref.cast i31ref (local.get $m)))) (if (i32.eqz (i32.and (local.get $mu) (i32.const 1))) (then (if (i32.eqz (local.get $mu)) (then (call $raise-division-by-zero) (unreachable))) (return (ref.i31 (i32.rem_s (local.get $nu) (local.get $mu)))))))))))) ;; Case 2: flonum/inexact ;; convert n to f64 (if (ref.test (ref $Flonum) (local.get $n)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $n))) (local.set $nf (struct.get $Flonum $v (local.get $fl)))) (else (if (ref.test (ref i31) (local.get $n)) (then (local.set $nu (i31.get_s (ref.cast i31ref (local.get $n)))) (if (i32.eqz (i32.and (local.get $nu) (i32.const 1))) (then (local.set $nf (f64.convert_i32_s (i32.shr_s (local.get $nu) (i32.const 1))))) (else (call $raise-expected-number (local.get $n)) (unreachable)))) (else (call $raise-expected-number (local.get $n)) (unreachable))))) ;; ensure n is a finite integer (if (f64.eq (local.get $nf) (f64.const inf)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (local.get $nf) (f64.const -inf)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (f64.floor (local.get $nf)) (local.get $nf)) (then) (else (call $raise-expected-number (local.get $n)) (unreachable))) ;; convert m to f64 (if (ref.test (ref $Flonum) (local.get $m)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $m))) (local.set $mf (struct.get $Flonum $v (local.get $fl)))) (else (if (ref.test (ref i31) (local.get $m)) (then (local.set $mu (i31.get_s (ref.cast i31ref (local.get $m)))) (if (i32.eqz (i32.and (local.get $mu) (i32.const 1))) (then (local.set $mf (f64.convert_i32_s (i32.shr_s (local.get $mu) (i32.const 1))))) (else (call $raise-expected-number (local.get $m)) (unreachable)))) (else (call $raise-expected-number (local.get $m)) (unreachable))))) ;; ensure m is a finite integer (if (f64.eq (local.get $mf) (f64.const inf)) (then (call $raise-expected-number (local.get $m)) (unreachable))) (if (f64.eq (local.get $mf) (f64.const -inf)) (then (call $raise-expected-number (local.get $m)) (unreachable))) (if (f64.eq (f64.floor (local.get $mf)) (local.get $mf)) (then) (else (call $raise-expected-number (local.get $m)) (unreachable))) ;; divide by zero? (if (f64.eq (local.get $mf) (f64.const 0.0)) (then (call $raise-division-by-zero) (unreachable))) ;; compute remainder in flonum (local.set $rf (f64.sub (local.get $nf) (f64.mul (f64.trunc (f64.div (local.get $nf) (local.get $mf))) (local.get $mf)))) (return (struct.new $Flonum (i32.const 0) (local.get $rf)))) (func $modulo (type $Prim2) (param $n (ref eq)) (param $m (ref eq)) (result (ref eq)) (local $r (ref eq)) (local $ru i32) (local $mu i32) (local $fl (ref $Flonum)) (local $mf f64) (local $rf f64) ;; Compute remainder; it validates numeric args and non-zero divisor. (local.set $r (call $remainder (local.get $n) (local.get $m))) ;; Convert m to f64 for sign checks; capture integer payload when fixnum. (if (ref.test (ref i31) (local.get $m)) (then (local.set $mu (i31.get_s (ref.cast (ref i31) (local.get $m)))) ;; Ensure it's a fixnum (lsb=0), else raise (should be unreachable if ;; $remainder already validated). (if (i32.eqz (i32.and (local.get $mu) (i32.const 1))) (then (local.set $mf (f64.convert_i32_s (i32.shr_s (local.get $mu) (i32.const 1))))) (else (call $raise-expected-number (local.get $m)) (unreachable)))) (else (local.set $fl (ref.cast (ref $Flonum) (local.get $m))) (local.set $mf (struct.get $Flonum $v (local.get $fl))))) ;; Result expression: handle integer and flonum remainders. (if (result (ref eq)) (ref.test (ref i31) (local.get $r)) ;; --- Integer remainder case ------------------------------------------ (then (block (result (ref eq)) (local.set $ru (i31.get_s (ref.cast (ref i31) (local.get $r)))) (if (result (ref eq)) (i32.eqz (local.get $ru)) ;; r == 0 → exact 0 (then (local.get $r)) ;; If r and m have same sign → r, else r + m (still a fixnum). (else (if (result (ref eq)) (i32.eq (i32.lt_s (local.get $ru) (i32.const 0)) (i32.lt_s (local.get $mu) (i32.const 0))) (then (local.get $r)) (else (ref.i31 (i32.add (local.get $ru) (local.get $mu))))))))) ;; --- Flonum remainder case ------------------------------------------- (else (block (result (ref eq)) (local.set $fl (ref.cast (ref $Flonum) (local.get $r))) (local.set $rf (struct.get $Flonum $v (local.get $fl))) (if (result (ref eq)) (f64.eq (local.get $rf) (f64.const 0.0)) ;; Preserve signed zero: 0.0 * mf carries mf's sign. (then (struct.new $Flonum (i32.const 0) (f64.mul (f64.const 0.0) (local.get $mf)))) ;; Non-zero: if signs match → r, else r + m. (else (if (result (ref eq)) (i32.eq (f64.lt (local.get $rf) (f64.const 0.0)) (f64.lt (local.get $mf) (f64.const 0.0))) (then (local.get $r)) (else (struct.new $Flonum (i32.const 0) (f64.add (local.get $rf) (local.get $mf))))))))))) (func $quotient/remainder (type $Prim2) (param $n (ref eq)) (param $m (ref eq)) (result (ref eq)) (local $nu i32) (local $mu i32) (local $q i32) (local $r i32) (local $fl (ref $Flonum)) (local $nf f64) (local $mf f64) (local $qf f64) (local $rf f64) ;; Case 1: both fixnums (if (ref.test (ref i31) (local.get $n)) (then (local.set $nu (i31.get_s (ref.cast i31ref (local.get $n)))) (if (i32.eqz (i32.and (local.get $nu) (i32.const 1))) (then (if (ref.test (ref i31) (local.get $m)) (then (local.set $mu (i31.get_s (ref.cast i31ref (local.get $m)))) (if (i32.eqz (i32.and (local.get $mu) (i32.const 1))) (then (if (i32.eqz (local.get $mu)) (then (call $raise-division-by-zero) (unreachable))) (local.set $q (i32.shl (i32.div_s (local.get $nu) (local.get $mu)) (i32.const 1))) (local.set $r (i32.rem_s (local.get $nu) (local.get $mu))) (return (array.new_fixed $Values 2 (ref.i31 (local.get $q)) (ref.i31 (local.get $r)))))))))))) ;; Case 2: flonum/inexact ;; convert n to f64 (if (ref.test (ref $Flonum) (local.get $n)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $n))) (local.set $nf (struct.get $Flonum $v (local.get $fl)))) (else (if (ref.test (ref i31) (local.get $n)) (then (local.set $nu (i31.get_s (ref.cast i31ref (local.get $n)))) (if (i32.eqz (i32.and (local.get $nu) (i32.const 1))) (then (local.set $nf (f64.convert_i32_s (i32.shr_s (local.get $nu) (i32.const 1))))) (else (call $raise-expected-number (local.get $n)) (unreachable)))) (else (call $raise-expected-number (local.get $n)) (unreachable))))) ;; ensure n is a finite integer (if (f64.eq (local.get $nf) (f64.const inf)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (local.get $nf) (f64.const -inf)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (f64.eq (f64.floor (local.get $nf)) (local.get $nf)) (then) (else (call $raise-expected-number (local.get $n)) (unreachable))) ;; convert m to f64 (if (ref.test (ref $Flonum) (local.get $m)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $m))) (local.set $mf (struct.get $Flonum $v (local.get $fl)))) (else (if (ref.test (ref i31) (local.get $m)) (then (local.set $mu (i31.get_s (ref.cast i31ref (local.get $m)))) (if (i32.eqz (i32.and (local.get $mu) (i32.const 1))) (then (local.set $mf (f64.convert_i32_s (i32.shr_s (local.get $mu) (i32.const 1))))) (else (call $raise-expected-number (local.get $m)) (unreachable)))) (else (call $raise-expected-number (local.get $m)) (unreachable))))) ;; ensure m is a finite integer (if (f64.eq (local.get $mf) (f64.const inf)) (then (call $raise-expected-number (local.get $m)) (unreachable))) (if (f64.eq (local.get $mf) (f64.const -inf)) (then (call $raise-expected-number (local.get $m)) (unreachable))) (if (f64.eq (f64.floor (local.get $mf)) (local.get $mf)) (then) (else (call $raise-expected-number (local.get $m)) (unreachable))) ;; divide by zero? (if (f64.eq (local.get $mf) (f64.const 0.0)) (then (call $raise-division-by-zero) (unreachable))) ;; compute quotient and remainder in flonum (local.set $qf (f64.trunc (f64.div (local.get $nf) (local.get $mf)))) (local.set $rf (f64.sub (local.get $nf) (f64.mul (local.get $qf) (local.get $mf)))) (return (array.new_fixed $Values 2 (struct.new $Flonum (i32.const 0) (local.get $qf)) (struct.new $Flonum (i32.const 0) (local.get $rf))))) (func $add1 (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $v/fx i32) (local $sum i32) (local $v/fl (ref $Flonum)) (local $f64v f64) ;; Case 1: Fixnum (if (ref.test (ref i31) (local.get $v)) (then (local.set $v/fx (i31.get_u (ref.cast (ref i31) (local.get $v)))) (if (i32.eqz (i32.and (local.get $v/fx) (i32.const 1))) (then (local.set $sum (i32.add (local.get $v/fx) (i32.const 2))) ;; add1 on unshifted (return (ref.i31 (local.get $sum))))))) ;; Case 2: Flonum (if (ref.test (ref $Flonum) (local.get $v)) (then (local.set $v/fl (ref.cast (ref $Flonum) (local.get $v))) (local.set $f64v (struct.get $Flonum $v (local.get $v/fl))) (return (struct.new $Flonum (i32.const 0) (f64.add (local.get $f64v) (f64.const 1.0)))))) ;; Not a number (call $raise-expected-number (local.get $v)) (unreachable)) (func $sub1 (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $v/fx i32) (local $diff i32) (local $v/fl (ref $Flonum)) (local $f64v f64) ;; Case 1: Fixnum (if (ref.test (ref i31) (local.get $v)) (then (local.set $v/fx (i31.get_u (ref.cast (ref i31) (local.get $v)))) (if (i32.eqz (i32.and (local.get $v/fx) (i32.const 1))) (then (local.set $diff (i32.sub (local.get $v/fx) (i32.const 2))) ;; sub1 = -1 << 1 (return (ref.i31 (local.get $diff))))))) ;; Case 2: Flonum (if (ref.test (ref $Flonum) (local.get $v)) (then (local.set $v/fl (ref.cast (ref $Flonum) (local.get $v))) (local.set $f64v (struct.get $Flonum $v (local.get $v/fl))) (return (struct.new $Flonum (i32.const 0) (f64.sub (local.get $f64v) (f64.const 1.0)))))) ;; Not a number (call $raise-expected-number (local.get $v)) (unreachable)) (func $sqr (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) ;; Case 1: Fixnum (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_u (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (return (call $fx* (local.get $x) (local.get $x))))))) ;; Case 2: Flonum (if (ref.test (ref $Flonum) (local.get $x)) (then (return (call $fl* (local.get $x) (local.get $x))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) (func $sqrt (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) ;; If x is a fixnum, compute sqrt and return fixnum if integral (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $f64 (call $js-math-sqrt (f64.convert_i32_s (i32.shr_s (local.get $bits) (i32.const 1))))) (if (f64.eq (f64.floor (local.get $f64)) (local.get $f64)) (then (local.set $bits (i32.trunc_f64_s (local.get $f64))) (return (ref.i31 (i32.shl (local.get $bits) (i32.const 1))))) (else (return (struct.new $Flonum (i32.const 0) (local.get $f64)))))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) ;; If x is a flonum, compute sqrt and box (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (return (struct.new $Flonum (i32.const 0) (call $js-math-sqrt (local.get $f64)))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) ;; Generic numeric unary functions ; name kind flonum-expr ; Implements: $abs $round $floor $ceiling $truncate ,@(let ([ops '((abs fx-abs (f64.abs (local.get $f64))) (round fx-id (f64.nearest (local.get $f64))) (floor fx-id (f64.floor (local.get $f64))) (ceiling fx-id (f64.ceil (local.get $f64))) (truncate fx-id (f64.trunc (local.get $f64))))]) (for/list ([p ops]) (define name (car p)) (define kind (cadr p)) (define expr (caddr p)) (define fixstmts (case kind [(fx-id) `((return (local.get $x)))] [(fx-abs) `((local.set $bits (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.lt_s (local.get $bits) (i32.const 0)) (then (local.set $bits (i32.sub (i32.const 0) (local.get $bits))))) (if (i32.lt_u (local.get $bits) (i32.const 1073741824)) (then (return (ref.i31 (i32.shl (local.get $bits) (i32.const 1))))) (else (return (struct.new $Flonum (i32.const 0) (f64.convert_i32_s (local.get $bits)))))))])) `(func ,(string->symbol (format "$~a" name)) (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) ;; Fixnum case (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then ,@fixstmts) (else (call $raise-expected-number (local.get $x)) (unreachable))))) ;; Flonum case (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (return (struct.new $Flonum (i32.const 0) ,expr)))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)))) (func $sgn (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) ;; Fixnum case (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $bits (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.eqz (local.get $bits)) (then (return (local.get $x))) (else (if (i32.gt_s (local.get $bits) (i32.const 0)) (then (return ,(Imm 1))) (else (return ,(Imm -1))))))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) ;; Flonum case (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (if (f64.eq (local.get $f64) (f64.const 0.0)) (then (return (local.get $x))) (else (if (f64.gt (local.get $f64) (f64.const 0.0)) (then (return (struct.new $Flonum (i32.const 0) (f64.const 1.0)))) (else (if (f64.lt (local.get $f64) (f64.const 0.0)) (then (return (struct.new $Flonum (i32.const 0) (f64.const -1.0)))) (else (return (struct.new $Flonum (i32.const 0) (f64.div (f64.const 0.0) (f64.const 0.0)))))))))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)) ;; Exact integer rounding functions ;; Implements: $exact-round $exact-floor $exact-ceiling $exact-truncate ,@(let ([ops '((exact-round f64.nearest) (exact-floor f64.floor) (exact-ceiling f64.ceil) (exact-truncate f64.trunc))]) (for/list ([p ops]) (define name (car p)) (define expr (cadr p)) `(func ,(string->symbol (format "$~a" name)) (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) ;; Fixnum case (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (return (local.get $x))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) ;; Flonum case (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (if (f64.ne (local.get $f64) (local.get $f64)) (then (call $raise-expected-number (local.get $x)) (unreachable))) (if (f64.eq (local.get $f64) (f64.const +inf)) (then (call $raise-expected-number (local.get $x)) (unreachable))) (if (f64.eq (local.get $f64) (f64.const -inf)) (then (call $raise-expected-number (local.get $x)) (unreachable))) (local.set $f64 (,expr (local.get $f64))) (if (f64.gt (local.get $f64) (f64.const 1073741823.0)) (then (call $raise-expected-number (local.get $x)) (unreachable))) (if (f64.lt (local.get $f64) (f64.const -1073741824.0)) (then (call $raise-expected-number (local.get $x)) (unreachable))) (local.set $bits (i32.trunc_f64_s (local.get $f64))) (return (ref.i31 (i32.shl (local.get $bits) (i32.const 1)))))) ;; Not a number (call $raise-expected-number (local.get $x)) (unreachable)))) ;; Trigonometric functions ; name js inbits outbits ,@(let ([ops '((sin $js-math-sin 0 0) ; sin(0) = 0 (cos $js-math-cos 0 2) ; cos(0) = 1 (tan $js-math-tan 0 0) ; tan(0) = 0 (asin $js-math-asin 0 0) ; asin(0) = 0 (acos $js-math-acos 2 0) ; acos(1) = 0 (sinh $js-math-sinh 0 0) ; sinh(0) = 0 (cosh $js-math-cosh 0 2) ; cosh(0) = 1 (tanh $js-math-tanh 0 0) ; tanh(0) = 0 (asinh $js-math-asinh 0 0) ; asinh(0) = 0 (acosh $js-math-acosh 2 0) ; acosh(1) = 0 (atanh $js-math-atanh 0 0))]) ; atanh(0) = 0 ;; inbits and outbits are raw i31 fixnum encodings. ;; They mark trivial exact identities of trig functions ;; (e.g. sin 0 = 0, cos 0 = 1, acos 1 = 0). ;; This avoids JS calls and flonum allocation in those cases. (for/list ([p ops]) (define name (car p)) (define js (cadr p)) (define inbits (caddr p)) (define outbits (cadddr p)) `(func ,(string->symbol (format "$~a" name)) (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (if (i32.eq (local.get $bits) (i32.const ,inbits)) (then (return (ref.i31 (i32.const ,outbits))))) (return (struct.new $Flonum (i32.const 0) (call ,js (f64.convert_i32_s (i32.shr_s (local.get $bits) (i32.const 1))))))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (return (struct.new $Flonum (i32.const 0) (call ,js (local.get $f64)))))) (call $raise-expected-number (local.get $x)) (unreachable)))) (func $atan (type $Prim12) (param $y (ref eq)) (param $x (ref eq)) (result (ref eq)) (local $bits i32) (local $y/f64 f64) (local $x/f64 f64) (local $y-exact i32) ;; Decode y. In the one-argument case this is the ordinary ;; unary atan input; with x present, Racket computes atan2(y, x). (if (ref.test (ref i31) (local.get $y)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $y)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $y/f64 (f64.convert_i32_s (i32.shr_s (local.get $bits) (i32.const 1)))) (local.set $y-exact (i32.const 1))) (else (call $raise-expected-number (local.get $y)) (unreachable)))) (else (if (ref.test (ref $Flonum) (local.get $y)) (then (local.set $y/f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $y))))) (else (call $raise-expected-number (local.get $y)) (unreachable))))) (if (ref.eq (local.get $x) (global.get $missing)) (then (if (i32.and (local.get $y-exact) (f64.eq (local.get $y/f64) (f64.const 0.0))) (then (return ,(Imm 0)))) (return (struct.new $Flonum (i32.const 0) (call $js-math-atan (local.get $y/f64)))))) ;; Decode x for the two-argument case. (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $x/f64 (f64.convert_i32_s (i32.shr_s (local.get $bits) (i32.const 1))))) (else (call $raise-expected-number (local.get $x)) (unreachable)))) (else (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $x/f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $x))))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) (struct.new $Flonum (i32.const 0) (call $js-math-atan2 (local.get $y/f64) (local.get $x/f64)))) ;; Angle conversion functions ,@(let ([ops '((degrees->radians 0.017453292519943295) (radians->degrees 57.29577951308232))]) (for/list ([p ops]) (define name (car p)) (define factor (cadr p)) `(func ,(string->symbol (format "$~a" name)) (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $bits i32) (local $fl (ref $Flonum)) (local $f64 f64) (if (ref.test (ref i31) (local.get $x)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $f64 (f64.mul (f64.convert_i32_s (i32.shr_s (local.get $bits) (i32.const 1))) (f64.const ,factor))) (return (struct.new $Flonum (i32.const 0) (local.get $f64)))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (return (struct.new $Flonum (i32.const 0) (f64.mul (local.get $f64) (f64.const ,factor)))))) (call $raise-expected-number (local.get $x)) (unreachable)))) (func $order-of-magnitude (type $Prim1) (param $r (ref eq)) (result (ref eq)) (local $bits i32) (local $i i32) (local $fl (ref $Flonum)) (local $f64 f64) (local $m i32) (local $p f64) (local $u f64) (local $ok i32) (local.set $ok (i32.const 0)) (if (ref.test (ref i31) (local.get $r)) (then (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $r)))) (if (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then (local.set $i (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.gt_s (local.get $i) (i32.const 0)) (then (local.set $f64 (f64.convert_i32_s (local.get $i))) (local.set $ok (i32.const 1))) (else (call $raise-argument-error (local.get $r)) (unreachable)))) (else (call $raise-argument-error (local.get $r)) (unreachable))))) (if (i32.eqz (local.get $ok)) (then (if (ref.test (ref $Flonum) (local.get $r)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $r))) (local.set $f64 (struct.get $Flonum $v (local.get $fl))) (if (f64.le (local.get $f64) (f64.const 0)) (then (call $raise-argument-error (local.get $r)) (unreachable))) (if (f64.eq (local.get $f64) (f64.const inf)) (then (call $raise-argument-error (local.get $r)) (unreachable))) (if (f64.ne (local.get $f64) (local.get $f64)) (then (call $raise-argument-error (local.get $r)) (unreachable))) (local.set $ok (i32.const 1)))))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error (local.get $r)) (unreachable))) (local.set $m (i32.trunc_f64_s (f64.floor (f64.mul (call $js-math-log (local.get $f64)) (f64.const 0.4342944819032518))))) (local.set $p (call $js-math-pow (f64.const 10) (f64.convert_i32_s (local.get $m)))) (block $down (loop $loop-down (br_if $down (f64.le (local.get $p) (local.get $f64))) (local.set $m (i32.sub (local.get $m) (i32.const 1))) (local.set $p (f64.mul (local.get $p) (f64.const 0.1))) (br $loop-down))) (local.set $u (f64.mul (local.get $p) (f64.const 10))) (block $up (loop $loop-up (br_if $up (f64.lt (local.get $f64) (local.get $u))) (local.set $m (i32.add (local.get $m) (i32.const 1))) (local.set $p (local.get $u)) (local.set $u (f64.mul (local.get $u) (f64.const 10))) (br $loop-up))) (ref.i31 (i32.shl (local.get $m) (i32.const 1)))) (func $raise-expected-number (param $x (ref eq)) (call $js-log (local.get $x)) (unreachable)) (func $raise-expected-number1 (param $x (ref eq)) (call $js-log (local.get $x)) (unreachable)) (func $raise-expected-number2 (param $x (ref eq)) (call $js-log (local.get $x)) (unreachable)) (func $raise-expected-number3 (param $x (ref eq)) (call $js-log (local.get $x)) (call $js-log (call $fixnum? (local.get $x))) (call $js-log (call $flonum? (local.get $x))) (call $js-log (call $string? (local.get $x))) (call $js-log (call $external? (local.get $x))) (call $js-log (local.get $x)) (unreachable)) (func $raise-expected-number4 (param $x (ref eq)) (call $js-log (local.get $x)) (unreachable)) (func $raise-expected-real (param $who (ref eq)) ;; symbol (param $x (ref eq)) (local $out (ref $GrowableArray)) (local $message (ref $String)) ;; TODO: include argument position and other arguments like Racket. (local.set $out (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $out) (call $format/display (local.get $who))) (call $growable-array-add! (local.get $out) (global.get $string:contract-violation:prefix)) (call $growable-array-add! (local.get $out) (global.get $string:real?)) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:given)) (call $growable-array-add! (local.get $out) (call $format/display (local.get $x))) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) (func $raise-expected-keyword (param $who (ref eq)) ;; symbol (param $x (ref eq)) (local $out (ref $GrowableArray)) (local $message (ref $String)) ;; TODO: include argument position and other arguments like Racket. (local.set $out (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $out) (call $format/display (local.get $who))) (call $growable-array-add! (local.get $out) (global.get $string:contract-violation:prefix)) (call $growable-array-add! (local.get $out) (global.get $string:keyword?)) (call $growable-array-add! (local.get $out) (global.get $string:arity-error:given)) (call $growable-array-add! (local.get $out) (call $format/display (local.get $x))) (local.set $message (call $growable-array-of-strings->string (local.get $out))) (call $raise (call $exn:fail:contract/make (local.get $message) (call $current-continuation-marks (global.get $missing))) (global.get $true)) (unreachable)) ,@(let () (define (binop $op $fxop $flop) `(func ,$op (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (if (result (ref eq)) (call $fx?/i32 (local.get $x)) ; x fixnum (then (if (result (ref eq)) (call $fx?/i32 (local.get $y)) ; x and y fixnums (then (call ,$fxop (local.get $x) (local.get $y))) ; x fixnum, y non-fixnum (else (if (result (ref eq)) (call $fl?/i32 (local.get $y)) ; x fixnum, y flonum (then (call ,$flop (call $fx->fl/precise (local.get $x)) (local.get $y))) ; x fixnum, y not a number (else (call $raise-expected-number1 (local.get $y)) (unreachable)))))) ; x not fixnum (else (if (result (ref eq)) (call $fl?/i32 (local.get $x)) ; x flonum (then (if (result (ref eq)) (call $fl?/i32 (local.get $y)) ; x flonum, y flonum (then (call ,$flop (local.get $x) (local.get $y))) ; x flonum, y not flonum (else (if (result (ref eq)) (call $fx?/i32 (local.get $y)) ; x flonum, y fixnum (then (call ,$flop (local.get $x) (call $fx->fl/precise (local.get $y)))) ; x flonum, y not a number (else (call $raise-expected-number2 (local.get $y)) (unreachable)))))) ; x is not a number (else (call $raise-expected-number3 (local.get $x)) (unreachable))))))) (list (binop '$+/2 '$fx+ '$fl+) (binop '$-/2 '$fx- '$fl-) (binop '$*/2 '$fx* '$fl*))) (func $+ (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $r (global.get $zero)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call $+/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)) (func $* (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $r (global.get $one)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.eq (call $fxzero? (local.get $v)) (global.get $true)) (then (local.set $r (global.get $zero)) (br $done)) (else (local.set $r (call $*/2 (local.get $r) (local.get $v))))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)) (func $- (type $Prim>=1) (param $a1 (ref eq)) (param $rest (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (local.get $rest)) (if (result (ref eq)) (ref.eq (local.get $xs) (global.get $null)) (then (call $-/2 (global.get $zero) (local.get $a1))) (else (local.set $r (local.get $a1)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call $-/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)))) ;; Note: fx/ doesn't exist, but fxquotient do. (func $/2 (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $x/fl (ref $Flonum)) (local $y/fl (ref $Flonum)) (local $xi i32) (local $yi i32) (local $r i32) ;; --- Check that $x is a number --- ; Note: $x is not a number if: it is not a fixnum and not a flonum (if (i32.and (i32.eqz (ref.test (ref i31) (local.get $x))) (i32.eqz (ref.test (ref $Flonum) (local.get $x)))) (then (call $raise-expected-number (local.get $x)) (unreachable))) ;; --- Check that $y is a number --- (if (i32.and (i32.eqz (ref.test (ref i31) (local.get $y))) (i32.eqz (ref.test (ref $Flonum) (local.get $y)))) (then (call $raise-expected-number (local.get $y)) (unreachable))) ;; --- Both fixnums? try exact division --- (if (result (ref eq)) (i32.and (ref.test (ref i31) (local.get $x)) (ref.test (ref i31) (local.get $y))) (then (local.set $xi ,(Half `(i31.get_s (ref.cast i31ref (local.get $x))))) (local.set $yi ,(Half `(i31.get_s (ref.cast i31ref (local.get $y))))) (if (i32.eqz (local.get $yi)) (then (call $raise-division-by-zero) (unreachable))) (local.set $r (i32.rem_s (local.get $xi) (local.get $yi))) (if (result (ref eq)) (i32.eqz (local.get $r)) (then (ref.i31 ,(Double `(i32.div_s (local.get $xi) (local.get $yi))))) (else (local.set $x/fl (call $fx->fl/precise (local.get $x))) (local.set $y/fl (call $fx->fl/precise (local.get $y))) (call $fl/ (local.get $x/fl) (local.get $y/fl))))) (else (local.set $x/fl (if (result (ref $Flonum)) (ref.test (ref $Flonum) (local.get $x)) (then (ref.cast (ref $Flonum) (local.get $x))) (else (call $fx->fl/precise (local.get $x))))) (local.set $y/fl (if (result (ref $Flonum)) (ref.test (ref $Flonum) (local.get $y)) (then (ref.cast (ref $Flonum) (local.get $y))) (else (call $fx->fl/precise (local.get $y))))) (call $fl/ (local.get $x/fl) (local.get $y/fl))))) (func $/ (type $Prim>=1) (param $a1 (ref eq)) (param $rest (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (local.get $rest)) (if (result (ref eq)) (ref.eq (local.get $xs) (global.get $null)) (then (call $/2 (global.get $one) (local.get $a1))) (else (local.set $r (local.get $a1)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call $/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)))) (func $gcd/2 (type $Prim2) (param $n (ref eq)) (param $m (ref eq)) (result (ref eq)) (local $tmp (ref eq)) (local.set $n (call $abs (local.get $n))) (local.set $m (call $abs (local.get $m))) (block $done (loop $loop (br_if $done (ref.eq (call $zero? (local.get $m)) (global.get $true))) (local.set $tmp (local.get $m)) (local.set $m (call $remainder (local.get $n) (local.get $m))) (local.set $n (local.get $tmp)) (br $loop))) (local.get $n)) (func $gcd (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $r ,(Imm 0)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call $gcd/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)) (func $lcm/2 (type $Prim2) (param $n (ref eq)) (param $m (ref eq)) (result (ref eq)) (local $g (ref eq)) ;; Work with absolute values. (local.set $n (call $abs (local.get $n))) (local.set $m (call $abs (local.get $m))) ;; Expression style: the top-level IF yields the final (ref eq). (if (result (ref eq)) (ref.eq (call $zero? (local.get $n)) (global.get $true)) ;; THEN: n = 0 (then (if (result (ref eq)) (ref.eq (call $zero? (local.get $m)) (global.get $true)) ;; both zero: prefer inexact if any is inexact (then (if (result (ref eq)) (ref.eq (call $exact? (local.get $n)) (global.get $true)) (then (if (result (ref eq)) (ref.eq (call $exact? (local.get $m)) (global.get $true)) (then (local.get $n)) ;; both exact zeros → n (exact 0) (else (local.get $m)))) ;; m is inexact zero → m (else (local.get $n)))) ;; n is inexact zero → n ;; n = 0, m ≠ 0 → 0 (preserve n’s zero) (else (local.get $n)))) ;; ELSE: n ≠ 0 (else (if (result (ref eq)) (ref.eq (call $zero? (local.get $m)) (global.get $true)) ;; m = 0, n ≠ 0 → 0 (preserve m’s zero) (then (local.get $m)) ;; general case: lcm(n,m) = (n / gcd(n,m)) * m (else (block (result (ref eq)) (local.set $g (call $gcd/2 (local.get $n) (local.get $m))) (call $*/2 (if (result (ref eq)) (i32.and (call $fx?/i32 (local.get $n)) (call $fx?/i32 (local.get $g))) (then (call $quotient (local.get $n) (local.get $g))) (else (call $/2 (local.get $n) (local.get $g)))) (local.get $m)))))))) (func $lcm (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $r ,(Imm 1)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call $lcm/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)) ;; Binary number comparators ,@(let () (define (gencmp cmp fxcmp flcmp) `(func ,cmp (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $x/is-fx i32) (local $y/is-fx i32) (local $x/is-fl i32) (local $y/is-fl i32) (local $x-fx i32) (local $y-fx i32) (local $x-fl (ref null $Flonum)) (local $y-fl (ref null $Flonum)) ;; --- Check if x is a fixnum --- (local.set $x/is-fx (ref.test (ref i31) (local.get $x))) (if (local.get $x/is-fx) (then (local.set $x-fx (i31.get_u (ref.cast (ref i31) (local.get $x)))) ;; Check low bit is 0 => valid fixnum (if (i32.and (local.get $x-fx) (i32.const 1)) (then (call $raise-check-fixnum (local.get $x)) (unreachable))))) ;; --- Check if x is a flonum --- (local.set $x/is-fl (ref.test (ref $Flonum) (local.get $x))) ;; --- Raise if x is not a number --- (if (i32.eqz (i32.or (local.get $x/is-fx) (local.get $x/is-fl))) (then (call $raise-expected-number (local.get $x)) (unreachable))) ;; --- Check if y is a fixnum --- (local.set $y/is-fx (ref.test (ref i31) (local.get $y))) (if (local.get $y/is-fx) (then (local.set $y-fx (i31.get_u (ref.cast (ref i31) (local.get $y)))) (if (i32.and (local.get $y-fx) (i32.const 1)) (then (call $raise-check-fixnum (local.get $y)) (unreachable))))) ;; --- Check if y is a flonum --- (local.set $y/is-fl (ref.test (ref $Flonum) (local.get $y))) ;; --- Raise if y is not a number --- (if (i32.eqz (i32.or (local.get $y/is-fx) (local.get $y/is-fl))) (then (call $raise-expected-number (local.get $y)) (unreachable))) ;; --- If both are fixnums, use fx< --- (if (i32.and (local.get $x/is-fx) (local.get $y/is-fx)) (then (return (call ,fxcmp (local.get $x) (local.get $y))))) ;; --- If both are flonums, use fl< --- (if (i32.and (local.get $x/is-fl) (local.get $y/is-fl)) (then (return (call ,flcmp (local.get $x) (local.get $y))))) ;; --- Mixed case: promote to flonum as needed --- (if (local.get $x/is-fl) (then (local.set $x-fl (ref.cast (ref $Flonum) (local.get $x)))) (else (local.set $x-fl (call $fx->fl/precise (local.get $x))))) (if (local.get $y/is-fl) (then (local.set $y-fl (ref.cast (ref $Flonum) (local.get $y)))) (else (local.set $y-fl (call $fx->fl/precise (local.get $y))))) (call ,flcmp (ref.as_non_null (local.get $x-fl)) (ref.as_non_null (local.get $y-fl))))) (list (gencmp '$=/2 '$fx=/2 '$fl=) ; maybe specialize this one? (gencmp '$/2 '$fx>/2 '$fl>) (gencmp '$<=/2 '$fx<=/2 '$fl<=) (gencmp '$>=/2 '$fx>=/2 '$fl>=))) ;; Variadic Number Comparators ,@(let () (define (gen-variadic-cmp name cmp/2) `(func ,name (type $Prim>=1) (param $x0 (ref eq)) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $prev (ref eq)) (local $curr (ref eq)) (local $res (ref eq)) (local $x/is-fx i32) (local $x/is-fl i32) (local $x-fx i32) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) ;; Single argument: ensure it is a number and return #t. (if (ref.eq (local.get $xs) (global.get $null)) (then (local.set $x/is-fx (ref.test (ref i31) (local.get $x0))) (if (local.get $x/is-fx) (then (local.set $x-fx (i31.get_u (ref.cast (ref i31) (local.get $x0)))) (if (i32.and (local.get $x-fx) (i32.const 1)) (then (call $raise-check-fixnum (local.get $x0)) (unreachable))))) (local.set $x/is-fl (ref.test (ref $Flonum) (local.get $x0))) (if (i32.eqz (i32.or (local.get $x/is-fx) (local.get $x/is-fl))) (then (call $raise-expected-number (local.get $x0)) (unreachable))) (return ,(Imm #t)))) (local.set $res ,(Imm #t)) (local.set $prev (local.get $x0)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $curr (struct.get $Pair $a (local.get $node))) (local.set $res (call ,cmp/2 (local.get $prev) (local.get $curr))) (if (ref.eq (local.get $res) ,(Imm #f)) (then (return ,(Imm #f)))) (local.set $prev (local.get $curr)) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $res))) (list (gen-variadic-cmp '$= '$=/2) (gen-variadic-cmp '$< '$ '$>/2) (gen-variadic-cmp '$<= '$<=/2) (gen-variadic-cmp '$>= '$>=/2))) ,@(let () (define (gen-minmax name fxop flop) (define $name/2 (string->symbol (~a "$" name "/2"))) (define $name (string->symbol (~a "$" name))) `((func ,$name/2 (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $x/is-fx i32) (local $x/is-fl i32) (local $x-fx i32) (local $x-fl (ref $Flonum)) (local $y/is-fx i32) (local $y/is-fl i32) (local $y-fx i32) (local $y-fl (ref $Flonum)) ; Initialize non-defaultable locals (local.set $x-fl (ref.cast (ref $Flonum) (global.get $flzero))) (local.set $y-fl (ref.cast (ref $Flonum) (global.get $flzero))) ;; Type check $x (local.set $x/is-fx (ref.test (ref i31) (local.get $x))) (if (local.get $x/is-fx) (then (local.set $x-fx (i31.get_u (ref.cast (ref i31) (local.get $x)))) (if (i32.and (local.get $x-fx) (i32.const 1)) (then (call $raise-check-fixnum (local.get $x)) (unreachable))))) (local.set $x/is-fl (ref.test (ref $Flonum) (local.get $x))) (if (i32.eqz (i32.or (local.get $x/is-fx) (local.get $x/is-fl))) (then (call $raise-expected-number (local.get $x)) (unreachable))) ;; Type check $y (local.set $y/is-fx (ref.test (ref i31) (local.get $y))) (if (local.get $y/is-fx) (then (local.set $y-fx (i31.get_u (ref.cast (ref i31) (local.get $y)))) (if (i32.and (local.get $y-fx) (i32.const 1)) (then (call $raise-check-fixnum (local.get $y)) (unreachable))))) (local.set $y/is-fl (ref.test (ref $Flonum) (local.get $y))) (if (i32.eqz (i32.or (local.get $y/is-fx) (local.get $y/is-fl))) (then (call $raise-expected-number (local.get $y)) (unreachable))) (if (i32.and (local.get $x/is-fx) (local.get $y/is-fx)) (then (return (call ,fxop (local.get $x) (local.get $y))))) (if (local.get $x/is-fl) (then (local.set $x-fl (ref.cast (ref $Flonum) (local.get $x)))) (else (local.set $x-fl (call $fx->fl/precise (local.get $x))))) (if (local.get $y/is-fl) (then (local.set $y-fl (ref.cast (ref $Flonum) (local.get $y)))) (else (local.set $y-fl (call $fx->fl/precise (local.get $y))))) (call ,flop (ref.as_non_null (local.get $x-fl)) (ref.as_non_null (local.get $y-fl)))) (func ,$name (type $Prim>=1) (param $x0 (ref eq)) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (if (ref.eq (local.get $xs) (global.get $null)) (then (return (call ,$name/2 (local.get $x0) (local.get $x0))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call ,$name/2 (local.get $x0) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call ,$name/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)))) (append (gen-minmax 'min '$fxmin/2 '$unsafe-flmin/2) (gen-minmax 'max '$fxmax/2 '$unsafe-flmax/2))) ,@(let () (define (gen-bitop name fxop) (define $name/2 (string->symbol (~a "$" name "/2"))) (define $name (string->symbol (~a "$" name))) `((func ,$name/2 (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (call ,fxop (local.get $x) (local.get $y))) (func ,$name (type $Prim>=1) (param $x0 (ref eq)) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $r (local.get $x0)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call ,$name/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)))) (append (gen-bitop 'bitwise-and '$fxand/2) (gen-bitop 'bitwise-ior '$fxior/2) (gen-bitop 'bitwise-xor '$fxxor/2))) (func $bitwise-not (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $x/fx i32) ; raw fixnum bits (local $x/i i32) ; unboxed signed i32 ;; --- Validate $x --- (if (i32.eqz (call $fx?/i32 (local.get $x))) (then (call $raise-expected-number (local.get $x)) (unreachable))) ;; --- Extract $x/i --- (local.set $x/fx (i31.get_u (ref.cast (ref i31) (local.get $x)))) (local.set $x/i (i32.shr_s (local.get $x/fx) (i32.const 1))) ;; --- Compute --- (ref.i31 (i32.shl (i32.xor (local.get $x/i) (i32.const -1)) (i32.const 1)))) (func $bitwise-bit-set? (type $Prim2) (param $n (ref eq)) (param $m (ref eq)) (result (ref eq)) (local $n/fx i32) ; raw bits of n (local $m/fx i32) ; raw bits of m (local $n/i i32) ; unboxed signed n (local $m/i i32) ; unboxed unsigned m ;; --- Validate $n and $m --- (if (i32.eqz (call $fx?/i32 (local.get $n))) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (i32.eqz (call $fx?/i32 (local.get $m))) (then (call $raise-expected-number (local.get $m)) (unreachable))) ;; --- Extract values --- (local.set $n/fx (i31.get_u (ref.cast (ref i31) (local.get $n)))) (local.set $m/fx (i31.get_u (ref.cast (ref i31) (local.get $m)))) (local.set $n/i (i32.shr_s (local.get $n/fx) (i32.const 1))) (local.set $m/i (i32.shr_u (local.get $m/fx) (i32.const 1))) ;; --- Compute --- (if (result (ref eq)) (i32.ge_u (local.get $m/i) (i32.const 30)) (then (if (result (ref eq)) (i32.lt_s (local.get $n/i) (i32.const 0)) (then (global.get $true)) (else (global.get $false)))) (else (if (result (ref eq)) (i32.eqz (i32.and (local.get $n/i) (i32.shl (i32.const 1) (local.get $m/i)))) (then (global.get $false)) (else (global.get $true)))))) (func $bitwise-first-bit-set (type $Prim1) (param $n (ref eq)) (result (ref eq)) (local $n/fx i32) (local $n/i i32) ;; --- Validate $n --- (if (i32.eqz (call $fx?/i32 (local.get $n))) (then (call $raise-expected-number (local.get $n)) (unreachable))) ;; --- Extract $n/i --- (local.set $n/fx (i31.get_u (ref.cast (ref i31) (local.get $n)))) (local.set $n/i (i32.shr_s (local.get $n/fx) (i32.const 1))) ;; --- Compute --- (if (result (ref eq)) (i32.eqz (local.get $n/i)) (then (ref.i31 (i32.const -2))) (else (ref.i31 (i32.shl (i32.ctz (local.get $n/i)) (i32.const 1)))))) ;; NOTE: Only supports fixnum arguments; start/end must be ;; non-negative fixnums and `end` must be >= `start`. (func $bitwise-bit-field (type $Prim3) (param $n (ref eq)) (param $start (ref eq)) (param $end (ref eq)) (result (ref eq)) (local $n/fx i32) (local $start/fx i32) (local $end/fx i32) (local $n/i i32) (local $start/i i32) (local $end/i i32) (local $width i32) (local $mask i32) (local $shifted i32) ;; --- Validate inputs --- (if (i32.eqz (call $fx?/i32 (local.get $n))) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (i32.eqz (call $fx?/i32 (local.get $start))) (then (call $raise-expected-number (local.get $start)) (unreachable))) (if (i32.eqz (call $fx?/i32 (local.get $end))) (then (call $raise-expected-number (local.get $end)) (unreachable))) ;; --- Extract values --- (local.set $n/fx (i31.get_s (ref.cast (ref i31) (local.get $n)))) (local.set $start/fx (i31.get_s (ref.cast (ref i31) (local.get $start)))) (local.set $end/fx (i31.get_s (ref.cast (ref i31) (local.get $end)))) (local.set $n/i (i32.shr_s (local.get $n/fx) (i32.const 1))) (local.set $start/i (i32.shr_s (local.get $start/fx) (i32.const 1))) (local.set $end/i (i32.shr_s (local.get $end/fx) (i32.const 1))) ;; --- Range checks --- (if (i32.lt_s (local.get $start/i) (i32.const 0)) (then (call $raise-argument-error (local.get $start)) (unreachable))) (if (i32.lt_s (local.get $end/i) (i32.const 0)) (then (call $raise-argument-error (local.get $end)) (unreachable))) (if (i32.lt_s (local.get $end/i) (local.get $start/i)) (then (call $raise-argument-error (local.get $end)) (unreachable))) ;; --- Compute width and mask --- (local.set $width (i32.sub (local.get $end/i) (local.get $start/i))) (local.set $mask (if (result i32) (i32.ge_u (local.get $width) (i32.const 30)) (then (i32.const -1)) (else (i32.sub (i32.shl (i32.const 1) (local.get $width)) (i32.const 1))))) ;; --- Shift and mask --- (local.set $shifted (i32.shr_s (local.get $n/i) (local.get $start/i))) (ref.i31 (i32.shl (i32.and (local.get $shifted) (local.get $mask)) (i32.const 1)))) ;; NOTE: Only supports fixnum arguments; bignum shifts are not implemented. (func $arithmetic-shift (type $Prim2) (param $n (ref eq)) ; exact-integer (param $m (ref eq)) ; exact-integer (result (ref eq)) ; exact-integer (local $n/fx i32) ; raw bits of n (local $m/fx i32) ; raw bits of m (local $n/i i32) ; unboxed signed n (local $m/i i32) ; unboxed signed m ;; --- Validate inputs --- (if (ref.eq (call $exact-integer? (local.get $n)) (global.get $false)) (then (call $raise-expected-number (local.get $n)) (unreachable))) (if (ref.eq (call $exact-integer? (local.get $m)) (global.get $false)) (then (call $raise-expected-number (local.get $m)))) ;; --- Extract values --- (local.set $n/fx (i31.get_s (ref.cast (ref i31) (local.get $n)))) (local.set $m/fx (i31.get_s (ref.cast (ref i31) (local.get $m)))) (local.set $n/i (i32.shr_s (local.get $n/fx) (i32.const 1))) (local.set $m/i (i32.shr_s (local.get $m/fx) (i32.const 1))) ;; --- Compute --- (if (result (ref eq)) (i32.lt_s (local.get $m/i) (i32.const 0)) (then (ref.i31 (i32.shl (i32.shr_s (local.get $n/i) (i32.sub (i32.const 0) (local.get $m/i))) (i32.const 1)))) (else (ref.i31 (i32.shl (i32.shl (local.get $n/i) (local.get $m/i)) (i32.const 1)))))) (func $integer-length (type $Prim1) (param $n (ref eq)) (result (ref eq)) (local $bits i32) (local $n/fx i32) (local $len i32) ;; Validate: must be a fixnum (ref i31 with lsb = 0); otherwise raise. (if (result (ref eq)) (ref.test (ref i31) (local.get $n)) (then (local.set $bits (i31.get_u (ref.cast (ref i31) (local.get $n)))) (if (result (ref eq)) (i32.eqz (i32.and (local.get $bits) (i32.const 1))) (then ;; Extract unboxed signed i32 from fixnum: (bits << 1) >> 2. (local.set $n/fx (i32.shr_s (i32.shl (local.get $bits) (i32.const 1)) (i32.const 2))) ;; Compute integer-length per Racket’s definition. (if (result (ref eq)) (i32.ge_s (local.get $n/fx) (i32.const 0)) (then (if (result (ref eq)) (i32.eqz (local.get $n/fx)) (then (ref.i31 (i32.const 0))) (else (local.set $len (i32.sub (i32.const 32) (i32.clz (local.get $n/fx)))) (ref.i31 (i32.shl (local.get $len) (i32.const 1)))))) (else ;; For negatives, use bit-length of (~n). (local.set $len (i32.sub (i32.const 32) (i32.clz (i32.xor (local.get $n/fx) (i32.const -1))))) (ref.i31 (i32.shl (local.get $len) (i32.const 1)))))) (else (call $raise-expected-number (local.get $n)) (unreachable)))) (else (call $raise-expected-number (local.get $n)) (unreachable)))) ;; Shared state for the pseudo-random number generator. ;; The initial value is chosen arbitrarily and can be replaced ;; with a fixed seed if reproducibility is needed. (global $random-state (mut i32) (i32.const 0x9E3779B9)) ;; A simple SplitMix32 generator that produces uniformly ;; distributed 32-bit integers. It is fast and has a large ;; period, which is sufficient for the primitives implemented ;; here. The algorithm is the 32-bit variant of the SplitMix ;; step used by multiple languages as an initialization phase ;; for stronger generators. (func $random-u32 (result i32) (local $z i32) (local.set $z (i32.add (global.get $random-state) (i32.const 0x9E3779B9))) (global.set $random-state (local.get $z)) (local.set $z (i32.xor (local.get $z) (i32.shr_u (local.get $z) (i32.const 16)))) (local.set $z (i32.mul (local.get $z) (i32.const 0x85EBCA6B))) (local.set $z (i32.xor (local.get $z) (i32.shr_u (local.get $z) (i32.const 13)))) (local.set $z (i32.mul (local.get $z) (i32.const 0xC2B2AE35))) (local.set $z (i32.xor (local.get $z) (i32.shr_u (local.get $z) (i32.const 16)))) (local.get $z)) (func $random (type $Prim02) ; two optional arguments ;; (random) -> flonum in (0,1) ;; (random k) -> exact integer in [0,k) ;; (random min max) -> exact integer in [min,max) (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (local $k i32) (local $min i32) (local $max i32) (local $range i32) (local $r i32) (if (ref.eq (local.get $a) (global.get $missing)) (then (local.set $r (call $random-u32)) (return (struct.new $Flonum (i32.const 0) (f64.div (f64.add (f64.convert_i32_u (local.get $r)) (f64.const 1)) (f64.const 4294967298.0))))) (else (if (ref.eq (local.get $b) (global.get $missing)) (then (if (i32.eqz (call $fx?/i32 (local.get $a))) (then (call $raise-expected-number (local.get $a)) (unreachable))) (local.set $k ,(Half `(i31.get_s (ref.cast i31ref (local.get $a))))) (if (i32.le_s (local.get $k) (i32.const 0)) (then (call $raise-argument-error (local.get $a)) (unreachable))) (local.set $r (i32.rem_u (call $random-u32) (local.get $k))) (return (ref.i31 (i32.shl (local.get $r) (i32.const 1))))) (else (if (i32.eqz (call $fx?/i32 (local.get $a))) (then (call $raise-expected-number (local.get $a)) (unreachable))) (if (i32.eqz (call $fx?/i32 (local.get $b))) (then (call $raise-expected-number (local.get $b)) (unreachable))) (local.set $min ,(Half `(i31.get_s (ref.cast i31ref (local.get $a))))) (local.set $max ,(Half `(i31.get_s (ref.cast i31ref (local.get $b))))) (local.set $range (i32.sub (local.get $max) (local.get $min))) (if (i32.le_s (local.get $range) (i32.const 0)) (then (call $raise-argument-error (local.get $b)) (unreachable))) (local.set $r (i32.add (local.get $min) (i32.rem_u (call $random-u32) (local.get $range)))) (return (ref.i31 (i32.shl (local.get $r) (i32.const 1)))))))) (unreachable)) (func $system-big-endian? (type $Prim0) (result (ref eq)) ;; WebAssembly's linear memory is defined as little-endian, ;; so WebRacket always reports a little-endian system. (global.get $false)) ;;; ;;; 4.3.4 Fixnums ;;; ;; https://docs.racket-lang.org/reference/fixnums.html (func $raise-not-fixnum (param $x (ref eq)) (unreachable)) (func $fixnum? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test i31ref (local.get $v)) (then (if (result (ref eq)) (i32.eqz (i32.and (i31.get_s (ref.cast i31ref (local.get $v))) (i32.const ,fixnum-mask))) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $fx?/i32 (param $v (ref eq)) (result i32) (local $v/tag i32) (if (ref.test (ref i31) (local.get $v)) (then (local.set $v/tag (i31.get_u (ref.cast (ref i31) (local.get $v)))) (return (i32.eqz (i32.and (local.get $v/tag) (i32.const 1))))) (else (return (i32.const 0)))) (unreachable)) (func $fxzero? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test i31ref (local.get $x)) (then (if (result (ref eq)) (i32.eqz (i31.get_s (ref.cast i31ref (local.get $x)))) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) #;(func $fx+ (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.add (i31.get_s (ref.cast i31ref (local.get $x))) (i31.get_s (ref.cast i31ref (local.get $y)))))) (func $fx+ (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $sum i64) ;; tagged sum payload: 2x + 2y ;; Check fixnum arguments ;; - note: this avoids functions calls in the non-error case (if (i32.eqz (i32.and (if (result i32) (ref.test (ref i31) (local.get $x)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $x))) (i32.const 1)))) (else (i32.const 0))) (if (result i32) (ref.test (ref i31) (local.get $y)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $y))) (i32.const 1)))) (else (i32.const 0))))) (then (call $ensure-fixnum-argument (global.get $symbol:fx+) (local.get $x)) (call $ensure-fixnum-argument (global.get $symbol:fx+) (local.get $y))) (else)) ;; Add (local.set $sum (i64.add (i64.extend_i32_s (i31.get_s (ref.cast (ref i31) (local.get $x)))) (i64.extend_i32_s (i31.get_s (ref.cast (ref i31) (local.get $y)))))) ;; Tagged payload must fit in signed i31 and remain even: ;; range is [-2^30, 2^30-2]. (if (i32.or (i64.lt_s (local.get $sum) (i64.const -1073741824)) ;; -2^30 (i64.gt_s (local.get $sum) (i64.const 1073741822))) ;; 2^30-2 (then (call $raise-fx-overflow (global.get $symbol:fx+) (local.get $x) (local.get $y)) (unreachable)) (else)) (ref.i31 (i32.wrap_i64 (local.get $sum)))) #;(func $fx- (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.sub (i31.get_s (ref.cast i31ref (local.get $x))) (i31.get_s (ref.cast i31ref (local.get $y)))))) (func $fx- (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $dt i64) ;; tagged difference payload: 2(x - y) ;; Check fixnum arguments ;; - note: this avoids functions calls in the non-error case (if (i32.eqz (i32.and (if (result i32) (ref.test (ref i31) (local.get $x)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $x))) (i32.const 1)))) (else (i32.const 0))) (if (result i32) (ref.test (ref i31) (local.get $y)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $y))) (i32.const 1)))) (else (i32.const 0))))) (then (call $ensure-fixnum-argument (global.get $symbol:fx-) (local.get $x)) (call $ensure-fixnum-argument (global.get $symbol:fx-) (local.get $y))) (else)) ;; The subtraction (local.set $dt (i64.sub (i64.extend_i32_s (i31.get_s (ref.cast (ref i31) (local.get $x)))) (i64.extend_i32_s (i31.get_s (ref.cast (ref i31) (local.get $y)))))) ;; Tagged payload must fit in signed i31 and remain even: ;; range is [-2^30, 2^30-2]. (if (i32.or (i64.lt_s (local.get $dt) (i64.const -1073741824)) ;; -2^30 (i64.gt_s (local.get $dt) (i64.const 1073741822))) ;; 2^30-2 (then (call $raise-fx-overflow (global.get $symbol:fx-) (local.get $x) (local.get $y)) (unreachable)) (else)) (ref.i31 (i32.wrap_i64 (local.get $dt)))) #;(func $fx/ (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.div_s (i31.get_s (ref.cast i31ref (local.get $x))) ,(Double `(i31.get_s (ref.cast i31ref (local.get $y))))))) (func $fx/ (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $xi i32) ;; untagged x (local $yi i32) ;; untagged y (local $q i32) ;; untagged quotient ;; Check fixnum arguments ;; - note: this avoids functions calls in the non-error case (if (i32.eqz (i32.and (if (result i32) (ref.test (ref i31) (local.get $x)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $x))) (i32.const 1)))) (else (i32.const 0))) (if (result i32) (ref.test (ref i31) (local.get $y)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $y))) (i32.const 1)))) (else (i32.const 0))))) (then (call $ensure-fixnum-argument (global.get $symbol:fx/) (local.get $x)) (call $ensure-fixnum-argument (global.get $symbol:fx/) (local.get $y))) (else)) (local.set $xi (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $x))) (i32.const 1))) (local.set $yi (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $y))) (i32.const 1))) (if (i32.eqz (local.get $yi)) (then (call $raise-division-by-zero) (unreachable)) (else)) ;; Tagged quotient would overflow when -2^29 / -1 = 2^29. (if (i32.and (i32.eq (local.get $xi) (i32.const -536870912)) (i32.eq (local.get $yi) (i32.const -1))) (then (call $raise-fx-overflow (global.get $symbol:fx/) (local.get $x) (local.get $y)) (unreachable)) (else)) (local.set $q (i32.div_s (local.get $xi) (local.get $yi))) (ref.i31 (i32.shl (local.get $q) (i32.const 1)))) ; Since an integer n is represented as 2n, we need to halve one argument. ; This version doesn't throw an exception on overflow. #;(func $fx* (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.mul (i31.get_s (ref.cast i31ref (local.get $x))) ,(Half `(i31.get_s (ref.cast i31ref (local.get $y))))))) (func $raise-fx-overflow (param $who (ref eq)) (param $x (ref eq)) (param $y (ref eq)) (local $message (ref $String)) (local $who-str (ref $String)) (local $x-str (ref $String)) (local $y-str (ref $String)) (local $exn (ref $Struct)) (local.set $who-str (call $format/display (local.get $who))) (local.set $x-str (call $format/display (local.get $x))) (local.set $y-str (call $format/display (local.get $y))) (local.set $message (call $string-append/2 (local.get $who-str) (global.get $string:fx-overflow:middle))) (local.set $message (call $string-append/2 (local.get $message) (local.get $x-str))) (local.set $message (call $string-append/2 (local.get $message) (global.get $string:fx-overflow:and))) (local.set $message (call $string-append/2 (local.get $message) (local.get $y-str))) (local.set $exn (call $exn:fail:contract:non-fixnum-result/make (ref.cast (ref eq) (local.get $message)) (global.get $null))) ;; barrier? is ignored by WebRacket raise, but pass #t for compatibility (call $raise (ref.cast (ref eq) (local.get $exn)) (global.get $true)) (unreachable)) (func $fixnum?/i32 (param $v (ref eq)) (result i32) (if (result i32) (ref.test (ref i31) (local.get $v)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $v))) (i32.const 1)))) (else (i32.const 0)))) (func $ensure-fixnum-argument (param $who (ref eq)) (param $x (ref eq)) (local $message (ref $String)) (local $who-str (ref $String)) (local $x-str (ref $String)) (local $exn (ref $Struct)) (if (i32.eqz (call $fixnum?/i32 (local.get $x))) (then (local.set $who-str (call $format/display (local.get $who))) (local.set $x-str (call $format/display (local.get $x))) (local.set $message (call $string-append/2 (local.get $who-str) (global.get $string:expected-fixnum:got))) (local.set $message (call $string-append/2 (local.get $message) (local.get $x-str))) (local.set $exn (call $exn:fail:contract/make (ref.cast (ref eq) (local.get $message)) (global.get $null))) (call $raise (ref.cast (ref eq) (local.get $exn)) (global.get $true)) (unreachable)))) (func $fx* (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $pt i64) ;; tagged product payload: 2xy ;; Check fixnum arguments ;; - note: this avoids functions calls in the non-error case (if (i32.eqz (i32.and (if (result i32) (ref.test (ref i31) (local.get $x)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $x))) (i32.const 1)))) (else (i32.const 0))) (if (result i32) (ref.test (ref i31) (local.get $y)) (then (i32.eqz (i32.and (i31.get_u (ref.cast (ref i31) (local.get $y))) (i32.const 1)))) (else (i32.const 0))))) (then (call $ensure-fixnum-argument (global.get $symbol:fx*) (local.get $x)) (call $ensure-fixnum-argument (global.get $symbol:fx*) (local.get $y))) (else)) ;; Fixnums are tagged as 2n. ;; If x = 2a and y = 2b, then: ;; (x * y) >> 1 = (2a * 2b) >> 1 = 2ab ;; which is exactly the tagged payload for the product. (local.set $pt (i64.shr_s (i64.mul (i64.extend_i32_s (i31.get_s (ref.cast (ref i31) (local.get $x)))) (i64.extend_i32_s (i31.get_s (ref.cast (ref i31) (local.get $y))))) (i64.const 1))) ;; Tagged payload must fit in signed i31 and remain even: ;; range is [-2^30, 2^30-2]. (if (i32.or (i64.lt_s (local.get $pt) (i64.const -1073741824)) ;; -2^30 (i64.gt_s (local.get $pt) (i64.const 1073741822))) ;; 2^30-2 (then (call $raise-fx-overflow (global.get $symbol:fx*) (local.get $x) (local.get $y)) (unreachable)) (else)) (ref.i31 (i32.wrap_i64 (local.get $pt)))) (func $raise-division-by-zero (unreachable)) (func $unsafe-fxquotient (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 ,(Double `(i32.div_s ,(Half `(i31.get_s (ref.cast i31ref (local.get $x)))) ,(Half `(i31.get_s (ref.cast i31ref (local.get $y)))))))) (func $fxquotient (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $xu i32) (local $yu i32) ;; raw tagged bits (local $xi i32) (local $yi i32) ;; untagged i32s (local $q i32) ;; quotient ;; --- check $x is a fixnum --- (if (i32.eqz (ref.test (ref i31) (local.get $x))) (then (call $raise-check-fixnum (local.get $x)) (unreachable))) (local.set $xu (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.and (local.get $xu) (i32.const 1)) (then (call $raise-check-fixnum (local.get $x)) (unreachable))) (local.set $xi (i32.shr_s (local.get $xu) (i32.const 1))) ;; --- check $y is a fixnum --- (if (i32.eqz (ref.test (ref i31) (local.get $y))) (then (call $raise-check-fixnum (local.get $y)) (unreachable))) (local.set $yu (i31.get_s (ref.cast (ref i31) (local.get $y)))) (if (i32.and (local.get $yu) (i32.const 1)) (then (call $raise-check-fixnum (local.get $y)) (unreachable))) (local.set $yi (i32.shr_s (local.get $yu) (i32.const 1))) ;; --- divide by zero? --- (if (i32.eqz (local.get $yi)) (then (call $raise-division-by-zero) (unreachable))) ;; --- compute truncating quotient --- (local.set $q (i32.div_s (local.get $xi) (local.get $yi))) ;; --- (optional) fixnum-range check: [-2^29, 2^29-1] --- ;; uncomment if you want to signal overflow instead of wrapping ;; (if (i32.or ;; (i32.lt_s (local.get $q) (i32.const -536870912)) ;; -2^29 ;; (i32.gt_s (local.get $q) (i32.const 536870911))) ;; 2^29-1 ;; (then (call $raise-fixnum-overflow) (unreachable))) ;; --- re-tag as fixnum --- (ref.i31 (i32.shl (local.get $q) (i32.const 1)))) (func $unsafe-fxremainder (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.rem_s (i31.get_s (ref.cast i31ref (local.get $x))) (i31.get_s (ref.cast i31ref (local.get $y)))))) (func $fxremainder (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.rem_s (i31.get_s (ref.cast i31ref (local.get $x))) (i31.get_s (ref.cast i31ref (local.get $y)))))) (func $unsafe-fxmodulo (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $xv i32) (local $yv i32) (local $r i32) (local.set $xv (i31.get_s (ref.cast i31ref (local.get $x)))) (local.set $yv (i31.get_s (ref.cast i31ref (local.get $y)))) (local.set $r (i32.rem_s (local.get $xv) (local.get $yv))) (if (result (ref eq)) (i32.eqz (local.get $r)) (then (ref.i31 (local.get $r))) (else (if (result (ref eq)) (i32.eq (i32.lt_s (local.get $r) (i32.const 0)) (i32.lt_s (local.get $yv) (i32.const 0))) (then (ref.i31 (local.get $r))) (else (ref.i31 (i32.add (local.get $r) (local.get $yv)))))))) (func $fxmodulo (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $xv i32) (local $yv i32) (local $r i32) (local.set $xv (i31.get_s (ref.cast i31ref (local.get $x)))) (local.set $yv (i31.get_s (ref.cast i31ref (local.get $y)))) (local.set $r (i32.rem_s (local.get $xv) (local.get $yv))) (if (result (ref eq)) (i32.eqz (local.get $r)) (then (ref.i31 (local.get $r))) (else (if (result (ref eq)) (i32.eq (i32.lt_s (local.get $r) (i32.const 0)) (i32.lt_s (local.get $yv) (i32.const 0))) (then (ref.i31 (local.get $r))) (else (ref.i31 (i32.add (local.get $r) (local.get $yv)))))))) (func $unsafe-fxabs (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $xi i32) (local.set $xi (i31.get_s (ref.cast i31ref (local.get $x)))) (ref.i31 (if (result i32) (i32.lt_s (local.get $xi) (i32.const 0)) (then (i32.sub (i32.const 0) (local.get $xi))) (else (local.get $xi))))) (func $fxabs (type $Prim1) (param $x (ref eq)) (result (ref eq)) (local $xi i32) (local.set $xi (i31.get_s (ref.cast i31ref (local.get $x)))) (ref.i31 (if (result i32) (i32.lt_s (local.get $xi) (i32.const 0)) (then (i32.sub (i32.const 0) (local.get $xi))) (else (local.get $xi))))) (func $fxand/2 (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.and (i31.get_s (ref.cast i31ref (local.get $x))) (i31.get_s (ref.cast i31ref (local.get $y)))))) (func $fxand (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $r ,(Imm -1)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call $fxand/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)) (func $fxior/2 (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.or (i31.get_s (ref.cast i31ref (local.get $x))) (i31.get_s (ref.cast i31ref (local.get $y)))))) (func $fxior (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $r (global.get $zero)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call $fxior/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)) (func $fxxor/2 (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.xor (i31.get_s (ref.cast i31ref (local.get $x))) (i31.get_s (ref.cast i31ref (local.get $y)))))) (func $fxxor (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $r (ref eq)) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $r (global.get $zero)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $r (call $fxxor/2 (local.get $r) (local.get $v))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $r)) (func $fxnot (type $Prim1) (param $x (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (i32.xor ,(Half `(i31.get_s (ref.cast i31ref (local.get $x)))) (i32.const -1)) (i32.const 1)))) (func $fxlshift (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (i31.get_s (ref.cast i31ref (local.get $x))) ,(Half `(i31.get_s (ref.cast i31ref (local.get $y))))))) (func $fxrshift (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.shr_s (i31.get_s (ref.cast i31ref (local.get $x))) ,(Half `(i31.get_s (ref.cast i31ref (local.get $y))))))) (func $fxpopcount (type $Prim1) (param $x (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (i32.popcnt ,(Half `(i31.get_u (ref.cast i31ref (local.get $x))))) (i32.const 1)))) (func $fxpopcount32 (type $Prim1) (param $x (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (i32.popcnt ,(Half `(i31.get_u (ref.cast i31ref (local.get $x))))) (i32.const 1)))) (func $fxpopcount16 (type $Prim1) (param $x (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (i32.popcnt (i32.and ,(Half `(i31.get_u (ref.cast i31ref (local.get $x)))) (i32.const 65535))) (i32.const 1)))) (func $fx+/wraparound (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.add (i31.get_s (ref.cast i31ref (local.get $x))) (i31.get_s (ref.cast i31ref (local.get $y)))))) (func $fx-/wraparound (type $Prim>=1) (param $a1 (ref eq)) (param $rest (ref eq)) (result (ref eq)) (local $a (ref eq)) (local $b (ref eq)) (local $node (ref $Pair)) ;; Eager init so locals are definitely assigned before any possible get. (local.set $a (ref.i31 (i32.const 0))) (local.set $b (local.get $a1)) ;; Determine arguments based on rest list (if (ref.eq (local.get $rest) (global.get $null)) (then ;; (0 - a1) (local.set $a (ref.i31 (i32.const 0))) (local.set $b (local.get $a1))) (else (if (ref.test (ref $Pair) (local.get $rest)) (then (local.set $node (ref.cast (ref $Pair) (local.get $rest))) (local.set $a (local.get $a1)) (local.set $b (struct.get $Pair $a (local.get $node))) ;; Ensure no extra arguments (if (ref.eq (struct.get $Pair $d (local.get $node)) (global.get $null)) (then (nop)) (else (call $raise-arity-error:exactly) (unreachable)))) (else (call $raise-arity-error:exactly) (unreachable))))) (ref.i31 (i32.sub (i31.get_s (ref.cast (ref i31) (local.get $a))) (i31.get_s (ref.cast (ref i31) (local.get $b)))))) (func $fx*/wraparound (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.mul (i31.get_s (ref.cast i31ref (local.get $x))) ,(Half `(i31.get_s (ref.cast i31ref (local.get $y))))))) (func $fxlshift/wraparound (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (i31.get_s (ref.cast i31ref (local.get $x))) ,(Half `(i31.get_s (ref.cast i31ref (local.get $y))))))) (func $fxrshift/logical (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (ref.i31 ,(Double `(i32.shr_u ,(Half `(i31.get_u (ref.cast (ref i31) (local.get $x)))) ,(Half `(i31.get_u (ref.cast (ref i31) (local.get $y)))))))) (func $most-positive-fixnum (type $Prim0) (result (ref eq)) ,(Imm most-positive-fixnum)) (func $most-negative-fixnum (type $Prim0) (result (ref eq)) ,(Imm most-negative-fixnum)) (func $fx=/2 (type $Prim2) (param $v1 (ref eq)) (param $v2 (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (call $fixnum? (local.get $v1)) (global.get $true)) (then (return_call $eq? (local.get $v1) (local.get $v2))) (else (global.get $false)))) ,@(for/list ([$fx-cmp '($fx/2 $fx<=/2 $fx>=/2)] [inst '(i32.lt_s i32.gt_s i32.le_s i32.ge_s)]) `(func ,$fx-cmp (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) ; type check (if (i32.eqz (ref.test (ref i31) (local.get $x))) (then (call $raise-check-fixnum (local.get $x)) (unreachable))) (if (i32.eqz (ref.test (ref i31) (local.get $y))) (then (call $raise-check-fixnum (local.get $y)) (unreachable))) ; compare (if (result (ref eq)) (,inst (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))) (then (global.get $true)) (else (global.get $false))))) ,@(for/list ([$cmp (in-list '($fx= $fx< $fx> $fx<= $fx>=))] [$cmp/2 (in-list '($fx=/2 $fx/2 $fx<=/2 $fx>=/2))]) `(func ,$cmp (param $x0 (ref eq)) (param $xs (ref eq)) (result (ref eq)) (local $node (ref $Pair)) (local $fx (ref eq)) ;; Validate the first argument (if (i32.eqz (ref.test (ref i31) (local.get $x0))) (then (call $raise-check-fixnum (local.get $x0)) (unreachable))) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $fx (struct.get $Pair $a (local.get $node))) (if (i32.eqz (ref.test (ref i31) (local.get $fx))) (then (call $raise-check-fixnum (local.get $fx)) (unreachable))) (if (ref.eq (call ,$cmp/2 (local.get $x0) (local.get $fx)) (global.get $false)) (then (return (global.get $false)))) (local.set $x0 (local.get $fx)) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (global.get $true))) ,@(for/list ([name '( $fxmin/2 $fxmax/2 )] [inst '( i32.lt_s i32.gt_s )]) `(func ,name (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (if (i32.eqz (ref.test (ref i31) (local.get $x))) (then (call $raise-check-fixnum (local.get $x)) (unreachable))) (if (i32.eqz (ref.test (ref i31) (local.get $y))) (then (call $raise-check-fixnum (local.get $y)) (unreachable))) (if (result (ref eq)) (,inst (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))) (then (local.get $x)) (else (local.get $y))))) ,@(for/list ([name '( $fxmin $fxmax )] [cmp '( $fxmin/2 $fxmax/2 )]) `(func ,name (param $x0 (ref eq)) (param $xs (ref eq)) (result (ref eq)) (local $node (ref $Pair)) (local $fx (ref eq)) (local $best (ref eq)) (if (i32.eqz (ref.test (ref i31) (local.get $x0))) (then (call $raise-check-fixnum (local.get $x0)) (unreachable))) (local.set $best (local.get $x0)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $fx (struct.get $Pair $a (local.get $node))) (if (i32.eqz (ref.test (ref i31) (local.get $fx))) (then (call $raise-check-fixnum (local.get $fx)) (unreachable))) (local.set $best (call ,cmp (local.get $best) (local.get $fx))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $best))) (func $fixnum->i32 (param $x (ref eq)) (result i32) (local $val i32) (if (ref.test (ref i31) (local.get $x)) (then (local.set $val (i31.get_s (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $val) (i32.const 1))) (then (return (i32.shr_s (local.get $val) (i32.const 1)))) ;; return unboxed i32 (else (call $raise-not-fixnum (local.get $x))))) (else (call $raise-not-fixnum (local.get $x)))) (unreachable)) (func $fixnum-for-every-system? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $bits i32) (local $value i32) ;; Validate that v is a fixnum with low bit 0. (if (i32.eqz (ref.test (ref i31) (local.get $v))) (then (return (global.get $false)))) (local.set $bits (i31.get_s (ref.cast (ref i31) (local.get $v)))) (if (i32.and (local.get $bits) (i32.const 1)) (then (return (global.get $false)))) ;; Decode the fixnum payload and ensure it fits the cross-platform range. (local.set $value (i32.shr_s (local.get $bits) (i32.const 1))) (if (i32.lt_s (local.get $value) (i32.const -536870912)) (then (return (global.get $false)))) (if (i32.gt_s (local.get $value) (i32.const 536870911)) (then (return (global.get $false)))) (global.get $true)) ;;; ;;; 4.3.3 Floating Point Numbers ;;; ;; https://docs.racket-lang.org/reference/flonums.html (func $i32->flonum (param $n i32) (result (ref $Flonum)) (struct.new $Flonum (i32.const 0) ;; initial hash = 0 (f64.convert_i32_s (local.get $n)))) ;; fl?/i32 : (ref eq) -> i32 ;; Returns 1 if the value is a flonum, 0 otherwise (func $fl?/i32 (param $a (ref eq)) (result i32) (ref.test (ref $Flonum) (local.get $a))) ;; flonum? : (ref eq) -> (ref eq) ;; Returns #t if the value is a flonum, #f otherwise (func $flonum? (type $Prim1) (param $a (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Flonum) (local.get $a)) (then (global.get $true)) (else (global.get $false)))) ;; double-flonum? : (ref eq) -> (ref eq) ;; All flonums are double precision, so reuse flonum? (func $double-flonum? (type $Prim1) (param $a (ref eq)) (result (ref eq)) (call $flonum? (local.get $a))) (func $fx->fl/precise (param $v (ref eq)) (result (ref $Flonum)) (local $v/i32 i32) ;; Check that v is a fixnum (ref i31) and has low bit 0 (if (i32.eqz (ref.test (ref i31) (local.get $v))) (then (call $raise-check-fixnum (local.get $v)) (unreachable))) (local.set $v/i32 (i31.get_s (ref.cast (ref i31) (local.get $v)))) (if (i32.and (local.get $v/i32) (i32.const 1)) (then (call $raise-check-fixnum (local.get $v)) (unreachable))) ;; Convert fixnum to flonum (struct.new $Flonum (i32.const 0) ;; hash = 0 (f64.convert_i32_s (i32.shr_s (local.get $v/i32) (i32.const 1))))) (func $fx->fl (type $Prim1) (param $v (ref eq)) (result (ref eq)) ; a $Flonum (local $v/i32 i32) ;; Check that v is a fixnum (ref i31) and has low bit 0 (if (i32.eqz (ref.test (ref i31) (local.get $v))) (then (call $raise-check-fixnum (local.get $v)) (unreachable))) (local.set $v/i32 (i31.get_s (ref.cast (ref i31) (local.get $v)))) (if (i32.and (local.get $v/i32) (i32.const 1)) (then (call $raise-check-fixnum (local.get $v)) (unreachable))) ;; Convert fixnum to flonum (struct.new $Flonum (i32.const 0) ;; hash = 0 (f64.convert_i32_s (i32.shr_s (local.get $v/i32) (i32.const 1))))) (func $->fl (type $Prim1) (param $v (ref eq)) (result (ref eq)) (call $fx->fl (local.get $v))) (func $raise-fl->fx (param $x (ref eq)) (unreachable)) (func $fl->fx (type $Prim1) (param $v (ref eq)) (result (ref eq)) ; a fixnum (local $v/fl (ref $Flonum)) (local $x/f64 f64) (local $t/f64 f64) (local $i32 i32) ;; Check that v is a flonum (if (i32.eqz (ref.test (ref $Flonum) (local.get $v))) (then (call $raise-check-flonum (local.get $v)) (unreachable))) (local.set $v/fl (ref.cast (ref $Flonum) (local.get $v))) (local.set $x/f64 (struct.get $Flonum $v (local.get $v/fl))) ;; Truncate toward zero (local.set $t/f64 (f64.trunc (local.get $x/f64))) ;; NaN? (if (f64.ne (local.get $t/f64) (local.get $t/f64)) (then (call $raise-fl->fx (local.get $v)) (unreachable))) ;; Check fixnum range (if (i32.or (f64.gt (local.get $t/f64) (f64.const 536870911.0)) (f64.lt (local.get $t/f64) (f64.const -536870912.0))) (then (call $raise-fl->fx (local.get $v)) (unreachable))) ;; Convert to i32 and box (local.set $i32 (i32.trunc_f64_s (local.get $t/f64))) (ref.i31 (i32.shl (local.get $i32) (i32.const 1)))) (func $raise-fl->exact-integer (param $x (ref eq)) (unreachable)) (func $fl->exact-integer (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $v/fl (ref $Flonum)) (local $x/f64 f64) (local $t/f64 f64) (local $i32 i32) ;; Check that v is a flonum (if (i32.eqz (ref.test (ref $Flonum) (local.get $v))) (then (call $raise-check-flonum (local.get $v)) (unreachable))) (local.set $v/fl (ref.cast (ref $Flonum) (local.get $v))) (local.set $x/f64 (struct.get $Flonum $v (local.get $v/fl))) (local.set $t/f64 (f64.trunc (local.get $x/f64))) ;; Must be integer (if (f64.ne (local.get $x/f64) (local.get $t/f64)) (then (call $raise-fl->exact-integer (local.get $v)) (unreachable))) ;; NaN? (if (f64.ne (local.get $t/f64) (local.get $t/f64)) (then (call $raise-fl->exact-integer (local.get $v)) (unreachable))) ;; Check fixnum range (if (i32.or (f64.gt (local.get $t/f64) (f64.const 536870911.0)) (f64.lt (local.get $t/f64) (f64.const -536870912.0))) (then (call $raise-fl->exact-integer (local.get $v)) (unreachable))) (local.set $i32 (i32.trunc_f64_s (local.get $t/f64))) (ref.i31 (i32.shl (local.get $i32) (i32.const 1)))) #;(func $fl+ (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) ;; Type checks (if (i32.eqz (ref.test (ref $Flonum) (local.get $x))) (then (call $raise-argument-error:flonum-expected (local.get $x)))) (if (i32.eqz (ref.test (ref $Flonum) (local.get $y))) (then (call $raise-argument-error:flonum-expected (local.get $y)))) ;; Compute and box result (struct.new $Flonum (f64.add (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $x))) (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $y)))))) (func $raise-argument-error:flonum-expected (unreachable)) ,@(let () (define (flbinop name flbinop) `(func ,name (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) ;; Type checks (if (i32.eqz (ref.test (ref $Flonum) (local.get $x))) (then (call $raise-argument-error:flonum-expected (local.get $x)) (unreachable))) (if (i32.eqz (ref.test (ref $Flonum) (local.get $y))) (then (call $raise-argument-error:flonum-expected (local.get $y)) (unreachable))) ;; Compute and box result (struct.new $Flonum (i32.const 0) (,flbinop (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $x))) (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $y))))))) (map flbinop '($fl+ $fl- $fl*) '(f64.add f64.sub f64.mul))) (func $unsafe-fl/ (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (local $x/fl (ref $Flonum)) (local $y/fl (ref $Flonum)) (local $x/f64 f64) (local $y/f64 f64) ;; Validate via cast (will trap if not a Flonum - hence unsafe) (local.set $x/fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $y/fl (ref.cast (ref $Flonum) (local.get $y))) ;; Extract f64 values (local.set $x/f64 (struct.get $Flonum $v (local.get $x/fl))) (local.set $y/f64 (struct.get $Flonum $v (local.get $y/fl))) ;; Compute and box result (struct.new $Flonum (i32.const 0) ;; hash = 0 (f64.div (local.get $x/f64) (local.get $y/f64)))) (func $fl//checked (param $x (ref $Flonum)) (param $y (ref $Flonum)) (result (ref $Flonum)) (struct.new $Flonum (i32.const 0) ;; hash = 0 (f64.div (struct.get $Flonum $v (local.get $x)) (struct.get $Flonum $v (local.get $y))))) (func $raise-check-flonum (unreachable)) (func $fl/ (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) ; An (ref $Flonum) (local $x/fl (ref $Flonum)) (local $y/fl (ref $Flonum)) (local $x/f64 f64) (local $y/f64 f64) ;; Type checks (if (i32.eqz (ref.test (ref $Flonum) (local.get $x))) (then (call $raise-check-flonum (local.get $x)) (unreachable))) (if (i32.eqz (ref.test (ref $Flonum) (local.get $y))) (then (call $raise-check-flonum (local.get $y)) (unreachable))) ;; Cast and extract (local.set $x/fl (ref.cast (ref $Flonum) (local.get $x))) (local.set $y/fl (ref.cast (ref $Flonum) (local.get $y))) (local.set $x/f64 (struct.get $Flonum $v (local.get $x/fl))) (local.set $y/f64 (struct.get $Flonum $v (local.get $y/fl))) ;; Compute and box result (struct.new $Flonum (i32.const 0) (f64.div (local.get $x/f64) (local.get $y/f64)))) ;; flonum -> flonum ,@(let ([ops '((flabs (f64.abs (local.get $a/f64))) (flround (f64.nearest (local.get $a/f64))) (flfloor (f64.floor (local.get $a/f64))) (flceiling (f64.ceil (local.get $a/f64))) (fltruncate (f64.trunc (local.get $a/f64))) (flsingle (f64.promote_f32 (f32.demote_f64 (local.get $a/f64)))) (flsin (call $js-math-sin (local.get $a/f64))) (flcos (call $js-math-cos (local.get $a/f64))) (fltan (call $js-math-tan (local.get $a/f64))) (flasin (call $js-math-asin (local.get $a/f64))) (flacos (call $js-math-acos (local.get $a/f64))) (flatan (call $js-math-atan (local.get $a/f64))) (flsinh (call $js-math-sinh (local.get $a/f64))) (flcosh (call $js-math-cosh (local.get $a/f64))) (fltanh (call $js-math-tanh (local.get $a/f64))) (flasinh (call $js-math-asinh (local.get $a/f64))) (flacosh (call $js-math-acosh (local.get $a/f64))) (flatanh (call $js-math-atanh (local.get $a/f64))) (fllog (call $js-math-log (local.get $a/f64))) (flexp (call $js-math-exp (local.get $a/f64))) (flsqrt (call $js-math-sqrt (local.get $a/f64))))]) (append (for/list ([p ops]) (define name (car p)) (define expr (cadr p)) `(func ,(string->symbol (format "$~a" name)) (type $Prim1) (param $a (ref eq)) (result (ref eq)) (local $a/fl (ref $Flonum)) (local $a/f64 f64) (if (i32.eqz (ref.test (ref $Flonum) (local.get $a))) (then (call $raise-argument-error:flonum-expected (local.get $a)) (unreachable))) (local.set $a/fl (ref.cast (ref $Flonum) (local.get $a))) (local.set $a/f64 (struct.get $Flonum $v (local.get $a/fl))) (struct.new $Flonum (i32.const 0) ,expr))) (for/list ([p ops]) (define name (car p)) (define expr (cadr p)) `(func ,(string->symbol (format "$unsafe-~a" name)) (type $Prim1) (param $a (ref eq)) (result (ref eq)) (local $a/fl (ref $Flonum)) (local $a/f64 f64) (local.set $a/fl (ref.cast (ref $Flonum) (local.get $a))) (local.set $a/f64 (struct.get $Flonum $v (local.get $a/fl))) (struct.new $Flonum (i32.const 0) ,expr))))) ;; flonum flonum -> flonum ,@(let ([ops '((flmin/2 (f64.min (local.get $a/f64) (local.get $b/f64))) (flmax/2 (f64.max (local.get $a/f64) (local.get $b/f64))) (flexpt (call $pow-special (local.get $a/f64) (local.get $b/f64))))]) (append (for/list ([p ops]) (define name (car p)) (define expr (cadr p)) `(func ,(string->symbol (format "$~a" name)) (type $Prim2) (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (local $a/fl (ref $Flonum)) (local $b/fl (ref $Flonum)) (local $a/f64 f64) (local $b/f64 f64) (if (i32.eqz (ref.test (ref $Flonum) (local.get $a))) (then (call $raise-argument-error:flonum-expected (local.get $a)) (unreachable))) (if (i32.eqz (ref.test (ref $Flonum) (local.get $b))) (then (call $raise-argument-error:flonum-expected (local.get $b)) (unreachable))) (local.set $a/fl (ref.cast (ref $Flonum) (local.get $a))) (local.set $b/fl (ref.cast (ref $Flonum) (local.get $b))) (local.set $a/f64 (struct.get $Flonum $v (local.get $a/fl))) (local.set $b/f64 (struct.get $Flonum $v (local.get $b/fl))) (struct.new $Flonum (i32.const 0) ,expr))) (for/list ([p ops]) (define name (car p)) (define expr (cadr p)) `(func ,(string->symbol (format "$unsafe-~a" name)) (type $Prim2) (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (local $a/fl (ref $Flonum)) (local $b/fl (ref $Flonum)) (local $a/f64 f64) (local $b/f64 f64) (local.set $a/fl (ref.cast (ref $Flonum) (local.get $a))) (local.set $b/fl (ref.cast (ref $Flonum) (local.get $b))) (local.set $a/f64 (struct.get $Flonum $v (local.get $a/fl))) (local.set $b/f64 (struct.get $Flonum $v (local.get $b/fl))) (struct.new $Flonum (i32.const 0) ,expr))))) ;; variadic flonum min/max built on binary helpers ,@(let () (define (fl-min/max name cmp unsafe?) `(func ,name (param $x0 (ref eq)) (param $xs (ref eq)) (result (ref eq)) (local $node (ref $Pair)) (local $fx (ref eq)) (local $best (ref eq)) ,@(if unsafe? '() `((if (i32.eqz (ref.test (ref $Flonum) (local.get $x0))) (then (call $raise-argument-error:flonum-expected (local.get $x0)) (unreachable))))) (local.set $best (local.get $x0)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $fx (struct.get $Pair $a (local.get $node))) ,@(if unsafe? '() `((if (i32.eqz (ref.test (ref $Flonum) (local.get $fx))) (then (call $raise-argument-error:flonum-expected (local.get $fx)) (unreachable))))) (local.set $best (call ,cmp (local.get $best) (local.get $fx))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $best))) (append (for/list ([name '( $flmin $flmax )] [cmp '( $flmin/2 $flmax/2)]) (fl-min/max name cmp #f)) (for/list ([name '( $unsafe-flmin $unsafe-flmax )] [cmp '( $unsafe-flmin/2 $unsafe-flmax/2)]) (fl-min/max name cmp #t)))) ,@(let () (define (fl-cmp flname flcmp) `(func ,flname (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) ;; Type checks (if (i32.eqz (ref.test (ref $Flonum) (local.get $x))) (then (call $raise-argument-error:flonum-expected (local.get $x)) (unreachable))) (if (i32.eqz (ref.test (ref $Flonum) (local.get $y))) (then (call $raise-argument-error:flonum-expected (local.get $y)) (unreachable))) ;; Compute and box result (if (result (ref eq)) (,flcmp (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $x))) (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $y)))) (then (global.get $true)) (else (global.get $false))))) (map fl-cmp '($fl= $fl< $fl> $fl<= $fl>=) '(f64.eq f64.lt f64.gt f64.le f64.ge))) (func $flrandom (type $Prim01) ; one optional argument ;; (flrandom [rand-gen pseudo-random-generator?]) -> flonum in (0,1) ;; rand-gen : pseudo-random-generator? (optional, default: uses shared generator) ;; The rand-gen argument is currently ignored. (param $rg (ref eq)) (result (ref eq)) (local $r i32) (local.set $r (call $random-u32)) (struct.new $Flonum (i32.const 0) (f64.div (f64.add (f64.convert_i32_u (local.get $r)) (f64.const 1)) (f64.const 4294967298.0)))) (func $unsafe-flrandom (type $Prim01) ;; Unsafe variant, same behaviour as flrandom. ;; rand-gen : pseudo-random-generator? (optional, default ignored) (param $rg (ref eq)) (result (ref eq)) (local $r i32) (local.set $r (call $random-u32)) (struct.new $Flonum (i32.const 0) (f64.div (f64.add (f64.convert_i32_u (local.get $r)) (f64.const 1)) (f64.const 4294967298.0)))) ;; NOTE: Limited to results that fit in the fixnum range until bignums land. (func $flbit-field (type $Prim3) (param $a (ref eq)) ;; [a] flonum? (param $start (ref eq)) ;; [start] exact-nonnegative-integer? (param $end (ref eq)) ;; [end] exact-nonnegative-integer? (result (ref eq)) (local $fl (ref $Flonum)) (local $bits i64) (local $start/tag i32) (local $end/tag i32) (local $start/i i32) (local $end/i i32) (local $width i32) (local $mask i64) (local $shifted i64) (local $result64 i64) (local $result/i32 i32) ;; Validate flonum argument (if (i32.eqz (ref.test (ref $Flonum) (local.get $a))) (then (call $raise-argument-error:flonum-expected (local.get $a)) (unreachable))) (local.set $fl (ref.cast (ref $Flonum) (local.get $a))) (local.set $bits (i64.reinterpret_f64 (struct.get $Flonum $v (local.get $fl)))) ;; Validate start/end as fixnums (if (i32.eqz (call $fx?/i32 (local.get $start))) (then (call $raise-check-fixnum (local.get $start)) (unreachable))) (if (i32.eqz (call $fx?/i32 (local.get $end))) (then (call $raise-check-fixnum (local.get $end)) (unreachable))) ;; Extract integer values (local.set $start/tag (i31.get_s (ref.cast (ref i31) (local.get $start)))) (local.set $end/tag (i31.get_s (ref.cast (ref i31) (local.get $end)))) (local.set $start/i (i32.shr_s (local.get $start/tag) (i32.const 1))) (local.set $end/i (i32.shr_s (local.get $end/tag) (i32.const 1))) ;; Range checks: 0 <= start <= end <= 64 (if (i32.lt_s (local.get $start/i) (i32.const 0)) (then (call $raise-argument-error (local.get $start)) (unreachable))) (if (i32.lt_s (local.get $end/i) (i32.const 0)) (then (call $raise-argument-error (local.get $end)) (unreachable))) (if (i32.gt_u (local.get $start/i) (i32.const 64)) (then (call $raise-argument-error (local.get $start)) (unreachable))) (if (i32.gt_u (local.get $end/i) (i32.const 64)) (then (call $raise-argument-error (local.get $end)) (unreachable))) (if (i32.lt_s (local.get $end/i) (local.get $start/i)) (then (call $raise-argument-error (local.get $end)) (unreachable))) ;; Compute mask for requested width (local.set $width (i32.sub (local.get $end/i) (local.get $start/i))) (local.set $mask (if (result i64) (i32.ge_u (local.get $width) (i32.const 64)) (then (i64.const -1)) (else (i64.sub (i64.shl (i64.const 1) (i64.extend_i32_u (local.get $width))) (i64.const 1))))) ;; Shift and mask the raw bits (local.set $shifted (i64.shr_u (local.get $bits) (i64.extend_i32_u (local.get $start/i)))) (local.set $result64 (i64.and (local.get $shifted) (local.get $mask))) ;; Enforce fixnum-sized result (no bignum support yet) (if (i64.gt_u (local.get $result64) (i64.const 536870911)) (then (call $raise-argument-error (local.get $a)) (unreachable))) (local.set $result/i32 (i32.wrap_i64 (local.get $result64))) (ref.i31 (i32.shl (local.get $result/i32) (i32.const 1)))) ;;; ;;; Number / String conversions ;;; ;;; Helpers for $number->string (func $raise-number->string-bad-input (unreachable)) (func $raise-number->string-bad-radix (unreachable)) (func $number->string:check-radix (param $radix i32) (result i32) ; number->string accepts 2, 8, 10, or 16 as radix (if (result i32) (i32.or (i32.eq (local.get $radix) (i32.const 2)) (i32.or (i32.eq (local.get $radix) (i32.const 8)) (i32.or (i32.eq (local.get $radix) (i32.const 10)) (i32.eq (local.get $radix) (i32.const 16))))) (then (i32.const 1)) (else (i32.const 0)))) (func $number->string:max-length (param $radix i32) (result i32) ;; Returns the maximum length of a fixnum when converted to a string ;; in the given radix, including the sign character. (if (result i32) (i32.eq (local.get $radix) (i32.const 2)) (then (i32.const 30)) ;; binary: 29 bits + sign (else (if (result i32) (i32.eq (local.get $radix) (i32.const 8)) (then (i32.const 12)) ;; octal: 11 digits + sign (else (if (result i32) (i32.eq (local.get $radix) (i32.const 10)) (then (i32.const 11)) ;; decimal: 10 digits + sign (else (if (result i32) (i32.eq (local.get $radix) (i32.const 16)) (then (i32.const 9)) ;; hex: 8 digits + sign (else (i32.const 0)) ;; invalid radix, caller should check )))))))) (func $number->string:convert (param $n i32) (param $radix i32) (param $max-len i32) (result (ref $String)) (local $buf (ref $I32Array)) (local $i i32) (local $tmp i32) (local $neg i32) (local $abs i32) (local $out (ref $I32Array)) (local $len i32) ;; Special case for 0 (if (i32.eqz (local.get $n)) (then (return (call $i32->string (local.get $n))))) ;; Step 1: Determine sign and absolute value (local.set $neg (i32.lt_s (local.get $n) (i32.const 0))) (local.set $abs (if (result i32) (i32.ge_s (local.get $n) (i32.const 0)) (then (local.get $n)) (else (i32.sub (i32.const 0) (local.get $n))))) ;; Step 2: Create buffer of max-len, and fill from right to left (local.set $buf (array.new_default $I32Array (local.get $max-len))) (local.set $i (local.get $max-len)) (block $done (loop $digit-loop (br_if $done (i32.eqz (local.get $abs))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (local.set $tmp (i32.rem_u (local.get $abs) (local.get $radix))) (array.set $I32Array (local.get $buf) (local.get $i) (select (i32.add (local.get $tmp) (i32.const 48)) ;; '0'-'9' (i32.add (local.get $tmp) (i32.const 87)) ;; 'a'-'f' (i32.lt_u (local.get $tmp) (i32.const 10)))) (local.set $abs (i32.div_u (local.get $abs) (local.get $radix))) (br $digit-loop))) ;; Step 3: Add minus sign if negative (if (local.get $neg) (then (local.set $i (i32.sub (local.get $i) (i32.const 1))) (array.set $I32Array (local.get $buf) (local.get $i) (i32.const 45)))) ;; '-' ;; Step 4: Slice from $i to $max-len using array.copy (local.set $len (i32.sub (local.get $max-len) (local.get $i))) (local.set $out (array.new_default $I32Array (local.get $len))) (array.copy $I32Array $I32Array (local.get $out) (i32.const 0) (local.get $buf) (local.get $i) (local.get $len)) ;; Step 5: Wrap into a String object (struct.new $String (i32.const 0) ;; hash = 0 (i32.const 1) ;; immutable = true (local.get $out))) (func $number->string (type $Prim12) (param $z (ref eq)) (param $radix-raw (ref eq)) ;; optional: 10 or #f (result (ref eq)) ;; An (ref $String) (local $radix i32) (local $n i32) (local $max i32) (local $i31 (ref i31)) ;; Treat missing radix as #f to reuse existing defaults. (if (ref.eq (local.get $radix-raw) (global.get $missing)) (then (local.set $radix-raw (global.get $false)))) ;; Step 1: Check if $z is a fixnum or flonum (if (ref.test (ref i31) (local.get $z)) (then (local.set $n (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $z))) (i32.const 1)))) (else (if (ref.test (ref $Flonum) (local.get $z)) (then ;; Handle optional radix for flonum (must be 10 or #f) (if (ref.eq (local.get $radix-raw) (global.get $false)) (then (return (call $flonum->string (ref.cast (ref $Flonum) (local.get $z))))) (else (if (ref.test (ref i31) (local.get $radix-raw)) (then (local.set $radix (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $radix-raw))) (i32.const 1))) (if (i32.eq (local.get $radix) (i32.const 10)) (then (return (call $flonum->string (ref.cast (ref $Flonum) (local.get $z))))) (else (call $raise-number->string-bad-radix)))) (else (call $raise-number->string-bad-radix)))))) (else (call $raise-number->string-bad-input))))) ;; Step 2: Handle radix for fixnums (if (ref.eq (local.get $radix-raw) (global.get $false)) (then (local.set $radix (i32.const 10))) ;; default = 10 (else (if (ref.test (ref i31) (local.get $radix-raw)) (then (local.set $radix (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $radix-raw))) (i32.const 1)))) (else (call $raise-number->string-bad-radix))))) ;; Step 3: Validate the radix (if (i32.eqz (call $number->string:check-radix (local.get $radix))) (then (call $raise-number->string-bad-radix))) ;; Step 4: Get max length for this radix (local.set $max (call $number->string:max-length (local.get $radix))) ;; Step 5: Convert and return (call $number->string:convert (local.get $n) (local.get $radix) (local.get $max))) (func $raise-string->number:bad-argument (unreachable)) (func $raise-string->number:bad-radix (unreachable)) ;; string->number:parse-fractional-part : string? i32 -> f64 i32 ;; Parse decimal digits into an inexact fractional value. (func $string->number:parse-fractional-part (param $s (ref $String)) (param $i i32) (result f64 i32) (local $arr (ref $I32Array)) (local $len i32) (local $cp i32) (local $digit i32) (local $start i32) (local $frac f64) (local $scale f64) ;; Cache the source array and remember where the fractional scan began. (local.set $arr (struct.get $String $codepoints (local.get $s))) (local.set $len (call $i32array-length (local.get $arr))) (local.set $start (local.get $i)) ;; Build the fraction from left to right: ;; 0.1 * digit1 + 0.01 * digit2 + ... (local.set $frac (f64.const 0.0)) (local.set $scale (f64.const 0.1)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $cp (call $i32array-ref (local.get $arr) (local.get $i))) ;; Stop immediately if the next character is not a decimal digit. (if (i32.or (i32.lt_u (local.get $cp) (i32.const 48)) (i32.gt_u (local.get $cp) (i32.const 57))) (then (return (f64.const 0.0) (i32.const 0))) (else (nop))) (local.set $digit (i32.sub (local.get $cp) (i32.const 48))) ;; Add the next digit at the current decimal place. (local.set $frac (f64.add (local.get $frac) (f64.mul (f64.convert_i32_s (local.get $digit)) (local.get $scale)))) (local.set $scale (f64.mul (local.get $scale) (f64.const 0.1))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Return the parsed fraction and the number of consumed characters. (return (local.get $frac) (i32.sub (local.get $i) (local.get $start)))) (func $string->number:parse-integer (param $s (ref $String)) (param $i i32) (param $radix i32) ;; returns: ;; 1. fixnum or #f ;; 2. number of parsed characters or 0 (result (ref eq) i32) (local $arr (ref $I32Array)) (local $len i32) (local $cp i32) (local $digit i32) (local $acc i32) (local $start i32) (local $limit-div i32) (local $limit-rem i32) (local.set $arr (struct.get $String $codepoints (local.get $s))) (local.set $len (call $i32array-length (local.get $arr))) (local.set $start (local.get $i)) (local.set $acc (i32.const 0)) ;; largest non-negative 30-bit fixnum payload ;; = 2^29 - 1 = 536870911 (local.set $limit-div (i32.div_u (i32.const 536870911) (local.get $radix))) (local.set $limit-rem (i32.rem_u (i32.const 536870911) (local.get $radix))) (block $overflow (result (ref eq) i32) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $cp (call $i32array-ref (local.get $arr) (local.get $i))) ;; Try '0'..'9' (local.set $digit (i32.sub (local.get $cp) (i32.const 48))) (if (i32.lt_u (local.get $digit) (i32.const 10)) (then (nop)) (else ;; Try 'a'..'z' (local.set $digit (i32.sub (local.get $cp) (i32.const 97))) (if (i32.lt_u (local.get $digit) (i32.const 26)) (then (local.set $digit (i32.add (local.get $digit) (i32.const 10)))) (else ;; Try 'A'..'Z' (local.set $digit (i32.sub (local.get $cp) (i32.const 65))) (if (i32.lt_u (local.get $digit) (i32.const 26)) (then (local.set $digit (i32.add (local.get $digit) (i32.const 10)))) (else (br $done))))))) (if (i32.ge_u (local.get $digit) (local.get $radix)) (then (br $done)) (else (nop))) ;; Overflow check for: ;; acc * radix + digit <= 536870911 (if (i32.or (i32.gt_u (local.get $acc) (local.get $limit-div)) (i32.and (i32.eq (local.get $acc) (local.get $limit-div)) (i32.gt_u (local.get $digit) (local.get $limit-rem)))) (then (global.get $false) (i32.const 0) (br $overflow)) (else (nop))) (local.set $acc (i32.add (i32.mul (local.get $acc) (local.get $radix)) (local.get $digit))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Normal return (if (result (ref eq) i32) (i32.eq (local.get $i) (local.get $start)) (then (global.get $false) (i32.const 0)) (else (ref.i31 (i32.shl (local.get $acc) (i32.const 1))) (i32.sub (local.get $i) (local.get $start)))))) (func $string->number (type $Prim15) (param $s-raw (ref eq)) (param $radix-raw (ref eq)) (param $convert-mode (ref eq)) (param $decimal-mode (ref eq)) (param $single-mode (ref eq)) (result (ref eq)) (local $s (ref $String)) (local $radix i32) (local $arr (ref $I32Array)) (local $len i32) (local $i i32) (local $neg i32) (local $cp i32) (local $int (ref eq)) (local $n i32) (local $has-int i32) (local $acc i32) (local $frac f64) (local $m i32) (local $res f64) ;; Validate string (if (ref.test (ref $String) (local.get $s-raw)) (then (nop)) (else (call $raise-string->number:bad-argument) (unreachable))) (local.set $s (ref.cast (ref $String) (local.get $s-raw))) ;; Radix (if (ref.eq (local.get $radix-raw) (global.get $missing)) (then (local.set $radix (i32.const 10))) (else (if (ref.test (ref i31) (local.get $radix-raw)) (then (local.set $radix (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $radix-raw))) (i32.const 1)))) (else (call $raise-string->number:bad-radix) (unreachable))))) (if (i32.or (i32.lt_u (local.get $radix) (i32.const 2)) (i32.gt_u (local.get $radix) (i32.const 16))) (then (call $raise-string->number:bad-radix) (unreachable)) (else (nop))) ;; String data (local.set $arr (struct.get $String $codepoints (local.get $s))) (local.set $len (call $i32array-length (local.get $arr))) (if (i32.eqz (local.get $len)) (then (return (global.get $false))) (else (nop))) ;; Sign (local.set $i (i32.const 0)) (local.set $neg (i32.const 0)) (local.set $cp (call $i32array-ref (local.get $arr) (i32.const 0))) (if (i32.eq (local.get $cp) (i32.const 45)) (then (local.set $neg (i32.const 1)) (local.set $i (i32.const 1))) (else (if (i32.eq (local.get $cp) (i32.const 43)) (then (local.set $i (i32.const 1))) (else (nop))))) ;; Cannot be only sign (if (i32.eq (local.get $i) (local.get $len)) (then (return (global.get $false))) (else (nop))) ;; Parse integer (call $string->number:parse-integer (local.get $s) (local.get $i) (local.get $radix)) (local.set $n) (local.set $int) (if (ref.eq (local.get $int) (global.get $false)) (then (local.set $has-int (i32.const 0))) (else (local.set $has-int (i32.const 1)))) ;; Handle ".1" case (if (ref.eq (local.get $int) (global.get $false)) (then (if (i32.eq (local.get $i) (local.get $len)) (then (return (global.get $false))) (else (local.set $cp (call $i32array-ref (local.get $arr) (local.get $i))) (if (i32.eq (local.get $cp) (i32.const 46)) (then (local.set $int (ref.i31 (i32.const 0))) (local.set $n (i32.const 0))) (else (return (global.get $false))))))) (else (nop))) (local.set $i (i32.add (local.get $i) (local.get $n))) ;; No fractional part → return fixnum (if (i32.eq (local.get $i) (local.get $len)) (then (local.set $acc (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $int))) (i32.const 1))) (if (local.get $neg) (then (local.set $acc (i32.sub (i32.const 0) (local.get $acc)))) (else (nop))) (return (ref.i31 (i32.shl (local.get $acc) (i32.const 1))))) (else (nop))) ;; Must be '.' (local.set $cp (call $i32array-ref (local.get $arr) (local.get $i))) (if (i32.ne (local.get $cp) (i32.const 46)) (then (return (global.get $false))) (else (nop))) ;; Only decimal radix (if (i32.ne (local.get $radix) (i32.const 10)) (then (return (global.get $false))) (else (nop))) ;; Convert integer part (local.set $acc (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $int))) (i32.const 1))) ;; Skip '.' (local.set $i (i32.add (local.get $i) (i32.const 1))) ;; Parse fractional digits (call $string->number:parse-fractional-part (local.get $s) (local.get $i)) (local.set $m) (local.set $frac) ;; A bare decimal point is not a number. (if (i32.and (i32.eqz (local.get $has-int)) (i32.eqz (local.get $m))) (then (return (global.get $false))) (else (nop))) ;; Must consume entire string (local.set $i (i32.add (local.get $i) (local.get $m))) (if (i32.ne (local.get $i) (local.get $len)) (then (return (global.get $false))) (else (nop))) (local.set $res (f64.add (f64.convert_i32_s (local.get $acc)) (local.get $frac))) (if (local.get $neg) (then (local.set $res (f64.neg (local.get $res)))) (else (nop))) (struct.new $Flonum (i32.const 0) (local.get $res))) (func $floating-point-bytes->real (type $Prim4) (param $bstr (ref eq)) ; bytes? (param $big? (ref eq)) ; any, default to (system-big-endian?) which is #f (param $start (ref eq)) ; exact-non-negative-integer, defaults to 0 (param $end (ref eq)) ; exact-nonnegative-integer?, defaults to (bytes-length bstr) (result (ref eq)) (local $bs (ref null $Bytes)) (local $arr (ref $I8Array)) (local $from i32) (local $to i32) (local $len i32) (local $nbytes i32) (local $big-i32 i32) (local $i i32) (local $idx i32) (local $byte i32) (local $val i64) ;; --- Validate byte string --- (if (ref.test (ref $Bytes) (local.get $bstr)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr)))) (else (call $raise-check-bytes (local.get $bstr)) (unreachable))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (call $i8array-length (local.get $arr))) ;; --- Decode start --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $from (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $from (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.eqz (i32.and (local.get $from) (i32.const 1))) (then (local.set $from (i32.shr_u (local.get $from) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable))))) ;; --- Decode end --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $to (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $to (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.eqz (i32.and (local.get $to) (i32.const 1))) (then (local.set $to (i32.shr_u (local.get $to) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; --- Bounds check --- (if (i32.gt_u (local.get $from) (local.get $to)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $from) (local.get $to)) (unreachable))) (if (i32.gt_u (local.get $to) (local.get $len)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $from) (local.get $to)) (unreachable))) (local.set $nbytes (i32.sub (local.get $to) (local.get $from))) (if (i32.and (i32.ne (local.get $nbytes) (i32.const 4)) (i32.ne (local.get $nbytes) (i32.const 8))) (then (call $raise-argument-error (local.get $bstr)) (unreachable))) ;; --- Decode big-endian? --- (if (ref.eq (local.get $big?) (global.get $missing)) (then (local.set $big-i32 (i32.const 0))) (else (if (ref.eq (local.get $big?) (global.get $false)) (then (local.set $big-i32 (i32.const 0))) (else (if (ref.eq (local.get $big?) (global.get $true)) (then (local.set $big-i32 (i32.const 1))) (else (call $raise-argument-error (local.get $big?)) (unreachable))))))) ;; --- Accumulate bytes into i64 --- (local.set $val (i64.const 0)) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $nbytes))) (if (i32.eqz (local.get $big-i32)) (then (local.set $idx (i32.add (local.get $from) (local.get $i)))) (else (local.set $idx (i32.add (local.get $from) (i32.sub (i32.add (local.get $nbytes) (i32.const -1)) (local.get $i)))))) (local.set $byte (call $i8array-ref (local.get $arr) (local.get $idx))) (local.set $val (i64.or (local.get $val) (i64.shl (i64.extend_i32_u (local.get $byte)) (i64.extend_i32_u (i32.shl (local.get $i) (i32.const 3)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; --- Produce result --- (if (i32.eq (local.get $nbytes) (i32.const 4)) (then (return (struct.new $Flonum (i32.const 0) (f64.promote_f32 (f32.reinterpret_i32 (i32.wrap_i64 (local.get $val))))))) (else (return (struct.new $Flonum (i32.const 0) (f64.reinterpret_i64 (local.get $val)))))) (unreachable)) ;; This is a very naive implementation. ;; It is used as a place holder for now. (func $f64->string (param $x f64) (result (ref $String)) (local $abs f64) (local $int i32) (local $frac-scaled f64) (local $frac i32) (local $neg i32) (local $s-int (ref $String)) (local $s-frac (ref $String)) (local $dot (ref $String)) (local $minus (ref $String)) (local $nan (ref $String)) (local $pinf (ref $String)) (local $ninf (ref $String)) ;; --- Construct needed string segments --- (local.set $dot (call $codepoint->string (i32.const 46))) ;; "." (local.set $minus (call $codepoint->string (i32.const 45))) ;; "-" ;; "+nan.0" (local.set $nan (call $i32array->string (array.new_fixed $I32Array 6 (i32.const 43) ;; '+' (i32.const 110) ;; 'n' (i32.const 97) ;; 'a' (i32.const 110) ;; 'n' (i32.const 46) ;; '.' (i32.const 48) ;; '0' ))) ;; "+inf.0" (local.set $pinf (call $i32array->string (array.new_fixed $I32Array 6 (i32.const 43) ;; '+' (i32.const 105) ;; 'i' (i32.const 110) ;; 'n' (i32.const 102) ;; 'f' (i32.const 46) ;; '.' (i32.const 48) ;; '0' ))) ;; "-inf.0" (local.set $ninf (call $i32array->string (array.new_fixed $I32Array 6 (i32.const 45) ;; '-' (i32.const 105) ;; 'i' (i32.const 110) ;; 'n' (i32.const 102) ;; 'f' (i32.const 46) ;; '.' (i32.const 48) ;; '0' ))) ;; --- Handle special cases --- ;; NaN: x != x (if (f64.ne (local.get $x) (local.get $x)) (then (return (local.get $nan)))) ;; +inf.0 (if (f64.eq (local.get $x) (f64.const inf)) (then (return (local.get $pinf)))) ;; -inf.0 (if (f64.eq (local.get $x) (f64.const -inf)) (then (return (local.get $ninf)))) ;; --- Absolute value and sign --- (local.set $abs (f64.abs (local.get $x))) (local.set $neg (f64.lt (local.get $x) (f64.const 0))) ;; --- Integer part --- (local.set $int (i32.trunc_sat_f64_s (local.get $abs))) ; todo ? okay to use _sat_ ? ;; Scientific notation fallback if too large (if (i32.ge_u (local.get $int) (i32.const 1000000)) (then (return (call $f64->string/scientific (local.get $x))))) ;; --- Fractional part --- (local.set $frac-scaled (f64.mul (f64.sub (local.get $abs) (f64.convert_i32_s (local.get $int))) (f64.const 1000000.0))) (local.set $frac (i32.trunc_sat_f64_s (local.get $frac-scaled))) ; todo ? ;; --- Convert parts to strings --- (local.set $s-int (call $i32->string (local.get $int))) (local.set $s-frac (call $i32->string/pad6 (local.get $frac))) ;; --- Join int . frac --- (local.set $s-int (call $string-append/2 (call $string-append/2 (local.get $s-int) (local.get $dot)) (local.get $s-frac))) ;; --- Add minus sign if needed --- (if (result (ref $String)) (local.get $neg) (then (call $string-append/2 (local.get $minus) (local.get $s-int))) (else (local.get $s-int)))) (func $i32->string/pad6 ; Converts a non-negative integer to a decimal string padded with ; leading zeros to exactly 6 digits. (param $n i32) (result (ref $String)) (local $g (ref $I32GrowableArray)) (local $digits (ref $I32GrowableArray)) (local $digit i32) (local $count i32) ;; Make a growable array to collect digits (local.set $digits (call $make-i32growable-array (i32.const 6))) ;; Extract digits in reverse order (least significant first) (block $done (loop $loop ;; Append digit (local.set $digit (i32.rem_u (local.get $n) (i32.const 10))) (call $i32growable-array-add! (local.get $digits) (i32.add (local.get $digit) (i32.const 48))) ;; Prepare next (local.set $n (i32.div_u (local.get $n) (i32.const 10))) (br_if $loop (i32.ne (local.get $n) (i32.const 0))))) ;; Pad with '0's until we have at least 6 digits (local.set $count (call $i32growable-array-count (local.get $digits))) (block $pad (loop $pad-loop (br_if $pad (i32.ge_u (local.get $count) (i32.const 6))) (call $i32growable-array-add! (local.get $digits) (i32.const 48)) ;; '0' (local.set $count (i32.add (local.get $count) (i32.const 1))) (br $pad-loop))) ;; Create final output array and reverse digits (local.set $g (call $make-i32growable-array (i32.const 6))) (local.set $count (call $i32growable-array-count (local.get $digits))) (block $done (loop $rev (br_if $done (i32.eqz (local.get $count))) (local.set $count (i32.sub (local.get $count) (i32.const 1))) (call $i32growable-array-add! (local.get $g) (call $i32growable-array-ref (local.get $digits) (local.get $count))) (br $rev))) ;; Return as string (call $i32growable-array->string (local.get $g))) (func $f64->string/scientific ; TODO: This is a naive version. ; This function converts a 64-bit floating-point number (f64) into a ; human-readable scientific notation string like "1.234567e+02". ; It handles special cases (NaN, +inf.0, -inf.0), computes the normalized ; mantissa and exponent, formats the number to 6 decimal places, ; and constructs the result as a Racket-style string. (param $x f64) (result (ref $String)) (local $abs f64) (local $neg i32) (local $exp i32) (local $digit i32) (local $frac i32) (local $mant f64) (local $g (ref $I32GrowableArray)) (local $exp-str (ref $String)) (local $exp-cps (ref $I32Array)) (local $count i32) (local $i i32) ;; --- Special cases --- (if (f64.ne (local.get $x) (local.get $x)) ;; NaN (then (return (call $i32array->string (array.new_fixed $I32Array 5 ;; "nan.0" (i32.const 110) (i32.const 97) (i32.const 110) (i32.const 46) (i32.const 48)))))) (if (f64.eq (local.get $x) (f64.const inf)) ;; +inf.0 (then (return (call $i32array->string (array.new_fixed $I32Array 6 ;; +inf.0 (i32.const 43) (i32.const 105) (i32.const 110) (i32.const 102) (i32.const 46) (i32.const 48)))))) (if (f64.eq (local.get $x) (f64.const -inf)) (then (return (call $i32array->string (array.new_fixed $I32Array 6 ;; -inf.0 (i32.const 45) (i32.const 105) (i32.const 110) (i32.const 102) (i32.const 46) (i32.const 48)))))) ;; --- Normalize --- (local.set $neg (f64.lt (local.get $x) (f64.const 0))) (local.set $abs (f64.abs (local.get $x))) (local.set $exp (i32.const 0)) (block $down (loop $norm-down (br_if $down (f64.ge (local.get $abs) (f64.const 1))) (local.set $abs (f64.mul (local.get $abs) (f64.const 10))) (local.set $exp (i32.sub (local.get $exp) (i32.const 1))) (br $norm-down))) (block $up (loop $norm-up (br_if $up (f64.lt (local.get $abs) (f64.const 10))) (local.set $abs (f64.div (local.get $abs) (f64.const 10))) (local.set $exp (i32.add (local.get $exp) (i32.const 1))) (br $norm-up))) ;; --- Format mantissa --- (local.set $mant (local.get $abs)) (local.set $g (call $make-i32growable-array (i32.const 16))) ;; Optional sign (if (local.get $neg) (then (call $i32growable-array-add! (local.get $g) (i32.const 45)))) ;; '-' ;; Leading digit (local.set $digit (i32.trunc_f64_u (local.get $mant))) (call $i32growable-array-add! (local.get $g) (i32.add (i32.const 48) (local.get $digit))) ;; Decimal point (call $i32growable-array-add! (local.get $g) (i32.const 46)) ;; '.' ;; Fractional digits (local.set $mant (f64.sub (local.get $mant) (f64.convert_i32_u (local.get $digit)))) (local.set $frac (i32.const 0)) (block $exit (loop $frac-loop (br_if $exit (i32.ge_u (local.get $frac) (i32.const 6))) (local.set $mant (f64.mul (local.get $mant) (f64.const 10))) (local.set $digit (i32.trunc_f64_u (local.get $mant))) (call $i32growable-array-add! (local.get $g) (i32.add (i32.const 48) (local.get $digit))) (local.set $mant (f64.sub (local.get $mant) (f64.convert_i32_u (local.get $digit)))) (local.set $frac (i32.add (local.get $frac) (i32.const 1))) (br $frac-loop))) ;; 'e' (call $i32growable-array-add! (local.get $g) (i32.const 101)) ;; 'e' ;; Exponent (local.set $exp-str (ref.cast (ref $String) (call $number->string (ref.i31 (i32.shl (local.get $exp) (i32.const 1))) ;; boxed fixnum (ref.i31 (i32.const 20))))) ;; fixnum 10 (radix) (local.set $exp-cps (struct.get $String $codepoints (local.get $exp-str))) ;; Append minus sign if negative (if (i32.lt_s (local.get $exp) (i32.const 0)) (then (call $i32growable-array-add! (local.get $g) (i32.const 45)))) ;; '-' ;; Append digits (local.set $count (array.len (local.get $exp-cps))) (local.set $i (i32.const 0)) (block $done (loop $append-loop (br_if $done (i32.ge_u (local.get $i) (local.get $count))) (call $i32growable-array-add! (local.get $g) (array.get $I32Array (local.get $exp-cps) (local.get $i))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $append-loop))) (call $i32growable-array->string (local.get $g))) (func $old:flonum->string (param $f (ref $Flonum)) (result (ref $String)) (local $s (ref $String)) (local $len i32) (local $ch i32) ;; Convert to decimal string and trim trailing zeros (local.set $s (ref.cast (ref $String) (call $string-trim-right (call $f64->string (struct.get $Flonum $v (local.get $f))) ,(Imm #\0)))) (local.set $len (call $string-length/checked/i32 (local.get $s))) (local.set $ch (call $string-ref/checked/i32 (local.get $s) (i32.sub (local.get $len) (i32.const 1)))) (if (result (ref $String)) (i32.eq (local.get $ch) (i32.const 46)) ;; '.' (then (call $string-append/2 (local.get $s) (call $codepoint->string (i32.const 48)))) ;; '0' (else (local.get $s)))) (func $flonum->string (param $f (ref $Flonum)) (result (ref $String)) (ref.cast (ref $String) (call $linear-memory->string (call $js-flonum->string (struct.get $Flonum $v (local.get $f)))))) (func $i32->string (param $n i32) (result (ref $String)) (call $i32array->string (call $i32->codepoints (local.get $n)))) (func $arity-i32->string (param $arity i32) (result (ref $String)) (local $n i32) (if (result (ref $String)) (i32.ge_s (local.get $arity) (i32.const 0)) (then (call $i32->string (local.get $arity))) (else (local.set $n (i32.sub (i32.const -1) (local.get $arity))) (call $string-append/2 (global.get $string:arity-error:at-least) (call $i32->string (local.get $n))))) ) (func $i32->codepoints (param $n i32) (result (ref $I32Array)) (local $len i32) (local $tmp i32) (local $abs i32) (local $neg i32) (local $i i32) (local $d i32) (local $arr (ref $I32Array)) ;; Special case: 0 → [48] (if (i32.eqz (local.get $n)) (then (return (array.new_fixed $I32Array 1 (i32.const 48))))) ;; '0' ;; Check for negative (local.set $neg (i32.lt_s (local.get $n) (i32.const 0))) ;; Take absolute value safely (avoid overflow on min int) (local.set $abs (select (i32.sub (i32.const 0) (local.get $n)) ;; -n (local.get $n) (local.get $neg))) ;; Count digits (local.set $tmp (local.get $abs)) (local.set $len (i32.const 0)) (block $done-count (loop $count (br_if $done-count (i32.eqz (local.get $tmp))) (local.set $tmp (i32.div_u (local.get $tmp) (i32.const 10))) (local.set $len (i32.add (local.get $len) (i32.const 1))) (br $count))) ;; Add one more if negative (for '-') (if (local.get $neg) (then (local.set $len (i32.add (local.get $len) (i32.const 1))))) ;; Allocate array of codepoints (local.set $arr (array.new_default $I32Array (local.get $len))) ;; Fill from right to left (local.set $tmp (local.get $abs)) (local.set $i (i32.sub (local.get $len) (i32.const 1))) (block $fill-done (loop $fill (br_if $fill-done (i32.lt_s (local.get $i) (i32.const 0))) (local.set $d (i32.rem_u (local.get $tmp) (i32.const 10))) (array.set $I32Array (local.get $arr) (local.get $i) (i32.add (i32.const 48) (local.get $d))) ;; '0' + d (local.set $tmp (i32.div_u (local.get $tmp) (i32.const 10))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $fill))) ;; Add '-' if needed (if (local.get $neg) (then (array.set $I32Array (local.get $arr) (i32.const 0) (i32.const 45)))) ;; '-' (local.get $arr)) (func $format/display:flonum (param $val (ref $Flonum)) (result (ref $String)) (call $flonum->string (local.get $val))) ;;; ;;; - External host values ;;; (func $external? (type $Prim1) ,@(make-predicate-body '$External)) (func $external-null? (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $External) (local.get $v)) (then (if (result (ref eq)) (ref.is_null (struct.get $External $v (ref.cast (ref $External) (local.get $v)))) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $external-number->flonum (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $e (ref $External)) (local $raw externref) (local $f f64) ;; Check that $v is an $External (if (i32.eqz (ref.test (ref $External) (local.get $v))) (then (call $raise-argument-error (local.get $v)) (unreachable))) ;; Cast to $External (local.set $e (ref.cast (ref $External) (local.get $v))) ;; Extract underlying JS value (local.set $raw (struct.get $External $v (local.get $e))) ;; If null, return #f (if (ref.is_null (local.get $raw)) (then (return (global.get $false)))) ;; Non-null now: pass as (ref extern) directly (local.set $f (call $js-external-number->f64 (ref.as_non_null (local.get $raw)))) (struct.new $Flonum (i32.const 0) (local.get $f))) (func $external-string->string (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $e (ref $External)) (local $raw externref) (local $ptr i32) ;; Check that $v is an $External (if (i32.eqz (ref.test (ref $External) (local.get $v))) (then (call $raise-argument-error (local.get $v)) (unreachable))) ;; Cast to $External (local.set $e (ref.cast (ref $External) (local.get $v))) ;; Extract underlying JS value (local.set $raw (struct.get $External $v (local.get $e))) ;; If null, return #f (if (ref.is_null (local.get $raw)) (then (return (global.get $false)))) ;; Non-null now: pass as (ref extern) directly (local.set $ptr (call $js-external-string->string (ref.as_non_null (local.get $raw)))) (call $linear-memory->string (local.get $ptr))) ;;; ;;; 4.3 Byte Strings ;;; ;; https://docs.racket-lang.org/reference/bytestrings.html (func $raise-byte-out-of-range (param $x (ref eq)) (unreachable)) (func $raise-check-bytes (param $x (ref eq)) (unreachable)) (func $raise-check-byte (param $x (ref eq)) (unreachable)) ;; Out-of-range byte-string indexes must be catchable Racket errors, ;; not raw Wasm traps. (func $raise-bad-bytes-ref-index (param $x (ref eq)) (param $idx (ref eq)) (call $raise-argument-error1 (global.get $symbol:bytes-ref) (global.get $string:exact-nonnegative-integer?) (local.get $idx))) (func $raise-bad-bytes-set-index (param $x (ref eq)) (param $idx (ref eq)) (call $raise-argument-error1 (global.get $symbol:bytes-set!) (global.get $string:exact-nonnegative-integer?) (local.get $idx))) (func $raise-bad-bytes-range (param $x (ref eq)) (param $from i32) (param $to i32) (call $raise-argument-error1 (global.get $symbol:subbytes) (global.get $string:exact-nonnegative-integer?) (ref.i31 (i32.shl (local.get $from) (i32.const 1))))) (func $make-bytes (type $Prim12) (param $k-raw (ref eq)) ;; fixnum (param $b-raw (ref eq)) ;; optional byte (result (ref eq)) (local $len i32) (local $val i32) ;; Decode and check $k as fixnum (if (ref.test (ref i31) (local.get $k-raw)) (then (local.set $len (i31.get_u (ref.cast (ref i31) (local.get $k-raw)))) (if (i32.eqz (i32.and (local.get $len) (i32.const 1))) (then (local.set $len (i32.shr_u (local.get $len) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $k-raw)) (unreachable)))) (else (call $raise-check-fixnum (local.get $k-raw)) (unreachable))) ;; Handle optional byte argument (if (ref.eq (local.get $b-raw) (global.get $missing)) (then (local.set $val (i32.const 0))) (else (if (ref.test (ref i31) (local.get $b-raw)) (then (local.set $val (i31.get_u (ref.cast (ref i31) (local.get $b-raw)))) (if (i32.eqz (i32.and (local.get $val) (i32.const 1))) (then (local.set $val (i32.shr_u (local.get $val) (i32.const 1))) (if (i32.gt_u (local.get $val) (i32.const 255)) (then (call $raise-byte-out-of-range (local.get $b-raw)) (unreachable)))) (else (call $raise-check-fixnum (local.get $b-raw)) (unreachable)))) (else (call $raise-check-fixnum (local.get $b-raw)) (unreachable))))) ;; Construct mutable bytes object (struct.new $Bytes (i32.const 0) ;; hash (i32.const 0) ;; immutable = false (call $i8make-array (local.get $len) (local.get $val)))) (func $bytes (type $Prim>=0) ; handles both $Args and list rest arguments (param $args (ref eq)) (result (ref eq)) (local $as (ref $Args)) (local $len i32) (local $arr (ref $I8Array)) (local $i i32) (local $x (ref eq)) (local $v i32) (local $use-args? i32) (local $list (ref eq)) (local $node (ref eq)) (local $pair (ref $Pair)) ;; Initialize non-defaultable locals (local.set $as (array.new $Args (global.get $null) (i32.const 0))) (local.set $x (global.get $false)) ;; Determine whether we received an $Args array or a list of rest arguments. (local.set $use-args? (ref.test (ref $Args) (local.get $args))) (local.set $list (global.get $null)) (local.set $node (global.get $null)) (local.set $len (if (result i32) (local.get $use-args?) (then (local.set $as (ref.cast (ref $Args) (local.get $args))) (array.len (local.get $as))) (else (local.set $list (local.get $args)) (local.set $node (local.get $list)) (call $length/i32 (local.get $list))))) ;; Allocate mutable byte array (local.set $arr (call $i8make-array (local.get $len) (i32.const 0))) ;; Populate array from arguments or list elements (local.set $i (i32.const 0)) (block $done (if (i32.eqz (local.get $use-args?)) (then (loop $loop-list (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (if (ref.test (ref $Pair) (local.get $node)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $node))) (local.set $x (struct.get $Pair $a (local.get $pair))) (local.set $node (struct.get $Pair $d (local.get $pair)))) (else (call $raise-pair-expected (local.get $node)) (unreachable))) (if (ref.test (ref i31) (local.get $x)) (then (local.set $v (i31.get_u (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $v) (i32.const 1))) (then (local.set $v (i32.shr_u (local.get $v) (i32.const 1))) (if (i32.lt_u (local.get $v) (i32.const 256)) (then (call $i8array-set! (local.get $arr) (local.get $i) (local.get $v))) (else (call $raise-byte-out-of-range (local.get $x)) (unreachable)))) (else (call $raise-check-fixnum (local.get $x))(unreachable))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop-list))))) (else (loop $loop-args (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $x (array.get $Args (local.get $as) (local.get $i))) (if (ref.test (ref i31) (local.get $x)) (then (local.set $v (i31.get_u (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $v) (i32.const 1))) (then (local.set $v (i32.shr_u (local.get $v) (i32.const 1))) (if (i32.lt_u (local.get $v) (i32.const 256)) (then (call $i8array-set! (local.get $arr) (local.get $i) (local.get $v))) (else (call $raise-byte-out-of-range (local.get $x)) (unreachable)))) (else (call $raise-check-fixnum (local.get $x)) (unreachable)))) (else (call $raise-check-fixnum (local.get $x)) (unreachable))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop-args))))) ;; Wrap in Bytes struct and return (struct.new $Bytes (i32.const 0) (i32.const 0) (local.get $arr))) (func $bytes->immutable-bytes (type $Prim1) (param $b (ref eq)) (result (ref eq)) (local $bs (ref $Bytes)) (local.set $bs (ref.cast (ref $Bytes) (global.get $bytes:empty))) ;; 1. Check that b is a byte string (if (ref.test (ref $Bytes) (local.get $b)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $b)))) (else (call $raise-check-bytes (local.get $b)) (unreachable))) ;; 2. If already immutable, return it directly (if (result (ref eq)) (i32.eq (struct.get $Bytes $immutable (local.get $bs)) (i32.const 1)) (then (local.get $bs)) (else (struct.new $Bytes (struct.get $Bytes $hash (local.get $bs)) ;; inherit hash (i32.const 1) ;; immutable (call $i8array-copy (struct.get $Bytes $bs (local.get $bs)) (i32.const 0) (call $i8array-length (struct.get $Bytes $bs (local.get $bs)))))))) ;; real->floating-point-bytes ;; Convert a real number to its IEEE floating-point byte representation. ;; Only fixnums and flonums are supported for now. The start argument ;; defaults to 0 when omitted, and a provided destination must be a ;; mutable byte string of sufficient length. The big-endian? argument ;; defaults to #f. (func $real->floating-point-bytes (type $Prim25) (param $x (ref eq)) ; real? (param $size-raw (ref eq)) ; (or/c 4 8) (param $big-raw (ref eq)) ; any/c, defaults to (system-big-endian?) = #f (param $dest-raw (ref eq)) ; (and/c bytes? (not/c immutable?)) (param $start-raw (ref eq)) ; exact-nonnegative-integer? = 0 (result (ref eq)) (local $size i32) (local $big i32) (local $dest (ref $Bytes)) (local $arr (ref $I8Array)) (local $fl (ref $Flonum)) (local $val f64) (local $start i32) ;; --- Decode and validate size --- (if (i32.eqz (ref.test (ref i31) (local.get $size-raw))) (then (call $raise-check-fixnum (local.get $size-raw)) (unreachable))) (local.set $size (i31.get_u (ref.cast (ref i31) (local.get $size-raw)))) (if (i32.and (local.get $size) (i32.const 1)) (then (call $raise-check-fixnum (local.get $size-raw)) (unreachable))) (local.set $size (i32.shr_u (local.get $size) (i32.const 1))) (if (i32.and (i32.ne (local.get $size) (i32.const 4)) (i32.ne (local.get $size) (i32.const 8))) (then (call $raise-argument-error (local.get $size-raw)) (unreachable))) ;; --- Decode and validate start (exact-nonnegative-integer?, defaults to 0) --- (local.set $start (i32.const 0)) (if (ref.eq (local.get $start-raw) (global.get $missing)) (then) (else (if (i32.eqz (ref.test (ref i31) (local.get $start-raw))) (then (call $raise-check-fixnum (local.get $start-raw)) (unreachable))) (local.set $start (i31.get_s (ref.cast (ref i31) (local.get $start-raw)))) (if (i32.and (local.get $start) (i32.const 1)) (then (call $raise-check-fixnum (local.get $start-raw)) (unreachable))) (local.set $start (i32.shr_s (local.get $start) (i32.const 1))) (if (i32.lt_s (local.get $start) (i32.const 0)) (then (call $raise-argument-error (local.get $start-raw)) (unreachable))))) ;; --- Allocate or validate destination --- (if (ref.eq (local.get $dest-raw) (global.get $missing)) (then (local.set $dest-raw (call $make-bytes (local.get $size-raw) (global.get $missing))))) (if (i32.eqz (ref.test (ref $Bytes) (local.get $dest-raw))) (then (call $raise-argument-error (local.get $start-raw)) (unreachable))) (local.set $dest (ref.cast (ref $Bytes) (local.get $dest-raw))) (if (struct.get $Bytes $immutable (local.get $dest)) (then (call $raise-expected-mutable-bytes (local.get $dest-raw)) (unreachable))) (local.set $arr (struct.get $Bytes $bs (local.get $dest))) (if (i32.lt_u (call $i8array-length (local.get $arr)) (i32.add (local.get $start) (local.get $size))) (then (call $raise-argument-error (local.get $dest-raw)) (unreachable))) ;; --- Decode big-endian? flag, defaulting to (system-big-endian?) --- (local.set $big (i32.const 0)) (if (ref.eq (local.get $big-raw) (global.get $missing)) (then (if (ref.eq (call $system-big-endian?) (global.get $false)) (then) (else (local.set $big (i32.const 1))))) (else (if (ref.eq (local.get $big-raw) (global.get $false)) (then) (else (if (ref.eq (local.get $big-raw) (global.get $true)) (then (local.set $big (i32.const 1))) (else (call $raise-argument-error (local.get $big-raw)) (unreachable))))))) ;; --- Convert x to flonum --- (local.set $fl (ref.cast (ref $Flonum) (global.get $flzero))) (if (ref.test (ref $Flonum) (local.get $x)) (then (local.set $fl (ref.cast (ref $Flonum) (local.get $x)))) (else (if (ref.test (ref i31) (local.get $x)) (then (local.set $fl (call $fx->fl/precise (local.get $x)))) (else (call $raise-expected-number (local.get $x)) (unreachable))))) (local.set $val (struct.get $Flonum $v (local.get $fl))) ;; Do it! (return_call $real->floating-point-bytes/checked (local.get $val) (local.get $size) (local.get $big) (local.get $dest) (local.get $start))) (func $real->floating-point-bytes/checked (param $val f64) (param $size i32) (param $big i32) ; big endian? (param $dest (ref $Bytes)) (param $start i32) (result (ref eq)) (local $arr (ref $I8Array)) (local $bits64 i64) (local $bits32 i32) (local.set $arr (struct.get $Bytes $bs (local.get $dest))) (if (result (ref eq)) (i32.eq (local.get $size) (i32.const 8)) (then (local.set $bits64 (i64.reinterpret_f64 (local.get $val))) (if (i32.eqz (local.get $big)) (then (call $i8array-set! (local.get $arr) (local.get $start) (i32.wrap_i64 (local.get $bits64))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 1)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 8)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 2)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 16)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 3)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 24)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 4)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 32)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 5)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 40)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 6)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 48)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 7)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 56))))) (else (call $i8array-set! (local.get $arr) (local.get $start) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 56)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 1)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 48)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 2)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 40)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 3)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 32)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 4)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 24)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 5)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 16)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 6)) (i32.wrap_i64 (i64.shr_u (local.get $bits64) (i64.const 8)))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 7)) (i32.wrap_i64 (local.get $bits64))))) (local.get $dest)) (else (local.set $bits32 (i32.reinterpret_f32 (f32.demote_f64 (local.get $val)))) (if (i32.eqz (local.get $big)) (then (call $i8array-set! (local.get $arr) (local.get $start) (i32.and (local.get $bits32) (i32.const 255))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 1)) (i32.and (i32.shr_u (local.get $bits32) (i32.const 8)) (i32.const 255))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 2)) (i32.and (i32.shr_u (local.get $bits32) (i32.const 16)) (i32.const 255))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 3)) (i32.and (i32.shr_u (local.get $bits32) (i32.const 24)) (i32.const 255)))) (else (call $i8array-set! (local.get $arr) (local.get $start) (i32.and (i32.shr_u (local.get $bits32) (i32.const 24)) (i32.const 255))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 1)) (i32.and (i32.shr_u (local.get $bits32) (i32.const 16)) (i32.const 255))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 2)) (i32.and (i32.shr_u (local.get $bits32) (i32.const 8)) (i32.const 255))) (call $i8array-set! (local.get $arr) (i32.add (local.get $start) (i32.const 3)) (i32.and (local.get $bits32) (i32.const 255))))) (local.get $dest)))) (func $bytes-length (type $Prim1) (param $a (ref eq)) (result (ref eq)) (local $bs (ref null $Bytes)) (local $arr (ref $I8Array)) (local $len i32) ;; Check that $a is a byte string (if (ref.test (ref $Bytes) (local.get $a)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $a)))) (else (call $raise-check-bytes (local.get $a)) (unreachable))) ;; Get the backing array and compute length (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (call $i8array-length (local.get $arr))) ;; Convert to fixnum and return (ref.i31 (i32.shl (local.get $len) (i32.const 1)))) (func $bytes? (type $Prim1) (param $a (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Bytes) (local.get $a)) (then (global.get $true)) (else (global.get $false)))) (func $raise-expected-bytes (unreachable)) ,@(append* (for/list ([spec (in-list '( ($bytes=? i32.eq eq #t) ($bytes? i32.gt_u ord #f) ))]) (define name (list-ref spec 0)) (define cmp (list-ref spec 1)) (define kind (list-ref spec 2)) (define signed? (list-ref spec 3)) (define name/2 (string->symbol (~a name "/2"))) (define name/2/checked (string->symbol (~a name "/2/checked"))) (define name/checked (string->symbol (~a name "/checked"))) (define fast-path (if (eq? kind 'eq) '((if (ref.eq (local.get $b1) (local.get $b2)) (then (return (global.get $true))))) '())) (define v1-get '(array.get_u $I8Array (local.get $a1) (local.get $i))) (define v2-get '(array.get_u $I8Array (local.get $a2) (local.get $i))) (define diff (if (eq? kind 'eq) '(if (i32.ne (local.get $v1) (local.get $v2)) (then (return (global.get $false)))) `(if (i32.ne (local.get $v1) (local.get $v2)) (then (if (,cmp (local.get $v1) (local.get $v2)) (then (return (global.get $true))) (else (return (global.get $false)))))))) (define final (if (eq? kind 'eq) '(if (result (ref eq)) (i32.eq (local.get $len1) (local.get $len2)) (then (global.get $true)) (else (global.get $false))) `(if (result (ref eq)) (,cmp (local.get $len1) (local.get $len2)) (then (global.get $true)) (else (global.get $false))))) (list `(func ,name/2 (param $v1 (ref eq)) ;; bytes? (param $v2 (ref eq)) ;; bytes? (result (ref eq)) (if (i32.eqz (ref.test (ref $Bytes) (local.get $v1))) (then (call $raise-expected-bytes (local.get $v1)) (unreachable))) (if (i32.eqz (ref.test (ref $Bytes) (local.get $v2))) (then (call $raise-expected-bytes (local.get $v2)) (unreachable))) (return_call ,name/2/checked (ref.cast (ref $Bytes) (local.get $v1)) (ref.cast (ref $Bytes) (local.get $v2)))) `(func ,name/2/checked (param $b1 (ref $Bytes)) ;; bytes (param $b2 (ref $Bytes)) ;; bytes (result (ref eq)) (local $a1 (ref $I8Array)) (local $a2 (ref $I8Array)) (local $len1 i32) (local $len2 i32) (local $min i32) (local $i i32) (local $v1 i32) (local $v2 i32) ,@fast-path ;; Extract arrays and lengths (local.set $a1 (struct.get $Bytes $bs (local.get $b1))) (local.set $a2 (struct.get $Bytes $bs (local.get $b2))) (local.set $len1 (array.len (local.get $a1))) (local.set $len2 (array.len (local.get $a2))) ;; Determine minimum length (local.set $min (local.get $len1)) (if (i32.lt_u (local.get $len2) (local.get $min)) (then (local.set $min (local.get $len2)))) ;; Compare bytes one-by-one (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $min))) (local.set $v1 ,v1-get) (local.set $v2 ,v2-get) ,diff (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; All compared bytes equal – compare lengths ,final) `(func ,name/checked (param $b0 (ref $Bytes)) ;; first bytes (param $b1 (ref $Bytes)) ;; second bytes (param $bs (ref eq)) ;; rest bytes list (result (ref eq)) (local $node (ref $Pair)) (local $next/any (ref eq)) ;; raw car from the list (local $next (ref $Bytes)) ;; refined to (ref $Bytes) (local $curr (ref $Bytes)) ;; Compare first two (if (ref.eq (call ,name/2/checked (local.get $b0) (local.get $b1)) (global.get $false)) (then (return (global.get $false)))) (local.set $curr (local.get $b1)) ;; Iterate remaining arguments (block $done (loop $loop (br_if $done (ref.eq (local.get $bs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $bs))) (local.set $next/any (struct.get $Pair $a (local.get $node))) (if (i32.eqz (ref.test (ref $Bytes) (local.get $next/any))) (then (call $raise-expected-bytes (local.get $next/any)) (unreachable))) (local.set $next (ref.cast (ref $Bytes) (local.get $next/any))) (if (ref.eq (call ,name/2/checked (local.get $curr) (local.get $next)) (global.get $false)) (then (return (global.get $false)))) (local.set $curr (local.get $next)) (local.set $bs (struct.get $Pair $d (local.get $node))) (br $loop))) (global.get $true)) `(func ,name (type $Prim>=1) (param $b0 (ref eq)) ; bytes? (param $bs (ref eq)) ; rest list (result (ref eq)) (local $b1 (ref $Bytes)) (local $b2 (ref $Bytes)) (local $node (ref $Pair)) (local $v (ref eq)) ;; Initialize non-defaultable locals (local.set $b1 (ref.cast (ref $Bytes) (global.get $bytes:empty))) (local.set $b2 (ref.cast (ref $Bytes) (global.get $bytes:empty))) ;; Validate first argument (if (ref.test (ref $Bytes) (local.get $b0)) (then (local.set $b1 (ref.cast (ref $Bytes) (local.get $b0)))) (else (call $raise-expected-bytes (local.get $b0)) (unreachable))) ;; No more arguments → true (if (ref.eq (local.get $bs) (global.get $null)) (then (return (global.get $true)))) ;; Extract second argument (local.set $node (ref.cast (ref $Pair) (local.get $bs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $Bytes) (local.get $v)) (then (local.set $b2 (ref.cast (ref $Bytes) (local.get $v)))) (else (call $raise-expected-bytes (local.get $v)) (unreachable))) (local.set $bs (struct.get $Pair $d (local.get $node))) ;; Exactly two arguments? (if (ref.eq (local.get $bs) (global.get $null)) (then (return_call ,name/2/checked (local.get $b1) (local.get $b2))) (else (return_call ,name/checked (local.get $b1) (local.get $b2) (local.get $bs)))) (unreachable))))) (func $byte? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $i i32) (if (result (ref eq)) (ref.test (ref i31) (local.get $v)) (then (local.set $i (i31.get_u (ref.cast (ref i31) (local.get $v)))) (if (result (ref eq)) ;; Check: is even (fixnum) and in range 0–255 (i32.and (i32.eqz (i32.and (local.get $i) (i32.const 1))) ;; even tag bit = 0 (i32.le_u (i32.shr_u (local.get $i) (i32.const 1)) ;; untagged value (i32.const 255))) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $bytes-ref (type $Prim2) (param $a (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $b (ref null $Bytes)) (local $arr (ref $I8Array)) (local $idx i32) (local $v i32) ;; 1. Check that $a is a byte string (if (ref.test (ref $Bytes) (local.get $a)) (then (local.set $b (ref.cast (ref $Bytes) (local.get $a)))) (else (call $raise-check-bytes (local.get $a)))) ;; 2. Decode and check that $i is a fixnum (if (ref.test (ref i31) (local.get $i)) (then (local.set $idx (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $idx) (i32.const 1))) (then (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i)) ))) (else (call $raise-check-fixnum (local.get $i)))) ;; 3. Get byte array and bounds-check (local.set $arr (struct.get $Bytes $bs (local.get $b))) (if (i32.lt_u (local.get $idx) (array.len (local.get $arr))) (then ;; 4. Read and box byte (local.set $v (call $i8array-ref (local.get $arr) (local.get $idx))) (return (ref.i31 (i32.shl (local.get $v) (i32.const 1))))) (else (call $raise-bad-bytes-ref-index (local.get $a) (local.get $i)))) (unreachable)) (func $bytes-set! (param $a (ref eq)) (param $i (ref eq)) (param $v (ref eq)) (result (ref eq)) (local $b (ref null $Bytes)) (local $arr (ref $I8Array)) (local $idx i32) (local $bv i32) ;; 1. Check that $a is a byte string (if (ref.test (ref $Bytes) (local.get $a)) (then (local.set $b (ref.cast (ref $Bytes) (local.get $a)))) (else (call $raise-check-bytes (local.get $a)))) ;; Byte literals and bytes->immutable-bytes results must reject mutation. (if (i32.eq (struct.get $Bytes $immutable (local.get $b)) (i32.const 1)) (then (call $raise-expected-mutable-bytes (local.get $a)) (unreachable))) ;; 2. Decode and check fixnum index $i (if (ref.test (ref i31) (local.get $i)) (then (local.set $idx (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $idx) (i32.const 1))) (then (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) ;; 3. Decode and check fixnum byte value $v (if (ref.test (ref i31) (local.get $v)) (then (local.set $bv (i31.get_u (ref.cast (ref i31) (local.get $v)))) (if (i32.eqz (i32.and (local.get $bv) (i32.const 1))) (then (local.set $bv (i32.shr_u (local.get $bv) (i32.const 1))) (if (i32.gt_u (local.get $bv) (i32.const 255)) (then (call $raise-check-byte (local.get $v))))) (else (call $raise-check-byte (local.get $v))))) (else (call $raise-check-byte (local.get $v)))) ;; 4. Bounds check and set byte (local.set $arr (struct.get $Bytes $bs (local.get $b))) (if (i32.lt_u (local.get $idx) (call $i8array-length (local.get $arr))) (then (call $i8array-set! (local.get $arr) (local.get $idx) (local.get $bv)) (return (global.get $void))) (else (call $raise-bad-bytes-set-index (local.get $a) (local.get $i)))) (unreachable)) (func $bytes-set!/checked (param $a (ref $Bytes)) (param $i i32) (param $b i32) ; unsafe (local $arr (ref $I8Array)) (local.set $arr (struct.get $Bytes $bs (local.get $a))) (call $i8array-set! (local.get $arr) (local.get $i) (local.get $b))) (func $subbytes (param $b (ref eq)) ;; input byte string (param $start (ref eq)) ;; start index (param $end (ref eq)) ;; optional end index, default = (bytes-length $b) (result (ref eq)) (local $bs (ref null $Bytes)) (local $arr (ref $I8Array)) (local $from i32) (local $to i32) (local $len i32) ;; Check that $b is a byte string (if (ref.test (ref $Bytes) (local.get $b)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $b)))) (else (call $raise-check-bytes (local.get $b)) (unreachable))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (call $i8array-length (local.get $arr))) ;; Decode and validate fixnum $start (if (ref.test (ref i31) (local.get $start)) (then (local.set $from (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.eqz (i32.and (local.get $from) (i32.const 1))) (then (local.set $from (i32.shr_u (local.get $from) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable))) ;; Decode and validate fixnum $end (optional) (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $to (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $to (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.eqz (i32.and (local.get $to) (i32.const 1))) (then (local.set $to (i32.shr_u (local.get $to) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; Bounds check: 0 <= from <= to <= len (if (i32.gt_u (local.get $from) (local.get $to)) (then (call $raise-bad-bytes-range (local.get $b) (local.get $from) (local.get $to)) (unreachable))) (if (i32.gt_u (local.get $to) (local.get $len)) (then (call $raise-bad-bytes-range (local.get $b) (local.get $from) (local.get $to)) (unreachable))) ;; Copy the subarray (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8array-copy (local.get $arr) (local.get $from) (local.get $to)))) (func $bytes-copy! (param $dest (ref eq)) (param $dest-start (ref eq)) (param $src (ref eq)) (param $src-start (ref eq)) ;; optional fixnum, default = 0 (param $src-end (ref eq)) ;; optional fixnum, default = (bytes-length src) (result (ref eq)) (local $d (ref $Bytes)) (local $s (ref $Bytes)) (local $darr (ref $I8Array)) (local $sarr (ref $I8Array)) (local $di i32) (local $si i32) (local $ei i32) (local $src-len i32) (local $dest-len i32) ;; --- Validate $dest --- (if (i32.eqz (ref.test (ref $Bytes) (local.get $dest))) (then (call $raise-check-bytes (local.get $dest)))) ;; --- Validate $src --- (if (i32.eqz (ref.test (ref $Bytes) (local.get $src))) (then (call $raise-check-bytes (local.get $src)))) ;; --- Cast after validation --- (local.set $d (ref.cast (ref $Bytes) (local.get $dest))) (local.set $s (ref.cast (ref $Bytes) (local.get $src))) ;; --- Reject immutable destination --- (if (i32.eq (struct.get $Bytes $immutable (local.get $d)) (i32.const 1)) (then (call $raise-expected-mutable-bytes (local.get $dest)) (unreachable))) ;; --- Decode $dest-start --- (if (i32.eqz (ref.test (ref i31) (local.get $dest-start))) (then (call $raise-check-fixnum (local.get $dest-start)))) (if (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $dest-start))) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $dest-start)))) (local.set $di (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $dest-start))) (i32.const 1))) ;; --- Extract arrays and lengths --- (local.set $darr (struct.get $Bytes $bs (local.get $d))) (local.set $sarr (struct.get $Bytes $bs (local.get $s))) (local.set $src-len (call $i8array-length (local.get $sarr))) (local.set $dest-len (call $i8array-length (local.get $darr))) ;; --- Decode optional $src-start --- (if (ref.eq (local.get $src-start) (global.get $missing)) (then (local.set $si (i32.const 0))) (else (if (i32.eqz (ref.test (ref i31) (local.get $src-start))) (then (call $raise-check-fixnum (local.get $src-start)))) (if (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $src-start))) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $src-start)))) (local.set $si (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $src-start))) (i32.const 1))))) ;; --- Decode optional $src-end --- (if (ref.eq (local.get $src-end) (global.get $missing)) (then (local.set $ei (local.get $src-len))) (else (if (i32.eqz (ref.test (ref i31) (local.get $src-end))) (then (call $raise-check-fixnum (local.get $src-end)))) (if (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $src-end))) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $src-end)))) (local.set $ei (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $src-end))) (i32.const 1))))) ;; --- Range validation --- (if (i32.gt_u (local.get $si) (local.get $ei)) (then (call $raise-bad-bytes-range (local.get $src) (local.get $si) (local.get $ei)) (unreachable))) (if (i32.gt_u (local.get $ei) (local.get $src-len)) (then (call $raise-bad-bytes-range (local.get $src) (local.get $si) (local.get $ei)) (unreachable))) (if (i32.gt_u (i32.add (local.get $di) (i32.sub (local.get $ei) (local.get $si))) (local.get $dest-len)) (then (call $raise-bad-bytes-range (local.get $dest) (local.get $di) (i32.add (local.get $di) (i32.sub (local.get $ei) (local.get $si)))) (unreachable))) ;; --- Copy bytes --- (call $i8array-copy! (local.get $darr) (local.get $di) (local.get $sarr) (local.get $si) (local.get $ei)) (global.get $void)) (func $bytes-copy (type $Prim1) (param $src (ref eq)) (result (ref eq)) (local $b (ref null $Bytes)) (local $a (ref $I8Array)) (local $a2 (ref $I8Array)) ;; Check that $src is a byte string (if (ref.test (ref $Bytes) (local.get $src)) (then (local.set $b (ref.cast (ref $Bytes) (local.get $src)))) (else (call $raise-check-bytes (local.get $src)) (unreachable))) ;; Extract and copy the underlying I8Array (local.set $a (struct.get $Bytes $bs (local.get $b))) (local.set $a2 (call $i8array-copy (local.get $a) (i32.const 0) (call $i8array-length (local.get $a)))) ;; Return a new mutable Bytes struct (struct.new $Bytes (i32.const 0) ;; hash (i32.const 0) ;; immutable = false (local.get $a2))) (func $raise-expected-mutable-bytes (unreachable)) (func $bytes-fill! (type $Prim2) (param $dest (ref eq)) (param $b (ref eq)) (result (ref eq)) (local $bs (ref null $Bytes)) (local $arr (ref $I8Array)) (local $b/tag i32) ;; raw i31 payload (tagged) (local $val i32) ;; untagged byte value 0..255 ;; dest must be a (mutable) byte string (if (ref.test (ref $Bytes) (local.get $dest)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $dest)))) (else (call $raise-check-bytes (local.get $dest)) (unreachable))) ;; reject immutable byte strings (if (i32.eq (struct.get $Bytes $immutable (local.get $bs)) (i32.const 1)) (then (call $raise-expected-mutable-bytes (local.get $dest)) (unreachable))) ;; b must be a fixnum byte (i31 with lsb=0), then 0..255 (if (ref.test (ref i31) (local.get $b)) (then (local.set $b/tag (i31.get_u (ref.cast (ref i31) (local.get $b)))) ;; ensure lsb=0 => fixnum (not a char etc.) (if (i32.ne (i32.and (local.get $b/tag) (i32.const 1)) (i32.const 0)) (then (call $raise-check-byte (local.get $b)) (unreachable))) ;; untag: shift right by 1 (your fixnum convention) (local.set $val (i32.shr_u (local.get $b/tag) (i32.const 1))) ;; range check 0..255 (if (i32.ge_u (local.get $val) (i32.const 256)) (then (call $raise-check-byte (local.get $b)) (unreachable)))) (else (call $raise-check-byte (local.get $b)) (unreachable))) ;; Fill underlying byte array (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (call $i8array-fill! (local.get $arr) (local.get $val)) ;; return void (global.get $void)) (func $bytes-append (type $Prim>=0) (param $xs (ref eq)) ;; list of byte strings (result (ref eq)) (local $n i32) (local $node (ref $Pair)) (local $b (ref $Bytes)) (local $v (ref eq)) (local $orig (ref eq)) (local $total i32) (local $len i32) (local $arr (ref $I8Array)) (local $pos i32) ;; === initialize non-defaultable refs === (local.set $b (ref.cast (ref $Bytes) (global.get $bytes:empty))) ;; Preserve original list (local.set $orig (local.get $xs)) ;; Determine number of arguments (local.set $n (call $length/i32 (local.get $xs))) ;; Zero arguments -> empty byte string (if (i32.eqz (local.get $n)) (then (return (global.get $bytes:empty)))) ;; Extract and check first argument (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $Bytes) (local.get $v)) (then (local.set $b (ref.cast (ref $Bytes) (local.get $v)))) (else (call $raise-check-bytes (local.get $v)))) ;; Single argument -> copy to ensure fresh mutable bytes (if (i32.eq (local.get $n) (i32.const 1)) (then (return (call $bytes-copy (local.get $b))))) ;; Compute total length (local.set $total (call $i8array-length (struct.get $Bytes $bs (local.get $b)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (block $done1 (loop $loop1 (br_if $done1 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $Bytes) (local.get $v)) (then (local.set $b (ref.cast (ref $Bytes) (local.get $v))) (local.set $len (call $i8array-length (struct.get $Bytes $bs (local.get $b)))) (local.set $total (i32.add (local.get $total) (local.get $len)))) (else (call $raise-check-bytes (local.get $v)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop1))) ;; All byte strings empty -> return empty byte string (if (i32.eqz (local.get $total)) (then (return (global.get $bytes:empty)))) ;; Allocate result array (local.set $arr (call $i8make-array (local.get $total) (i32.const 0))) ;; Copy byte strings into result array (local.set $xs (local.get $orig)) (local.set $pos (i32.const 0)) (block $done2 (loop $loop2 (br_if $done2 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $b (ref.cast (ref $Bytes) (struct.get $Pair $a (local.get $node)))) (local.set $len (call $i8array-length (struct.get $Bytes $bs (local.get $b)))) (call $i8array-copy! (local.get $arr) (local.get $pos) (struct.get $Bytes $bs (local.get $b)) (i32.const 0) (local.get $len)) (local.set $pos (i32.add (local.get $pos) (local.get $len))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop2))) (struct.new $Bytes (i32.const 0) (i32.const 0) (local.get $arr))) (func $bytes-append/2 (param $b1 (ref eq)) (param $b2 (ref eq)) (result (ref eq)) (local $bs1 (ref null $Bytes)) (local $bs2 (ref null $Bytes)) (local $a1 (ref $I8Array)) (local $a2 (ref $I8Array)) (local $new (ref $I8Array)) ;; Check both arguments are byte strings (if (ref.test (ref $Bytes) (local.get $b1)) (then (local.set $bs1 (ref.cast (ref $Bytes) (local.get $b1)))) (else (call $raise-check-bytes (local.get $b1)) (unreachable))) (if (ref.test (ref $Bytes) (local.get $b2)) (then (local.set $bs2 (ref.cast (ref $Bytes) (local.get $b2)))) (else (call $raise-check-bytes (local.get $b2)) (unreachable))) ;; Extract the underlying arrays (local.set $a1 (struct.get $Bytes $bs (local.get $bs1))) (local.set $a2 (struct.get $Bytes $bs (local.get $bs2))) ;; Call append function on the I8Arrays (local.set $new (call $i8array-append (local.get $a1) (local.get $a2))) ;; Wrap in new mutable Bytes struct (struct.new $Bytes (i32.const 0) ;; hash (i32.const 0) ;; mutable (local.get $new))) (func $bytes-append* (type $Prim>=1) (param $str (ref eq)) ;; bytes? or list of bytes (param $rest (ref eq)) ;; additional bytes, last element list (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $acc (ref eq)) (local $last (ref eq)) (local $args (ref eq)) ;; initialize locals with no defaults (local.set $last (global.get $false)) ;; no extra args: first arg supplies list of bytes (if (ref.eq (local.get $rest) (global.get $null)) (then (return (call $bytes-append (local.get $str))))) ;; separate last list from preceding byte arguments (local.set $xs (local.get $rest)) (local.set $acc (global.get $null)) (block $done (loop $loop (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $xs (struct.get $Pair $d (local.get $node))) (if (ref.eq (local.get $xs) (global.get $null)) (then (local.set $last (struct.get $Pair $a (local.get $node))) (br $done)) (else (local.set $acc (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $node)) (local.get $acc))) (br $loop))))) ;; rebuild argument list in proper order (local.set $args (local.get $last)) (block $done2 (loop $loop2 (br_if $done2 (ref.eq (local.get $acc) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $acc))) (local.set $args (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $node)) (local.get $args))) (local.set $acc (struct.get $Pair $d (local.get $node))) (br $loop2))) (local.set $args (struct.new $Pair (i32.const 0) (local.get $str) (local.get $args))) (call $bytes-append (local.get $args))) (func $bytes-join (type $Prim2) (param $strs (ref eq)) ;; listof bytes? (param $sep (ref eq)) ;; bytes? (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $bs (ref $Bytes)) (local $sep-bs (ref $Bytes)) (local $sep-len i32) (local $n i32) (local $total i32) (local $len i32) (local $arr (ref $I8Array)) (local $pos i32) ;; --- Initialize non-defaultable locals --- (local.set $bs (ref.cast (ref $Bytes) (global.get $bytes:empty))) (local.set $sep-bs (ref.cast (ref $Bytes) (global.get $bytes:empty))) ;; --- Check separator --- (if (ref.test (ref $Bytes) (local.get $sep)) (then (local.set $sep-bs (ref.cast (ref $Bytes) (local.get $sep)))) (else (call $raise-check-bytes (local.get $sep)) (unreachable))) ;; --- Determine separator length --- (local.set $sep-len (call $i8array-length (struct.get $Bytes $bs (local.get $sep-bs)))) ;; --- Determine number of byte strings --- (local.set $n (call $length/i32 (local.get $strs))) (if (i32.eqz (local.get $n)) (then (return (global.get $bytes:empty)))) ;; --- Compute total length --- (local.set $xs (local.get $strs)) (local.set $total (i32.mul (i32.sub (local.get $n) (i32.const 1)) (local.get $sep-len))) (block $done1 (loop $loop1 (br_if $done1 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $Bytes) (local.get $v)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $v))) (local.set $len (call $i8array-length (struct.get $Bytes $bs (local.get $bs)))) (local.set $total (i32.add (local.get $total) (local.get $len)))) (else (call $raise-check-bytes (local.get $v)) (unreachable))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop1))) ;; --- All empty -> empty byte string --- (if (i32.eqz (local.get $total)) (then (return (global.get $bytes:empty)))) ;; --- Allocate result array --- (local.set $arr (call $i8make-array (local.get $total) (i32.const 0))) ;; --- Copy byte strings and separators --- (local.set $xs (local.get $strs)) (local.set $pos (i32.const 0)) (block $done2 (loop $loop2 (br_if $done2 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $bs (ref.cast (ref $Bytes) (struct.get $Pair $a (local.get $node)))) (local.set $len (call $i8array-length (struct.get $Bytes $bs (local.get $bs)))) (call $i8array-copy! (local.get $arr) (local.get $pos) (struct.get $Bytes $bs (local.get $bs)) (i32.const 0) (local.get $len)) (local.set $pos (i32.add (local.get $pos) (local.get $len))) (local.set $xs (struct.get $Pair $d (local.get $node))) (if (ref.eq (local.get $xs) (global.get $null)) (then (nop)) (else (call $i8array-copy! (local.get $arr) (local.get $pos) (struct.get $Bytes $bs (local.get $sep-bs)) (i32.const 0) (local.get $sep-len)) (local.set $pos (i32.add (local.get $pos) (local.get $sep-len))))) (br $loop2))) ;; --- Build result byte string --- (struct.new $Bytes (i32.const 0) (i32.const 0) (local.get $arr))) (func $bytes->list (type $Prim1) (param $bstr (ref eq)) (result (ref eq)) (local $bs (ref null $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $i i32) (local $val i32) (local $acc (ref eq)) ;; Check input is a byte string (if (ref.test (ref $Bytes) (local.get $bstr)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr)))) (else (call $raise-check-bytes (local.get $bstr)) (unreachable))) ;; Extract underlying byte array (local.set $arr (struct.get $Bytes $bs (local.get $bs))) ;; Get its length (local.set $len (call $i8array-length (local.get $arr))) ;; Build list in reverse (local.set $i (i32.sub (local.get $len) (i32.const 1))) (local.set $acc (global.get $null)) (block $done (loop $loop (br_if $done (i32.lt_s (local.get $i) (i32.const 0))) (local.set $val (call $i8array-ref (local.get $arr) (local.get $i))) (local.set $acc (struct.new $Pair (i32.const 0) (ref.i31 (i32.shl (local.get $val) (i32.const 1))) ;; encode fixnum (local.get $acc))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $loop))) (local.get $acc)) (func $list->bytes (type $Prim1) (param $xs (ref eq)) (result (ref eq)) (local $bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $i i32) (local $len i32) (local $x (ref eq)) (local $v i32) ;; Step 1: Compute length of list (local.set $len (call $length/i32 (local.get $xs))) ;; Step 2: Allocate mutable byte array of length $len (local.set $arr (call $i8make-array (local.get $len) (i32.const 0))) ;; Step 3: Allocate mutable Bytes struct (local.set $bs (struct.new $Bytes (i32.const 0) (i32.const 0) (local.get $arr))) ;; Step 4: Iterate through list and populate byte array (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (if (ref.test (ref $Pair) (local.get $xs)) (then (local.set $x (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $xs)))) (if (ref.test (ref i31) (local.get $x)) (then (local.set $v (i31.get_u (ref.cast (ref i31) (local.get $x)))) (if (i32.eqz (i32.and (local.get $v) (i32.const 1))) (then (local.set $v (i32.shr_u (local.get $v) (i32.const 1))) (if (i32.lt_u (local.get $v) (i32.const 256)) (then (call $i8array-set! (local.get $arr) (local.get $i) (local.get $v)) (local.set $i (i32.add (local.get $i) (i32.const 1)))) (else (call $raise-byte-out-of-range (local.get $x)) (unreachable)))) (else (call $raise-check-fixnum (local.get $x)) (unreachable)))) (else (call $raise-check-fixnum (local.get $x)) (unreachable))) (local.set $xs (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $xs))))) (else (call $raise-pair-expected (local.get $xs)) (unreachable))) (br $loop))) ;; Step 5: Return the byte string struct (local.get $bs)) (func $raise-bytes-utf-8-length:bad-argument (unreachable)) (func $raise-bytes-utf-8-length:invalid-err-char (unreachable)) (func $raise-bytes-utf-8-length:range-error (unreachable)) (func $bytes-utf-8-length (type $Prim14) (param $bstr (ref eq)) ; bytes? (param $err-char (ref eq)) ; optional char?, defaults to #f (param $start-raw (ref eq)) ; optional exact-nonnegative-integer?, defaults to 0 (param $end-raw (ref eq)) ; optional exact-nonnegative-integer?, defaults to (bytes-length bstr) (result (ref eq)) (local $bs (ref null $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $start i32) (local $end i32) (local $use-err-char i32) (local $i i32) (local $byte i32) (local $need i32) (local $acc i32) (local $b2 i32) (local $count i32) ;; --- Type check for bytes argument --- (if (ref.test (ref $Bytes) (local.get $bstr)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr)))) (else (call $raise-bytes-utf-8-length:bad-argument))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (array.len (local.get $arr))) ;; --- Decode optional start --- (if (ref.eq (local.get $start-raw) (global.get $missing)) (then (local.set $start (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start-raw)) (then (local.set $start (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $start-raw))) (i32.const 1)))) (else (call $raise-bytes-utf-8-length:bad-argument))))) ;; --- Decode optional end --- (if (ref.eq (local.get $end-raw) (global.get $missing)) (then (local.set $end (local.get $len))) (else (if (ref.test (ref i31) (local.get $end-raw)) (then (local.set $end (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $end-raw))) (i32.const 1)))) (else (call $raise-bytes-utf-8-length:bad-argument))))) ;; --- Decode optional err-char --- (if (ref.eq (local.get $err-char) (global.get $missing)) (then (local.set $use-err-char (i32.const 0))) (else (if (ref.eq (local.get $err-char) (global.get $false)) (then (local.set $use-err-char (i32.const 0))) (else (if (ref.test (ref i31) (local.get $err-char)) (then (local.set $use-err-char (i32.const 1))) (else (call $raise-bytes-utf-8-length:invalid-err-char))))))) ;; --- Range checks --- (if (i32.or (i32.gt_u (local.get $start) (local.get $end)) (i32.gt_u (local.get $end) (local.get $len))) (then (call $raise-bytes-utf-8-length:range-error))) ;; --- Main decoding loop --- (local.set $i (local.get $start)) (local.set $count (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $end))) (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $i))) ;; ASCII fast path (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (local.set $count (i32.add (local.get $count) (i32.const 1))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (call $bytes->string/utf-8:determine-utf-8-sequence (local.get $byte)) (local.set $acc) (local.set $need) ;; Invalid lead byte (if (i32.lt_s (local.get $need) (i32.const 0)) (then (if (i32.eqz (local.get $use-err-char)) (then (return (global.get $false))) (else (local.set $count (i32.add (local.get $count) (i32.const 1))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))))) ;; Not enough bytes left in substring (if (i32.gt_u (i32.add (local.get $i) (local.get $need)) (local.get $end)) (then (if (i32.eqz (local.get $use-err-char)) (then (return (global.get $false))) (else (local.set $count (i32.add (local.get $count) (i32.const 1))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))))) ;; Consume continuation bytes (local.set $i (i32.add (local.get $i) (i32.const 1))) (block $cont-fail (loop $cont-loop (br_if $cont-fail (i32.eqz (local.get $need))) (local.set $b2 (array.get_u $I8Array (local.get $arr) (local.get $i))) (if (i32.and (i32.ge_u (local.get $b2) (i32.const 128)) (i32.lt_u (local.get $b2) (i32.const 192))) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $need (i32.sub (local.get $need) (i32.const 1))) (br $cont-loop))))) ;; Invalid continuation sequence (if (i32.ne (local.get $need) (i32.const 0)) (then (if (i32.eqz (local.get $use-err-char)) (then (return (global.get $false))) (else (local.set $count (i32.add (local.get $count) (i32.const 1))) (br $loop))))) ;; Successful multi-byte sequence (local.set $count (i32.add (local.get $count) (i32.const 1))) (br $loop))) (ref.i31 (i32.shl (local.get $count) (i32.const 1)))) (func $raise-bytes->string/latin-1 (unreachable)) (func $raise-bytes->string/latin-1:invalid-err-char (unreachable)) (func $bytes->string/latin-1 (type $Prim14) (param $bstr (ref eq)) ; bytes? (param $err-char (ref eq)) ; optional char?, defaults to #f (param $start-raw (ref eq)) ; optional exact-nonnegative-integer?, defaults to 0 (param $end-raw (ref eq)) ; optional exact-nonnegative-integer?, defaults to (bytes-length bstr) (result (ref eq)) (local $bs (ref null $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $start i32) (local $end i32) (local $count i32) (local $codepoints (ref $I32Array)) (local $i i32) (local $idx i32) (local $byte i32) ;; --- Validate byte string argument --- (if (ref.test (ref $Bytes) (local.get $bstr)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr)))) (else (call $raise-bytes->string/latin-1))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (array.len (local.get $arr))) ;; --- Decode optional start --- (if (ref.eq (local.get $start-raw) (global.get $missing)) (then (local.set $start (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start-raw)) (then (local.set $start (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $start-raw))) (i32.const 1)))) (else (call $raise-bytes->string/latin-1))))) ;; --- Decode optional end --- (if (ref.eq (local.get $end-raw) (global.get $missing)) (then (local.set $end (local.get $len))) (else (if (ref.test (ref i31) (local.get $end-raw)) (then (local.set $end (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $end-raw))) (i32.const 1)))) (else (call $raise-bytes->string/latin-1))))) ;; --- Validate optional err-char (ignored value) --- (if (ref.eq (local.get $err-char) (global.get $missing)) (then (nop)) (else (if (ref.eq (local.get $err-char) (global.get $false)) (then (nop)) (else (if (ref.test (ref i31) (local.get $err-char)) (then (nop)) (else (call $raise-bytes->string/latin-1:invalid-err-char))))))) ;; --- Range checks --- (if (i32.gt_u (local.get $start) (local.get $end)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $start) (local.get $end)) (unreachable))) (if (i32.gt_u (local.get $end) (local.get $len)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $start) (local.get $end)) (unreachable))) ;; --- Allocate codepoint array and copy bytes --- (local.set $count (i32.sub (local.get $end) (local.get $start))) (local.set $codepoints (call $i32array-make (local.get $count) (i32.const 0))) (local.set $i (i32.const 0)) (local.set $idx (local.get $start)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $count))) (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $idx))) (array.set $I32Array (local.get $codepoints) (local.get $i) (local.get $byte)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $idx (i32.add (local.get $idx) (i32.const 1))) (br $loop))) (call $i32array->immutable-string (local.get $codepoints))) (func $raise-bytes->string/utf-8 (unreachable)) (func $raise-bytes->string/utf-8:invalid-err-char (unreachable)) (func $bytes->string/utf-8 (type $Prim14) (param $bstr (ref eq)) (param $err-char (ref eq)) ; optional character?, defaults to #f (param $start-raw (ref eq)) ; optional exact-nonnegative-integer?, defaults to 0 (param $end-raw (ref eq)) ; optional exact-nonnegative-integer?, defaults to (bytes-length bstr) (result (ref eq)) (local $bs (ref null $Bytes)) (local $start i32) (local $end i32) (local $use-err-char i32) (local $decoded-err-char i32) ;; Cast input to $Bytes (if (ref.test (ref $Bytes) (local.get $bstr)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr)))) (else (call $raise-bytes->string/utf-8))) ;; Decode start (if (ref.eq (local.get $start-raw) (global.get $missing)) (then (local.set $start (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start-raw)) (then (local.set $start (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $start-raw))) (i32.const 1)))) (else (call $raise-bytes->string/utf-8))))) ;; Decode end (if (ref.eq (local.get $end-raw) (global.get $missing)) (then (local.set $end (array.len (struct.get $Bytes $bs (local.get $bs))))) (else (if (ref.test (ref i31) (local.get $end-raw)) (then (local.set $end (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $end-raw))) (i32.const 1)))) (else (call $raise-bytes->string/utf-8))))) ;; Decode err-char (if (ref.eq (local.get $err-char) (global.get $missing)) ; missing (then (local.set $use-err-char (i32.const 0)) (local.set $decoded-err-char (i32.const 0))) (else ; false (if (ref.eq (local.get $err-char) (global.get $false)) (then (local.set $use-err-char (i32.const 0)) (local.set $decoded-err-char (i32.const 0))) (else ; character (if (ref.test (ref i31) (local.get $err-char)) (then (local.set $use-err-char (i32.const 1)) (local.set $decoded-err-char (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $err-char))) (i32.const 1)))) (else (call $raise-bytes->string/utf-8:invalid-err-char))))))) ;; Delegate to implementation (call $bytes->string/utf-8:work (ref.as_non_null (local.get $bs)) (local.get $use-err-char) (local.get $decoded-err-char) (local.get $start) (local.get $end))) (func $bytes->string/utf-8/defaults (param $bs (ref $Bytes)) (result (ref $String)) (ref.cast (ref $String) (call $bytes->string/utf-8 (local.get $bs) (global.get $missing) ;; err-char = #f (global.get $missing) ;; start = 0 (global.get $missing)))) ;; end = full length (func $bytes->string/utf-8/checked (param $bs (ref $Bytes)) (result (ref $String)) (local $end i32) (local.set $end (array.len (struct.get $Bytes $bs (local.get $bs)))) (call $bytes->string/utf-8:work (local.get $bs) (i32.const 0) ;; use-err-char? = false (i32.const 0) ;; err-char = dummy (i32.const 0) ;; start (local.get $end))) (func $bytes->string/utf-8:work (param $bs (ref $Bytes)) (param $use-err-char i32) (param $err-char i32) (param $start i32) (param $end i32) (result (ref $String)) (local $buf (ref $I32GrowableArray)) (local $arr (ref $I8Array)) (local $i i32) (local $byte i32) (local $need i32) (local $acc i32) (local $b2 i32) (local $cp i32) ;; Get underlying I8Array from Bytes (local.set $arr (struct.get $Bytes $bs (local.get $bs))) ;; Allocate buffer for codepoints (local.set $buf (call $make-i32growable-array (i32.const 16))) ;; Start decoding loop (local.set $i (local.get $start)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $end))) (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $i))) ;; ASCII fast path (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (call $i32growable-array-add! (local.get $buf) (local.get $byte)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Determine UTF-8 sequence size and initial accumulator (call $bytes->string/utf-8:determine-utf-8-sequence (local.get $byte)) (local.set $acc) (local.set $need) ;; Invalid lead byte (if (i32.lt_s (local.get $need) (i32.const 0)) (then (if (i32.eqz (local.get $use-err-char)) (then (call $raise-bytes->string/utf-8)) (else (call $i32growable-array-add! (local.get $buf) (local.get $err-char)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))))) ;; Not enough bytes left (if (i32.gt_u (i32.add (local.get $i) (local.get $need)) (local.get $end)) (then (if (i32.eqz (local.get $use-err-char)) (then (call $raise-bytes->string/utf-8)) (else (call $i32growable-array-add! (local.get $buf) (local.get $err-char)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))))) ;; Decode continuation bytes (local.set $cp (local.get $acc)) (local.set $i (i32.add (local.get $i) (i32.const 1))) ;; skip lead byte (block $cont-fail (loop $cont-loop (br_if $cont-fail (i32.eqz (local.get $need))) (local.set $b2 (array.get_u $I8Array (local.get $arr) (local.get $i))) (if (i32.and (i32.ge_u (local.get $b2) (i32.const 128)) (i32.lt_u (local.get $b2) (i32.const 192))) (then (local.set $cp (i32.or (i32.shl (local.get $cp) (i32.const 6)) (i32.and (local.get $b2) (i32.const 0x3F)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $need (i32.sub (local.get $need) (i32.const 1))) (br $cont-loop))))) ;; If we didn't finish the sequence, it's invalid (if (i32.ne (local.get $need) (i32.const 0)) (then (if (i32.eqz (local.get $use-err-char)) (then (call $raise-bytes->string/utf-8)) (else (call $i32growable-array-add! (local.get $buf) (local.get $err-char)) (br $loop))))) ;; Valid sequence (call $i32growable-array-add! (local.get $buf) (local.get $cp)) (br $loop))) ;; Convert buffer to immutable string (call $i32growable-array->immutable-string (local.get $buf))) (func $bytes->string/utf-8:determine-utf-8-sequence ; returns two i32s ; This function determines how many continuation bytes are needed for ; a given UTF-8 lead byte, and extracts the initial bits for the code point accumulator. (param $lead i32) (result i32 i32) ;; (need acc) or (-1 -1) if invalid (if (i32.and (i32.ge_u (local.get $lead) (i32.const 0xC0)) (i32.lt_u (local.get $lead) (i32.const 0xE0))) (then (return (i32.const 1) (i32.and (local.get $lead) (i32.const 0x1F))))) (if (i32.and (i32.ge_u (local.get $lead) (i32.const 0xE0)) (i32.lt_u (local.get $lead) (i32.const 0xF0))) (then (return (i32.const 2) (i32.and (local.get $lead) (i32.const 0x0F))))) (if (i32.and (i32.ge_u (local.get $lead) (i32.const 0xF0)) (i32.lt_u (local.get $lead) (i32.const 0xF8))) (then (return (i32.const 3) (i32.and (local.get $lead) (i32.const 0x07))))) ;; Not a valid lead byte (return (i32.const -1) (i32.const -1))) ;;; ;;; 4.4 Strings ;;; ;; https://docs.racket-lang.org/reference/strings.html (func $raise-check-string (param $x (ref eq)) (unreachable)) (func $raise-bad-string-index (param $x (ref eq)) (param $i (ref eq)) (unreachable)) (func $raise-bad-string-index/i32 (param $x (ref eq)) (param $i i32) (unreachable)) (func $raise-string-length:bad-argument (unreachable)) (func $raise-string-index-out-of-bounds/i32 (param $x (ref eq)) (param $i i32) (param $n i32) (unreachable)) ; for single character strings (func $codepoint->string (param $ch i32) ;; Unicode scalar value (result (ref $String)) (local $cp (ref $I32Array)) ;; Validate that ch is a Unicode scalar (i.e. not in surrogate range) (if (i32.or (i32.lt_u (local.get $ch) (i32.const 0)) (i32.gt_u (local.get $ch) (i32.const 0x10FFFF))) (then (call $raise-argument-error:char-expected (ref.i31 (local.get $ch))) (unreachable))) (if (i32.and (i32.ge_u (local.get $ch) (i32.const 0xD800)) (i32.le_u (local.get $ch) (i32.const 0xDFFF))) (then (call $raise-argument-error:char-expected (ref.i31 (local.get $ch))) (unreachable))) ;; Allocate I32Array of length 1 (local.set $cp (array.new_fixed $I32Array 1 (local.get $ch))) ;; Create string with immutable = 1, hash = 0 (struct.new $String (i32.const 0) ;; hash (lazy) (i32.const 1) ;; immutable (local.get $cp))) (func $i32array->string (param $arr (ref $I32Array)) (result (ref $String)) ;; Constructs a $String from an I32Array of codepoints. ;; Assumes the string is mutable and un-hashed (hash = 0). (struct.new $String (i32.const 0) ;; hash = 0 (i32.const 0) ;; mutable (local.get $arr))) ;; codepoints (func $i32array->immutable-string (param $arr (ref $I32Array)) (result (ref $String)) ;; Constructs a $String from an I32Array of codepoints. ;; Assumes the string is immutable and un-hashed (hash = 0). (struct.new $String (i32.const 0) ;; hash = 0 (i32.const 1) ;; immutable (local.get $arr))) ;; codepoints (func $i32growable-array->string (param $g (ref $I32GrowableArray)) (result (ref $String)) (call $i32array->string (call $i32growable-array->array (local.get $g)))) (func $i32growable-array->immutable-string (param $g (ref $I32GrowableArray)) (result (ref $String)) (call $i32array->immutable-string (call $i32growable-array->array (local.get $g)))) (func $make-dummy-string (result (ref $String)) (struct.new $String (i32.const 0) ;; hash = 0 (i32.const 0) ;; mutable (call $i32array-make (i32.const 0) (i32.const 0)))) ;; 4.4.1 String Constructors, Selectors, and Mutators (func $string? (type $Prim1) (param $s (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $String) (local.get $s)) (then (global.get $true)) (else (global.get $false)))) (func $non-empty-string? (type $Prim1) (param $s (ref eq)) (result (ref eq)) (local $str (ref $String)) (local $len i32) (if (result (ref eq)) (ref.test (ref $String) (local.get $s)) (then (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $len (call $i32array-length (struct.get $String $codepoints (local.get $str)))) (if (result (ref eq)) (i32.eqz (local.get $len)) (then (global.get $false)) (else (global.get $true)))) (else (global.get $false)))) ;; Constructors (func $string (type $Prim>=0) (param $args (ref eq)) (result (ref eq)) (local $argv (ref $Args)) (local $len i32) (local $arr (ref $I32Array)) (local $i i32) (local $ch (ref eq)) (local $use-args? i32) (local $list (ref eq)) (local $node (ref eq)) (local $pair (ref $Pair)) ;; Initialize non-defaultable locals (local.set $argv (array.new $Args (global.get $null) (i32.const 0))) (local.set $ch (global.get $false)) ;; Support both $Args arrays and ordinary rest argument lists. (local.set $use-args? (ref.test (ref $Args) (local.get $args))) (local.set $list (global.get $null)) (local.set $node (global.get $null)) (local.set $len (if (result i32) (local.get $use-args?) (then (local.set $argv (ref.cast (ref $Args) (local.get $args))) (array.len (local.get $argv))) (else (local.set $list (local.get $args)) (local.set $node (local.get $list)) (call $length/i32 (local.get $list))))) ;; Allocate array for codepoints (local.set $arr (array.new $I32Array (i32.const 0) (local.get $len))) ;; Fill array with characters from either representation (local.set $i (i32.const 0)) (if (i32.eqz (local.get $use-args?)) (then (local.set $node (local.get $list)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (if (ref.test (ref $Pair) (local.get $node)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $node))) (local.set $ch (struct.get $Pair $a (local.get $pair))) (local.set $node (struct.get $Pair $d (local.get $pair)))) (else (call $raise-pair-expected (local.get $node)) (unreachable))) (array.set $I32Array (local.get $arr) (local.get $i) (call $char->integer/i32 (local.get $ch))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)))) (else (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $ch (array.get $Args (local.get $argv) (local.get $i))) (array.set $I32Array (local.get $arr) (local.get $i) (call $char->integer/i32 (local.get $ch))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))))) ;; Construct mutable string (struct.new $String (i32.const 0) ;; hash (i32.const 0) ;; mutable (local.get $arr))) (func $raise-make-string:bad-length (unreachable)) (func $raise-make-string:bad-char (unreachable)) (func $raise-argument-error:char-expected (unreachable)) (func $make-string (type $Prim12) (param $n-raw (ref eq)) ;; fixnum (param $ch-raw (ref eq)) ;; optional immediate character (result (ref eq)) (local $n i32) (local $ch-tagged i32) (local $ch i32) ;; --- Type check for length --- (if (i32.eqz (ref.test (ref i31) (local.get $n-raw))) (then (call $raise-make-string:bad-length))) ;; --- Decode length --- (local.set $n (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $n-raw))) (i32.const 1))) ;; --- Handle optional character --- (if (ref.eq (local.get $ch-raw) (global.get $missing)) (then (local.set $ch (i32.const 0))) (else ;; --- Validate character --- (if (i32.eqz (ref.test (ref i31) (local.get $ch-raw))) (then (call $raise-make-string:bad-char))) (local.set $ch-tagged (i31.get_u (ref.cast (ref i31) (local.get $ch-raw)))) (if (i32.ne (i32.and (local.get $ch-tagged) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-make-string:bad-char))) ;; --- Extract code point from character --- (local.set $ch (i32.shr_u (local.get $ch-tagged) (i32.const ,char-shift))))) ;; --- Delegate --- (call $make-string/checked (local.get $n) (local.get $ch))) (func $make-string/checked (param $n i32) ;; number of characters (param $ch i32) ;; codepoint to fill (result (ref $String)) (local $arr (ref $I32Array)) ;; Create backing array and fill with `ch` (local.set $arr (array.new $I32Array (local.get $ch) (local.get $n))) ;; Construct mutable string (struct.new $String (i32.const 0) ;; hash = 0 (i32.const 0) ;; mutable (local.get $arr))) (func $make-immutable-string/checked (param $n i32) ;; number of characters (param $ch i32) ;; codepoint to fill (result (ref $String)) (local $arr (ref $I32Array)) ;; Create backing array and fill with `ch` (local.set $arr (array.new $I32Array (local.get $ch) (local.get $n))) ;; Construct mutable string (struct.new $String (i32.const 0) ;; hash = 0 (i32.const 1) ;; immutable (local.get $arr))) (func $string->immutable-string (type $Prim1) (param $s (ref eq)) (result (ref eq)) (local $str (ref $String)) (local.set $str (call $make-dummy-string)) ;; 1. Check that s is a String (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (local.set $str (ref.cast (ref $String) (local.get $s))) ;; 2. If already immutable, return it directly (if (result (ref eq)) (i32.eq (struct.get $String $immutable (local.get $str)) (i32.const 1)) (then (local.get $str)) (else ;; Otherwise, create a new immutable copy (struct.new $String ; Note: now the mutable and immutable string gets the same hash code. ; Okay? (struct.get $String $hash (local.get $str)) ;; inherit hash (i32.const 1) ;; immutable (call $i32array-copy (struct.get $String $codepoints (local.get $str)) (i32.const 0) (call $i32array-length (struct.get $String $codepoints (local.get $str)))))))) (func $raise-build-string:bad-length (unreachable)) (func $raise-build-string:char-expected (param $v (ref eq)) (call $js-log (local.get $v)) (unreachable)) (func $build-string (type $Prim2) (param $n-raw (ref eq)) (param $proc (ref eq)) (result (ref eq)) (local $n i32) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $arr (ref $I32Array)) (local $i i32) (local $res (ref eq)) (local $cp i32) (local $str (ref $String)) ;; --- Check arguments --- (if (i32.eqz (ref.test (ref i31) (local.get $n-raw))) (then (call $raise-build-string:bad-length))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; --- Decode and prepare --- (local.set $n (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $n-raw))) (i32.const 1))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $arr (call $i32array-make (local.get $n) (i32.const 0))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) ;; --- Loop --- (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $n))) ;; Set argument to current index as fixnum (array.set $Args (local.get $args) (i32.const 0) (ref.i31 (i32.shl (local.get $i) (i32.const 1)))) ;; Call procedure (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) ;; Validate character result (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (call $raise-build-string:char-expected (local.get $res)) (unreachable))) (local.set $cp (i31.get_u (ref.cast (ref i31) (local.get $res)))) (if (i32.ne (i32.and (local.get $cp) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-build-string:char-expected (local.get $res)) (unreachable))) (local.set $cp (i32.shr_u (local.get $cp) (i32.const ,char-shift))) (call $i32array-set! (local.get $arr) (local.get $i) (local.get $cp)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; --- Construct string --- (local.set $str (struct.new $String (i32.const 0) (i32.const 0) (local.get $arr))) (local.get $str)) (func $string-length (type $Prim1) (param $s-raw (ref eq)) (result (ref eq)) (local $s (ref $String)) (local $len i32) ;; --- Type check --- (if (i32.eqz (ref.test (ref $String) (local.get $s-raw))) (then (call $raise-string-length:bad-argument))) ;; --- Decode --- (local.set $s (ref.cast (ref $String) (local.get $s-raw))) ;; --- Compute --- (local.set $len (call $i32array-length (struct.get $String $codepoints (local.get $s)))) ;; --- Return fixnum --- (ref.i31 (i32.shl (local.get $len) (i32.const 1)))) (func $string-length/checked/i32 (param $s (ref $String)) (result i32) ;; Fetch the length of the codepoints array (call $i32array-length (struct.get $String $codepoints (local.get $s)))) (func $string-ref (type $Prim2) (param $s (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $str (ref $String)) (local $idx-i31 (ref i31)) (local $idx i32) ;; --- All tests --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (if (i32.eqz (ref.test (ref i31) (local.get $i))) (then (call $raise-check-fixnum (local.get $i)))) (local.set $idx-i31 (ref.cast (ref i31) (local.get $i))) (local.set $idx (i31.get_u (local.get $idx-i31))) (if (i32.ne (i32.and (local.get $idx) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $i)))) ;; --- All decoding --- (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1))) ;; --- Bounds check and delegate --- (if (result (ref eq)) (i32.lt_u (local.get $idx) (call $string-length/checked/i32 (local.get $str))) (then (call $string-ref/checked (local.get $str) (local.get $idx))) (else (call $raise-bad-string-index/i32 (local.get $s) (local.get $idx)) (unreachable)))) (func $string-ref/checked ; unsafe: no bounds check (param $str (ref $String)) (param $idx i32) (result (ref eq)) (local $arr (ref $I32Array)) (local $cp i32) (local.set $arr (struct.get $String $codepoints (local.get $str))) (local.set $cp (call $i32array-ref (local.get $arr) (local.get $idx))) ;; Return (char): (cp << char-shift) | char-tag (ref.i31 (i32.or (i32.shl (local.get $cp) (i32.const ,char-shift)) (i32.const ,char-tag)))) (func $string-ref/checked/i32 ; unsafe: no bounds check (param $str (ref $String)) (param $idx i32) (result i32) (local $arr (ref $I32Array)) (local $cp i32) (local.set $arr (struct.get $String $codepoints (local.get $str))) (local.set $cp (call $i32array-ref (local.get $arr) (local.get $idx))) (local.get $cp)) (func $raise-string-set!:string-expected (param $x (ref eq)) (unreachable)) (func $raise-string-set!:fixnum-expected (param $x (ref eq)) (unreachable)) (func $raise-string-set!:char-expected (param $x (ref eq)) (unreachable)) (func $raise-string-set!:string-immutable (param $x (ref eq)) (unreachable)) (func $string-set! (param $s (ref eq)) (param $i (ref eq)) (param $ch (ref eq)) (result (ref eq)) (local $str (ref $String)) (local $arr (ref $I32Array)) (local $idx i32) (local $cp i32) (local $tagged i32) ;; --- Type checks --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-string-set!:string-expected (local.get $s)))) (if (i32.eqz (ref.test (ref i31) (local.get $i))) (then (call $raise-string-set!:fixnum-expected (local.get $i)))) (if (i32.eqz (ref.test (ref i31) (local.get $ch))) (then (call $raise-string-set!:char-expected (local.get $ch)))) ;; --- Decode --- (local.set $str (ref.cast (ref $String) (local.get $s))) ;; Check for immutability (if (i32.eq (struct.get $String $immutable (local.get $str)) (i32.const 1)) (then (call $raise-string-set!:string-immutable (local.get $s)))) ;; Decode and validate fixnum index (local.set $idx (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $idx) (i32.const 1))) (then (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1)))) (else (call $raise-string-set!:fixnum-expected (local.get $i)))) ;; Decode and validate character (local.set $tagged (i31.get_u (ref.cast (ref i31) (local.get $ch)))) (if (i32.ne (i32.and (local.get $tagged) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-string-set!:char-expected (local.get $ch)))) (local.set $cp (i32.shr_u (local.get $tagged) (i32.const ,char-shift))) ;; --- Bounds-check and write --- (local.set $arr (struct.get $String $codepoints (local.get $str))) (if (i32.lt_u (local.get $idx) (call $i32array-length (local.get $arr))) (then (call $i32array-set! (local.get $arr) (local.get $idx) (local.get $cp)) ;; Keep the hash. The eq-identity doesn't change. (return (global.get $void))) (else (call $raise-bad-string-index/i32 (local.get $s) (local.get $idx)))) (unreachable)) (func $substring (type $Prim23) (param $s (ref eq)) (param $start (ref eq)) (param $end (ref eq)) ; optional, default to (string-length s) (result (ref eq)) (local $str (ref null $String)) (local $arr (ref $I32Array)) (local $i32start i32) (local $i32end i32) (local $len i32) ;; check string (if (ref.test (ref $String) (local.get $s)) (then (local.set $str (ref.cast (ref $String) (local.get $s)))) (else (call $raise-check-string (local.get $s)))) ;; decode and check start index (if (ref.test (ref i31) (local.get $start)) (then (local.set $i32start (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.ne (i32.and (local.get $i32start) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $start)))) (local.set $i32start (i32.shr_u (local.get $i32start) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)))) ;; get array and length (local.set $arr (struct.get $String $codepoints (local.get $str))) (local.set $len (call $i32array-length (local.get $arr))) ;; supply default value for the optional end index (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $end (ref.i31 (i32.shl (local.get $len) (i32.const 1)))))) ;; decode and check end index (if (ref.test (ref i31) (local.get $end)) (then (local.set $i32end (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.ne (i32.and (local.get $i32end) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $end)))) (local.set $i32end (i32.shr_u (local.get $i32end) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)))) ;; bounds check: start <= end <= len (if (i32.or (i32.gt_u (local.get $i32start) (local.get $i32end)) (i32.gt_u (local.get $i32end) (local.get $len))) (then (call $raise-string-index-out-of-bounds/i32 (local.get $s) (local.get $i32end) (local.get $len)))) ;; create new string (struct.new $String (i32.const 0) ; hash (i32.const 0) ; mutable (also for immutable input) (call $i32array-copy (local.get $arr) (local.get $i32start) (local.get $i32end)))) (func $string-copy (type $Prim1) (param $s (ref eq)) (result (ref eq)) (local $str (ref $String)) (local $arr (ref $I32Array)) (local $len i32) (local $copy (ref $I32Array)) ;; --- Type check --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) ;; --- Cast and extract --- (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $arr (struct.get $String $codepoints (local.get $str))) (local.set $len (array.len (local.get $arr))) ;; --- Copy the codepoint array --- (local.set $copy (call $i32array-copy (local.get $arr) (i32.const 0) (local.get $len))) ;; --- Construct new mutable string with hash = 0 --- (struct.new $String (i32.const 0) ;; $hash (i32.const 0) ;; mutable (local.get $copy))) ;; $codepoints (func $raise-immutable-string (param $x (ref eq)) (unreachable)) (func $string-fill! (type $Prim2) (param $s (ref eq)) (param $ch (ref eq)) (result (ref eq)) (local $str (ref $String)) (local $arr (ref $I32Array)) (local $cp i32) ;; --- 1. Check and cast string --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (local.set $str (ref.cast (ref $String) (local.get $s))) ;; --- 2. Raise if immutable --- (if (i32.ne (struct.get $String $immutable (local.get $str)) (i32.const 0)) (then (call $raise-immutable-string (local.get $s)))) ;; --- 3. Decode and check char immediate --- (if (i32.eqz (ref.test (ref i31) (local.get $ch))) (then (call $raise-check-char (local.get $ch)))) (local.set $cp (i31.get_u (ref.cast (ref i31) (local.get $ch)))) (if (i32.ne (i32.and (local.get $cp) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $ch)))) (local.set $cp (i32.shr_u (local.get $cp) (i32.const ,char-shift))) ;; --- 4. Fill (keep eq-hash-code) --- (local.set $arr (struct.get $String $codepoints (local.get $str))) (call $i32array-fill! (local.get $arr) (local.get $cp)) ;; --- 5. Return void --- (global.get $void)) (func $string-append/2 (param $s1 (ref eq)) (param $s2 (ref eq)) (result (ref $String)) (local $str1 (ref null $String)) (local $str2 (ref null $String)) (if (ref.test (ref $String) (local.get $s1)) (then (local.set $str1 (ref.cast (ref $String) (local.get $s1)))) (else (call $raise-check-string (local.get $s1)))) (if (ref.test (ref $String) (local.get $s2)) (then (local.set $str2 (ref.cast (ref $String) (local.get $s2)))) (else (call $raise-check-string (local.get $s2)))) (struct.new $String (i32.const 0) (i32.const 0) (call $i32array-append (struct.get $String $codepoints (local.get $str1)) (struct.get $String $codepoints (local.get $str2))))) (func $string-append (type $Prim>=0) (param $xs (ref eq)) ;; expects a list of strings (result (ref eq)) (local $n i32) (local $node (ref $Pair)) (local $s (ref $String)) (local $v (ref eq)) (local $orig (ref eq)) (local $total i32) (local $len i32) (local $arr (ref $I32Array)) (local $pos i32) ;; === initialize non-defaultable refs === (local.set $s (ref.cast (ref $String) (global.get $string:empty))) ;; Preserve original list (local.set $orig (local.get $xs)) ;; Determine number of arguments (local.set $n (call $length/i32 (local.get $xs))) ;; Zero arguments -> existing empty string (if (i32.eqz (local.get $n)) (then (return (global.get $string:empty)))) ;; Extract and check first argument (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $String) (local.get $v)) (then (local.set $s (ref.cast (ref $String) (local.get $v)))) (else (call $raise-check-string (local.get $v)))) ;; Single argument -> copy to ensure fresh mutable string (if (i32.eq (local.get $n) (i32.const 1)) (then (if (ref.eq (local.get $s) (global.get $string:empty)) (then (return (global.get $string:empty))) (else (return (call $string-copy (local.get $s))))))) ;; Compute total length (local.set $total (call $i32array-length (struct.get $String $codepoints (local.get $s)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (block $done1 (loop $loop1 (br_if $done1 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $String) (local.get $v)) (then (local.set $s (ref.cast (ref $String) (local.get $v))) (local.set $len (call $i32array-length (struct.get $String $codepoints (local.get $s)))) (local.set $total (i32.add (local.get $total) (local.get $len)))) (else (call $raise-check-string (local.get $v)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop1))) ;; All strings empty -> return empty string (if (i32.eqz (local.get $total)) (then (return (global.get $string:empty)))) ;; Allocate result array (local.set $arr (call $i32array-make (local.get $total) (i32.const 0))) ;; Copy strings into result array (local.set $xs (local.get $orig)) (local.set $pos (i32.const 0)) (block $done2 (loop $loop2 (br_if $done2 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $s (ref.cast (ref $String) (struct.get $Pair $a (local.get $node)))) (local.set $len (call $i32array-length (struct.get $String $codepoints (local.get $s)))) (call $i32array-copy! (local.get $arr) (local.get $pos) (struct.get $String $codepoints (local.get $s)) (i32.const 0) (local.get $len)) (local.set $pos (i32.add (local.get $pos) (local.get $len))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop2))) (struct.new $String (i32.const 0) (i32.const 0) (local.get $arr))) (func $string-append-immutable (type $Prim>=0) (param $xs (ref eq)) ;; list of strings (result (ref eq)) (local $s (ref $String)) (local $arr (ref $I32Array)) ;; Do the append (returns a fresh $String) (local.set $s (ref.cast (ref $String) (call $string-append (local.get $xs)))) ;; Reuse its codepoint array (local.set $arr (struct.get $String $codepoints (local.get $s))) ;; Build a new $String that is immutable (=1). Hash = 0 (lazy). (struct.new $String (i32.const 0) ;; $hash (i32.const 1) ;; $immutable (local.get $arr))) ;; $codepoints (func $string-append* (type $Prim>=1) (param $str (ref eq)) ;; string? or list of strings (param $rest (ref eq)) ;; additional strings, last element list (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $acc (ref eq)) (local $last (ref eq)) (local $args (ref eq)) ; initialize locals with no defaults (local.set $last (global.get $false)) ;; no extra args: first arg supplies list of strings (if (ref.eq (local.get $rest) (global.get $null)) (then (return (call $string-append (local.get $str))))) ;; separate last list from preceding string arguments (local.set $xs (local.get $rest)) (local.set $acc (global.get $null)) (block $done (loop $loop (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $xs (struct.get $Pair $d (local.get $node))) (if (ref.eq (local.get $xs) (global.get $null)) (then (local.set $last (struct.get $Pair $a (local.get $node))) (br $done)) (else (local.set $acc (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $node)) (local.get $acc))) (br $loop))))) ;; rebuild argument list in proper order (local.set $args (local.get $last)) (block $done2 (loop $loop2 (br_if $done2 (ref.eq (local.get $acc) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $acc))) (local.set $args (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $node)) (local.get $args))) (local.set $acc (struct.get $Pair $d (local.get $node))) (br $loop2))) (local.set $args (struct.new $Pair (i32.const 0) (local.get $str) (local.get $args))) (call $string-append (local.get $args))) ;; Note: Simplified version: accepts list of strings and optional separator only. (func $string-join (type $Prim12) (param $strs (ref eq)) ;; listof string? (param $sep (ref eq)) ;; optional string?, default = " " (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $str (ref $String)) (local $sep-str (ref $String)) (local $sep-len i32) (local $n i32) (local $total i32) (local $len i32) (local $arr (ref $I32Array)) (local $pos i32) ;; --- Handle optional separator --- (local.set $sep-str (if (result (ref $String)) (ref.eq (local.get $sep) (global.get $missing)) (then (ref.cast (ref $String) (global.get $string:space))) (else (if (result (ref $String)) (ref.test (ref $String) (local.get $sep)) (then (ref.cast (ref $String) (local.get $sep))) (else (call $raise-check-string (local.get $sep)) (unreachable)))))) ;; --- Determine separator length --- (local.set $sep-len (call $i32array-length (struct.get $String $codepoints (local.get $sep-str)))) ;; --- Determine number of strings --- (local.set $n (call $length/i32 (local.get $strs))) (if (i32.eqz (local.get $n)) (then (return (global.get $string:empty)))) ;; --- Compute total length --- (local.set $xs (local.get $strs)) (local.set $total (i32.mul (i32.sub (local.get $n) (i32.const 1)) (local.get $sep-len))) (block $done1 (loop $loop1 (br_if $done1 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $String) (local.get $v)) (then (local.set $str (ref.cast (ref $String) (local.get $v))) (local.set $len (call $i32array-length (struct.get $String $codepoints (local.get $str)))) (local.set $total (i32.add (local.get $total) (local.get $len)))) (else (call $raise-check-string (local.get $v)) (unreachable))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop1))) ;; --- All empty -> empty string --- (if (i32.eqz (local.get $total)) (then (return (global.get $string:empty)))) ;; --- Allocate result array --- (local.set $arr (call $i32array-make (local.get $total) (i32.const 0))) ;; --- Copy strings and separators --- (local.set $xs (local.get $strs)) (local.set $pos (i32.const 0)) (block $done2 (loop $loop2 (br_if $done2 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $str (ref.cast (ref $String) (struct.get $Pair $a (local.get $node)))) (local.set $len (call $i32array-length (struct.get $String $codepoints (local.get $str)))) (call $i32array-copy! (local.get $arr) (local.get $pos) (struct.get $String $codepoints (local.get $str)) (i32.const 0) (local.get $len)) (local.set $pos (i32.add (local.get $pos) (local.get $len))) (local.set $xs (struct.get $Pair $d (local.get $node))) (if (ref.eq (local.get $xs) (global.get $null)) (then (nop)) (else (call $i32array-copy! (local.get $arr) (local.get $pos) (struct.get $String $codepoints (local.get $sep-str)) (i32.const 0) (local.get $sep-len)) (local.set $pos (i32.add (local.get $pos) (local.get $sep-len))))) (br $loop2))) ;; --- Build result string --- (struct.new $String (i32.const 0) (i32.const 0) (local.get $arr))) ;; Note: Unlike Racket's string-split, this variant does not support ;; keyword arguments or regular-expression separators. (func $string-split (type $Prim14) (param $str-raw (ref eq)) ;; string? (param $sep-raw (ref eq)) ;; optional string?, default = " " (param $trim-raw (ref eq)) ;; optional any/c, default = #t (param $repeat-raw (ref eq)) ;; optional any/c, default = #f (result (ref eq)) (local $str (ref $String)) (local $sep (ref $String)) (local $trim-val (ref eq)) (local $repeat-val (ref eq)) (local $arr-str (ref $I32Array)) (local $arr-sep (ref $I32Array)) (local $len-str i32) (local $len-sep i32) (local $trim-flag i32) (local $repeat-flag i32) (local $start i32) (local $end i32) (local $match-pos i32) (local $segment-len i32) (local $acc (ref eq)) (local $piece (ref $String)) (local $idx i32) (local $empty (ref $String)) (local $found i32) (local $pos i32) ;; --- Decode defaults --- (if (i32.eqz (ref.test (ref $String) (local.get $str-raw))) (then (call $raise-check-string (local.get $str-raw)))) (local.set $str (ref.cast (ref $String) (local.get $str-raw))) (local.set $sep (if (result (ref $String)) (ref.eq (local.get $sep-raw) (global.get $missing)) (then (ref.cast (ref $String) (global.get $string:space))) (else (if (result (ref $String)) (ref.test (ref $String) (local.get $sep-raw)) (then (ref.cast (ref $String) (local.get $sep-raw))) (else (call $raise-check-string (local.get $sep-raw)) (unreachable)))))) (local.set $trim-val (if (result (ref eq)) (ref.eq (local.get $trim-raw) (global.get $missing)) (then (global.get $true)) (else (local.get $trim-raw)))) (local.set $repeat-val (if (result (ref eq)) (ref.eq (local.get $repeat-raw) (global.get $missing)) (then (global.get $false)) (else (local.get $repeat-raw)))) (local.set $trim-flag (if (result i32) (ref.eq (local.get $trim-val) (global.get $false)) (then (i32.const 0)) (else (i32.const 1)))) (local.set $repeat-flag (if (result i32) (ref.eq (local.get $repeat-val) (global.get $false)) (then (i32.const 0)) (else (i32.const 1)))) (local.set $arr-str (struct.get $String $codepoints (local.get $str))) (local.set $arr-sep (struct.get $String $codepoints (local.get $sep))) (local.set $len-str (call $i32array-length (local.get $arr-str))) (local.set $len-sep (call $i32array-length (local.get $arr-sep))) (local.set $start (i32.const 0)) (local.set $end (local.get $len-str)) ;; --- Trim using literal separator when requested --- (if (i32.and (local.get $trim-flag) (i32.ne (local.get $len-sep) (i32.const 0))) (then (local.set $start (call $string-split:trim-left (local.get $arr-str) (local.get $arr-sep) (local.get $len-sep) (local.get $start) (local.get $end) (local.get $repeat-flag))) (local.set $end (call $string-split:trim-right (local.get $arr-str) (local.get $arr-sep) (local.get $len-sep) (local.get $start) (local.get $end) (local.get $repeat-flag))))) ;; --- Special-case empty after trimming --- (if (i32.eq (local.get $start) (local.get $end)) (then (if (local.get $trim-flag) (then (return (global.get $null))) (else (local.set $empty (ref.cast (ref $String) (global.get $string:empty))) (return (struct.new $Pair (i32.const 0) (local.get $empty) (global.get $null))))))) ;; --- Handle empty separator by splitting into codepoints --- (if (i32.eqz (local.get $len-sep)) (then (local.set $acc (global.get $null)) (local.set $idx (i32.sub (local.get $end) (i32.const 1))) (block $chars-done (loop $chars (br_if $chars-done (i32.lt_s (local.get $idx) (local.get $start))) (local.set $piece (call $codepoint->string (call $i32array-ref (local.get $arr-str) (local.get $idx)))) (local.set $acc (struct.new $Pair (i32.const 0) (local.get $piece) (local.get $acc))) (if (i32.eq (local.get $idx) (local.get $start)) (then (br $chars-done))) (local.set $idx (i32.sub (local.get $idx) (i32.const 1))) (br $chars))) (return (local.get $acc)))) (local.set $acc (global.get $null)) (local.set $pos (local.get $start)) (local.set $empty (ref.cast (ref $String) (global.get $string:empty))) (block $done (loop $split (local.set $match-pos (call $string-split:find-match (local.get $arr-str) (local.get $arr-sep) (local.get $len-sep) (local.get $pos) (local.get $end))) (local.set $segment-len (i32.sub (local.get $match-pos) (local.get $pos))) (local.set $found (i32.ne (local.get $match-pos) (local.get $end))) (if (i32.or (i32.ne (local.get $segment-len) (i32.const 0)) (i32.eqz (local.get $repeat-flag))) (then (local.set $piece (if (result (ref $String)) (i32.eqz (local.get $segment-len)) (then (local.get $empty)) (else (call $i32array->string (call $i32array-copy (local.get $arr-str) (local.get $pos) (local.get $match-pos)))))) (local.set $acc (struct.new $Pair (i32.const 0) (local.get $piece) (local.get $acc))))) (if (i32.eqz (local.get $found)) (then (br $done))) (local.set $pos (i32.add (local.get $match-pos) (local.get $len-sep))) (if (local.get $repeat-flag) (then (local.set $pos (call $string-split:skip-repeats (local.get $arr-str) (local.get $arr-sep) (local.get $len-sep) (local.get $pos) (local.get $end))))) (br $split))) (call $reverse (local.get $acc))) (func $string-split:matches? (param $arr-str (ref $I32Array)) (param $arr-sep (ref $I32Array)) (param $pos i32) (param $len-sep i32) (result i32) (local $j i32) (local.set $j (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $j) (local.get $len-sep))) (if (i32.ne (call $i32array-ref (local.get $arr-str) (i32.add (local.get $pos) (local.get $j))) (call $i32array-ref (local.get $arr-sep) (local.get $j))) (then (return (i32.const 0)))) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $loop))) (i32.const 1)) (func $string-split:trim-left (param $arr-str (ref $I32Array)) (param $arr-sep (ref $I32Array)) (param $len-sep i32) (param $start i32) (param $end i32) (param $repeat i32) (result i32) (local $pos i32) (local.set $pos (local.get $start)) (block $done (loop $loop (br_if $done (i32.lt_u (i32.sub (local.get $end) (local.get $pos)) (local.get $len-sep))) (if (i32.eqz (call $string-split:matches? (local.get $arr-str) (local.get $arr-sep) (local.get $pos) (local.get $len-sep))) (then (br $done))) (local.set $pos (i32.add (local.get $pos) (local.get $len-sep))) (if (i32.eqz (local.get $repeat)) (then (br $done))) (br $loop))) (local.get $pos)) (func $string-split:trim-right (param $arr-str (ref $I32Array)) (param $arr-sep (ref $I32Array)) (param $len-sep i32) (param $start i32) (param $end i32) (param $repeat i32) (result i32) (local $limit i32) (local $match-start i32) (local.set $limit (local.get $end)) (block $done (loop $loop (br_if $done (i32.lt_u (i32.sub (local.get $limit) (local.get $start)) (local.get $len-sep))) (local.set $match-start (i32.sub (local.get $limit) (local.get $len-sep))) (if (i32.eqz (call $string-split:matches? (local.get $arr-str) (local.get $arr-sep) (local.get $match-start) (local.get $len-sep))) (then (br $done))) (local.set $limit (local.get $match-start)) (if (i32.eqz (local.get $repeat)) (then (br $done))) (br $loop))) (local.get $limit)) (func $string-split:find-match (param $arr-str (ref $I32Array)) (param $arr-sep (ref $I32Array)) (param $len-sep i32) (param $pos i32) (param $end i32) (result i32) (local $limit i32) (local $scan i32) (if (i32.eqz (local.get $len-sep)) (then (return (local.get $pos)))) (if (i32.gt_u (local.get $len-sep) (local.get $end)) (then (return (local.get $end)))) (local.set $limit (i32.sub (local.get $end) (local.get $len-sep))) (if (i32.gt_u (local.get $pos) (local.get $limit)) (then (return (local.get $end)))) (local.set $scan (local.get $pos)) (block $done (loop $loop (br_if $done (i32.gt_u (local.get $scan) (local.get $limit))) (if (call $string-split:matches? (local.get $arr-str) (local.get $arr-sep) (local.get $scan) (local.get $len-sep)) (then (return (local.get $scan)))) (local.set $scan (i32.add (local.get $scan) (i32.const 1))) (br $loop))) (local.get $end)) (func $string-split:skip-repeats (param $arr-str (ref $I32Array)) (param $arr-sep (ref $I32Array)) (param $len-sep i32) (param $pos i32) (param $end i32) (result i32) (local $scan i32) (local $limit i32) (if (i32.eqz (local.get $len-sep)) (then (return (local.get $pos)))) (if (i32.gt_u (local.get $len-sep) (local.get $end)) (then (return (local.get $pos)))) (local.set $limit (i32.sub (local.get $end) (local.get $len-sep))) (local.set $scan (local.get $pos)) (block $done (loop $loop (br_if $done (i32.gt_u (local.get $scan) (local.get $limit))) (if (i32.eqz (call $string-split:matches? (local.get $arr-str) (local.get $arr-sep) (local.get $scan) (local.get $len-sep))) (then (br $done))) (local.set $scan (i32.add (local.get $scan) (local.get $len-sep))) (br $loop))) (local.get $scan)) ;; 4.4.3 String Conversions ,@(for/list ([name+imp (in-list '(($string-upcase $char-upcase/ucs) ($string-downcase $char-downcase/ucs) ($string-foldcase $char-foldcase/ucs)))]) (define name (car name+imp)) (define imp (cadr name+imp)) `(func ,name (type $Prim1) (param $s (ref eq)) (result (ref eq)) (local $str (ref $String)) (local $arr (ref $I32Array)) (local $len i32) (local $res (ref $I32Array)) (local $i i32) (local $cp i32) (local $cp2 i32) ;; Type check (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) ;; Extract codepoint array and length (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $arr (struct.get $String $codepoints (local.get $str))) (local.set $len (array.len (local.get $arr))) ;; Allocate result array (local.set $res (call $i32array-make (local.get $len) (i32.const 0))) ;; Loop over characters (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $cp (call $i32array-ref (local.get $arr) (local.get $i))) (local.set $cp2 (call ,imp (local.get $cp))) (call $i32array-set! (local.get $res) (local.get $i) (local.get $cp2)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Build new mutable string (struct.new $String (i32.const 0) (i32.const 0) (local.get $res)))) ;; string-titlecase : string? -> string? ;; Note: Approximates Unicode Case_Ignorable via general categories ;; Mn, Me, Cf, Lm, Sk. No optional parameters. (func $string-titlecase (type $Prim1) (param $s (ref eq)) (result (ref eq)) (local $t (ref $String)) (local $len i32) (local $i i32) (local $in-run i32) (local $c (ref eq)) (local $cat (ref eq)) ;; --- Type check --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)) (unreachable))) ;; --- Make mutable copy and get length --- (local.set $t (ref.cast (ref $String) (call $string-copy (local.get $s)))) (local.set $len (call $string-length/checked/i32 (local.get $t))) (local.set $i (i32.const 0)) (local.set $in-run (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $c (call $string-ref/checked (local.get $t) (local.get $i))) (if (i32.or (ref.eq (call $char-upper-case? (local.get $c)) (global.get $true)) (ref.eq (call $char-lower-case? (local.get $c)) (global.get $true))) (then (drop ; rememember, string-set! returns `void` (call $string-set! (local.get $t) (ref.i31 (i32.shl (local.get $i) (i32.const 1))) (if (result (ref eq)) (local.get $in-run) (then (call $char-downcase (local.get $c))) (else (call $char-upcase (local.get $c)))))) (local.set $in-run (i32.const 1))) (else (local.set $cat (call $char-general-category (local.get $c))) (if (i32.eqz (i32.or (ref.eq (local.get $cat) (global.get $symbol:mn)) (i32.or (ref.eq (local.get $cat) (global.get $symbol:me)) (i32.or (ref.eq (local.get $cat) (global.get $symbol:cf)) (i32.or (ref.eq (local.get $cat) (global.get $symbol:lm)) (ref.eq (local.get $cat) (global.get $symbol:sk))))))) (then (local.set $in-run (i32.const 0)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $t)) (func $string->list (type $Prim1) (param $s (ref eq)) (result (ref eq)) (local $str (ref null $String)) (local $arr (ref $I32Array)) (local $len i32) (local $i i32) (local $res (ref $Pair)) (local $cp i32) ;; check string (if (ref.test (ref $String) (local.get $s)) (then (local.set $str (ref.cast (ref $String) (local.get $s)))) (else (call $raise-check-string (local.get $s)))) (local.set $arr (struct.get $String $codepoints (local.get $str))) (local.set $len (call $i32array-length (local.get $arr))) ;; special case len=0 (if (result (ref eq)) (i32.eqz (local.get $len)) (then (return (global.get $null))) ; ;; special case len>=1 (else (local.set $i (i32.sub (local.get $len) (i32.const 1))) ; from end (local.set $cp (call $i32array-ref (local.get $arr) (local.get $i))) (local.set $res (struct.new $Pair (i32.const 0) (ref.i31 (i32.or (i32.shl (local.get $cp) (i32.const 8)) (i32.const ,char-tag))) (global.get $null))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (block $done (loop $build (br_if $done (i32.lt_s (local.get $i) (i32.const 0))) (local.set $cp (call $i32array-ref (local.get $arr) (local.get $i))) ;; tag cp as char (local.set $res (struct.new $Pair (i32.const 0) (ref.i31 (i32.or (i32.shl (local.get $cp) (i32.const 8)) (i32.const ,char-tag))) (local.get $res))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $build))) (local.get $res)))) (func $list->string (type $Prim1) (param $xs (ref eq)) (result (ref eq)) (local $len i32) (local $str (ref $String)) (local $arr (ref $I32Array)) (local $i i32) (local $node (ref eq)) (local $chimm (ref eq)) (local $cp i32) ;; 1. Compute list length (will raise if not proper list) (local.set $len (call $length/i32 (local.get $xs))) ;; 2. Create new mutable string (local.set $str (struct.new $String (i32.const 0) ;; hash (i32.const 0) ;; immutable = false (call $i32array-make (local.get $len) (i32.const 0)))) ;; 3. Grab codepoint array once (local.set $arr (struct.get $String $codepoints (local.get $str))) ;; 4. Fill from list directly (local.set $i (i32.const 0)) (local.set $node (local.get $xs)) (block $done (loop $fill (br_if $done (ref.eq (local.get $node) (global.get $null))) ;; Check node is a pair (if (ref.test (ref $Pair) (local.get $node)) (then ;; extract car (local.set $chimm (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $node)))) ;; decode and validate character (local.set $cp (i31.get_u (ref.cast (ref i31) (local.get $chimm)))) (if (i32.ne (i32.and (local.get $cp) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $chimm)) (unreachable))) ;; extract codepoint (local.set $cp (i32.shr_u (local.get $cp) (i32.const 8))) ;; write codepoint (call $i32array-set! (local.get $arr) (local.get $i) (local.get $cp)) ;; advance (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $node (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $node))))) (else (call $raise-pair-expected (local.get $node)) (unreachable))) (br $fill))) ;; 5. Return string (local.get $str)) ;; 4.4.2 String Comparisons (func $string=?/2 (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (if (result (ref eq)) (call $string=?/i32 (local.get $a) (local.get $b)) (then (global.get $true)) (else (global.get $false)))) ;; string=? : string? string? ... -> boolean? (at least 1) (func $string=? (type $Prim>=1) (param $a (ref eq)) ;; string? (param $rest (ref eq)) ;; list of string? (result (ref eq)) (local $prev (ref $String)) (local $curr (ref $String)) (local $restlist (ref eq)) (local $pair (ref $Pair)) (local $next (ref eq)) ;; Type check first argument (if (i32.eqz (ref.test (ref $String) (local.get $a))) (then (call $raise-argument-error:string-expected (local.get $a)) (unreachable))) (local.set $prev (ref.cast (ref $String) (local.get $a))) (local.set $restlist (local.get $rest)) ;; Single argument => #t (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (loop $loop (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $restlist))) (then (call $raise-pair-expected (local.get $restlist)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $restlist))) (local.set $next (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $String) (local.get $next))) (then (call $raise-argument-error:string-expected (local.get $next)) (unreachable))) (local.set $curr (ref.cast (ref $String) (local.get $next))) (if (i32.eqz (call $string=?/i32/checked (local.get $prev) (local.get $curr))) (then (return (global.get $false)))) (local.set $prev (local.get $curr)) (local.set $restlist (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) (func $string=?/i32 (param $a-raw (ref eq)) (param $b-raw (ref eq)) (result i32) (local $a (ref $String)) (local $b (ref $String)) ;; Check types (if (i32.eqz (ref.test (ref $String) (local.get $a-raw))) (then (return (i32.const 0)))) (if (i32.eqz (ref.test (ref $String) (local.get $b-raw))) (then (return (i32.const 0)))) ;; Cast and delegate (local.set $a (ref.cast (ref $String) (local.get $a-raw))) (local.set $b (ref.cast (ref $String) (local.get $b-raw))) (return_call $string=?/i32/checked (local.get $a) (local.get $b))) (func $string=?/i32/checked (param $a (ref $String)) (param $b (ref $String)) (result i32) (return_call $i32array-equal? (struct.get $String $codepoints (local.get $a)) (struct.get $String $codepoints (local.get $b)))) (func $string boolean? (at least 1) (func $string=1) (param $a (ref eq)) ;; string? (param $rest (ref eq)) ;; list of string? (result (ref eq)) (local $prev (ref $String)) (local $curr (ref $String)) (local $restlist (ref eq)) (local $pair (ref $Pair)) (local $next (ref eq)) ;; Type check first argument (if (i32.eqz (ref.test (ref $String) (local.get $a))) (then (call $raise-argument-error:string-expected (local.get $a)) (unreachable))) (local.set $prev (ref.cast (ref $String) (local.get $a))) (local.set $restlist (local.get $rest)) ;; Single argument => #t (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (loop $loop (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $restlist))) (then (call $raise-pair-expected (local.get $restlist)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $restlist))) (local.set $next (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $String) (local.get $next))) (then (call $raise-argument-error:string-expected (local.get $next)) (unreachable))) (local.set $curr (ref.cast (ref $String) (local.get $next))) (if (i32.eqz (call $string boolean? (at least 1) (func $string<=? (type $Prim>=1) (param $a (ref eq)) ;; string? (param $rest (ref eq)) ;; list of string? (result (ref eq)) (local $prev (ref $String)) (local $curr (ref $String)) (local $restlist (ref eq)) (local $pair (ref $Pair)) (local $next (ref eq)) ;; Type check first argument (if (i32.eqz (ref.test (ref $String) (local.get $a))) (then (call $raise-argument-error:string-expected (local.get $a)) (unreachable))) (local.set $prev (ref.cast (ref $String) (local.get $a))) (local.set $restlist (local.get $rest)) ;; Single argument => #t (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (loop $loop (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $restlist))) (then (call $raise-pair-expected (local.get $restlist)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $restlist))) (local.set $next (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $String) (local.get $next))) (then (call $raise-argument-error:string-expected (local.get $next)) (unreachable))) (local.set $curr (ref.cast (ref $String) (local.get $next))) ;; Fail if current < previous (if (i32.ne (call $string?/2 (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (if (result (ref eq)) (call $string? : string? string? ... -> boolean? (at least 1) (func $string>? (type $Prim>=1) (param $a (ref eq)) ;; string? (param $rest (ref eq)) ;; list of string? (result (ref eq)) (local $prev (ref $String)) (local $curr (ref $String)) (local $restlist (ref eq)) (local $pair (ref $Pair)) (local $next (ref eq)) ;; Type check first argument (if (i32.eqz (ref.test (ref $String) (local.get $a))) (then (call $raise-argument-error:string-expected (local.get $a)) (unreachable))) (local.set $prev (ref.cast (ref $String) (local.get $a))) (local.set $restlist (local.get $rest)) ;; Single argument => #t (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (loop $loop (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $restlist))) (then (call $raise-pair-expected (local.get $restlist)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $restlist))) (local.set $next (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $String) (local.get $next))) (then (call $raise-argument-error:string-expected (local.get $next)) (unreachable))) (local.set $curr (ref.cast (ref $String) (local.get $next))) (if (i32.eqz (call $string=?/2 (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (if (result (ref eq)) (i32.or (call $string=? : string? string? ... -> boolean? (at least 1) (func $string>=? (type $Prim>=1) (param $a (ref eq)) ;; string? (param $rest (ref eq)) ;; list of string? (result (ref eq)) (local $prev (ref $String)) (local $curr (ref $String)) (local $restlist (ref eq)) (local $pair (ref $Pair)) (local $next (ref eq)) ;; Type check first argument (if (i32.eqz (ref.test (ref $String) (local.get $a))) (then (call $raise-argument-error:string-expected (local.get $a)) (unreachable))) (local.set $prev (ref.cast (ref $String) (local.get $a))) (local.set $restlist (local.get $rest)) ;; Single argument => #t (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (loop $loop (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $restlist))) (then (call $raise-pair-expected (local.get $restlist)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $restlist))) (local.set $next (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $String) (local.get $next))) (then (call $raise-argument-error:string-expected (local.get $next)) (unreachable))) (local.set $curr (ref.cast (ref $String) (local.get $next))) ;; Fail if previous < current (if (i32.ne (call $string? gt) ($string-ci>=? ge)))] [form (in-list (let* ([cmp (car entry)] [kind (cadr entry)] [cmp/2 (string->symbol (~a cmp "/2"))] [cmp/i32 (string->symbol (~a cmp "/i32"))] [cmp/i32/checked (string->symbol (~a cmp "/i32/checked"))] [cond-expr (case kind [(eq) `(i32.eqz (call $string-ci-compare/checked (local.get $a) (local.get $b)))] [(lt) `(i32.lt_s (call $string-ci-compare/checked (local.get $a) (local.get $b)) (i32.const 0))] [(le) `(i32.le_s (call $string-ci-compare/checked (local.get $a) (local.get $b)) (i32.const 0))] [(gt) `(i32.gt_s (call $string-ci-compare/checked (local.get $a) (local.get $b)) (i32.const 0))] [(ge) `(i32.ge_s (call $string-ci-compare/checked (local.get $a) (local.get $b)) (i32.const 0))])] [fail-cond (case kind [(eq) `(i32.ne (local.get $cmp) (i32.const 0))] [(lt) `(i32.ge_s (local.get $cmp) (i32.const 0))] [(le) `(i32.gt_s (local.get $cmp) (i32.const 0))] [(gt) `(i32.le_s (local.get $cmp) (i32.const 0))] [(ge) `(i32.lt_s (local.get $cmp) (i32.const 0))])]) (list `(func ,cmp/2 (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (if (result (ref eq)) (call ,cmp/i32 (local.get $a) (local.get $b)) (then (global.get $true)) (else (global.get $false)))) `(func ,cmp (type $Prim>=1) (param $a (ref eq)) ;; string? (param $rest (ref eq)) ;; list of string? (result (ref eq)) (local $prev (ref $String)) (local $curr (ref $String)) (local $restlist (ref eq)) (local $pair (ref $Pair)) (local $next (ref eq)) (local $cmp i32) ;; Type check first argument (if (i32.eqz (ref.test (ref $String) (local.get $a))) (then (call $raise-argument-error:string-expected (local.get $a)) (unreachable))) (local.set $prev (ref.cast (ref $String) (local.get $a))) (local.set $restlist (local.get $rest)) ;; Single argument => #t (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (loop $loop (if (ref.eq (local.get $restlist) (global.get $null)) (then (return (global.get $true)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $restlist))) (then (call $raise-pair-expected (local.get $restlist)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $restlist))) (local.set $next (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $String) (local.get $next))) (then (call $raise-argument-error:string-expected (local.get $next)) (unreachable))) (local.set $curr (ref.cast (ref $String) (local.get $next))) (local.set $cmp (call $string-ci-compare/checked (local.get $prev) (local.get $curr))) (if ,fail-cond (then (return (global.get $false)))) (local.set $prev (local.get $curr)) (local.set $restlist (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) `(func ,cmp/i32 (param $a-raw (ref eq)) (param $b-raw (ref eq)) (result i32) (local $a (ref $String)) (local $b (ref $String)) (if (i32.eqz (ref.test (ref $String) (local.get $a-raw))) (then (return (i32.const 0)))) (if (i32.eqz (ref.test (ref $String) (local.get $b-raw))) (then (return (i32.const 0)))) (local.set $a (ref.cast (ref $String) (local.get $a-raw))) (local.set $b (ref.cast (ref $String) (local.get $b-raw))) (return_call ,cmp/i32/checked (local.get $a) (local.get $b))) `(func ,cmp/i32/checked (param $a (ref $String)) (param $b (ref $String)) (result i32) (return ,cond-expr)))))]) form) ;;; (func $raise-invalid-utf8-input (param $bad (ref eq)) (result (ref eq)) (unreachable)) (func $raise-invalid-utf8-start (param $bad (ref eq)) (result (ref eq)) (unreachable)) (func $raise-invalid-utf8-end (param $bad (ref eq)) (result (ref eq)) (unreachable)) (func $raise-invalid-utf8-range (param $start (ref eq)) (param $end (ref eq)) (result (ref eq)) (unreachable)) (func $raise-string->bytes/utf8 (unreachable)) (func $raise-string->bytes/utf8:expected-string (unreachable)) (func $raise-string->bytes/utf8:range-error (unreachable)) (func $string->bytes/utf-8 (export "string->bytes/utf-8") (param $str (ref eq)) (param $err-byte (ref eq)) ;; Ignored (param $start-raw (ref eq)) (param $end-raw (ref eq)) (result (ref eq)) (local $s (ref null $String)) (local $cp (ref $I32Array)) (local $start i32) (local $end i32) (local $len i32) (local $out-bytes (ref $I8Array)) (local $idx i32) (local $i i32) (local $char i32) (local $bs (ref $Bytes)) (local $raw-start i32) (local $raw-end i32) ;; Check and cast $str (if (ref.test (ref $String) (local.get $str)) (then (local.set $s (ref.cast (ref $String) (local.get $str)))) (else (call $raise-string->bytes/utf8:expected-string))) ;; code 1 = not a string ;; Get codepoints array and length (local.set $cp (struct.get $String $codepoints (local.get $s))) (local.set $len (array.len (local.get $cp))) ;; Decode start (if (ref.test (ref i31) (local.get $start-raw)) (then (local.set $raw-start (i31.get_u (ref.cast (ref i31) (local.get $start-raw)))) (if (i32.eq (local.get $raw-start) (i32.const ,(immediate-rep #f))) (then (local.set $start (i32.const 0))) ;; #f => use 0 (else (local.set $start (i32.shr_u (local.get $raw-start) (i32.const 1)))))) (else (call $raise-string->bytes/utf8))) ;; not a fixnum ;; Decode end (if (ref.test (ref i31) (local.get $end-raw)) (then (local.set $raw-end (i31.get_u (ref.cast (ref i31) (local.get $end-raw)))) (if (i32.eq (local.get $raw-end) (i32.const ,(immediate-rep #f))) ; #f (then (local.set $end (local.get $len))) ;; #f => use length (else (local.set $end (i32.shr_u (local.get $raw-end) (i32.const 1)))))) (else (call $raise-string->bytes/utf8))) ;; Bounds check (if (i32.or (i32.gt_u (local.get $start) (local.get $end)) (i32.gt_u (local.get $end) (local.get $len))) (then (call $raise-string->bytes/utf8:range-error))) ;; First pass: compute output size (local.set $i (local.get $start)) (local.set $idx (i32.const 0)) (loop $size-loop (if (i32.lt_u (local.get $i) (local.get $end)) (then (local.set $char (array.get $I32Array (local.get $cp) (local.get $i))) (local.set $idx (i32.add (local.get $idx) (call $utf8-size (local.get $char)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $size-loop)))) ;; Allocate byte array (local.set $out-bytes (array.new_default $I8Array (local.get $idx))) (local.set $bs (struct.new $Bytes (i32.const 0) ;; hash (i32.const 1) ;; immutable (local.get $out-bytes))) ;; Second pass: encode into buffer (local.set $i (local.get $start)) (local.set $idx (i32.const 0)) (loop $encode-loop (if (i32.lt_u (local.get $i) (local.get $end)) (then (local.set $char (array.get $I32Array (local.get $cp) (local.get $i))) (local.set $idx (call $write-utf8 (local.get $bs) (local.get $idx) (local.get $char))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $encode-loop)))) (local.get $bs)) (func $raise-string-utf-8-length:bad-argument (unreachable)) (func $raise-string-utf-8-length:range-error (unreachable)) (func $string-utf-8-length (type $Prim13) (param $str (ref eq)) (param $start-raw (ref eq)) ;; fixnum or $missing (param $end-raw (ref eq)) ;; fixnum or $missing (result (ref eq)) (local $s (ref null $String)) (local $cp (ref $I32Array)) (local $len i32) (local $start i32) (local $end i32) (local $i i32) (local $char i32) (local $total i32) ;; --- Type check for string --- (if (ref.test (ref $String) (local.get $str)) (then (local.set $s (ref.cast (ref $String) (local.get $str)))) (else (call $raise-string-utf-8-length:bad-argument))) ;; --- Extract codepoints and length --- (local.set $cp (struct.get $String $codepoints (local.get $s))) (local.set $len (array.len (local.get $cp))) ;; --- Decode start --- (if (ref.eq (local.get $start-raw) (global.get $missing)) (then (local.set $start (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start-raw)) (then (local.set $start (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $start-raw))) (i32.const 1)))) (else (call $raise-string-utf-8-length:bad-argument))))) ;; --- Decode end --- (if (ref.eq (local.get $end-raw) (global.get $missing)) (then (local.set $end (local.get $len))) (else (if (ref.test (ref i31) (local.get $end-raw)) (then (local.set $end (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $end-raw))) (i32.const 1)))) (else (call $raise-string-utf-8-length:bad-argument))))) ;; --- Range check --- (if (i32.or (i32.gt_u (local.get $start) (local.get $end)) (i32.gt_u (local.get $end) (local.get $len))) (then (call $raise-string-utf-8-length:range-error))) ;; --- Compute UTF-8 length --- (local.set $i (local.get $start)) (local.set $total (i32.const 0)) (loop $len-loop (if (i32.lt_u (local.get $i) (local.get $end)) (then (local.set $char (array.get $I32Array (local.get $cp) (local.get $i))) (local.set $total (i32.add (local.get $total) (call $utf8-size (local.get $char)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $len-loop)))) (ref.i31 (i32.shl (local.get $total) (i32.const 1)))) (func $raise-string-copy!:bad-destination (unreachable)) (func $raise-string-copy!:bad-destination-start (unreachable)) (func $raise-string-copy!:bad-source (unreachable)) (func $raise-string-copy!:bad-source-start (unreachable)) (func $raise-string-copy!:bad-source-end (unreachable)) (func $raise-string-copy!:bad-source-range (unreachable)) (func $raise-string-copy!:bad-destination-range (unreachable)) (func $string-copy! (type $Prim35) (param $dst-raw (ref eq)) (param $dst-start-raw (ref eq)) (param $src-raw (ref eq)) (param $src-start-raw (ref eq)) ;; fixnum or $missing (param $src-end-raw (ref eq)) ;; fixnum or $missing (result (ref eq)) (local $dst (ref $String)) (local $src (ref $String)) (local $dst-start i32) (local $src-start i32) (local $src-end i32) (local $src-len i32) (local $dst-len i32) ;; --- Type + fixnum checks --- (if (i32.eqz (ref.test (ref $String) (local.get $dst-raw))) (then (call $raise-string-copy!:bad-destination))) (if (i32.eqz (ref.test (ref i31) (local.get $dst-start-raw))) (then (call $raise-string-copy!:bad-destination-start))) (if (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $dst-start-raw))) (i32.const 1)) (i32.const 0)) (then (call $raise-string-copy!:bad-destination-start))) (if (i32.eqz (ref.test (ref $String) (local.get $src-raw))) (then (call $raise-string-copy!:bad-source))) ;; src-start optional (if (i32.eqz (ref.eq (local.get $src-start-raw) (global.get $missing))) (then (if (i32.eqz (ref.test (ref i31) (local.get $src-start-raw))) (then (call $raise-string-copy!:bad-source-start))) (if (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $src-start-raw))) (i32.const 1)) (i32.const 0)) (then (call $raise-string-copy!:bad-source-start))))) ;; src-end optional (if (i32.eqz (ref.eq (local.get $src-end-raw) (global.get $missing))) (then (if (i32.eqz (ref.test (ref i31) (local.get $src-end-raw))) (then (call $raise-string-copy!:bad-source-end))) (if (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $src-end-raw))) (i32.const 1)) (i32.const 0)) (then (call $raise-string-copy!:bad-source-end))))) ;; --- Decode --- (local.set $dst (ref.cast (ref $String) (local.get $dst-raw))) (local.set $src (ref.cast (ref $String) (local.get $src-raw))) (local.set $dst-start (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $dst-start-raw))) (i32.const 1))) (local.set $src-len (call $string-length/checked/i32 (local.get $src))) (if (ref.eq (local.get $src-start-raw) (global.get $missing)) (then (local.set $src-start (i32.const 0))) (else (local.set $src-start (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $src-start-raw))) (i32.const 1))))) (if (ref.eq (local.get $src-end-raw) (global.get $missing)) (then (local.set $src-end (local.get $src-len))) (else (local.set $src-end (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $src-end-raw))) (i32.const 1))))) ;; --- Mutability Check --- (if (i32.ne (struct.get $String $immutable (local.get $dst)) (i32.const 0)) (then (call $raise-immutable-string (local.get $dst)))) ;; --- Range Validation --- (local.set $dst-len (call $string-length/checked/i32 (local.get $dst))) (if (i32.gt_u (local.get $src-start) (local.get $src-end)) (then (call $raise-string-copy!:bad-source-range))) (if (i32.gt_u (local.get $src-end) (local.get $src-len)) (then (call $raise-string-copy!:bad-source-range))) (if (i32.gt_u (i32.add (local.get $dst-start) (i32.sub (local.get $src-end) (local.get $src-start))) (local.get $dst-len)) (then (call $raise-string-copy!:bad-destination-range))) ;; --- Delegate --- (call $string-copy!/checked (local.get $dst) (local.get $dst-start) (local.get $src) (local.get $src-start) (local.get $src-end)) ;; --- Invalidate hash --- (struct.set $String $hash (local.get $dst) (i32.const 0)) ;; --- Return --- (global.get $void)) (func $raise-string-copy!/checked:out-of-bounds (unreachable)) (func $string-copy!/checked (param $dst (ref $String)) ;; Destination string (param $dst-start i32) ;; Start index in destination (param $src (ref $String)) ;; Source string (param $src-start i32) ;; Start index in source (param $src-end i32) ;; End index in source (exclusive) (local $dst-len i32) (local $src-len i32) (local $len i32) ;; --- Compute source slice length --- (local.set $len (i32.sub (local.get $src-end) (local.get $src-start))) ;; --- Range Checks --- (if (i32.lt_u (local.get $src-end) (local.get $src-start)) (then (call $raise-string-copy!/checked:out-of-bounds))) (local.set $dst-len (call $string-length/checked/i32 (local.get $dst))) (local.set $src-len (call $string-length/checked/i32 (local.get $src))) (if (i32.gt_u (i32.add (local.get $dst-start) (local.get $len)) (local.get $dst-len)) (then (call $raise-string-copy!/checked:out-of-bounds))) (if (i32.gt_u (local.get $src-end) (local.get $src-len)) (then (call $raise-string-copy!/checked:out-of-bounds))) ;; --- Copy elements --- (call $i32array-copy! (struct.get $String $codepoints (local.get $dst)) (local.get $dst-start) (struct.get $String $codepoints (local.get $src)) (local.get $src-start) (local.get $src-end)) ;; --- Invalidate destination hash --- (struct.set $String $hash (local.get $dst) (i32.const 0))) (func $array-of-strings->string (param $arr (ref $Array)) (result (ref $String)) (local $n i32) (local $i i32) (local $total i32) (local $s (ref $String)) (local $len i32) (local $dst (ref $String)) (local $pos i32) ;; Pass 1: compute total length (local.set $n (array.len (local.get $arr))) (local.set $i (i32.const 0)) (local.set $total (i32.const 0)) (block $count-done (loop $count-loop (br_if $count-done (i32.ge_u (local.get $i) (local.get $n))) (local.set $s (ref.cast (ref $String) (array.get $Array (local.get $arr) (local.get $i)))) (local.set $len (call $string-length/checked/i32 (local.get $s))) (local.set $total (i32.add (local.get $total) (local.get $len))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $count-loop))) ;; Allocate destination string (local.set $dst (call $make-string/checked (local.get $total) (i32.const 0))) ;; Pass 2: copy individual strings into destination (local.set $i (i32.const 0)) (local.set $pos (i32.const 0)) (block $copy-done (loop $copy-loop (br_if $copy-done (i32.ge_u (local.get $i) (local.get $n))) (local.set $s (ref.cast (ref $String) (array.get $Array (local.get $arr) (local.get $i)))) (local.set $len (call $string-length/checked/i32 (local.get $s))) (call $string-copy!/checked (local.get $dst) (local.get $pos) (local.get $s) (i32.const 0) (local.get $len)) (local.set $pos (i32.add (local.get $pos) (local.get $len))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $copy-loop))) (local.get $dst)) (func $growable-array-of-strings->string (param $g (ref $GrowableArray)) (result (ref $String)) ;; Convert growable array to normal array and concatenate its strings (call $array-of-strings->string (call $growable-array->array (local.get $g)))) (func $string-take (type $Prim2) (param $s (ref eq)) (param $n (ref eq)) (result (ref eq)) ; an (ref $String) (local $str (ref $String)) (local $n/tag i32) (local $n/i32 i32) (local $len i32) (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (if (i32.eqz (ref.test (ref i31) (local.get $n))) (then (call $raise-check-fixnum (local.get $n)))) (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $n/tag (i31.get_u (ref.cast (ref i31) (local.get $n)))) (if (i32.and (local.get $n/tag) (i32.const 1)) (then (call $raise-check-fixnum (local.get $n)))) (local.set $n/i32 (i32.shr_u (local.get $n/tag) (i32.const 1))) (local.set $len (call $string-length/checked/i32 (local.get $str))) (if (i32.gt_u (local.get $n/i32) (local.get $len)) (then (call $raise-bad-string-index/i32 (local.get $s) (local.get $n/i32)))) (call $string-take/checked (local.get $str) (local.get $n/i32))) (func $string-take/checked (param $s (ref $String)) (param $n i32) (result (ref $String)) (call $i32array->string (call $i32array-take (struct.get $String $codepoints (local.get $s)) (local.get $n)))) (func $string-take-right (type $Prim2) (param $s (ref eq)) (param $n (ref eq)) (result (ref eq)) ; an (ref $String) (local $str (ref $String)) (local $n/tag i32) (local $n/i32 i32) (local $len i32) (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (if (i32.eqz (ref.test (ref i31) (local.get $n))) (then (call $raise-check-fixnum (local.get $n)))) (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $n/tag (i31.get_u (ref.cast (ref i31) (local.get $n)))) (if (i32.and (local.get $n/tag) (i32.const 1)) (then (call $raise-check-fixnum (local.get $n)))) (local.set $n/i32 (i32.shr_u (local.get $n/tag) (i32.const 1))) (local.set $len (call $string-length/checked/i32 (local.get $str))) (if (i32.gt_u (local.get $n/i32) (local.get $len)) (then (call $raise-bad-string-index/i32 (local.get $s) (local.get $n/i32)) (unreachable))) (call $string-take-right/checked (local.get $str) (local.get $n/i32))) (func $string-take-right/checked (param $s (ref $String)) (param $n i32) (result (ref $String)) (call $i32array->string (call $i32array-take-right (struct.get $String $codepoints (local.get $s)) (local.get $n)))) (func $string-drop (type $Prim2) (param $s (ref eq)) (param $n (ref eq)) (result (ref eq)) ; an (ref $String) (local $str (ref $String)) (local $n/tag i32) (local $n/i32 i32) (local $len i32) (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (if (i32.eqz (ref.test (ref i31) (local.get $n))) (then (call $raise-check-fixnum (local.get $n)))) (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $n/tag (i31.get_u (ref.cast (ref i31) (local.get $n)))) (if (i32.and (local.get $n/tag) (i32.const 1)) (then (call $raise-check-fixnum (local.get $n)))) (local.set $n/i32 (i32.shr_u (local.get $n/tag) (i32.const 1))) (local.set $len (call $string-length/checked/i32 (local.get $str))) (if (i32.gt_u (local.get $n/i32) (local.get $len)) (then (call $raise-bad-string-index/i32 (local.get $s) (local.get $n/i32)))) (call $string-drop/checked (local.get $str) (local.get $n/i32))) (func $string-drop/checked (param $s (ref $String)) (param $n i32) (result (ref $String)) (call $i32array->string (call $i32array-drop (struct.get $String $codepoints (local.get $s)) (local.get $n)))) (func $string-drop-right (type $Prim2) (param $s (ref eq)) (param $n (ref eq)) (result (ref eq)) ; an (ref $String) (local $str (ref $String)) (local $n/tag i32) (local $n/i32 i32) (local $len i32) (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (if (i32.eqz (ref.test (ref i31) (local.get $n))) (then (call $raise-check-fixnum (local.get $n)))) (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $n/tag (i31.get_u (ref.cast (ref i31) (local.get $n)))) (if (i32.and (local.get $n/tag) (i32.const 1)) (then (call $raise-check-fixnum (local.get $n)))) (local.set $n/i32 (i32.shr_u (local.get $n/tag) (i32.const 1))) (local.set $len (call $string-length/checked/i32 (local.get $str))) (if (i32.gt_u (local.get $n/i32) (local.get $len)) (then (call $raise-bad-string-index/i32 (local.get $s) (local.get $n/i32)))) (call $string-drop-right/checked (local.get $str) (local.get $n/i32))) (func $string-drop-right/checked (param $s (ref $String)) (param $n i32) (result (ref $String)) (call $i32array->string (call $i32array-drop-right (struct.get $String $codepoints (local.get $s)) (local.get $n)))) (func $raise-argument-error:string-expected (unreachable)) (func $bomb (unreachable)) (func $string-trim-right (type $Prim2) (param $s (ref eq)) ;; any value, must be a string (param $sep (ref eq)) ;; a character (tagged i31) or #f (result (ref eq)) ;; an (ref $String) (local $str (ref $String)) (local $len i32) (local $new-len i32) (local $sep/tag i32) ;; still-tagged char (local $sep-ch i32) ;; decoded code point (local $use-whitespace? i32) (local $ch i32) ;; --- Type check inputs --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-argument-error:string-expected (local.get $s)) (unreachable))) (if (ref.eq (local.get $sep) (global.get $false)) (then (local.set $use-whitespace? (i32.const 1))) (else (if (i32.eqz (ref.test (ref i31) (local.get $sep))) (then (call $raise-argument-error:char-expected (local.get $sep)) (unreachable))) (local.set $sep/tag (i31.get_u (ref.cast (ref i31) (local.get $sep)))) (if (i32.ne (i32.and (local.get $sep/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-argument-error:char-expected (local.get $sep)) (unreachable))) (local.set $use-whitespace? (i32.const 0)))) ;; --- Decode after checks --- (local.set $str (ref.cast (ref $String) (local.get $s))) (if (i32.eq (local.get $use-whitespace?) (i32.const 0)) (then (local.set $sep-ch (i32.shr_u (local.get $sep/tag) (i32.const ,char-shift))))) ;; --- Get length --- (local.set $len (call $string-length/checked/i32 (local.get $str))) (local.set $new-len (local.get $len)) ;; --- Scan backward --- (block $done (loop $scan (br_if $done (i32.eqz (local.get $new-len))) (local.set $ch (call $string-ref/checked/i32 (local.get $str) (i32.sub (local.get $new-len) (i32.const 1)))) (if (i32.eq (local.get $use-whitespace?) (i32.const 1)) ;; --- Trim if whitespace --- (then (if (ref.eq (call $char-whitespace?/ucs (local.get $ch)) (global.get $true)) (then (local.set $new-len (i32.sub (local.get $new-len) (i32.const 1))) (br $scan)))) ;; --- Else trim if equals sep-ch --- (else (if (i32.eq (local.get $ch) (local.get $sep-ch)) (then (local.set $new-len (i32.sub (local.get $new-len) (i32.const 1))) (br $scan)))))) ) ;; --- Return result --- (if (i32.eq (local.get $new-len) (local.get $len)) (then (return (local.get $str))) (else (return (call $string-take/checked (local.get $str) (local.get $new-len))))) (unreachable)) (func $string-trim-left (type $Prim2) (param $s (ref eq)) ;; any value, must be a string (param $sep (ref eq)) ;; a character (i31) or #f (result (ref eq)) ;; an (ref $String) (local $str (ref $String)) (local $len i32) (local $i i32) (local $sep-ch i32) (local $use-whitespace? i32) ;; boolean flag (local $sep/tag i32) (local $ch i32) ;; --- Check inputs --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-argument-error:string-expected (local.get $s)) (unreachable))) (if (ref.eq (local.get $sep) (global.get $false)) (then (local.set $use-whitespace? (i32.const 1))) (else ;; Check: is it a (ref i31)? (if (i32.eqz (ref.test (ref i31) (local.get $sep))) (then (call $raise-argument-error:char-expected (local.get $sep)) (unreachable))) ;; Extract raw tagged value (local.set $sep/tag (i31.get_u (ref.cast (ref i31) (local.get $sep)))) ;; Check tag matches ,char-tag (if (i32.ne (i32.and (local.get $sep/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-argument-error:char-expected (local.get $sep)) (unreachable))) ;; Passed: decode (local.set $use-whitespace? (i32.const 0)) (local.set $sep-ch (i32.shr_u (local.get $sep/tag) (i32.const ,char-shift))))) ;; --- Decode after checks --- (local.set $str (ref.cast (ref $String) (local.get $s))) ;; --- Get string length (as i32) --- (local.set $len (call $string-length/checked/i32 (local.get $str))) (local.set $i (i32.const 0)) ;; --- Scan forward --- (block $done (loop $scan (br_if $done (i32.eq (local.get $i) (local.get $len))) (local.set $ch (call $string-ref/checked/i32 (local.get $str) (local.get $i))) (if (i32.eq (local.get $use-whitespace?) (i32.const 1)) ;; --- Trim if character is whitespace --- (then (if (ref.eq (call $char-whitespace?/ucs (local.get $ch)) (global.get $true)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $scan)))) ;; --- Else trim if char equals $sep-ch --- (else (if (i32.eq (local.get $ch) (local.get $sep-ch)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $scan))))))) ;; --- Return result --- (if (i32.eqz (local.get $i)) (then (return (local.get $str))) (else (return (call $string-drop (local.get $str) (ref.i31 (i32.shl (local.get $i) (i32.const 1))))))) (unreachable)) (func $string-trim (type $Prim15) (param $s (ref eq)) ;; string? (param $sep (ref eq)) ;; optional string?, default = " " (param $left? (ref eq)) ;; optional any/c, default = #t (param $right? (ref eq)) ;; optional any/c, default = #t (param $repeat? (ref eq)) ;; optional any/c, default = #f (result (ref eq)) (local $str (ref $String)) (local $sep-str (ref $String)) (local $result (ref $String)) (local $use-whitespace? i32) (local $trim-left? i32) (local $trim-right? i32) (local $repeat-flag i32) (local $sep-len i32) ;; NOTE: WebRacket does not yet support keyword arguments or regular-expression separators here. ;; --- Initilize non-defaultable --- (local.set $str (ref.cast (ref $String) (global.get $string:empty))) (local.set $sep-str (ref.cast (ref $String) (global.get $string:empty))) ; (local.set $str (ref.cast (ref $String) (global.get $string:empty))) ;; --- Check and decode required string argument --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $result (local.get $str)) ;; --- Handle optional separator (defaults to whitespace trimming) --- (if (ref.eq (local.get $sep) (global.get $missing)) (then (local.set $use-whitespace? (i32.const 1))) (else (if (i32.eqz (ref.test (ref $String) (local.get $sep))) (then (call $raise-check-string (local.get $sep)))) (local.set $sep-str (ref.cast (ref $String) (local.get $sep))) (local.set $use-whitespace? (i32.const 0)))) ;; --- Decode left? flag (default #t) --- (if (ref.eq (local.get $left?) (global.get $missing)) (then (local.set $trim-left? (i32.const 1))) (else (local.set $trim-left? (if (result i32) (ref.eq (local.get $left?) (global.get $false)) (then (i32.const 0)) (else (i32.const 1)))))) ;; --- Decode right? flag (default #t) --- (if (ref.eq (local.get $right?) (global.get $missing)) (then (local.set $trim-right? (i32.const 1))) (else (local.set $trim-right? (if (result i32) (ref.eq (local.get $right?) (global.get $false)) (then (i32.const 0)) (else (i32.const 1)))))) ;; --- Decode repeat? flag (default #f) --- (if (ref.eq (local.get $repeat?) (global.get $missing)) (then (local.set $repeat-flag (i32.const 0))) (else (local.set $repeat-flag (if (result i32) (ref.eq (local.get $repeat?) (global.get $false)) (then (i32.const 0)) (else (i32.const 1)))))) ;; --- Whitespace trimming delegates to existing helpers --- (if (i32.eq (local.get $use-whitespace?) (i32.const 1)) (then (if (local.get $trim-left?) (then (local.set $result (ref.cast (ref $String) (call $string-trim-left (local.get $result) (global.get $false)))))) (if (local.get $trim-right?) (then (local.set $result (ref.cast (ref $String) (call $string-trim-right (local.get $result) (global.get $false)))))) (return (local.get $result)))) ;; --- Literal separator trimming --- (local.set $sep-len (call $string-length/checked/i32 (local.get $sep-str))) (if (i32.eqz (local.get $sep-len)) (then (return (local.get $result)))) ;; Trim left side if requested (if (local.get $trim-left?) (then (block $left-done (loop $left-loop (br_if $left-done (i32.eqz (call $string-prefix?/i32/checked (local.get $result) (local.get $sep-str)))) (local.set $result (call $string-drop/checked (local.get $result) (local.get $sep-len))) (if (i32.eqz (local.get $repeat-flag)) (then (br $left-done))) (br $left-loop))))) ;; Trim right side if requested (if (local.get $trim-right?) (then (block $right-done (loop $right-loop (br_if $right-done (i32.eqz (call $string-suffix?/i32/checked (local.get $result) (local.get $sep-str)))) (local.set $result (call $string-drop-right/checked (local.get $result) (local.get $sep-len))) (if (i32.eqz (local.get $repeat-flag)) (then (br $right-done))) (br $right-loop))))) (local.get $result)) ;; 4.4.6 Additional String Functions (racket/string) (func $string-suffix? (type $Prim2) (param $s (ref eq)) (param $suffix (ref eq)) (result (ref eq)) (if (result (ref eq)) (call $string-suffix?/i32 (local.get $s) (local.get $suffix)) (then (global.get $true)) (else (global.get $false)))) (func $string-suffix?/i32 (param $s-raw (ref eq)) (param $suffix-raw (ref eq)) (result i32) (local $s (ref $String)) (local $suf (ref $String)) ;; Type checks (if (i32.eqz (ref.test (ref $String) (local.get $s-raw))) (then (return (i32.const 0)))) (if (i32.eqz (ref.test (ref $String) (local.get $suffix-raw))) (then (return (i32.const 0)))) ;; Cast and delegate (local.set $s (ref.cast (ref $String) (local.get $s-raw))) (local.set $suf (ref.cast (ref $String) (local.get $suffix-raw))) (return_call $string-suffix?/i32/checked (local.get $s) (local.get $suf))) (func $string-suffix?/i32/checked (param $s (ref $String)) (param $suf (ref $String)) (result i32) (local $arr-s (ref $I32Array)) (local $arr-suf (ref $I32Array)) (local $len-s i32) (local $len-suf i32) (local $offset i32) (local $i i32) (local.set $arr-s (struct.get $String $codepoints (local.get $s))) (local.set $arr-suf (struct.get $String $codepoints (local.get $suf))) (local.set $len-s (array.len (local.get $arr-s))) (local.set $len-suf (array.len (local.get $arr-suf))) ;; If suffix longer than string, fail (if (i32.gt_u (local.get $len-suf) (local.get $len-s)) (then (return (i32.const 0)))) (local.set $offset (i32.sub (local.get $len-s) (local.get $len-suf))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len-suf))) (if (i32.ne (array.get $I32Array (local.get $arr-s) (i32.add (local.get $offset) (local.get $i))) (array.get $I32Array (local.get $arr-suf) (local.get $i))) (then (return (i32.const 0)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (i32.const 1)) (func $string-prefix? (type $Prim2) (param $s (ref eq)) (param $prefix (ref eq)) (result (ref eq)) (if (result (ref eq)) (call $string-prefix?/i32 (local.get $s) (local.get $prefix)) (then (global.get $true)) (else (global.get $false)))) (func $string-prefix?/i32 (param $s-raw (ref eq)) (param $prefix-raw (ref eq)) (result i32) (local $s (ref $String)) (local $p (ref $String)) (if (i32.eqz (ref.test (ref $String) (local.get $s-raw))) (then (call $raise-check-string (local.get $s-raw)))) (if (i32.eqz (ref.test (ref $String) (local.get $prefix-raw))) (then (call $raise-check-string (local.get $prefix-raw)))) (local.set $s (ref.cast (ref $String) (local.get $s-raw))) (local.set $p (ref.cast (ref $String) (local.get $prefix-raw))) (return_call $string-prefix?/i32/checked (local.get $s) (local.get $p))) (func $string-prefix?/i32/checked (param $s (ref $String)) (param $p (ref $String)) (result i32) (local $arr-s (ref $I32Array)) (local $arr-p (ref $I32Array)) (local $len-s i32) (local $len-p i32) (local $i i32) (local $cp-s i32) (local $cp-p i32) (local.set $arr-s (struct.get $String $codepoints (local.get $s))) (local.set $arr-p (struct.get $String $codepoints (local.get $p))) (local.set $len-s (array.len (local.get $arr-s))) (local.set $len-p (array.len (local.get $arr-p))) (if (i32.lt_u (local.get $len-s) (local.get $len-p)) (then (return (i32.const 0)))) (local.set $i (i32.const 0)) (block $exit (loop $loop (br_if $exit (i32.ge_u (local.get $i) (local.get $len-p))) (local.set $cp-s (array.get $I32Array (local.get $arr-s) (local.get $i))) (local.set $cp-p (array.get $I32Array (local.get $arr-p) (local.get $i))) (if (i32.ne (local.get $cp-s) (local.get $cp-p)) (then (return (i32.const 0)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (i32.const 1)) (func $string-contains? (type $Prim2) (param $s (ref eq)) ;; string? (param $contained (ref eq)) ;; string? (result (ref eq)) ;; Delegate to string-find and report only presence. (if (result (ref eq)) (ref.eq (call $string-find (local.get $s) (local.get $contained)) (global.get $false)) (then (global.get $false)) (else (global.get $true)))) (func $string-find (type $Prim2) (param $s (ref eq)) (param $contained (ref eq)) (result (ref eq)) (local $str (ref $String)) (local $sub (ref $String)) (local $arr-s (ref $I32Array)) (local $arr-c (ref $I32Array)) (local $len-s i32) (local $len-c i32) (local $limit i32) (local $i i32) (local $j i32) (local $ch-s i32) (local $ch-c i32) ;; --- Check inputs --- (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-argument-error:string-expected (local.get $s)) (unreachable))) (if (i32.eqz (ref.test (ref $String) (local.get $contained))) (then (call $raise-argument-error:string-expected (local.get $contained)) (unreachable))) ;; --- Decode after checks --- (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $sub (ref.cast (ref $String) (local.get $contained))) (local.set $arr-s (struct.get $String $codepoints (local.get $str))) (local.set $arr-c (struct.get $String $codepoints (local.get $sub))) (local.set $len-s (array.len (local.get $arr-s))) (local.set $len-c (array.len (local.get $arr-c))) ;; --- Edge cases --- (if (i32.eqz (local.get $len-c)) (then (return (global.get $zero)))) (if (i32.lt_u (local.get $len-s) (local.get $len-c)) (then (return (global.get $false)))) (local.set $limit (i32.sub (local.get $len-s) (local.get $len-c))) (local.set $i (i32.const 0)) (block $not-found (loop $outer (br_if $not-found (i32.gt_u (local.get $i) (local.get $limit))) (local.set $j (i32.const 0)) (block $mismatch (loop $inner (br_if $mismatch (i32.ge_u (local.get $j) (local.get $len-c))) (local.set $ch-s (call $i32array-ref (local.get $arr-s) (i32.add (local.get $i) (local.get $j)))) (local.set $ch-c (call $i32array-ref (local.get $arr-c) (local.get $j))) (if (i32.ne (local.get $ch-s) (local.get $ch-c)) (then (br $mismatch))) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $inner))) (if (i32.eq (local.get $j) (local.get $len-c)) (then (return (ref.i31 (i32.shl (local.get $i) (i32.const 1)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $outer))) (global.get $false)) (func $string-replace (type $Prim34) (param $s (ref eq)) ;; string? (param $from (ref eq)) ;; string? (param $to (ref eq)) ;; string? (param $all? (ref eq)) ;; boolean?, optional with default #t (result (ref eq)) (local $str (ref $String)) (local $f (ref $String)) (local $t (ref $String)) (local $arr-s (ref $I32Array)) (local $arr-f (ref $I32Array)) (local $arr-t (ref $I32Array)) (local $n i32) (local $m i32) (local $r i32) (local $idxs (ref $I32GrowableArray)) (local $limit i32) (local $i i32) (local $j i32) (local $k i32) (local $res-len i32) (local $res (ref $I32Array)) (local $dst i32) (local $src i32) (local $idx i32) ;; Handle optional all? with default #t (if (ref.eq (local.get $all?) (global.get $missing)) (then (local.set $all? (global.get $true)))) ;; Type checks (if (i32.eqz (ref.test (ref $String) (local.get $s))) (then (call $raise-check-string (local.get $s)))) (if (i32.eqz (ref.test (ref $String) (local.get $from))) (then (call $raise-check-string (local.get $from)))) (if (i32.eqz (ref.test (ref $String) (local.get $to))) (then (call $raise-check-string (local.get $to)))) (if (ref.eq (call $boolean? (local.get $all?)) (global.get $false)) (then (call $raise-argument-error (local.get $all?)) (unreachable))) ;; Decode after checks (local.set $str (ref.cast (ref $String) (local.get $s))) (local.set $f (ref.cast (ref $String) (local.get $from))) (local.set $t (ref.cast (ref $String) (local.get $to))) (local.set $arr-s (struct.get $String $codepoints (local.get $str))) (local.set $arr-f (struct.get $String $codepoints (local.get $f))) (local.set $arr-t (struct.get $String $codepoints (local.get $t))) (local.set $n (array.len (local.get $arr-s))) (local.set $m (array.len (local.get $arr-f))) (local.set $r (array.len (local.get $arr-t))) ;; Collect match indices (local.set $idxs (call $make-i32growable-array (i32.const 4))) (if (i32.eqz (local.get $m)) (then (if (ref.eq (local.get $all?) (global.get $false)) (then (call $i32growable-array-add! (local.get $idxs) (i32.const 0))) (else (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.gt_u (local.get $i) (local.get $n))) (call $i32growable-array-add! (local.get $idxs) (local.get $i)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)))))) (else (local.set $limit (i32.sub (local.get $n) (local.get $m))) (local.set $i (i32.const 0)) (if (ref.eq (local.get $all?) (global.get $false)) (then (block $done (loop $loop (br_if $done (i32.gt_u (local.get $i) (local.get $limit))) (local.set $j (i32.const 0)) (block $mismatch (loop $inner (br_if $mismatch (i32.ge_u (local.get $j) (local.get $m))) (br_if $mismatch (i32.ne (call $i32array-ref (local.get $arr-s) (i32.add (local.get $i) (local.get $j))) (call $i32array-ref (local.get $arr-f) (local.get $j)))) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $inner))) (if (i32.eq (local.get $j) (local.get $m)) (then (call $i32growable-array-add! (local.get $idxs) (local.get $i)) (br $done))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)))) (else (block $end (loop $loop (br_if $end (i32.gt_u (local.get $i) (local.get $limit))) (local.set $j (i32.const 0)) (block $mismatch (loop $inner (br_if $mismatch (i32.ge_u (local.get $j) (local.get $m))) (br_if $mismatch (i32.ne (call $i32array-ref (local.get $arr-s) (i32.add (local.get $i) (local.get $j))) (call $i32array-ref (local.get $arr-f) (local.get $j)))) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $inner))) (if (i32.eq (local.get $j) (local.get $m)) (then (call $i32growable-array-add! (local.get $idxs) (local.get $i)) (local.set $i (i32.add (local.get $i) (local.get $m))) (br $loop))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))))))) (local.set $k (call $i32growable-array-count (local.get $idxs))) (if (i32.eqz (local.get $k)) (then (return (local.get $str)))) (local.set $res-len (i32.add (local.get $n) (i32.mul (local.get $k) (i32.sub (local.get $r) (local.get $m))))) (local.set $res (array.new_default $I32Array (local.get $res-len))) (local.set $dst (i32.const 0)) (local.set $src (i32.const 0)) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $k))) (local.set $idx (call $i32growable-array-ref (local.get $idxs) (local.get $i))) (call $i32array-copy! (local.get $res) (local.get $dst) (local.get $arr-s) (local.get $src) (local.get $idx)) (local.set $dst (i32.add (local.get $dst) (i32.sub (local.get $idx) (local.get $src)))) (call $i32array-copy! (local.get $res) (local.get $dst) (local.get $arr-t) (i32.const 0) (local.get $r)) (local.set $dst (i32.add (local.get $dst) (local.get $r))) (local.set $src (i32.add (local.get $idx) (local.get $m))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (call $i32array-copy! (local.get $res) (local.get $dst) (local.get $arr-s) (local.get $src) (local.get $n)) (struct.new $String (i32.const 0) (i32.const 0) (local.get $res))) ;;; ;;; 4.6 Characters ;;; ;; https://docs.racket-lang.org/reference/characters.html (func $raise-check-char (param $x (ref eq)) (unreachable)) (func $raise-invalid-codepoint (unreachable)) ;; 4.6.1 Characters and Scalar Values (func $char? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $i31 (ref i31)) ; Is $v an immediate? (if (i32.eqz (ref.test (ref i31) (local.get $v))) (then (return (global.get $false)))) (local.set $i31 (ref.cast (ref i31) (local.get $v))) ; Is it a character? (if (result (ref eq)) (i32.eq (i32.and (i31.get_s (local.get $i31)) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (global.get $true)) (else (global.get $false)))) (func $char->integer (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) ;; Check if $c is an i31 (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Check character tag (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) ;; Extract codepoint and return as fixnum (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) (ref.i31 (i32.shl (local.get $cp) (i32.const 1)))) (func $char->integer/i32 (param $c (ref eq)) (result i32) (local $raw i32) ;; Check that $c is an i31 (if (ref.test (ref i31) (local.get $c)) (then ;; Extract the raw bits (local.set $raw (i31.get_u (ref.cast (ref i31) (local.get $c)))) ;; Verify the tag is 0x0F (if (i32.eq (i32.and (local.get $raw) (i32.const ,char-mask)) (i32.const ,char-tag)) (then ;; Return the codepoint: raw >> 8 (return (i32.shr_u (local.get $raw) (i32.const ,char-shift)))) (else (call $raise-check-char (local.get $c))))) (else (call $raise-check-char (local.get $c)))) (unreachable)) (func $integer->char (type $Prim1) (param $k (ref eq)) (result (ref eq)) (local $k/i32 i32) ;; Fail early if not a fixnum (if (i32.eqz (ref.test (ref i31) (local.get $k))) (then (call $raise-expected-fixnum (local.get $k)) (unreachable))) ;; Unpack fixnum (must have LSB = 0) (local.set $k/i32 (i31.get_u (ref.cast (ref i31) (local.get $k)))) (if (i32.and (local.get $k/i32) (i32.const 1)) (then (call $raise-expected-fixnum (local.get $k)) (unreachable))) (local.set $k/i32 (i32.shr_u (local.get $k/i32) (i32.const 1))) ;; Check allowed Unicode code point range: ;; [0, 0xD7FF] or [0xE000, 0x10FFFF] (if (i32.or (i32.and (i32.ge_u (local.get $k/i32) (i32.const #xD800)) (i32.le_u (local.get $k/i32) (i32.const #xDFFF))) (i32.gt_u (local.get $k/i32) (i32.const #x10FFFF))) (then (call $raise-invalid-codepoint (local.get $k)) (unreachable))) ;; TODO: Shared character object for 0 <= k < 256 ;; (if needed, insert lookup here) ;; Pack as character: (k << (char-shift - 1)) | char-tag (ref.i31 (i32.or (i32.shl (local.get $k/i32) (i32.const ,char-shift)) (i32.const ,char-tag)))) (func $char-utf-8-length (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $cp i32) (local $len i32) ;; Returns bytes needed to encode $c in UTF-8. ;; Characters are limited to U+10FFFF so result is in [1,4]. (local.set $cp (call $char->integer/i32 (local.get $c))) (local.set $len (if (result i32) (i32.le_u (local.get $cp) (i32.const #x7F)) (then (i32.const 1)) (else (if (result i32) (i32.le_u (local.get $cp) (i32.const #x7FF)) (then (i32.const 2)) (else (if (result i32) (i32.le_u (local.get $cp) (i32.const #xFFFF)) (then (i32.const 3)) (else (i32.const 4)))))))) (ref.i31 (i32.shl (local.get $len) (i32.const 1)))) ;; 4.6.2 Character Comparisons ,@(for/list ([$cmp (in-list '($char=? $char? $char>=?))] [$cmp/2 (in-list '($char=?/2 $char?/2 $char>=?/2))] [inst (in-list '(#f i32.lt_u i32.le_u i32.gt_u i32.ge_u))]) ; binary version `(func ,$cmp/2 (param $c1 (ref eq)) (param $c2 (ref eq)) (result (ref eq)) ;; Ensure both arguments are characters (if (ref.eq (call $char? (local.get $c1)) (global.get $false)) (then (call $raise-check-char (local.get $c1)))) (if (ref.eq (call $char? (local.get $c2)) (global.get $false)) (then (call $raise-check-char (local.get $c2)))) ,(if inst `(if (result (ref eq)) (,inst (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $c1))) (i32.const ,char-shift)) (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $c2))) (i32.const ,char-shift))) (then (global.get $true)) (else (global.get $false))) `(return_call $eq? (local.get $c1) (local.get $c2))))) ,@(for/list ([$cmp (in-list '($char=? $char? $char>=?))] [$cmp/2 (in-list '($char=?/2 $char?/2 $char>=?/2))]) ; variadic version `(func ,$cmp (type $Prim>=1) (param $c0 (ref eq)) (param $cs (ref eq)) (result (ref eq)) (local $node (ref $Pair)) (local $ch (ref eq)) ;; Validate the first argument (if (ref.eq (call $char? (local.get $c0)) (global.get $false)) (then (call $raise-check-char (local.get $c0)))) (block $done (loop $loop (br_if $done (ref.eq (local.get $cs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $cs))) (local.set $ch (struct.get $Pair $a (local.get $node))) ;; Validate each subsequent argument (if (ref.eq (call $char? (local.get $ch)) (global.get $false)) (then (call $raise-check-char (local.get $ch)))) (if (ref.eq (call ,$cmp/2 (local.get $c0) (local.get $ch)) (global.get $false)) (then (return (global.get $false)))) (local.set $cs (struct.get $Pair $d (local.get $node))) (br $loop))) (global.get $true))) ,@(for/list ([$cmp (in-list '($char-ci=? $char-ci? $char-ci>=?))] [$cmp/2 (in-list '($char-ci=?/2 $char-ci?/2 $char-ci>=?/2))] [inst (in-list '(#f i32.lt_u i32.le_u i32.gt_u i32.ge_u))]) ; binary case-insensitive version `(func ,$cmp/2 (param $c1 (ref eq)) (param $c2 (ref eq)) (result (ref eq)) (local $cp1 i32) (local $cp2 i32) (local $fc1 i32) (local $fc2 i32) ;; Ensure both arguments are characters (if (ref.eq (call $char? (local.get $c1)) (global.get $false)) (then (call $raise-check-char (local.get $c1)))) (if (ref.eq (call $char? (local.get $c2)) (global.get $false)) (then (call $raise-check-char (local.get $c2)))) ;; Extract codepoints (local.set $cp1 (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $c1))) (i32.const ,char-shift))) (local.set $cp2 (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $c2))) (i32.const ,char-shift))) ;; Foldcase codepoints (local.set $fc1 (call $char-foldcase/ucs (local.get $cp1))) (local.set $fc2 (call $char-foldcase/ucs (local.get $cp2))) ,(if inst `(if (result (ref eq)) (,inst (local.get $fc1) (local.get $fc2)) (then (global.get $true)) (else (global.get $false))) `(if (result (ref eq)) (i32.eq (local.get $fc1) (local.get $fc2)) (then (global.get $true)) (else (global.get $false)))))) ,@(for/list ([$cmp (in-list '($char-ci=? $char-ci? $char-ci>=?))] [$cmp/2 (in-list '($char-ci=?/2 $char-ci?/2 $char-ci>=?/2))]) ; variadic case-insensitive version `(func ,$cmp (type $Prim>=1) (param $c0 (ref eq)) (param $cs (ref eq)) (result (ref eq)) (local $node (ref $Pair)) (local $ch (ref eq)) ;; Validate the first argument (if (ref.eq (call $char? (local.get $c0)) (global.get $false)) (then (call $raise-check-char (local.get $c0)))) (block $done (loop $loop (br_if $done (ref.eq (local.get $cs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $cs))) (local.set $ch (struct.get $Pair $a (local.get $node))) ;; Validate each subsequent argument (if (ref.eq (call $char? (local.get $ch)) (global.get $false)) (then (call $raise-check-char (local.get $ch)))) (if (ref.eq (call ,$cmp/2 (local.get $c0) (local.get $ch)) (global.get $false)) (then (return (global.get $false)))) (local.set $cs (struct.get $Pair $d (local.get $node))) (br $loop))) (global.get $true))) ;; 4.6.3 Classifications ,@(for/list ([name+imp (in-list '(( $char-alphabetic? $char-alphabetic?/ucs) ( $char-lower-case? $char-lower-case?/ucs) ( $char-upper-case? $char-upper-case?/ucs) ( $char-title-case? $char-title-case?/ucs) ( $char-numeric? $char-numeric?/ucs) ( $char-symbolic? $char-symbolic?/ucs) ( $char-punctuation? $char-punctuation?/ucs) ( $char-graphic? $char-graphic?/ucs) ))]) (define name (car name+imp)) (define imp (cadr name+imp)) `(func ,name (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) (if (result (ref eq)) (call ,imp (local.get $cp)) (then (global.get $true)) (else (global.get $false))))) (func $char-blank? (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Delegate (call $char-blank?/ucs (local.get $cp))) (func $char-blank?/ucs (param $cp i32) (result (ref eq)) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\tab))) (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\space))) (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u00A0))) (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u1680))) (then (return (global.get $true)))) ;; U+2000–U+200A (if (i32.le_u (local.get $cp) (i32.const ,(char->integer #\u200A))) (then (if (i32.ge_u (local.get $cp) (i32.const ,(char->integer #\u2000))) (then (return (global.get $true)))))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u202F))) (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u205F))) (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u3000))) (then (return (global.get $true)))) (global.get $false)) (func $char-iso-control? (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) (if (i32.le_u (local.get $cp) (i32.const ,(char->integer #\u001F))) (then (return (global.get $true)))) (if (i32.le_u (local.get $cp) (i32.const ,(char->integer #\u009F))) (then (if (i32.ge_u (local.get $cp) (i32.const ,(char->integer #\rubout))) (then (return (global.get $true)))))) (global.get $false)) (func $char-extended-pictographic? (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) (if (result (ref eq)) (call $char-extended-pictographic?/ucs (local.get $cp)) (then (global.get $true)) (else (global.get $false)))) (func $char-whitespace? (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Delegate (call $char-whitespace?/ucs (local.get $cp))) (func $char-whitespace?/ucs (param $cp i32) (result (ref eq)) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\space))) (then (return (global.get $true)))) ;; U+0009..000D: tab, newline, vtab, formfeed, return (if (i32.le_u (local.get $cp) (i32.const ,(char->integer #\return))) (then (if (i32.ge_u (local.get $cp) (i32.const ,(char->integer #\tab))) (then (return (global.get $true)))))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u0085))) ; NEXT LINE (NEL) (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u00A0))) ; NO-BREAK SPACE (NBSP) (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u1680))) ; OGHAM SPACE MARK (then (return (global.get $true)))) ;; U+2000–U+200A (if (i32.le_u (local.get $cp) (i32.const ,(char->integer #\u200A))) (then (if (i32.ge_u (local.get $cp) (i32.const ,(char->integer #\u2000))) (then (return (global.get $true)))))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u2028))) ; LINE SEPARATOR (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u2029))) ; PARAGRAPH SEPARATOR (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u202F))) ; NARROW NO-BREAK SPACE (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u205F))) ; MEDIUM MATHEMATICAL SPACE (then (return (global.get $true)))) (if (i32.eq (local.get $cp) (i32.const ,(char->integer #\u3000))) ; IDEOGRAPHIC SPACE (then (return (global.get $true)))) (global.get $false)) (func $char-general-category (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) (local $idx i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Delegate to host (local.set $idx (call $char-general-category/ucs (local.get $cp))) ;; Lookup symbol (array.get $Array (global.get $char-general-category-symbols) (local.get $idx))) (func $char-grapheme-break-property (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) (local $prop i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)) (unreachable))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)) (unreachable))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Delegate to host (local.set $prop (call $char-grapheme-break-property/ucs (local.get $cp))) (block $ret (result (ref eq)) (if (i32.eq (local.get $prop) (i32.const 1)) (then (br $ret (global.get $symbol:CR)))) (if (i32.eq (local.get $prop) (i32.const 2)) (then (br $ret (global.get $symbol:LF)))) (if (i32.eq (local.get $prop) (i32.const 3)) (then (br $ret (global.get $symbol:Control)))) (if (i32.eq (local.get $prop) (i32.const 4)) (then (br $ret (global.get $symbol:Extend)))) (if (i32.eq (local.get $prop) (i32.const 5)) (then (br $ret (global.get $symbol:ZWJ)))) (if (i32.eq (local.get $prop) (i32.const 6)) (then (br $ret (global.get $symbol:Regional_Indicator)))) (if (i32.eq (local.get $prop) (i32.const 7)) (then (br $ret (global.get $symbol:Prepend)))) (if (i32.eq (local.get $prop) (i32.const 8)) (then (br $ret (global.get $symbol:SpacingMark)))) (if (i32.eq (local.get $prop) (i32.const 9)) (then (br $ret (global.get $symbol:L)))) (if (i32.eq (local.get $prop) (i32.const 10)) (then (br $ret (global.get $symbol:V)))) (if (i32.eq (local.get $prop) (i32.const 11)) (then (br $ret (global.get $symbol:T)))) (if (i32.eq (local.get $prop) (i32.const 12)) (then (br $ret (global.get $symbol:LV)))) (if (i32.eq (local.get $prop) (i32.const 13)) (then (br $ret (global.get $symbol:LVT)))) (br $ret (global.get $symbol:Other)))) ;; 4.6.4 Character Conversions (func $char-upcase (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) (local $cp2 i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Call host to compute upcase mapping (local.set $cp2 (call $char-upcase/ucs (local.get $cp))) ;; Return tagged character (ref.i31 (i32.or (i32.shl (local.get $cp2) (i32.const ,char-shift)) (i32.const ,char-tag)))) (func $char-downcase (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) (local $cp2 i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Call host to compute downcase mapping (local.set $cp2 (call $char-downcase/ucs (local.get $cp))) ;; Return tagged character (ref.i31 (i32.or (i32.shl (local.get $cp2) (i32.const ,char-shift)) (i32.const ,char-tag)))) (func $char-titlecase (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) (local $cp2 i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Call host to compute titlecase mapping (local.set $cp2 (call $char-titlecase/ucs (local.get $cp))) ;; Return tagged character (ref.i31 (i32.or (i32.shl (local.get $cp2) (i32.const ,char-shift)) (i32.const ,char-tag)))) ;; Note: JavaScript doesn't have a unicode aware `casefold` so instead ; toLower is used. This is not 100% correct. (func $char-foldcase (type $Prim1) (param $c (ref eq)) (result (ref eq)) (local $i31 (ref i31)) (local $c/tag i32) (local $cp i32) (local $cp2 i32) ;; Type check (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)))) (local.set $i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $i31))) ;; Decode codepoint (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Call host to compute foldcase mapping (local.set $cp2 (call $char-foldcase/ucs (local.get $cp))) ;; Return tagged character (ref.i31 (i32.or (i32.shl (local.get $cp2) (i32.const ,char-shift)) (i32.const ,char-tag)))) ;; 4.6.5 Character Grapheme-Cluster Streaming (func $char-grapheme-step (type $Prim2) (param $c (ref eq)) (param $state (ref eq)) (result (ref eq)) (local $c/i31 (ref i31)) (local $c/tag i32) (local $cp i32) (local $state/i31 (ref i31)) (local $state/raw i32) (local $state-val i32) (local $prev-last i32) (local $prev-base-field i32) (local $prev-base i32) (local $prev-ri i32) (local $prev-ext i32) (local $prev-zwj i32) (local $curr-prop i32) (local $curr-ext i32) (local $boundary i32) (local $consumed i32) (local $prev-base/eff i32) (local $prev-ri/eff i32) (local $prev-ext/eff i32) (local $prev-zwj/eff i32) (local $new-base i32) (local $new-ri i32) (local $new-zwj i32) (local $new-ext i32) (local $new-state i32) (local $base-field i32) (local $last-field i32) ;; Validate character argument (if (i32.eqz (ref.test (ref i31) (local.get $c))) (then (call $raise-check-char (local.get $c)) (unreachable))) (local.set $c/i31 (ref.cast (ref i31) (local.get $c))) (local.set $c/tag (i31.get_u (local.get $c/i31))) (if (i32.ne (i32.and (local.get $c/tag) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (call $raise-check-char (local.get $c)) (unreachable))) (local.set $cp (i32.shr_u (local.get $c/tag) (i32.const ,char-shift))) ;; Validate fixnum state (if (i32.eqz (ref.test (ref i31) (local.get $state))) (then (call $raise-check-fixnum (local.get $state)) (unreachable))) (local.set $state/i31 (ref.cast (ref i31) (local.get $state))) (local.set $state/raw (i31.get_s (local.get $state/i31))) (if (i32.ne (i32.and (local.get $state/raw) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $state)) (unreachable))) (local.set $state-val (i32.shr_s (local.get $state/raw) (i32.const 1))) ;; Decode previous-state fields (local.set $prev-last (i32.sub (i32.and (local.get $state-val) (i32.const 15)) (i32.const 1))) (local.set $prev-base-field (i32.and (i32.shr_u (local.get $state-val) (i32.const 4)) (i32.const 15))) (local.set $prev-base (local.get $prev-base-field)) (if (i32.eqz (local.get $prev-base-field)) (then (if (i32.eqz (local.get $state-val)) (then (local.set $prev-base (i32.const -1))) (else (local.set $prev-base (i32.const 0))))) (else (local.set $prev-base (i32.sub (local.get $prev-base-field) (i32.const 1))))) (local.set $prev-ri (i32.and (i32.shr_u (local.get $state-val) (i32.const 8)) (i32.const 1))) (local.set $prev-ext (i32.and (i32.shr_u (local.get $state-val) (i32.const 9)) (i32.const 1))) (local.set $prev-zwj (i32.and (i32.shr_u (local.get $state-val) (i32.const 10)) (i32.const 1))) ;; Classify current character (local.set $curr-prop (call $char-grapheme-break-property/ucs (local.get $cp))) (local.set $curr-ext (i32.and (call $char-extended-pictographic?/ucs (local.get $cp)) (i32.const 1))) ;; Determine whether a boundary has been crossed before this char (if (i32.ge_s (local.get $prev-last) (i32.const 0)) (then (local.set $boundary (i32.const 1))) (else (local.set $boundary (i32.const 0)))) (if (i32.ge_s (local.get $prev-last) (i32.const 0)) (then ;; Handle CR × LF specially (if (i32.and (i32.eq (local.get $prev-last) (i32.const 1)) (i32.eq (local.get $curr-prop) (i32.const 2))) (then (local.set $boundary (i32.const 0))) (else ;; Forced breaks around controls (if (i32.or (i32.eq (local.get $prev-last) (i32.const 1)) (i32.or (i32.eq (local.get $prev-last) (i32.const 2)) (i32.eq (local.get $prev-last) (i32.const 3)))) (then (local.set $boundary (i32.const 1))) (else (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 1)) (i32.or (i32.eq (local.get $curr-prop) (i32.const 2)) (i32.eq (local.get $curr-prop) (i32.const 3)))) (then (local.set $boundary (i32.const 1))) (else ;; Suppress breaks for specific combinations (if (i32.eq (local.get $prev-last) (i32.const 7)) (then (local.set $boundary (i32.const 0)))) (if (i32.eq (local.get $curr-prop) (i32.const 8)) (then (local.set $boundary (i32.const 0)))) (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 4)) (i32.eq (local.get $curr-prop) (i32.const 5))) (then (local.set $boundary (i32.const 0)))) (if (i32.eq (local.get $prev-base) (i32.const 9)) (then (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 9)) (i32.or (i32.eq (local.get $curr-prop) (i32.const 10)) (i32.or (i32.eq (local.get $curr-prop) (i32.const 12)) (i32.eq (local.get $curr-prop) (i32.const 13))))) (then (local.set $boundary (i32.const 0)))))) (if (i32.or (i32.eq (local.get $prev-base) (i32.const 12)) (i32.eq (local.get $prev-base) (i32.const 10))) (then (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 10)) (i32.eq (local.get $curr-prop) (i32.const 11))) (then (local.set $boundary (i32.const 0)))))) (if (i32.or (i32.eq (local.get $prev-base) (i32.const 13)) (i32.eq (local.get $prev-base) (i32.const 11))) (then (if (i32.eq (local.get $curr-prop) (i32.const 11)) (then (local.set $boundary (i32.const 0)))))) (if (i32.and (i32.eq (local.get $prev-last) (i32.const 6)) (i32.eq (local.get $curr-prop) (i32.const 6))) (then (if (i32.eq (local.get $prev-ri) (i32.const 1)) (then (local.set $boundary (i32.const 0)))))) (if (i32.and (local.get $prev-zwj) (local.get $curr-ext)) (then (local.set $boundary (i32.const 0)))))))))))) ;; Determine whether a cluster has completed (local.set $consumed (local.get $boundary)) (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 2)) (i32.eq (local.get $curr-prop) (i32.const 3))) (then (local.set $consumed (i32.const 1)))) ;; Reset effective context when a cluster completed (local.set $prev-base/eff (local.get $prev-base)) (local.set $prev-ri/eff (local.get $prev-ri)) (local.set $prev-ext/eff (local.get $prev-ext)) (local.set $prev-zwj/eff (local.get $prev-zwj)) (if (i32.eqz (local.get $consumed)) (then) (else (local.set $prev-base/eff (i32.const -1)) (local.set $prev-ri/eff (i32.const 0)) (local.set $prev-ext/eff (i32.const 0)) (local.set $prev-zwj/eff (i32.const 0)))) ;; Update contextual fields with the current character (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 4)) (i32.or (i32.eq (local.get $curr-prop) (i32.const 5)) (i32.eq (local.get $curr-prop) (i32.const 8)))) (then (local.set $new-base (local.get $prev-base/eff)) (local.set $new-ext (local.get $prev-ext/eff))) (else (local.set $new-base (local.get $curr-prop)) (local.set $new-ext (local.get $curr-ext)))) (if (i32.eq (local.get $curr-prop) (i32.const 6)) (then (if (i32.eq (local.get $prev-base/eff) (i32.const 6)) (then (local.set $new-ri (i32.xor (local.get $prev-ri/eff) (i32.const 1)))) (else (local.set $new-ri (i32.const 1))))) (else (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 4)) (i32.or (i32.eq (local.get $curr-prop) (i32.const 5)) (i32.eq (local.get $curr-prop) (i32.const 8)))) (then (local.set $new-ri (local.get $prev-ri/eff))) (else (local.set $new-ri (i32.const 0)))))) (if (i32.eq (local.get $curr-prop) (i32.const 5)) (then (if (i32.eq (local.get $prev-ext/eff) (i32.const 1)) (then (local.set $new-zwj (i32.const 1))) (else (local.set $new-zwj (i32.const 0))))) (else (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 4)) (i32.eq (local.get $curr-prop) (i32.const 8))) (then (local.set $new-zwj (local.get $prev-zwj/eff))) (else (local.set $new-zwj (i32.const 0)))))) ;; Assemble new state (0 for LF or Control) (if (i32.or (i32.eq (local.get $curr-prop) (i32.const 2)) (i32.eq (local.get $curr-prop) (i32.const 3))) (then (local.set $new-state (i32.const 0))) (else (local.set $base-field (i32.const 0)) (if (i32.lt_s (local.get $new-base) (i32.const 0)) (then (local.set $base-field (i32.const 0))) (else (if (i32.eqz (local.get $new-base)) (then (local.set $base-field (i32.const 0))) (else (local.set $base-field (i32.add (local.get $new-base) (i32.const 1))))))) (local.set $last-field (i32.add (local.get $curr-prop) (i32.const 1))) (local.set $new-state (i32.or (local.get $last-field) (i32.or (i32.shl (local.get $base-field) (i32.const 4)) (i32.or (i32.shl (i32.and (local.get $new-ri) (i32.const 1)) (i32.const 8)) (i32.or (i32.shl (i32.and (local.get $new-ext) (i32.const 1)) (i32.const 9)) (i32.shl (i32.and (local.get $new-zwj) (i32.const 1)) (i32.const 10))))))))) ;; Return two values as an array (array.new_fixed $Values 2 (if (result (ref eq)) (local.get $consumed) (then (global.get $true)) (else (global.get $false))) (ref.i31 (i32.shl (local.get $new-state) (i32.const 1))))) ;;; ;;; 4.7 SYMBOLS ;;; ;; https://docs.racket-lang.org/reference/symbols.html ;; (type $Symbol ;; (sub $Heap ;; (struct ;; (field $hash (mut i32)) ;; cached hash ;; (field $name (ref $String)) ;; symbol name (string) ;; (field $property-list (mut (ref eq)))))) ;; user-defined properties (func $symbol? (type $Prim1) (param $x (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Symbol) (local.get $x)) (then (global.get $true)) (else (global.get $false)))) (func $symbol=? (type $Prim2) (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $a) (local.get $b)) (then (global.get $true)) (else (global.get $false)))) (func $symbol=?/i32 (param $a (ref eq)) (param $b (ref eq)) (result i32) (ref.eq (local.get $a) (local.get $b))) (func $raise-symbol->string:bad-argument (param $v (ref eq)) (call $js-log (local.get $v)) (unreachable)) (func $symbol->string (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $sym (ref $Symbol)) (local $name (ref $String)) ;; Check that input is a symbol (if (ref.test (ref $Symbol) (local.get $v)) (then ;; Cast to $Symbol (local.set $sym (ref.cast (ref $Symbol) (local.get $v))) ;; Extract name field (local.set $name (struct.get $Symbol $name (local.get $sym))) ;; Return a fresh mutable copy (return (call $string-copy (local.get $name)))) (else ;; Not a symbol, raise error (call $raise-symbol->string:bad-argument (local.get $v)))) (unreachable)) (func $symbol->immutable-string (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $sym (ref $Symbol)) (local $name (ref $String)) (local $src (ref $I32Array)) (local $dst (ref $I32Array)) (local $len i32) (if (ref.test (ref $Symbol) (local.get $v)) (then (local.set $sym (ref.cast (ref $Symbol) (local.get $v))) (local.set $name (struct.get $Symbol $name (local.get $sym))) ;; Already immutable? Return as-is. (if (i32.eq (struct.get $String $immutable (local.get $name)) (i32.const 1)) (then (return (local.get $name))) (else ;; Copy codepoints with i32array-copy [0, len) (local.set $src (struct.get $String $codepoints (local.get $name))) (local.set $len (array.len (local.get $src))) (local.set $dst (call $i32array-copy (local.get $src) (i32.const 0) (local.get $len))) ;; Build fresh immutable string (hash=0, immutable=1) (return (struct.new $String (i32.const 0) (i32.const 1) (local.get $dst)))))) (else (call $raise-symbol->string:bad-argument (local.get $v)) (unreachable))) (unreachable)) (func $raise-string->symbol:bad-argument (param $v (ref eq)) (unreachable)) (func $string->symbol (type $Prim1) (param $v (ref eq)) (result (ref eq)) ; An (ref $Symbol) (if (ref.test (ref $String) (local.get $v)) (then (return (call $string->symbol/checked (ref.cast (ref $String) (local.get $v))))) (else (call $raise-string->symbol:bad-argument (local.get $v)))) (unreachable)) (func $string->symbol/checked (param $str (ref $String)) (result (ref $Symbol)) (local $existing (ref eq)) (local $sym (ref $Symbol)) ;; Look up the string in the symbol table (local.set $existing (call $symbol-table-find (ref.as_non_null (global.get $the-symbol-table)) (local.get $str))) ;; If found, return it (cast to (ref $Symbol)) (if (ref.test (ref $Symbol) (local.get $existing)) (then (return (ref.cast (ref $Symbol) (local.get $existing))))) ;; Otherwise, construct a new interned symbol (local.set $sym (struct.new $Symbol (i32.const 0) ;; hash = 0 (not computed) (local.get $str) ;; name (global.get $null))) ;; empty property list ;; Insert it into the symbol table (call $symbol-table-insert (ref.as_non_null (global.get $the-symbol-table)) (local.get $str) (local.get $sym)) ;; Return the new symbol (local.get $sym)) (func $raise-string->uninterned-symbol:bad-argument (param $v (ref eq)) (unreachable)) (func $string->uninterned-symbol (type $Prim1) (param $v (ref eq)) (result (ref eq)) ; An (ref $Symbol) (if (ref.test (ref $String) (local.get $v)) (then (return (call $string->uninterned-symbol/checked (ref.cast (ref $String) (local.get $v))))) (else (call $raise-string->uninterned-symbol:bad-argument (local.get $v)))) (unreachable)) (func $string->uninterned-symbol/checked (param $str (ref $String)) (result (ref $Symbol)) (struct.new $Symbol (i32.const 0) ;; hash = 0 (deferred) (local.get $str) ;; name (global.get $null))) ;; empty property list (func $symbol-interned? (type $Prim1) (param $sym (ref eq)) (result (ref eq)) (local $str (ref $String)) (local $found (ref eq)) ;; Check that it's a symbol (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-check-symbol (local.get $sym)))) (local.set $str (struct.get $Symbol $name (ref.cast (ref $Symbol) (local.get $sym)))) (local.set $found (call $symbol-table-find (ref.as_non_null (global.get $the-symbol-table)) (local.get $str))) ;; If found symbol == input symbol => interned (if (result (ref eq)) (ref.eq (local.get $found) (local.get $sym)) (then (global.get $true)) (else (global.get $false)))) (func $raise-check-symbol (param $x (ref eq)) (unreachable)) (func $symbol=1) (param $a (ref eq)) (param $rest (ref eq)) (result (ref eq)) (local $prev-str (ref $String)) (local $curr-str (ref $String)) (local $rest-list (ref eq)) (local $pair (ref $Pair)) (local $next (ref eq)) ;; Type check: the first argument must be a symbol (if (i32.eqz (ref.test (ref $Symbol) (local.get $a))) (then (call $raise-check-symbol (local.get $a)))) ;; Extract the name of the first symbol and prepare to iterate over the rest (local.set $prev-str (struct.get $Symbol $name (ref.cast (ref $Symbol) (local.get $a)))) (local.set $rest-list (local.get $rest)) ;; With a single argument, the result is #t (if (ref.eq (local.get $rest-list) (global.get $null)) (then (return (global.get $true)))) (loop $loop (if (ref.eq (local.get $rest-list) (global.get $null)) (then (return (global.get $true)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $rest-list))) (then (call $raise-pair-expected (local.get $rest-list)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $rest-list))) (local.set $next (struct.get $Pair $a (local.get $pair))) ;; Ensure each remaining argument is a symbol (if (i32.eqz (ref.test (ref $Symbol) (local.get $next))) (then (call $raise-check-symbol (local.get $next)))) (local.set $curr-str (struct.get $Symbol $name (ref.cast (ref $Symbol) (local.get $next)))) (if (ref.eq (call $stringstring ; convert $n from an i32 to a fixnum (ref.i31 (i32.shl (local.get $n) (i32.const 1))) ,(Imm 10)))) ;; Append prefix and number string (ref.cast (ref $String) (call $string-append/2 (local.get $prefix) (local.get $n-str)))) (func $gensym (type $Prim01) (param $base (ref eq)) ;; optional base (string or symbol), default = "g" (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $base) (global.get $missing)) (then (call $gensym:0)) (else (call $gensym:1 (local.get $base))))) (func $gensym:0 (result (ref $Symbol)) ;; Use "g" as default prefix (call $gensym:1 (ref.cast (ref $String) (global.get $string:g)))) (func $raise-gensym:bad-base (param $x (ref eq)) (unreachable)) (func $gensym:1 (param $base (ref eq)) (result (ref $Symbol)) (local $prefix (ref null $String)) (local $name (ref $String)) ;; Convert symbol -> string if needed (if (ref.test (ref $Symbol) (local.get $base)) (then (local.set $prefix (struct.get $Symbol $name (ref.cast (ref $Symbol) (local.get $base))))) (else (if (ref.test (ref $String) (local.get $base)) (then (local.set $prefix (ref.cast (ref $String) (local.get $base)))) (else (call $raise-gensym:bad-base (local.get $base)) (unreachable))))) ;; Generate name string (local.set $name (call $make-gensym-name (ref.as_non_null (local.get $prefix)))) ;; Return new uninterned symbol (struct.new $Symbol (i32.const 0) ;; hash = 0 (local.get $name) ;; name (global.get $null))) ;; empty property list ;;; ;;; 4.8 REGULAR EXPRESSIONS ;;; ;; https://docs.racket-lang.org/reference/regexp.html ;; TODO - Implement regular expressions. ;;; ;;; 4.9 KEYWORDS ;;; ;; https://docs.racket-lang.org/reference/keywords.html ;; Keywords are interned using `the-keywords-table` which maps strings (without #:) ;; to keywords. (func $keyword?/i32 (param $v (ref eq)) (result i32) (ref.test (ref $Keyword) (local.get $v))) (func $keyword? (type $Prim1) ,@(make-predicate-body '$Keyword)) (func $string->keyword (type $Prim1) (param $str (ref eq)) (result (ref eq)) ; an (ref $Keyword) ;; Type check: must be a string (if (i32.eqz (ref.test (ref $String) (local.get $str))) (then (call $raise-argument-error:string-expected (local.get $str)) (unreachable))) ;; Cast and delegate (call $string->keyword/checked (ref.cast (ref $String) (local.get $str)))) (func $string->keyword/checked (param $str (ref $String)) (result (ref $Keyword)) (local $existing (ref eq)) (local $kw (ref $Keyword)) ;; Look up in table (local.set $existing (call $symbol-table-find (ref.as_non_null (global.get $the-keyword-table)) (local.get $str))) (if (result (ref $Keyword)) (ref.eq (local.get $existing) (global.get $missing)) (then ;; Not found – allocate and intern new keyword (local.set $kw (struct.new $Keyword (i32.const 0) ;; hash will be assigned later (local.get $str))) (call $symbol-table-insert (ref.as_non_null (global.get $the-keyword-table)) (local.get $str) (local.get $kw)) (local.get $kw)) (else (ref.cast (ref $Keyword) (local.get $existing))))) (func $raise-argument-error:keyword-expected (unreachable)) (func $keyword->string (type $Prim1) ; the result does not contain #: (param $kw (ref eq)) (result (ref eq)) ; an (ref $String) ;; Type check: must be a keyword (if (i32.eqz (ref.test (ref $Keyword) (local.get $kw))) (then (call $raise-argument-error:keyword-expected (local.get $kw)) (unreachable))) ;; Cast and delegate (call $keyword->string/checked (ref.cast (ref $Keyword) (local.get $kw)))) (func $keyword->string/checked (param $kw (ref $Keyword)) (result (ref $String)) (local $name (ref $String)) (local.set $name (struct.get $Keyword $str (local.get $kw))) (ref.cast (ref $String) (call $string-copy (local.get $name)))) (func $keyword->immutable-string (type $Prim1) (param $kw (ref eq)) (result (ref eq)) ; an (ref $String) ;; Type check: must be a keyword (if (i32.eqz (ref.test (ref $Keyword) (local.get $kw))) (then (call $raise-argument-error:keyword-expected (local.get $kw)) (unreachable))) ;; Cast and delegate (call $keyword->immutable-string/checked (ref.cast (ref $Keyword) (local.get $kw)))) (func $keyword->immutable-string/checked (param $kw (ref $Keyword)) (result (ref $String)) (local $name (ref $String)) (local $src (ref $I32Array)) (local $dst (ref $I32Array)) (local $len i32) (local.set $name (struct.get $Keyword $str (local.get $kw))) (if (result (ref $String)) (i32.eq (struct.get $String $immutable (local.get $name)) (i32.const 1)) (then (local.get $name)) (else (local.set $src (struct.get $String $codepoints (local.get $name))) (local.set $len (array.len (local.get $src))) (local.set $dst (call $i32array-copy (local.get $src) (i32.const 0) (local.get $len))) (struct.new $String (i32.const 0) (i32.const 1) (local.get $dst))))) (func $raise-keyword-expected (unreachable)) ;; keyword (ref eq) ;; returns #t/#f (func $keyword=1) (param $a (ref eq)) (param $rest (ref eq)) (result (ref eq)) (local $prev-str (ref $String)) (local $curr-str (ref $String)) (local $rest-list (ref eq)) (local $pair (ref $Pair)) (local $next (ref eq)) ;; Type check: the first argument must be a keyword (if (i32.eqz (ref.test (ref $Keyword) (local.get $a))) (then (call $raise-expected-keyword (global.get $symbol:keyword=1) (param $a (ref eq)) (param $tail (ref eq)) (result (ref eq)) (local $head (ref $Pair)) (local $prev (ref $Pair)) (local $next (ref eq)) (local $rest (ref eq)) (local $node (ref $Pair)) (local $newp (ref $Pair)) ;; (list* x) => x (if (ref.eq (local.get $tail) (global.get $null)) (then (return (local.get $a)))) ;; Build prefix pairs, and use the last argument as final tail. (local.set $head (struct.new $Pair (i32.const 0) (local.get $a) (global.get $null))) (local.set $prev (local.get $head)) (local.set $rest (local.get $tail)) (block $done (loop $loop (local.set $node (ref.cast (ref $Pair) (local.get $rest))) (local.set $next (struct.get $Pair $d (local.get $node))) (if (ref.eq (local.get $next) (global.get $null)) (then (struct.set $Pair $d (local.get $prev) (struct.get $Pair $a (local.get $node))) (return (local.get $head)))) (local.set $newp (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $node)) (global.get $null))) (struct.set $Pair $d (local.get $prev) (local.get $newp)) (local.set $prev (local.get $newp)) (local.set $rest (local.get $next)) (br $loop))) (local.get $head)) (func $car (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Pair) (local.get $v)) (then (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $v)))) (else (call $raise-pair-expected (local.get $v)) (unreachable)))) (func $cdr (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Pair) (local.get $v)) (then (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $v)))) (else (call $raise-pair-expected (local.get $v)) (unreachable)))) ,@(for/list ([name '(caar cadr cdar cddr caaar caadr cadar caddr cdaar cdadr cddar cdddr caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr)]) (let* ([str (symbol->string name)] [letters (substring str 1 (sub1 (string-length str)))] [ops (for/list ([ch (in-list (reverse (string->list letters)))]) (case ch [(#\a) 'car] [(#\d) 'cdr] [else (error 'generate-runtime "invalid pair accessor name: ~a" name)]))] [body (for/fold ([expr '(local.get $v)]) ([op (in-list ops)]) (case op [(car) `(call $car ,expr)] [(cdr) `(call $cdr ,expr)]))]) `(func ,(string->symbol (~a "$" name)) (type $Prim1) (param $v (ref eq)) (result (ref eq)) ,body))) (func $list? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (block $exit (result (ref eq)) (loop $loop (if (ref.eq (local.get $v) (global.get $null)) (then (return (global.get $true)))) (if (ref.test (ref $Pair) (local.get $v)) (then (local.set $v (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $v)))) (br $loop)) (else (return (global.get $false))))) ;; fallthrough: not a proper list (global.get $false))) (func $list (type $Prim>=0) (param $args (ref eq)) (result (ref eq)) (local.get $args)) ;; #;(call $rest-arguments->list ;; (ref.cast (ref $Args) (local.get $args)) ;; (i32.const 0))) (func $length/i32 (param $xs (ref eq)) (result i32) (local $i i32) (local.set $i (i32.const 0)) (block $done (loop $count ;; if we've reached null, return the count so far (if (ref.eq (local.get $xs) (global.get $null)) (then (return (local.get $i)))) ;; else, must be a pair: follow its cdr (if (ref.test (ref $Pair) (local.get $xs)) (then (local.set $xs (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $xs))))) ;; neither null nor pair: error (else (call $raise-pair-expected (local.get $xs)))) ;; increment and repeat (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $count))) ;; fall-through just returns the current count (local.get $i)) (func $length (type $Prim1) (param $xs (ref eq)) (result (ref eq)) (local $i i32) (local.set $i (i32.const 0)) (block $done (loop $count (if (ref.eq (local.get $xs) (global.get $null)) (then (br $done))) (if (ref.test (ref $Pair) (local.get $xs)) (then (local.set $xs (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $xs))))) (else (call $raise-pair-expected (local.get $xs)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $count))) (ref.i31 (i32.shl (local.get $i) (i32.const 1)))) ;; list-ref/checked: takes a Pair and an unboxed i32 index. ;; Works for improper lists as long as the index doesn't step past the last Pair. (func $list-ref/checked (param $xs (ref $Pair)) (param $i i32) (result (ref eq)) (local $v (ref $Pair)) (local $k i32) (local $next (ref eq)) (local $len i32) (local.set $v (local.get $xs)) (local.set $k (local.get $i)) (loop $loop ;; If we've reached the desired pair, return its car. (if (i32.eqz (local.get $k)) (then (return (struct.get $Pair $a (local.get $v))))) ;; Otherwise, try to step to the next pair. (local.set $next (struct.get $Pair $d (local.get $v))) (if (ref.test (ref $Pair) (local.get $next)) (then (local.set $v (ref.cast (ref $Pair) (local.get $next))) (local.set $k (i32.sub (local.get $k) (i32.const 1))) (br $loop)) (else ;; Ran out of pairs before reaching index: compute length of the ;; pair-chain we've actually got and raise. ;; len = steps_so_far + 1 = i - k + 1 (local.set $len (i32.add (i32.sub (local.get $i) (local.get $k)) (i32.const 1))) (call $raise-bad-list-ref-index (local.get $xs) (local.get $i) (local.get $len)) (unreachable)))) ;; Should not fall through. (unreachable)) (func $list-ref (type $Prim2) (param $xs (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $idx i32) ;; Decode & check fixnum index (if (ref.test (ref i31) (local.get $i)) (then (local.set $idx (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.ne (i32.and (local.get $idx) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $i)))) (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i)))) ;; Check & dispatch on Pair (if (result (ref eq)) (ref.test (ref $Pair) (local.get $xs)) (then (call $list-ref/checked (ref.cast (ref $Pair) (local.get $xs)) (local.get $idx))) (else (call $raise-pair-expected (local.get $xs)) (unreachable)))) ,@(for/list ([name '($first $second $third $fourth $fifth $sixth $seventh $eighth $ninth $tenth $eleventh $twelfth $thirteenth $fourteenth $fifteenth)] [idx (in-naturals)]) `(func ,name (type $Prim1) (param $xs (ref eq)) (result (ref eq)) ;; Type check: non-empty proper list (if (ref.eq (local.get $xs) (global.get $null)) (then (call $js-log (call $format/display (local.get $xs))) (call $raise-argument-error (local.get $xs)) (unreachable))) (if (ref.eq (call $list? (local.get $xs)) (global.get $false)) (then (call $js-log (call $format/display (local.get $xs))) (call $raise-argument-error (local.get $xs)) (unreachable))) ;; Retrieve element using list-ref (call $list-ref (local.get $xs) (ref.i31 (i32.shl (i32.const ,idx) (i32.const 1)))))) (func $rest (type $Prim1) (param $xs (ref eq)) (result (ref eq)) ;; Type check: non-empty proper list (if (ref.eq (local.get $xs) (global.get $null)) (then (call $raise-argument-error (local.get $xs)) (unreachable))) (if (ref.eq (call $list? (local.get $xs)) (global.get $false)) (then (call $raise-argument-error (local.get $xs)) (unreachable))) ;; Return tail (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $xs)))) ;; last-pair/checked: takes a Pair and returns the last Pair in the chain (func $last-pair/checked (param $p (ref $Pair)) (result (ref $Pair)) (local $node (ref $Pair)) (local $next (ref eq)) (local.set $node (local.get $p)) (loop $loop (local.set $next (struct.get $Pair $d (local.get $node))) (if (ref.test (ref $Pair) (local.get $next)) (then (local.set $node (ref.cast (ref $Pair) (local.get $next))) (br $loop)) (else (return (local.get $node))))) (unreachable)) (func $last (type $Prim1) (param $xs (ref eq)) (result (ref eq)) (local $p (ref $Pair)) ;; Type check: non-empty proper list (if (ref.eq (local.get $xs) (global.get $null)) (then (call $raise-argument-error (local.get $xs)) (unreachable))) (if (ref.eq (call $list? (local.get $xs)) (global.get $false)) (then (call $raise-argument-error (local.get $xs)) (unreachable))) ;; Retrieve last element (local.set $p (call $last-pair/checked (ref.cast (ref $Pair) (local.get $xs)))) (struct.get $Pair $a (local.get $p))) (func $last-pair (type $Prim1) (param $xs (ref eq)) (result (ref eq)) ;; Type check: pair? (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (call $last-pair/checked (ref.cast (ref $Pair) (local.get $xs)))) ;; list-tail/checked: xs is known to be a Pair and i > 0. ;; Returns the result of cdr^i(xs). Works with improper lists: ;; if the i-th cdr is a non-pair, it is returned. If we need to ;; cdr again past a non-pair, raise pair-expected. (func $list-tail/checked (param $xs (ref $Pair)) (param $i i32) (result (ref eq)) (local $v (ref $Pair)) (local $k i32) (local $next (ref eq)) (local.set $v (local.get $xs)) (local.set $k (local.get $i)) (loop $loop ;; If no steps remain, return current tail (a Pair value is fine as (ref eq)). (if (i32.eqz (local.get $k)) (then (return (local.get $v)))) ;; Step once. (local.set $next (struct.get $Pair $d (local.get $v))) (local.set $k (i32.sub (local.get $k) (i32.const 1))) ;; If that single step completed all steps, return whatever we landed on ;; (pair or not). (if (i32.eqz (local.get $k)) (then (return (local.get $next)))) ;; Otherwise, we must continue stepping. Ensure next is a Pair. (if (ref.test (ref $Pair) (local.get $next)) (then (local.set $v (ref.cast (ref $Pair) (local.get $next))) (br $loop)) (else (call $raise-pair-expected (local.get $next))))) (unreachable)) (func $list-tail (type $Prim2) (param $xs (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $pair (ref $Pair)) (local $idx i32) ;; Initialize non-defaultable local to a safe value. (local.set $pair (global.get $dummy-pair)) ;; Decode and check fixnum index (i31 with lsb=0). (if (ref.test (ref i31) (local.get $i)) (then (local.set $idx (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.ne (i32.and (local.get $idx) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $i)))) (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i)))) ;; (list-tail xs 0) => xs (if (i32.eqz (local.get $idx)) (then (return (local.get $xs)))) ;; For idx > 0, xs must be a Pair. Make dominance explicit with `unreachable`. (if (ref.test (ref $Pair) (local.get $xs)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $xs)))) (else (call $raise-pair-expected (local.get $xs)) (unreachable))) (call $list-tail/checked (local.get $pair) (local.get $idx))) ;; list-set/checked: xs is a Pair and i >= 0 within bounds. ;; Returns a new list with the element at index i replaced by v. (func $list-set/checked (param $xs (ref $Pair)) (param $i i32) (param $v (ref eq)) (result (ref eq)) (local $p (ref $Pair)) (local $k i32) (local $next (ref eq)) (local $rev (ref eq)) (local $len i32) (local $tail (ref eq)) (local $res (ref eq)) (local $prefix (ref eq)) (local.set $p (local.get $xs)) (local.set $k (local.get $i)) (local.set $rev (global.get $null)) (loop $loop (if (i32.eqz (local.get $k)) (then (local.set $tail (struct.get $Pair $d (local.get $p))) (local.set $res (struct.new $Pair (i32.const 0) (local.get $v) (local.get $tail))) (local.set $prefix (local.get $rev)) (loop $rebuild (if (ref.eq (local.get $prefix) (global.get $null)) (then (return (local.get $res)))) (local.set $res (struct.new $Pair (i32.const 0) (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $prefix))) (local.get $res))) (local.set $prefix (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $prefix)))) (br $rebuild)))) (local.set $rev (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $p)) (local.get $rev))) (local.set $next (struct.get $Pair $d (local.get $p))) (if (ref.test (ref $Pair) (local.get $next)) (then (local.set $p (ref.cast (ref $Pair) (local.get $next))) (local.set $k (i32.sub (local.get $k) (i32.const 1))) (br $loop)) (else (local.set $len (i32.add (i32.sub (local.get $i) (local.get $k)) (i32.const 1))) (call $raise-bad-list-set-index (local.get $xs) (local.get $i) (local.get $len)) (unreachable)))) (unreachable)) (func $list-set (type $Prim3) (param $xs (ref eq)) (param $i (ref eq)) (param $v (ref eq)) (result (ref eq)) (local $idx i32) (local $pair (ref $Pair)) ;; Decode & check fixnum index (if (ref.test (ref i31) (local.get $i)) (then (local.set $idx (i31.get_s (ref.cast (ref i31) (local.get $i)))) (if (i32.ne (i32.and (local.get $idx) (i32.const 1)) (i32.const 0)) (then (call $raise-argument-error1 (global.get $symbol:list-set) (global.get $string:exact-nonnegative-integer?) (local.get $i)) (unreachable))) (local.set $idx (i32.shr_s (local.get $idx) (i32.const 1)))) (else (call $raise-argument-error1 (global.get $symbol:list-set) (global.get $string:exact-nonnegative-integer?) (local.get $i)) (unreachable))) (if (i32.lt_s (local.get $idx) (i32.const 0)) (then (call $raise-argument-error1 (global.get $symbol:list-set) (global.get $string:exact-nonnegative-integer?) (local.get $i)) (unreachable))) ;; Ensure non-empty proper list (if (ref.eq (local.get $xs) (global.get $null)) (then (call $raise-bad-list-set-index (local.get $xs) (local.get $idx) (i32.const 0)) (unreachable))) (if (ref.eq (call $list? (local.get $xs)) (global.get $false)) (then (call $raise-argument-error (local.get $xs)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $xs))) (call $list-set/checked (local.get $pair) (local.get $idx) (local.get $v))) (func $drop (type $Prim2) (param $lst (ref eq)) ; list (param $pos (ref eq)) ; exact-nonnegative-integer? (result (ref eq)) (local $n i32) (local $remaining i32) (local $orig (ref $Pair)) (local $node (ref $Pair)) (local $next (ref eq)) (local $len i32) ;; Initialize non-defaultable locals (local.set $orig (global.get $dummy-pair)) (local.set $node (global.get $dummy-pair)) (local.set $next (global.get $null)) ;; Decode and validate index (if (ref.test (ref i31) (local.get $pos)) (then (local.set $n (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $pos))) (i32.const 1))) (if (i32.lt_s (local.get $n) (i32.const 0)) (then (call $raise-argument-error (local.get $pos)) (unreachable)))) (else (call $raise-check-fixnum (local.get $pos)) (unreachable))) ;; Dropping zero elements returns the list unchanged (if (i32.eqz (local.get $n)) (then (return (local.get $lst)))) ;; Non-empty list required when dropping more than zero (if (ref.eq (local.get $lst) (global.get $null)) (then (call $raise-argument-error (local.get $lst)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lst))) (then (call $raise-pair-expected (local.get $lst)) (unreachable))) (local.set $orig (ref.cast (ref $Pair) (local.get $lst))) (local.set $node (local.get $orig)) (local.set $remaining (local.get $n)) (loop $loop (local.set $next (struct.get $Pair $d (local.get $node))) (local.set $remaining (i32.sub (local.get $remaining) (i32.const 1))) (if (i32.eqz (local.get $remaining)) (then (return (local.get $next)))) (if (ref.test (ref $Pair) (local.get $next)) (then (local.set $node (ref.cast (ref $Pair) (local.get $next))) (br $loop)) (else (local.set $len (i32.sub (local.get $n) (local.get $remaining))) (call $raise-bad-list-ref-index (local.get $orig) (local.get $n) (local.get $len)) (unreachable)))) (unreachable)) (func $drop-right (type $Prim2) (param $lst (ref eq)) ; list (param $pos (ref eq)) ; exact-nonnegative-integer? (result (ref eq)) (local $n i32) (local $remaining i32) (local $first (ref $Pair)) (local $lead (ref eq)) (local $lag (ref eq)) (local $lead-pair (ref $Pair)) (local $lag-pair (ref $Pair)) (local $acc (ref eq)) (local $tail (ref eq)) (local $rev (ref eq)) (local $len i32) ;; Initialize locals for non-defaultable references (local.set $first (global.get $dummy-pair)) (local.set $lead (local.get $lst)) (local.set $lag (local.get $lst)) (local.set $lead-pair (global.get $dummy-pair)) (local.set $lag-pair (global.get $dummy-pair)) (local.set $acc (global.get $null)) (local.set $tail (global.get $null)) (local.set $rev (global.get $null)) ;; Decode and validate index (if (ref.test (ref i31) (local.get $pos)) (then (local.set $n (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $pos))) (i32.const 1))) (if (i32.lt_s (local.get $n) (i32.const 0)) (then (call $raise-argument-error (local.get $pos)) (unreachable)))) (else (call $raise-check-fixnum (local.get $pos)) (unreachable))) (if (i32.eqz (local.get $n)) (then (return (local.get $lst)))) (if (ref.eq (local.get $lst) (global.get $null)) (then (call $raise-argument-error (local.get $lst)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lst))) (then (call $raise-pair-expected (local.get $lst)) (unreachable))) (local.set $first (ref.cast (ref $Pair) (local.get $lst))) (local.set $lead (local.get $lst)) (local.set $lag (local.get $lst)) (local.set $remaining (local.get $n)) (block $advance-done (loop $advance (br_if $advance-done (i32.eqz (local.get $remaining))) (if (ref.test (ref $Pair) (local.get $lead)) (then (local.set $lead-pair (ref.cast (ref $Pair) (local.get $lead))) (local.set $lead (struct.get $Pair $d (local.get $lead-pair))) (local.set $remaining (i32.sub (local.get $remaining) (i32.const 1))) (br $advance)) (else (if (ref.eq (local.get $lead) (global.get $null)) (then (local.set $len (i32.sub (local.get $n) (local.get $remaining))) (call $raise-bad-list-ref-index (local.get $first) (local.get $n) (local.get $len)) (unreachable)) (else (call $raise-pair-expected (local.get $lead)) (unreachable))))))) (local.set $lag (local.get $lst)) (local.set $acc (global.get $null)) (block $build-done (loop $build (if (i32.eqz (ref.test (ref $Pair) (local.get $lead))) (then (br $build-done))) (local.set $lag-pair (ref.cast (ref $Pair) (local.get $lag))) (local.set $acc (call $cons (struct.get $Pair $a (local.get $lag-pair)) (local.get $acc))) (local.set $lag (struct.get $Pair $d (local.get $lag-pair))) (local.set $lead (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $lead)))) (br $build))) (local.set $tail (local.get $lead)) (local.set $rev (call $reverse (local.get $acc))) (if (ref.eq (local.get $tail) (global.get $null)) (then (return (local.get $rev)))) (return (call $append/2 (local.get $rev) (local.get $tail)))) (func $dropf (type $Prim2) (param $lst (ref eq)) ; list (param $proc (ref eq)) ; predicate (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $r (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $cur (local.get $lst)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $cur)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (return (local.get $cur)))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $r) (global.get $false)) (then (return (local.get $cur)))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) (func $dropf-right (type $Prim2) (param $lst (ref eq)) ; list (param $proc (ref eq)) ; predicate (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $r (ref eq)) (local $acc (ref eq)) (local $suffix (ref eq)) (local $tmp (ref eq)) (local $node (ref $Pair)) (local $tail (ref eq)) (local $rev (ref eq)) ;; Initialize locals for non-defaultable references (local.set $acc (global.get $null)) (local.set $suffix (global.get $null)) (local.set $tmp (global.get $null)) (local.set $tail (global.get $null)) (local.set $rev (global.get $null)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $cur (local.get $lst)) (block $done (loop $loop (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (br $done))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $r) (global.get $false)) (then (if (ref.eq (local.get $suffix) (global.get $null)) (then (nop)) (else (local.set $tmp (call $reverse (local.get $suffix))) (local.set $suffix (global.get $null)) (block $flush (loop $flush-loop (if (ref.eq (local.get $tmp) (global.get $null)) (then (br $flush))) (local.set $node (ref.cast (ref $Pair) (local.get $tmp))) (local.set $acc (call $cons (struct.get $Pair $a (local.get $node)) (local.get $acc))) (local.set $tmp (struct.get $Pair $d (local.get $node))) (br $flush-loop))))) (local.set $acc (call $cons (local.get $elem) (local.get $acc)))) (else (local.set $suffix (call $cons (local.get $elem) (local.get $suffix))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop))) (local.set $tail (local.get $cur)) (local.set $rev (call $reverse (local.get $acc))) (if (ref.eq (local.get $tail) (global.get $null)) (then (return (local.get $rev)))) (return (call $append/2 (local.get $rev) (local.get $tail))) ) (func $split-at (type $Prim2) (param $lst (ref eq)) ; list (param $pos (ref eq)) ; exact-nonnegative-integer? (result (ref eq)) ; returns two values (local $prefix (ref eq)) (local $suffix (ref eq)) (local.set $prefix (global.get $null)) (local.set $suffix (global.get $null)) (local.set $prefix (call $take (local.get $lst) (local.get $pos))) (local.set $suffix (call $drop (local.get $lst) (local.get $pos))) (array.new_fixed $Values 2 (local.get $prefix) (local.get $suffix))) (func $split-at-right (type $Prim2) (param $lst (ref eq)) ; list (param $pos (ref eq)) ; exact-nonnegative-integer? (result (ref eq)) ; returns two values (local $prefix (ref eq)) (local $suffix (ref eq)) (local $acc (ref eq)) (local $current (ref eq)) (local $pair (ref $Pair)) (local.set $prefix (global.get $null)) (local.set $suffix (global.get $null)) (local.set $acc (global.get $null)) (local.set $current (global.get $null)) (local.set $pair (global.get $dummy-pair)) (local.set $suffix (call $take-right (local.get $lst) (local.get $pos))) (local.set $current (local.get $lst)) (block $done (loop $build (if (ref.eq (local.get $current) (local.get $suffix)) (then (br $done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $current))) (then (call $raise-pair-expected (local.get $current)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $current))) (local.set $acc (call $cons (struct.get $Pair $a (local.get $pair)) (local.get $acc))) (local.set $current (struct.get $Pair $d (local.get $pair))) (br $build))) (local.set $prefix (call $reverse (local.get $acc))) (array.new_fixed $Values 2 (local.get $prefix) (local.get $suffix))) (func $splitf-at (type $Prim2) (param $lst (ref eq)) ; list (param $proc (ref eq)) ; predicate (result (ref eq)) ; returns two values (local $f (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $res (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $acc (ref eq)) (local $prefix (ref eq)) (local $suffix (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $inv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $pair (global.get $dummy-pair)) (local.set $acc (global.get $null)) (local.set $prefix (global.get $null)) (local.set $suffix (global.get $null)) (local.set $cur (local.get $lst)) (block $done (loop $loop (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (local.set $suffix (local.get $cur)) (br $done))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $inv))) (if (ref.eq (local.get $res) (global.get $false)) (then (local.set $suffix (local.get $cur)) (br $done))) (local.set $acc (call $cons (local.get $elem) (local.get $acc))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop))) (local.set $prefix (call $reverse (local.get $acc))) (array.new_fixed $Values 2 (local.get $prefix) (local.get $suffix))) (func $splitf-at-right (type $Prim2) (param $lst (ref eq)) ; list (param $proc (ref eq)) ; predicate (result (ref eq)) ; returns two values (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $r (ref eq)) (local $acc (ref eq)) (local $suffix-rev (ref eq)) (local $tmp (ref eq)) (local $node (ref $Pair)) (local $tail (ref eq)) (local $rev (ref eq)) (local $prefix (ref eq)) (local $suffix (ref eq)) (local $suffix-start (ref eq)) ;; Initialize locals for non-defaultable references (local.set $acc (global.get $null)) (local.set $suffix-rev (global.get $null)) (local.set $tmp (global.get $null)) (local.set $tail (global.get $null)) (local.set $rev (global.get $null)) (local.set $prefix (global.get $null)) (local.set $suffix (global.get $null)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $pair (global.get $dummy-pair)) (local.set $suffix-start (local.get $lst)) (local.set $cur (local.get $lst)) (block $done (loop $loop (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (local.set $tail (local.get $cur)) (br $done))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $r) (global.get $false)) (then (if (ref.eq (local.get $suffix-rev) (global.get $null)) (then (nop)) (else (local.set $tmp (call $reverse (local.get $suffix-rev))) (local.set $suffix-rev (global.get $null)) (block $flush (loop $flush-loop (if (ref.eq (local.get $tmp) (global.get $null)) (then (br $flush))) (local.set $node (ref.cast (ref $Pair) (local.get $tmp))) (local.set $acc (call $cons (struct.get $Pair $a (local.get $node)) (local.get $acc))) (local.set $tmp (struct.get $Pair $d (local.get $node))) (br $flush-loop))))) (local.set $acc (call $cons (local.get $elem) (local.get $acc))) (local.set $suffix-start (struct.get $Pair $d (local.get $pair)))) (else (local.set $suffix-rev (call $cons (local.get $elem) (local.get $suffix-rev))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop))) (local.set $rev (call $reverse (local.get $acc))) (local.set $prefix (if (result (ref eq)) (ref.eq (local.get $tail) (global.get $null)) (then (local.get $rev)) (else (call $append/2 (local.get $rev) (local.get $tail))))) (local.set $suffix (local.get $suffix-start)) (array.new_fixed $Values 2 (local.get $prefix) (local.get $suffix))) (func $append (type $Prim>=0) (param $xs (ref eq)) ;; list of arguments (result (ref eq)) (local $rev (ref eq)) (local $node (ref $Pair)) (local $arg (ref eq)) (local $acc (ref eq)) ;; Zero arguments -> null (if (ref.eq (local.get $xs) (global.get $null)) (then (return (global.get $null)))) ;; Reverse argument list to process from last to first (local.set $rev (call $reverse (local.get $xs))) ;; Initialize accumulator with last argument (local.set $node (ref.cast (ref $Pair) (local.get $rev))) (local.set $acc (struct.get $Pair $a (local.get $node))) (local.set $rev (struct.get $Pair $d (local.get $node))) ;; Fold over remaining arguments with $append/2 (block $done (loop $loop (br_if $done (ref.eq (local.get $rev) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $rev))) (local.set $arg (struct.get $Pair $a (local.get $node))) (local.set $acc (call $append/2 (local.get $arg) (local.get $acc))) (local.set $rev (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $acc)) (func $append/2 (param $xs (ref eq)) (param $ys (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $xs) (global.get $null)) (then (local.get $ys)) ; "the last list is used directly in the output" (else (if (result (ref eq)) (ref.test (ref $Pair) (local.get $xs)) (then (struct.new $Pair (i32.const 0) (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $xs))) (call $append/2 (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $xs))) (local.get $ys)))) (else (call $raise-pair-expected (local.get $xs)) (unreachable)))))) (func $append* (type $Prim>=1) (param $lst (ref eq)) ;; preceding list argument (param $rest (ref eq)) ;; list of remaining arguments; last supplies more append args (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $acc (ref eq)) (local $last (ref eq)) (local $args (ref eq)) ;; initialize locals with no defaults (local.set $last (global.get $false)) ;; no extra args: first argument already provides list of append arguments (if (ref.eq (local.get $rest) (global.get $null)) (then (return (call $append (local.get $lst))))) ;; separate final list argument from preceding list values (local.set $xs (local.get $rest)) (local.set $acc (global.get $null)) (block $done (loop $loop (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $xs (struct.get $Pair $d (local.get $node))) (if (ref.eq (local.get $xs) (global.get $null)) (then (local.set $last (struct.get $Pair $a (local.get $node))) (br $done)) (else (local.set $acc (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $node)) (local.get $acc))) (br $loop))))) ;; rebuild argument list so that last argument's list is appended at the end (local.set $args (local.get $last)) (block $done2 (loop $loop2 (br_if $done2 (ref.eq (local.get $acc) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $acc))) (local.set $args (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $node)) (local.get $args))) (local.set $acc (struct.get $Pair $d (local.get $node))) (br $loop2))) (local.set $args (struct.new $Pair (i32.const 0) (local.get $lst) (local.get $args))) (call $append (local.get $args))) (func $flatten (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $stack (ref eq)) (local $node (ref $Pair)) (local $cur (ref eq)) (local $p (ref $Pair)) (local $acc (ref eq)) ;; Initialize stack with initial value and empty accumulator (local.set $stack (call $cons (local.get $v) (global.get $null))) (local.set $acc (global.get $null)) (block $done (loop $loop (br_if $done (ref.eq (local.get $stack) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $stack))) (local.set $cur (struct.get $Pair $a (local.get $node))) (local.set $stack (struct.get $Pair $d (local.get $node))) (if (ref.eq (local.get $cur) (global.get $null)) (then (nop)) (else (if (ref.test (ref $Pair) (local.get $cur)) (then (local.set $p (ref.cast (ref $Pair) (local.get $cur))) ;; Push cdr then car to visit car first (local.set $stack (call $cons (struct.get $Pair $d (local.get $p)) (local.get $stack))) (local.set $stack (call $cons (struct.get $Pair $a (local.get $p)) (local.get $stack)))) (else (local.set $acc (call $cons (local.get $cur) (local.get $acc))))))) (br $loop))) (call $reverse (local.get $acc))) (func $reverse (type $Prim1) (param $xs (ref eq)) (result (ref eq)) (local $acc (ref eq)) (local.set $acc (global.get $null)) (block $done (loop $rev (if (ref.eq (local.get $xs) (global.get $null)) (then (return (local.get $acc)))) (if (ref.test (ref $Pair) (local.get $xs)) (then (local.set $acc (struct.new $Pair (i32.const 0) (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $xs))) (local.get $acc))) (local.set $xs (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $xs))))) (else (call $raise-pair-expected (local.get $xs)))) (br $rev))) (unreachable)) ; The original `alt-reverse` is defined `racket/private/reverse.rkt` and checks ; whether it is used in a module compiled in unsafe mode. If so, it skips ; the check that the input is a list. ; Here, for now, we simply have a copy of $reverse. ; Note: `alt-reverse` is used in the expansion of `for/list` loops. (func $alt-reverse (type $Prim1) (param $xs (ref eq)) (result (ref eq)) (local $acc (ref eq)) (local.set $acc (global.get $null)) (block $done (loop $rev (if (ref.eq (local.get $xs) (global.get $null)) (then (return (local.get $acc)))) (if (ref.test (ref $Pair) (local.get $xs)) (then (local.set $acc (struct.new $Pair (i32.const 0) (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $xs))) (local.get $acc))) (local.set $xs (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $xs))))) (else (call $raise-pair-expected (local.get $xs)))) (br $rev))) (unreachable)) #;(func $memq (type $Prim2) (param $needle (ref eq)) (param $xs (ref eq)) (result (ref eq)) (loop $search ;; 1) end-of-list? => not found (if (ref.eq (local.get $xs) (global.get $null)) (then (return (global.get $false)))) ;; 2) must be a Pair (if (ref.test (ref $Pair) (local.get $xs)) (then ;; compare needle to (car xs) (if (ref.eq (local.get $needle) (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $xs)))) (then (return (local.get $xs))) ;; found: return sublist ;; else: fall through to step 3 )) (else (call $raise-pair-expected (local.get $xs)))) ;; 3) advance to cdr (local.set $xs (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $xs)))) (br $search)) (unreachable)) ,@(for/list ([name '($memq $memv $member $memw)] [type '($Prim2 $Prim2 $Prim23 $Prim2)] [needs-proc '(0 0 1 0)] [cmp '((ref.eq (local.get $needle) (local.get $elem)) (ref.eq (call $eqv? (local.get $needle) (local.get $elem)) (global.get $true)) (ref.eq (global.get $false) (global.get $false)) ; unused for member (ref.eq (call $equal-always? (local.get $needle) (local.get $elem)) (global.get $true)))]) `(func ,name (type ,type) (param $needle (ref eq)) ;; value to find (param $xs (ref eq)) ;; list to search ,@(if (zero? needs-proc) '() '((param $same? (ref eq)))) ;; optional comparator, defaults to equal? (result (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) ,@(if (zero? needs-proc) '() '((local $args (ref $Args)) (local $res (ref eq)) (local $use-proc i32))) ,@(if (zero? needs-proc) '() `((if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable))) (local.set $use-proc (i32.const 1)))) (local.set $args (array.new $Args (global.get $null) (i32.const 2))))) (loop $search ;; 1) end-of-list? => not found (if (ref.eq (local.get $xs) (global.get $null)) (then (return (global.get $false)))) ;; 2) must be a Pair (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $xs))) (local.set $elem (struct.get $Pair $a (local.get $pair))) ,(if (zero? needs-proc) `(if ,cmp (then (return (local.get $xs)))) `(if (i32.eqz (local.get $use-proc)) (then (if (ref.eq (call $equal? (local.get $needle) (local.get $elem)) (global.get $true)) (then (return (local.get $xs))))) (else (array.set $Args (local.get $args) (i32.const 0) (local.get $needle)) (array.set $Args (local.get $args) (i32.const 1) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (local.get $args) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $true)) (then (return (local.get $xs))))))) ;; 3) advance to cdr (local.set $xs (struct.get $Pair $d (local.get $pair))) (br $search)) (unreachable))) (func $memf (type $Prim2) (param $proc (ref eq)) ;; predicate (param $lst (ref eq)) ;; list to search (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $cur (local.get $lst)) (loop $search (if (ref.eq (local.get $cur) (global.get $null)) (then (return (global.get $false)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $res) (global.get $false)) (then (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $search))) (return (local.get $cur))) (unreachable)) ,@(for/list ([name '($assoc $assw $assv $assq)] [type '($Prim23 $Prim2 $Prim2 $Prim2)] [needs-proc '(1 0 0 0)] [cmp '((ref.eq (call $equal? (local.get $needle) (local.get $key)) (global.get $true)) (ref.eq (call $equal-always? (local.get $needle) (local.get $key)) (global.get $true)) (ref.eq (call $eqv? (local.get $needle) (local.get $key)) (global.get $true)) (ref.eq (local.get $needle) (local.get $key)))]) `(func ,name (type ,type) (param $needle (ref eq)) ;; key to locate (param $alist (ref eq)) ;; association list ,@(if (zero? needs-proc) '() '((param $same? (ref eq)))) ;; optional comparator for assoc (result (ref eq)) (local $list-pair (ref $Pair)) (local $entry (ref eq)) (local $entry-pair (ref $Pair)) (local $key (ref eq)) ,@(if (zero? needs-proc) '() '((local $args (ref $Args)) (local $res (ref eq)) (local $use-proc i32))) ,@(if (zero? needs-proc) '() `((if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable))) (local.set $use-proc (i32.const 1)))) (local.set $args (array.new $Args (global.get $null) (i32.const 2))))) (loop $search (if (ref.eq (local.get $alist) (global.get $null)) (then (return (global.get $false)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $alist))) (then (call $raise-pair-expected (local.get $alist)) (unreachable))) (local.set $list-pair (ref.cast (ref $Pair) (local.get $alist))) (local.set $entry (struct.get $Pair $a (local.get $list-pair))) (if (i32.eqz (ref.test (ref $Pair) (local.get $entry))) (then (call $raise-pair-expected (local.get $entry)) (unreachable))) (local.set $entry-pair (ref.cast (ref $Pair) (local.get $entry))) (local.set $key (struct.get $Pair $a (local.get $entry-pair))) ,(if (zero? needs-proc) `(if ,cmp (then (return (local.get $entry)))) `(if (i32.eqz (local.get $use-proc)) (then (if (ref.eq (call $equal? (local.get $needle) (local.get $key)) (global.get $true)) (then (return (local.get $entry))))) (else (array.set $Args (local.get $args) (i32.const 0) (local.get $needle)) (array.set $Args (local.get $args) (i32.const 1) (local.get $key)) (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (local.get $args) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $true)) (then (return (local.get $entry))))))) (local.set $alist (struct.get $Pair $d (local.get $list-pair))) (br $search)) (unreachable))) (func $assf (type $Prim2) (param $proc (ref eq)) ;; predicate (param $alist (ref eq)) ;; association list (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $list-pair (ref $Pair)) (local $entry (ref eq)) (local $entry-pair (ref $Pair)) (local $key (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (loop $search (if (ref.eq (local.get $alist) (global.get $null)) (then (return (global.get $false)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $alist))) (then (call $raise-pair-expected (local.get $alist)) (unreachable))) (local.set $list-pair (ref.cast (ref $Pair) (local.get $alist))) (local.set $entry (struct.get $Pair $a (local.get $list-pair))) (if (i32.eqz (ref.test (ref $Pair) (local.get $entry))) (then (call $raise-pair-expected (local.get $entry)) (unreachable))) (local.set $entry-pair (ref.cast (ref $Pair) (local.get $entry))) (local.set $key (struct.get $Pair $a (local.get $entry-pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $key)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $res) (global.get $false)) (then (local.set $alist (struct.get $Pair $d (local.get $list-pair))) (br $search))) (return (local.get $entry))) (unreachable)) (func $findf (type $Prim2) (param $proc (ref eq)) ;; predicate (param $lst (ref eq)) ;; list to search (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $cur (local.get $lst)) (loop $search (if (ref.eq (local.get $cur) (global.get $null)) (then (return (global.get $false)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $res) (global.get $false)) (then (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $search))) (return (local.get $elem))) (unreachable)) (func $index-of (type $Prim23) (param $xs (ref eq)) ;; list (param $v (ref eq)) ;; value to find (param $same? (ref eq)) ;; optional comparator, default equal? (result (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local $use-proc i32) (local $i i32) ;; Handle optional comparator (if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable))) (local.set $use-proc (i32.const 1)))) ;; Unconditionally allocate args buffer once; overwrite each iteration. (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (local.set $cur (local.get $xs)) (local.set $i (i32.const 0)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (global.get $false)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (block $same (if (i32.eqz (local.get $use-proc)) (then (if (ref.eq (call $equal? (local.get $elem) (local.get $v)) (global.get $false)) (then (br $same)) (else (return (ref.i31 (i32.shl (local.get $i) (i32.const 1))))))) (else ;; Prepare args once per iteration (array.set $Args (local.get $args) (i32.const 0) (local.get $v)) (array.set $Args (local.get $args) (i32.const 1) (local.get $elem)) ;; Call provided comparator without storing non-defaultable locals (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (local.get $args) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $false)) (then (br $same)) (else (return (ref.i31 (i32.shl (local.get $i) (i32.const 1))))))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)) (unreachable)) (func $index-where (type $Prim2) (param $xs (ref eq)) ; list (param $proc (ref eq)) ; predicate (result (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $res (ref eq)) (local $i i32) ;; Ensure proc is a procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $cur (local.get $xs)) (local.set $i (i32.const 0)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (global.get $false)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $res) (global.get $false)) (then (nop)) (else (return (ref.i31 (i32.shl (local.get $i) (i32.const 1)))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)) (unreachable)) (func $indexes-of (type $Prim23) (param $xs (ref eq)) ;; list (param $v (ref eq)) ;; value to find (param $same? (ref eq)) ;; optional comparator, default equal? (result (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local $use-proc i32) (local $i i32) (local $acc (ref eq)) ;; Handle optional comparator (if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable))) (local.set $use-proc (i32.const 1)))) ;; Unconditionally allocate args buffer once; we overwrite each iteration. (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (local.set $cur (local.get $xs)) (local.set $i (i32.const 0)) (local.set $acc (global.get $null)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (call $reverse (local.get $acc))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (block $same (if (i32.eqz (local.get $use-proc)) (then (if (ref.eq (call $equal? (local.get $elem) (local.get $v)) (global.get $false)) (then (br $same)) (else (local.set $acc (call $cons (ref.i31 (i32.shl (local.get $i) (i32.const 1))) (local.get $acc)))))) (else ;; Prepare args for comparator: (v, elem) (array.set $Args (local.get $args) (i32.const 0) (local.get $v)) (array.set $Args (local.get $args) (i32.const 1) (local.get $elem)) ;; Call provided comparator without non-defaultable locals (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (local.get $args) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $false)) (then (br $same)) (else (local.set $acc (call $cons (ref.i31 (i32.shl (local.get $i) (i32.const 1))) (local.get $acc)))))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)) (unreachable)) (func $indexes-where (type $Prim2) (param $xs (ref eq)) ; list (param $proc (ref eq)) ; predicate (result (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $res (ref eq)) (local $i i32) (local $acc (ref eq)) ;; Ensure proc is a procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $cur (local.get $xs)) (local.set $i (i32.const 0)) (local.set $acc (global.get $null)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (call $reverse (local.get $acc))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $res) (global.get $false)) (then (nop)) (else (local.set $acc (call $cons (ref.i31 (i32.shl (local.get $i) (i32.const 1))) (local.get $acc))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)) (unreachable)) ;; Groups list elements by a key function. The third parameter is ;; optional and defaults to equal?. (func $group-by (type $Prim23) (param $proc (ref eq)) ;; key function (param $xs (ref eq)) ;; list (param $same? (ref eq)) ;; optional comparator, default equal? (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $x (ref eq)) (local $call (ref $Args)) (local $k (ref eq)) (local $groups (ref eq)) (local $use-proc i32) (local $same-f (ref $Procedure)) (local $same-inv (ref $ProcedureInvoker)) ; safe defaults for non-defaultable locals (local.set $same-f (ref.cast (ref $Procedure) (global.get $prim:equal?))) (local.set $same-inv (ref.cast (ref $ProcedureInvoker) (struct.get $Procedure $invoke (local.get $same-f)))) ;; Ensure proc is a procedure and fetch invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Handle optional comparator (if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable))) (local.set $use-proc (i32.const 1)) (local.set $same-f (ref.cast (ref $Procedure) (local.get $same?))) (local.set $same-inv (struct.get $Procedure $invoke (local.get $same-f))))) ;; Prepare argument buffer for key function (local.set $call (array.new $Args (global.get $null) (i32.const 1))) ;; Iterate over list building groups (local.set $groups (global.get $null)) (local.set $cur (local.get $xs)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (call $group-by:fix-order (call $group-by:extract-groups (local.get $groups)))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $x (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $call) (i32.const 0) (local.get $x)) (local.set $k (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (local.set $groups (if (result (ref eq)) (i32.eqz (local.get $use-proc)) (then (call $group-by:update-groups/equal? (local.get $k) (local.get $x) (local.get $groups))) (else (call $group-by:update-groups/general (local.get $k) (local.get $x) (local.get $groups) (local.get $same-f) (local.get $same-inv))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) ;; Fixes order of groups and their elements produced in reverse. ;; (define (group-by:fix-order groups) ;; (reverse (map (λ (xs) (reverse xs)) groups))) (func $group-by:fix-order (param $groups (ref eq)) ;; list of lists (result (ref eq)) (local $node (ref $Pair)) (local $xs (ref eq)) (local $acc (ref eq)) ;; reversed accumulator of fixed groups (local.set $acc (global.get $null)) (block $done (loop $loop ;; End of outer list -> return reversed accumulator (if (ref.eq (local.get $groups) (global.get $null)) (then (return (local.get $acc)))) ;; Ensure groups is a proper list (if (i32.eqz (ref.test (ref $Pair) (local.get $groups))) (then (call $raise-pair-expected (local.get $groups)) (unreachable))) (local.set $node (ref.cast (ref $Pair) (local.get $groups))) (local.set $xs (struct.get $Pair $a (local.get $node))) (local.set $groups (struct.get $Pair $d (local.get $node))) ;; Reverse each subgroup and cons onto accumulator (local.set $acc (struct.new $Pair (i32.const 0) (call $reverse (local.get $xs)) (local.get $acc))) (br $loop))) (unreachable)) (func $group-by:update-groups/equal? (param $k (ref eq)) ;; key (param $x (ref eq)) ;; element (param $groups (ref eq)) ;; groups list (result (ref eq)) (local $gs (ref eq)) (local $pair (ref $Pair)) (local $bucket (ref $Pair)) (local $bk (ref eq)) (local $box (ref eq)) (local.set $gs (local.get $groups)) (loop $loop (if (ref.eq (local.get $gs) (global.get $null)) (then (return (call $cons (call $cons (local.get $k) (call $box (call $cons (local.get $x) (global.get $null)))) (local.get $groups))))) (local.set $pair (ref.cast (ref $Pair) (local.get $gs))) (local.set $bucket (ref.cast (ref $Pair) (struct.get $Pair $a (local.get $pair)))) (local.set $bk (struct.get $Pair $a (local.get $bucket))) (local.set $box (struct.get $Pair $d (local.get $bucket))) (if (ref.eq (call $equal? (local.get $k) (local.get $bk)) (global.get $false)) (then (local.set $gs (struct.get $Pair $d (local.get $pair))) (br $loop)) (else (call $set-box! (local.get $box) (call $cons (local.get $x) (call $unbox (local.get $box)))) (return (local.get $groups))))) (unreachable)) (func $group-by:update-groups/general (param $k (ref eq)) ;; key (param $x (ref eq)) ;; element (param $groups (ref eq)) ;; groups list (param $same (ref $Procedure)) ;; comparator (param $same-inv (ref $ProcedureInvoker)) (result (ref eq)) (local $gs (ref eq)) (local $pair (ref $Pair)) (local $bucket (ref $Pair)) (local $bk (ref eq)) (local $box (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local.set $gs (local.get $groups)) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (loop $loop (if (ref.eq (local.get $gs) (global.get $null)) (then (return (call $cons (call $cons (local.get $k) (call $box (call $cons (local.get $x) (global.get $null)))) (local.get $groups))))) (local.set $pair (ref.cast (ref $Pair) (local.get $gs))) (local.set $bucket (ref.cast (ref $Pair) (struct.get $Pair $a (local.get $pair)))) (local.set $bk (struct.get $Pair $a (local.get $bucket))) (local.set $box (struct.get $Pair $d (local.get $bucket))) (array.set $Args (local.get $args) (i32.const 0) (local.get $k)) (array.set $Args (local.get $args) (i32.const 1) (local.get $bk)) (local.set $res (call_ref $ProcedureInvoker (local.get $same) (local.get $args) (local.get $same-inv))) (if (ref.eq (local.get $res) (global.get $false)) (then (local.set $gs (struct.get $Pair $d (local.get $pair))) (br $loop)) (else (call $set-box! (local.get $box) (call $cons (local.get $x) (call $unbox (local.get $box)))) (return (local.get $groups))))) (unreachable)) (func $group-by:extract-groups (param $groups (ref eq)) (result (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $bucket (ref $Pair)) (local $box (ref eq)) (local $acc (ref eq)) (local.set $cur (local.get $groups)) (local.set $acc (global.get $null)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (call $reverse (local.get $acc))))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $bucket (ref.cast (ref $Pair) (struct.get $Pair $a (local.get $pair)))) (local.set $box (struct.get $Pair $d (local.get $bucket))) (local.set $acc (call $cons (call $unbox (local.get $box)) (local.get $acc))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) (func $cartesian-product (type $Prim>=0) (param $xss (ref eq)) (result (ref eq)) (local $pair (ref $Pair)) (local $xs (ref eq)) (local $rest (ref eq)) (local $prod (ref eq)) (local $list (ref eq)) (local $elem (ref eq)) (local $rs (ref eq)) (local $rpair (ref $Pair)) (local $r (ref eq)) (local $tmp (ref eq)) (local $acc (ref eq)) ;; Base case: no lists -> '(()) (if (ref.eq (local.get $xss) (global.get $null)) (then (return (struct.new $Pair (i32.const 0) (global.get $null) (global.get $null))))) ;; Ensure xss is a proper list (if (i32.eqz (ref.test (ref $Pair) (local.get $xss))) (then (call $raise-pair-expected (local.get $xss)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $xss))) (local.set $xs (struct.get $Pair $a (local.get $pair))) (local.set $rest (struct.get $Pair $d (local.get $pair))) ;; Recursively compute product of remaining lists (local.set $prod (call $cartesian-product (local.get $rest))) ;; Iterate over first list and combine with rest (local.set $list (local.get $xs)) (local.set $acc (global.get $null)) (block $outer_done (loop $outer (if (ref.eq (local.get $list) (global.get $null)) (then (br $outer_done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $list))) (then (call $raise-pair-expected (local.get $list)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $list))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (local.set $rs (local.get $prod)) (block $inner_done (loop $inner (if (ref.eq (local.get $rs) (global.get $null)) (then (br $inner_done))) (local.set $rpair (ref.cast (ref $Pair) (local.get $rs))) (local.set $r (struct.get $Pair $a (local.get $rpair))) (local.set $tmp (call $cons (local.get $elem) (local.get $r))) (local.set $acc (call $cons (local.get $tmp) (local.get $acc))) (local.set $rs (struct.get $Pair $d (local.get $rpair))) (br $inner))) (local.set $list (struct.get $Pair $d (local.get $pair))) (br $outer))) (call $reverse (local.get $acc))) (func $permutations (type $Prim1) (param $lst (ref eq)) (result (ref eq)) (local $len i32) ;; length of input list (local $vec (ref $Vector)) ;; vector representation (local $cnt (ref $I32Array)) ;; counters for Heap's algorithm (local $i i32) ;; index (local $ci i32) ;; counter value at i (local $x (ref eq)) ;; temporary for swaps (local $y (ref eq)) (local $perm (ref eq)) ;; list for current permutation (local $acc (ref eq)) ;; accumulator of permutations ;; Determine length and ensure lst is a proper list (local.set $len (call $length/i32 (local.get $lst))) ;; If length <= 1, return list containing lst (if (i32.le_u (local.get $len) (i32.const 1)) (then (return (call $cons (local.get $lst) (global.get $null))))) ;; Convert list to vector and allocate counters (local.set $vec (ref.cast (ref $Vector) (call $list->vector (local.get $lst)))) (local.set $cnt (call $i32array-make (local.get $len) (i32.const 0))) ;; Initialize accumulator with original list (local.set $acc (call $cons (local.get $lst) (global.get $null))) ;; Main Heap's algorithm loop (local.set $i (i32.const 0)) (block $done (loop $loop (if (i32.ge_u (local.get $i) (local.get $len)) (then (br $done))) (local.set $ci (call $i32array-ref (local.get $cnt) (local.get $i))) (if (i32.lt_u (local.get $ci) (local.get $i)) (then ;; Swap depending on parity of i (if (i32.eqz (i32.and (local.get $i) (i32.const 1))) (then (local.set $x (call $vector-ref/checked (local.get $vec) (i32.const 0))) (local.set $y (call $vector-ref/checked (local.get $vec) (local.get $i))) (call $vector-set!/checked (local.get $vec) (i32.const 0) (local.get $y)) (call $vector-set!/checked (local.get $vec) (local.get $i) (local.get $x))) (else (local.set $x (call $vector-ref/checked (local.get $vec) (local.get $ci))) (local.set $y (call $vector-ref/checked (local.get $vec) (local.get $i))) (call $vector-set!/checked (local.get $vec) (local.get $ci) (local.get $y)) (call $vector-set!/checked (local.get $vec) (local.get $i) (local.get $x)))) ;; Record permutation (local.set $perm (call $vector->list (local.get $vec))) (local.set $acc (call $cons (local.get $perm) (local.get $acc))) ;; Increment counter and reset i (call $i32array-set! (local.get $cnt) (local.get $i) (i32.add (local.get $ci) (i32.const 1))) (local.set $i (i32.const 0))) (else ;; Reset counter and advance i (call $i32array-set! (local.get $cnt) (local.get $i) (i32.const 0)) (local.set $i (i32.add (local.get $i) (i32.const 1))))) (br $loop))) ;; Reverse accumulator to preserve generation order (call $reverse (local.get $acc))) (func $make-list (type $Prim2) (param $n-raw (ref eq)) ;; fixnum (param $v (ref eq)) ;; value to repeat (result (ref eq)) (local $n i32) ;; Check and unwrap fixnum (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $n-raw))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $n-raw))) (i32.const 1)) (i32.const 0))) (then (call $raise-argument-error (local.get $n-raw)))) ;; customize this if needed (local.set $n (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $n-raw))) (i32.const 1))) ;; Negative counts must fail before allocation; unsigned decoding ;; would otherwise turn them into huge positive counts. (if (i32.lt_s (local.get $n) (i32.const 0)) (then (call $raise-argument-error (local.get $n-raw)))) (call $make-list/checked (local.get $n) (local.get $v))) (func $make-list/checked (param $n i32) ;; number of elements (param $v (ref eq)) ;; value to repeat (result (ref eq)) ;; proper list (local $i i32) (local $acc (ref eq)) (local.set $i (i32.const 0)) (local.set $acc (global.get $null)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $n))) (local.set $acc (struct.new $Pair (i32.const 0) (local.get $v) (local.get $acc))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $acc)) (func $build-list (type $Prim2) (param $n-raw (ref eq)) ;; exact-nonnegative integer (param $proc (ref eq)) ;; procedure (result (ref eq)) (local $n i32) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) ;; Check and unwrap n (if (i32.eqz (ref.test (ref i31) (local.get $n-raw))) (then (call $raise-expected-fixnum (local.get $n-raw)))) (local.set $n (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $n-raw))) (i32.const 1))) (if (i32.lt_s (local.get $n) (i32.const 0)) (then (call $raise-argument-error (local.get $n-raw)))) ;; Check procedure and fetch invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Delegate (call $build-list/checked (local.get $n) (local.get $f) (local.get $finv))) (func $build-list/checked (param $n i32) (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (result (ref eq)) (local $args (ref $Args)) (local $acc (ref eq)) (local $r (ref eq)) (local $i i32) ;; Prepare argument array and accumulator (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $acc (global.get $null)) (local.set $i (local.get $n)) (block $done (loop $loop (br_if $done (i32.eqz (local.get $i))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (array.set $Args (local.get $args) (i32.const 0) (ref.i31 (i32.shl (local.get $i) (i32.const 1)))) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (local.set $acc (call $cons (local.get $r) (local.get $acc))) (br $loop))) (local.get $acc)) (func $range-proc (type $Prim13) (param $start (ref eq)) (param $end (ref eq)) (param $step (ref eq)) ;; $missing for default 1 (result (ref eq)) (call $range (local.get $start) (local.get $end) (local.get $step))) (func $range (type $Prim13) (param $start-raw (ref eq)) (param $end-raw (ref eq)) (param $step-raw (ref eq)) ;; $missing for defaults 0/1 (result (ref eq)) (local $use-fl i32) (local $start-i32 i32) (local $end-i32 i32) (local $step-i32 i32) (local $start-f64 f64) (local $end-f64 f64) (local $step-f64 f64) (local $start-is-fl i32) (local $end-is-fl i32) (local $step-is-fl i32) (local $start-val (ref eq)) (local $end-val (ref eq)) (local $step-val (ref eq)) ;; First-class calls use $missing for omitted optional arguments. ;; Normalize (range end) into (range 0 end 1) before validation. (local.set $start-val (global.get $missing)) (local.set $end-val (global.get $missing)) (local.set $step-val (global.get $missing)) (if (ref.eq (local.get $end-raw) (global.get $missing)) (then (local.set $start-val (ref.i31 (i32.const 0))) (local.set $end-val (local.get $start-raw)) (local.set $step-val (global.get $missing))) (else (local.set $start-val (local.get $start-raw)) (local.set $end-val (local.get $end-raw)) (local.set $step-val (local.get $step-raw)))) ;; start (optional, defaults to 0) (if (ref.eq (local.get $start-val) (global.get $missing)) (then (local.set $start-i32 (i32.const 0))) (else (if (call $fl?/i32 (local.get $start-val)) (then (local.set $use-fl (i32.const 1)) (local.set $start-is-fl (i32.const 1)) (local.set $start-f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $start-val))))) (else (if (call $fx?/i32 (local.get $start-val)) (then (local.set $start-i32 (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $start-val))) (i32.const 1)))) (else (call $raise-argument-error (local.get $start-val)) (unreachable))))))) ;; end (if (call $fl?/i32 (local.get $end-val)) (then (local.set $use-fl (i32.const 1)) (local.set $end-is-fl (i32.const 1)) (local.set $end-f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $end-val))))) (else (if (call $fx?/i32 (local.get $end-val)) (then (local.set $end-i32 (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $end-val))) (i32.const 1)))) (else (call $raise-argument-error (local.get $end-val)) (unreachable))))) ;; step (optional, defaults to 1) (if (ref.eq (local.get $step-val) (global.get $missing)) (then (if (i32.eqz (local.get $use-fl)) (then (local.set $step-i32 (i32.const 1))) (else (local.set $step-is-fl (i32.const 1)) (local.set $step-f64 (f64.const 1.0))))) (else (if (call $fl?/i32 (local.get $step-val)) (then (local.set $use-fl (i32.const 1)) (local.set $step-is-fl (i32.const 1)) (local.set $step-f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $step-val))))) (else (if (call $fx?/i32 (local.get $step-val)) (then (local.set $step-i32 (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $step-val))) (i32.const 1)))) (else (call $raise-argument-error (local.get $step-val)) (unreachable))))))) ;; step must be non-zero when provided (if (i32.eqz (ref.eq (local.get $step-val) (global.get $missing))) (then (if (i32.eqz (local.get $use-fl)) (then (if (i32.eq (local.get $step-i32) (i32.const 0)) (then (call $raise-argument-error (local.get $step-val)) (unreachable)))) (else (if (f64.eq (local.get $step-f64) (f64.const 0)) (then (call $raise-argument-error (local.get $step-val)) (unreachable))))))) ;; decide variant (if (i32.eqz (local.get $use-fl)) (then (return (call $range/fixnum (local.get $start-i32) (local.get $end-i32) (local.get $step-i32))))) ;; convert fixnums to flonums if needed (if (i32.eqz (local.get $start-is-fl)) (then (local.set $start-f64 (f64.convert_i32_s (local.get $start-i32))))) (if (i32.eqz (local.get $end-is-fl)) (then (local.set $end-f64 (f64.convert_i32_s (local.get $end-i32))))) (if (i32.eqz (local.get $step-is-fl)) (then (local.set $step-f64 (f64.convert_i32_s (local.get $step-i32))))) (call $range/flonum (local.get $start-f64) (local.get $end-f64) (local.get $step-f64) (local.get $start-val) (i32.eqz (local.get $start-is-fl)))) (func $range/fixnum (param $start i32) (param $end i32) (param $step i32) (result (ref eq)) (local $cur i32) (local $lst (ref eq)) (local.set $cur (local.get $start)) (local.set $lst (global.get $null)) (block $done (loop $loop (if (i32.gt_s (local.get $step) (i32.const 0)) (then (br_if $done (i32.ge_s (local.get $cur) (local.get $end)))) (else (br_if $done (i32.le_s (local.get $cur) (local.get $end))))) (local.set $lst (struct.new $Pair (i32.const 0) (ref.i31 (i32.shl (local.get $cur) (i32.const 1))) (local.get $lst))) (local.set $cur (i32.add (local.get $cur) (local.get $step))) (br $loop))) (call $reverse (local.get $lst))) (func $range/flonum (param $start f64) (param $end f64) (param $step f64) (param $start-raw (ref eq)) (param $preserve-start i32) (result (ref eq)) (local $n i32) (local $cur f64) (local $lst (ref eq)) (local.set $n (i32.const 0)) (local.set $lst (global.get $null)) (block $done (loop $loop (local.set $cur (f64.add (local.get $start) (f64.mul (f64.convert_i32_s (local.get $n)) (local.get $step)))) (if (f64.gt (local.get $step) (f64.const 0)) (then (br_if $done (f64.ge (local.get $cur) (local.get $end)))) (else (br_if $done (f64.le (local.get $cur) (local.get $end))))) (local.set $lst (struct.new $Pair (i32.const 0) (if (result (ref eq)) (i32.and (i32.eqz (local.get $n)) (local.get $preserve-start)) (then (local.get $start-raw)) (else (struct.new $Flonum (i32.const 0) (local.get $cur)))) (local.get $lst))) (local.set $n (i32.add (local.get $n) (i32.const 1))) (br $loop))) (call $reverse (local.get $lst))) (func $inclusive-range-proc (type $Prim23) (param $start (ref eq)) (param $end (ref eq)) (param $step (ref eq)) ;; $missing for default 1/-1 (result (ref eq)) (call $inclusive-range (local.get $start) (local.get $end) (local.get $step))) ;; Unlike Racket's @racket[inclusive-range], the step defaults to ;; either 1 or -1 based on the ordering of @racket[start] and ;; @racket[end] when omitted. (func $inclusive-range (type $Prim23) (param $start-raw (ref eq)) (param $end-raw (ref eq)) (param $step-raw (ref eq)) ;; $missing for default 1/-1 (result (ref eq)) (local $use-fl i32) (local $start-i32 i32) (local $end-i32 i32) (local $step-i32 i32) (local $start-f64 f64) (local $end-f64 f64) (local $step-f64 f64) (local $start-is-fl i32) (local $end-is-fl i32) (local $step-is-fl i32) ;; start (if (call $fl?/i32 (local.get $start-raw)) (then (local.set $use-fl (i32.const 1)) (local.set $start-is-fl (i32.const 1)) (local.set $start-f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $start-raw))))) (else (if (call $fx?/i32 (local.get $start-raw)) (then (local.set $start-i32 (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $start-raw))) (i32.const 1)))) (else (call $raise-argument-error (local.get $start-raw)) (unreachable))))) ;; end (if (call $fl?/i32 (local.get $end-raw)) (then (local.set $use-fl (i32.const 1)) (local.set $end-is-fl (i32.const 1)) (local.set $end-f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $end-raw))))) (else (if (call $fx?/i32 (local.get $end-raw)) (then (local.set $end-i32 (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $end-raw))) (i32.const 1)))) (else (call $raise-argument-error (local.get $end-raw)) (unreachable))))) ;; step (if (ref.eq (local.get $step-raw) (global.get $missing)) (then (if (i32.eqz (local.get $use-fl)) (then (if (i32.le_s (local.get $start-i32) (local.get $end-i32)) (then (local.set $step-i32 (i32.const 1))) (else (local.set $step-i32 (i32.const -1))))) (else (if (i32.eqz (local.get $start-is-fl)) (then (local.set $start-f64 (f64.convert_i32_s (local.get $start-i32))))) (if (i32.eqz (local.get $end-is-fl)) (then (local.set $end-f64 (f64.convert_i32_s (local.get $end-i32))))) (local.set $step-is-fl (i32.const 1)) (if (f64.le (local.get $start-f64) (local.get $end-f64)) (then (local.set $step-f64 (f64.const 1.0))) (else (local.set $step-f64 (f64.const -1.0))))))) (else (if (call $fl?/i32 (local.get $step-raw)) (then (local.set $use-fl (i32.const 1)) (local.set $step-is-fl (i32.const 1)) (local.set $step-f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $step-raw))))) (else (if (call $fx?/i32 (local.get $step-raw)) (then (local.set $step-i32 (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $step-raw))) (i32.const 1)))) (else (call $raise-argument-error (local.get $step-raw)) (unreachable))))))) ;; step must be non-zero when provided (if (i32.eqz (ref.eq (local.get $step-raw) (global.get $missing))) (then (if (i32.eqz (local.get $use-fl)) (then (if (i32.eq (local.get $step-i32) (i32.const 0)) (then (call $raise-argument-error (local.get $step-raw)) (unreachable)))) (else (if (f64.eq (local.get $step-f64) (f64.const 0)) (then (call $raise-argument-error (local.get $step-raw)) (unreachable))))))) ;; decide variant (if (i32.eqz (local.get $use-fl)) (then (return (call $inclusive-range/fixnum (local.get $start-i32) (local.get $end-i32) (local.get $step-i32))))) ;; convert fixnums to flonums if needed (if (i32.eqz (local.get $start-is-fl)) (then (local.set $start-f64 (f64.convert_i32_s (local.get $start-i32))))) (if (i32.eqz (local.get $end-is-fl)) (then (local.set $end-f64 (f64.convert_i32_s (local.get $end-i32))))) (if (i32.eqz (local.get $step-is-fl)) (then (local.set $step-f64 (f64.convert_i32_s (local.get $step-i32))))) (call $inclusive-range/flonum (local.get $start-f64) (local.get $end-f64) (local.get $step-f64))) (func $inclusive-range/fixnum (param $start i32) (param $end i32) (param $step i32) (result (ref eq)) (local $cur i32) (local $lst (ref eq)) (local.set $cur (local.get $start)) (local.set $lst (global.get $null)) (block $done (loop $loop (if (i32.gt_s (local.get $step) (i32.const 0)) (then (br_if $done (i32.gt_s (local.get $cur) (local.get $end)))) (else (br_if $done (i32.lt_s (local.get $cur) (local.get $end))))) (local.set $lst (struct.new $Pair (i32.const 0) (ref.i31 (i32.shl (local.get $cur) (i32.const 1))) (local.get $lst))) (local.set $cur (i32.add (local.get $cur) (local.get $step))) (br $loop))) (call $reverse (local.get $lst))) (func $inclusive-range/flonum (param $start f64) (param $end f64) (param $step f64) (result (ref eq)) (local $n i32) (local $cur f64) (local $lst (ref eq)) (local.set $n (i32.const 0)) (local.set $lst (global.get $null)) (block $done (loop $loop (local.set $cur (f64.add (local.get $start) (f64.mul (f64.convert_i32_s (local.get $n)) (local.get $step)))) (if (f64.gt (local.get $step) (f64.const 0)) (then (br_if $done (f64.gt (local.get $cur) (local.get $end)))) (else (br_if $done (f64.lt (local.get $cur) (local.get $end))))) (local.set $lst (struct.new $Pair (i32.const 0) (struct.new $Flonum (i32.const 0) (local.get $cur)) (local.get $lst))) (local.set $n (i32.add (local.get $n) (i32.const 1))) (br $loop))) (call $reverse (local.get $lst))) ;; list-update/checked: takes a Pair, an index, and an updater procedure. ;; Returns a list like the input except that the element at index ;; is replaced by the result of applying the updater. (func $list-update/checked (param $xs (ref $Pair)) (param $i i32) (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (param $args (ref $Args)) (result (ref eq)) (local $orig (ref $Pair)) (local $node (ref $Pair)) (local $acc (ref eq)) (local $k i32) (local $next (ref eq)) (local $elem (ref eq)) (local $updated (ref eq)) (local $len i32) (local.set $orig (local.get $xs)) (local.set $node (local.get $xs)) (local.set $acc (global.get $null)) (local.set $k (local.get $i)) (loop $loop (if (i32.eqz (local.get $k)) (then (local.set $elem (struct.get $Pair $a (local.get $node))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $updated (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (return (call $append/2 (call $reverse (local.get $acc)) (call $cons (local.get $updated) (struct.get $Pair $d (local.get $node))))))) (local.set $acc (call $cons (struct.get $Pair $a (local.get $node)) (local.get $acc))) (local.set $next (struct.get $Pair $d (local.get $node))) (if (ref.test (ref $Pair) (local.get $next)) (then (local.set $node (ref.cast (ref $Pair) (local.get $next))) (local.set $k (i32.sub (local.get $k) (i32.const 1))) (br $loop)) (else (local.set $len (i32.add (i32.sub (local.get $i) (local.get $k)) (i32.const 1))) (call $raise-bad-list-ref-index (local.get $orig) (local.get $i) (local.get $len)) (unreachable)))) (unreachable)) (func $list-update (type $Prim3) (param $xs (ref eq)) (param $i (ref eq)) (param $proc (ref eq)) (result (ref eq)) (local $idx i32) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (if (ref.test (ref i31) (local.get $i)) (then (local.set $idx (i31.get_s (ref.cast (ref i31) (local.get $i)))) (if (i32.ne (i32.and (local.get $idx) (i32.const 1)) (i32.const 0)) (then (call $raise-argument-error1 (global.get $symbol:list-update) (global.get $string:exact-nonnegative-integer?) (local.get $i)) (unreachable))) (local.set $idx (i32.shr_s (local.get $idx) (i32.const 1)))) (else (call $raise-argument-error1 (global.get $symbol:list-update) (global.get $string:exact-nonnegative-integer?) (local.get $i)) (unreachable))) (if (i32.lt_s (local.get $idx) (i32.const 0)) (then (call $raise-argument-error1 (global.get $symbol:list-update) (global.get $string:exact-nonnegative-integer?) (local.get $i)) (unreachable))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (if (result (ref eq)) (ref.test (ref $Pair) (local.get $xs)) (then (call $list-update/checked (ref.cast (ref $Pair) (local.get $xs)) (local.get $idx) (local.get $f) (local.get $finv) (local.get $args))) (else (call $raise-pair-expected (local.get $xs)) (unreachable)))) ,@(let () (define (gen-argminmax name cmp) (define $name (string->symbol (~a "$" name))) `((func ,$name (type $Prim2) (param $proc (ref eq)) (param $lst (ref eq)) (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $pair (ref $Pair)) (local $best (ref eq)) (local $bestv (ref eq)) (local $elem (ref eq)) (local $val (ref eq)) (local $cur (ref eq)) ;; Check that proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Prepare argument array (local.set $args (array.new $Args (global.get $null) (i32.const 1))) ;; Ensure lst is a non-empty proper list (if (ref.eq (local.get $lst) (global.get $null)) (then (call $raise-argument-error (local.get $lst)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lst))) (then (call $raise-pair-expected (local.get $lst)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $lst))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $best (local.get $elem)) (local.set $bestv (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (block $done (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (br $done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $val (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (call ,cmp (local.get $bestv) (local.get $val)) (global.get $true)) (then (local.set $best (local.get $elem)) (local.set $bestv (local.get $val)))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop))) (local.get $best)))) (append (gen-argminmax 'argmax '$/2))) (func $raise-argument-error (param $x (ref eq)) (call $raise-argument-error1 (global.get $symbol:error) (global.get $string:valid-argument) (local.get $x))) (func $raise-expected-fixnum (param $x (ref eq)) (unreachable)) (func $list-from-range (param $start-raw (ref eq)) ;; inclusive, fixnum (param $end-raw (ref eq)) ;; exclusive, fixnum (result (ref eq)) (local $start-i31 (ref i31)) (local $end-i31 (ref i31)) (local $start i32) (local $end i32) ;; Check and unwrap start (if (i32.eqz (ref.test (ref i31) (local.get $start-raw))) (then (call $raise-expected-fixnum (local.get $start-raw)))) (local.set $start-i31 (ref.cast (ref i31) (local.get $start-raw))) (local.set $start (i32.shr_u (i31.get_u (local.get $start-i31)) (i32.const 1))) ;; Check and unwrap end (if (i32.eqz (ref.test (ref i31) (local.get $end-raw))) (then (call $raise-expected-fixnum (local.get $end-raw)))) (local.set $end-i31 (ref.cast (ref i31) (local.get $end-raw))) (local.set $end (i32.shr_u (i31.get_u (local.get $end-i31)) (i32.const 1))) ;; Delegate (call $list-from-range/checked (local.get $start) (local.get $end))) (func $list-from-range/checked (param $start i32) ;; inclusive (param $end i32) ;; exclusive (result (ref eq)) ;; proper list of fixnums (local $i i32) (local $lst (ref eq)) ;; initially null ;; Start from end and build backwards (local.set $i (local.get $end)) (local.set $lst (global.get $null)) (block $done (loop $loop (br_if $done (i32.le_s (local.get $i) (local.get $start))) ;; Decrement i (local.set $i (i32.sub (local.get $i) (i32.const 1))) ;; Prepend (ref.i31 (i32.shl $i 1)) as fixnum (local.set $lst (struct.new $Pair (i32.const 0) ;; hash (ref.i31 (i32.shl (local.get $i) (i32.const 1))) (local.get $lst))) (br $loop))) (local.get $lst)) (func $map (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $xs0 (ref eq)) ;; list? (param $rest (ref eq)) ;; list of lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pair (ref $Pair)) (local $nlists i32) (local $xss (ref eq)) (local $lists (ref $Args)) ;; cursors for each list (local $call (ref $Args)) ;; args for f (length = nlists) (local $i i32) (local $cur (ref eq)) (local $stop i32) (local $acc (ref eq)) ;; reversed accumulator (local $res (ref eq)) ;; final result (local $r (ref eq)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Build list-of-lists from first list and rest (local.set $xss (struct.new $Pair (i32.const 0) (local.get $xs0) (local.get $rest))) ;; 3) Walk outer list xss to count #lists; ensure xss is proper and each element is a list head (local.set $nlists (call $validate-list-args (global.get $symbol:map) (local.get $xss))) ;; Racket's map requires at least one list argument (if (i32.eq (local.get $nlists) (i32.const 0)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:map)) (i32.const 1)) (unreachable))) ;; 4) Allocate arrays for list cursors and call arguments; seed list cursors from xss (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (local.get $nlists))) (call $seed-list-args (local.get $xss) (local.get $lists)) ;; 5) Main loop: stop at the shortest list (local.set $acc (global.get $null)) (loop $loop ;; (a) Check state of all lists; determine if we stop (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done))) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1))) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; If any list is empty → finish by reversing $acc (if (i32.ne (local.get $stop) (i32.const 0)) (then (local.set $res (global.get $null)) (local.set $cur (local.get $acc)) (loop $rev (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $res)))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $res (call $cons (struct.get $Pair $a (local.get $pair)) (local.get $res))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $rev)))) ;; (b) Build call args for f: cars of each list (local.set $i (i32.const 0)) (block $cars_done (loop $cars (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cars_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cars))) ;; (c) Apply f to those cars (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) ;; (d) cons the result onto the accumulator (local.set $acc (call $cons (local.get $r) (local.get $acc))) ;; (e) Advance each list (cdr) (local.set $i (i32.const 0)) (block $cdrs_done (loop $cdrs (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cdrs_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $lists) (local.get $i) (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cdrs))) (br $loop)) (unreachable)) (func $andmap (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $xs0 (ref eq)) ;; list? (param $rest (ref eq)) ;; list of lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pair (ref $Pair)) (local $nlists i32) (local $xss (ref eq)) (local $lists (ref $Args)) ;; cursors for each list (local $call (ref $Args)) ;; args for f (length = nlists) (local $i i32) (local $cur (ref eq)) (local $stop i32) (local $r (ref eq)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Build list-of-lists from first list and rest (local.set $xss (struct.new $Pair (i32.const 0) (local.get $xs0) (local.get $rest))) ;; 3) Walk outer list xss to count #lists; ensure xss is proper and each element is a list head (local.set $nlists (call $validate-list-args (global.get $symbol:andmap) (local.get $xss))) ;; Racket's andmap requires at least one list argument (if (i32.eq (local.get $nlists) (i32.const 0)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:andmap)) (i32.const 1)) (unreachable))) ;; 4) Allocate arrays for list cursors and call arguments; seed list cursors from xss (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (local.get $nlists))) (call $seed-list-args (local.get $xss) (local.get $lists)) ;; 5) Main loop: stop at the shortest list or when f returns #f (loop $loop ;; (a) Check state of all lists; determine if we stop (empty) (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done))) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1))) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; If any list is empty → return #t (if (i32.ne (local.get $stop) (i32.const 0)) (then (return (global.get $true)))) ;; (b) Build call args for f: cars of each list and advance lists (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $cars_done (loop $cars (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cars_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (array.set $Args (local.get $lists) (local.get $i) (local.get $cur)) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cars))) ;; (c) Apply f to those cars (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) ;; (d) If result is #f, return #f (if (ref.eq (local.get $r) (global.get $false)) (then (return (global.get $false)))) ;; (e) If we've reached the end, return the result (if (i32.ne (local.get $stop) (i32.const 0)) (then (return (local.get $r)))) ;; (f) Continue (br $loop)) (unreachable)) (func $ormap (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $xs0 (ref eq)) ;; list? (param $rest (ref eq)) ;; list of lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pair (ref $Pair)) (local $nlists i32) (local $xss (ref eq)) (local $lists (ref $Args)) ;; cursors for each list (local $call (ref $Args)) ;; args for f (length = nlists) (local $i i32) (local $cur (ref eq)) (local $stop i32) (local $r (ref eq)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Build list-of-lists from first list and rest (local.set $xss (struct.new $Pair (i32.const 0) (local.get $xs0) (local.get $rest))) ;; 3) Walk outer list xss to count #lists; ensure xss is proper and each element is a list head (local.set $nlists (call $validate-list-args (global.get $symbol:ormap) (local.get $xss))) ;; Racket's ormap requires at least one list argument (if (i32.eq (local.get $nlists) (i32.const 0)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:ormap)) (i32.const 1)) (unreachable))) ;; 4) Allocate arrays for list cursors and call arguments; seed list cursors from xss (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (local.get $nlists))) (call $seed-list-args (local.get $xss) (local.get $lists)) ;; 5) Main loop: stop at the shortest list or when f returns truthy (loop $loop ;; (a) Check state of all lists; determine if we stop (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done))) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1))) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; If any list is empty → return #f (if (i32.ne (local.get $stop) (i32.const 0)) (then (return (global.get $false)))) ;; (b) Build call args for f: cars of each list (local.set $i (i32.const 0)) (block $cars_done (loop $cars (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cars_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cars))) ;; (c) Apply f to those cars (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) ;; (d) If result is not #f, return it (if (ref.eq (local.get $r) (global.get $false)) (then (nop)) (else (return (local.get $r)))) ;; (e) Advance each list (cdr) (local.set $i (i32.const 0)) (block $cdrs_done (loop $cdrs (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cdrs_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $lists) (local.get $i) (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cdrs))) (br $loop)) (unreachable)) ;; Walks the outer list of lists to count the arity while validating ;; that the outer list is proper and every element is either null or ;; a pair (i.e. a list head). Uses `who` for raised argument errors ;; on malformed input. (func $validate-list-args (param $who (ref eq)) ;; primitive name symbol (param $xss (ref eq)) ;; list of lists (result i32) ;; number of lists (local $outer (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $nlists i32) (local.set $nlists (i32.const 0)) (local.set $outer (local.get $xss)) (block $count_done (loop $count (if (ref.eq (local.get $outer) (global.get $null)) (then (br $count_done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $outer))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:list?) (local.get $outer)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $outer))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (i32.or (ref.eq (local.get $elem) (global.get $null)) (ref.test (ref $Pair) (local.get $elem)))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:list?) (local.get $elem)) (unreachable))) (local.set $nlists (i32.add (local.get $nlists) (i32.const 1))) (local.set $outer (struct.get $Pair $d (local.get $pair))) (br $count))) (local.get $nlists)) ;; Seeds an array of list cursors from the validated list-of-lists ;; argument, storing each inner list head in $lists. (func $seed-list-args (param $xss (ref eq)) (param $lists (ref $Args)) (local $outer (ref eq)) (local $pair (ref $Pair)) (local $i i32) (local.set $outer (local.get $xss)) (local.set $i (i32.const 0)) (block $seed_done (loop $seed (if (ref.eq (local.get $outer) (global.get $null)) (then (br $seed_done))) (local.set $pair (ref.cast (ref $Pair) (local.get $outer))) (array.set $Args (local.get $lists) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $outer (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $seed)))) (func $append-map (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $xs0 (ref eq)) ;; list? (param $rest (ref eq)) ;; list of lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pair (ref $Pair)) (local $nlists i32) (local $xss (ref eq)) (local $lists (ref $Args)) ;; cursors for each list (local $call (ref $Args)) ;; args for f (length = nlists) (local $i i32) (local $cur (ref eq)) (local $stop i32) (local $res (ref eq)) ;; accumulated output (forward order) (local $tail (ref null $Pair)) ;; tail of $res when non-null (local $r (ref eq)) (local $seg-cur (ref eq)) ;; cursor for copying non-final segments (local $seg-head (ref eq)) (local $seg-tail (ref null $Pair)) (local $seg-pair (ref $Pair)) (local $new (ref $Pair)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Build list-of-lists from first list and rest (local.set $xss (struct.new $Pair (i32.const 0) (local.get $xs0) (local.get $rest))) ;; 3) Walk outer list xss to count #lists; ensure xss is proper and each element is a list head (local.set $nlists (call $validate-list-args (global.get $symbol:append-map) (local.get $xss))) ;; Racket's append-map requires at least one list argument (if (i32.eq (local.get $nlists) (i32.const 0)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:append-map)) (i32.const 1)) (unreachable))) ;; 4) Allocate arrays for list cursors and call arguments; seed list cursors from xss (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (local.get $nlists))) (call $seed-list-args (local.get $xss) (local.get $lists)) ;; 5) Main loop: stop at the shortest list (local.set $res (global.get $null)) (loop $loop ;; (a) Check state of all lists; determine if we stop (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done))) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1))) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; If any list is empty → return accumulated result (if (i32.ne (local.get $stop) (i32.const 0)) (then (return (local.get $res)))) ;; (b) Build call args for f: cars of each list (local.set $i (i32.const 0)) (block $cars_done (loop $cars (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cars_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cars))) ;; (c) Apply f to those cars (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) ;; (d) Advance each list (cdr) and note if this is the last iteration (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $cdrs_done (loop $cdrs (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cdrs_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (array.set $Args (local.get $lists) (local.get $i) (local.get $cur)) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cdrs))) ;; (e) If this is the final segment, splice it directly and return (if (i32.ne (local.get $stop) (i32.const 0)) (then (if (ref.eq (local.get $r) (global.get $null)) (then (return (local.get $res))) (else (if (ref.eq (local.get $res) (global.get $null)) (then (return (local.get $r))) (else (struct.set $Pair $d (ref.as_non_null (local.get $tail)) (local.get $r)) (return (local.get $res)))))))) ;; (f) Copy non-final segment and append to accumulated result (local.set $seg-cur (local.get $r)) (local.set $seg-head (global.get $null)) (block $copy_done (loop $copy (if (ref.eq (local.get $seg-cur) (global.get $null)) (then (br $copy_done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $seg-cur))) (then (call $raise-pair-expected (local.get $seg-cur)) (unreachable))) (local.set $seg-pair (ref.cast (ref $Pair) (local.get $seg-cur))) (local.set $new (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $seg-pair)) (global.get $null))) (if (ref.eq (local.get $seg-head) (global.get $null)) (then (local.set $seg-head (local.get $new))) (else (struct.set $Pair $d (ref.as_non_null (local.get $seg-tail)) (local.get $new)))) (local.set $seg-tail (local.get $new)) (local.set $seg-cur (struct.get $Pair $d (local.get $seg-pair))) (br $copy))) (if (ref.eq (local.get $seg-head) (global.get $null)) (then (br $loop))) (if (ref.eq (local.get $res) (global.get $null)) (then (local.set $res (local.get $seg-head))) (else (struct.set $Pair $d (ref.as_non_null (local.get $tail)) (local.get $seg-head)))) (local.set $tail (local.get $seg-tail)) (br $loop)) (unreachable)) (func $count (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $xs0 (ref eq)) ;; list? (param $rest (ref eq)) ;; list of lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pair (ref $Pair)) (local $nlists i32) (local $xss (ref eq)) (local $lists (ref $Args)) ;; cursors for each list (local $call (ref $Args)) ;; args for f (length = nlists) (local $i i32) (local $cur (ref eq)) (local $r (ref eq)) (local $cnt i32) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable)) (else)) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Build list-of-lists from first list and rest (local.set $xss (struct.new $Pair (i32.const 0) (local.get $xs0) (local.get $rest))) ;; 3) Walk outer list xss to count #lists; ensure xss is proper and each element is a list head (local.set $nlists (call $validate-list-args (global.get $symbol:count) (local.get $xss))) ;; Racket's count requires at least one list argument (if (i32.eq (local.get $nlists) (i32.const 0)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:count)) (i32.const 1)) (unreachable)) (else)) ;; Fast path when exactly one list is supplied (if (i32.eq (local.get $nlists) (i32.const 1)) (then ;; Recompute the sole inner list directly from $xss, do not rely on $elem (local.set $pair (ref.cast (ref $Pair) (local.get $xss))) (local.set $cur (struct.get $Pair $a (local.get $pair))) (local.set $call (array.new $Args (global.get $null) (i32.const 1))) (local.set $cnt (i32.const 0)) (loop $loop1 (if (ref.eq (local.get $cur) (global.get $null)) (then (return (ref.i31 (i32.shl (local.get $cnt) (i32.const 1))))) (else)) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable)) (else)) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (array.set $Args (local.get $call) (i32.const 0) (struct.get $Pair $a (local.get $pair))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (if (i32.eqz (ref.eq (local.get $r) (global.get $false))) (then (local.set $cnt (i32.add (local.get $cnt) (i32.const 1)))) (else)) (br $loop1)) (unreachable)) (else ;; General case for multiple lists ;; 3) Allocate arrays for list cursors and call arguments; seed list cursors from xss (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (local.get $nlists))) (call $seed-list-args (local.get $xss) (local.get $lists)) ;; 4) Main loop: stop at the shortest list (local.set $cnt (i32.const 0)) (loop $loop2 ;; (a) Check state of all lists; determine if we stop (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done)) (else)) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (return (ref.i31 (i32.shl (local.get $cnt) (i32.const 1))))) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable)) (else)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; (b) Build call args for f: cars of each list and advance lists (local.set $i (i32.const 0)) (block $cars_done (loop $cars (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cars_done)) (else)) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $lists) (local.get $i) (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cars))) ;; (c) Apply f to those cars (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) ;; (d) Increment count if result is truthy (if (i32.eqz (ref.eq (local.get $r) (global.get $false))) (then (local.set $cnt (i32.add (local.get $cnt) (i32.const 1)))) (else)) (br $loop2)) (unreachable))) (unreachable)) (func $for-each (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $xs0 (ref eq)) ;; list? (param $rest (ref eq)) ;; list of lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pair (ref $Pair)) (local $nlists i32) (local $xss (ref eq)) (local $lists (ref $Args)) ;; cursors for each list (local $call (ref $Args)) ;; args for f (length = nlists) (local $i i32) (local $cur (ref eq)) (local $stop i32) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Build list-of-lists from first list and rest (local.set $xss (struct.new $Pair (i32.const 0) (local.get $xs0) (local.get $rest))) ;; 3) Walk outer list xss to count #lists; ensure xss is proper and each element is a list head (local.set $nlists (call $validate-list-args (global.get $symbol:for-each) (local.get $xss))) ;; Racket's for-each requires at least one list argument (if (i32.eq (local.get $nlists) (i32.const 0)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:for-each)) (i32.const 1)) (unreachable))) ;; 4) Allocate arrays for list cursors and call arguments; seed list cursors from xss (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (local.get $nlists))) (call $seed-list-args (local.get $xss) (local.get $lists)) ;; 5) Main loop: stop at the shortest list (loop $loop ;; (a) Check state of all lists; determine if we stop (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done))) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1))) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; If any list is empty → return void (if (i32.ne (local.get $stop) (i32.const 0)) (then (return (global.get $void)))) ;; (b) Build call args for f: cars of each list (local.set $i (i32.const 0)) (block $cars_done (loop $cars (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cars_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cars))) ;; (c) Apply f to those cars and drop result (drop (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) ;; (d) Advance each list (cdr) (local.set $i (i32.const 0)) (block $cdrs_done (loop $cdrs (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cdrs_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $lists) (local.get $i) (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cdrs))) (br $loop)) (unreachable)) ;; foldl : general entry point that dispatches to helpers (func $foldl (type $Prim>=3) (param $proc (ref eq)) ;; procedure (param $init (ref eq)) ;; initial accumulator (param $xs (ref eq)) ;; first list (param $rest (ref eq)) ;; remaining lists (result (ref eq)) (local $xss (ref eq)) (local $nlists i32) (local $ys (ref eq)) (local $outer (ref null $Pair)) ;; Combine first list with remaining lists. ;; Use cons here: $rest is already the list of remaining list-arguments. ;; Calling list* here would treat the last list argument as final tail. (local.set $xss (call $cons (local.get $xs) (local.get $rest))) (local.set $ys (global.get $null)) ;; Walk outer list xss to count #lists; capture second list (local.set $nlists (call $validate-list-args (global.get $symbol:foldl) (local.get $xss))) (if (i32.ge_u (local.get $nlists) (i32.const 2)) (then (local.set $outer (ref.cast (ref $Pair) (local.get $xss))) (local.set $outer (ref.cast (ref $Pair) (struct.get $Pair $d (local.get $outer)))) (local.set $ys (struct.get $Pair $a (local.get $outer))))) ;; Racket's foldl requires at least one list argument (if (i32.eq (local.get $nlists) (i32.const 0)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:foldl)) (i32.const 1)) (unreachable))) ;; Dispatch based on number of lists (if (i32.eq (local.get $nlists) (i32.const 1)) (then (return (call $foldl/1 (local.get $proc) (local.get $init) (local.get $xs))))) (if (i32.eq (local.get $nlists) (i32.const 2)) (then (return (call $foldl/2 (local.get $proc) (local.get $init) (local.get $xs) (local.get $ys))))) (call $foldl/n (local.get $proc) (local.get $init) (local.get $xss))) ;; foldl/1 : (X Acc -> Acc) Acc (Listof X) -> Acc (func $foldl/1 (type $Prim3) (param $proc (ref eq)) ;; procedure (param $acc (ref eq)) ;; initial accumulator (param $xs (ref eq)) ;; list (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $call (ref $Args)) (local $r (ref eq)) ;; Ensure $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Prepare argument array for procedure (local.set $call (array.new_fixed $Args 2 (global.get $null) (global.get $null))) ;; Iterate through list (loop $loop (if (ref.eq (local.get $xs) (global.get $null)) (then (return (local.get $acc)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $xs))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $call) (i32.const 0) (local.get $elem)) (array.set $Args (local.get $call) (i32.const 1) (local.get $acc)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (local.set $acc (local.get $r)) (local.set $xs (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) ;; foldl/2 : (X Y Acc -> Acc) Acc (Listof X) (Listof Y) -> Acc (func $foldl/2 (type $Prim4) (param $proc (ref eq)) ;; procedure (param $acc (ref eq)) ;; initial accumulator (param $xs (ref eq)) ;; first list (param $ys (ref eq)) ;; second list (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pairx (ref $Pair)) (local $pairy (ref $Pair)) (local $call (ref $Args)) (local $r (ref eq)) ;; Ensure $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Prepare argument array for procedure (local.set $call (array.new_fixed $Args 3 (global.get $null) (global.get $null) (global.get $null))) ;; Iterate through both lists in lockstep; stop at shortest (loop $loop (if (i32.or (ref.eq (local.get $xs) (global.get $null)) (ref.eq (local.get $ys) (global.get $null))) (then (return (local.get $acc)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $ys))) (then (call $raise-pair-expected (local.get $ys)) (unreachable))) (local.set $pairx (ref.cast (ref $Pair) (local.get $xs))) (local.set $pairy (ref.cast (ref $Pair) (local.get $ys))) (array.set $Args (local.get $call) (i32.const 0) (struct.get $Pair $a (local.get $pairx))) (array.set $Args (local.get $call) (i32.const 1) (struct.get $Pair $a (local.get $pairy))) (array.set $Args (local.get $call) (i32.const 2) (local.get $acc)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (local.set $acc (local.get $r)) (local.set $xs (struct.get $Pair $d (local.get $pairx))) (local.set $ys (struct.get $Pair $d (local.get $pairy))) (br $loop)) (unreachable)) ;; foldl/n : (X1 X2 ... Xn Acc -> Acc) Acc (Listof X1) ... (Listof Xn) -> Acc (func $foldl/n (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $acc (ref eq)) ;; initial accumulator (param $xss (ref eq)) ;; list of lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $pair (ref $Pair)) (local $nlists i32) (local $lists (ref $Args)) (local $call (ref $Args)) (local $i i32) (local $cur (ref eq)) (local $stop i32) (local $r (ref eq)) ;; Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Walk outer list xss to count #lists; ensure xss is proper and each element is a list head (local.set $nlists (call $validate-list-args (global.get $symbol:foldl) (local.get $xss))) ;; Racket's foldl requires at least one list argument (if (i32.eq (local.get $nlists) (i32.const 0)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:foldl)) (i32.const 1)) (unreachable))) ;; Allocate arrays for list cursors and call arguments (extra slot for accumulator) (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (i32.add (local.get $nlists) (i32.const 1)))) ;; Seed list cursors from xss (call $seed-list-args (local.get $xss) (local.get $lists)) ;; Main loop: stop at the shortest list (loop $loop ;; (a) Check state of all lists; determine if we stop (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done))) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1))) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; If any list is empty → return accumulator (if (i32.ne (local.get $stop) (i32.const 0)) (then (return (local.get $acc)))) ;; (b) Build call args for f: cars of each list + accumulator (local.set $i (i32.const 0)) (block $cars_done (loop $cars (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cars_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cars))) (array.set $Args (local.get $call) (local.get $nlists) (local.get $acc)) ;; (c) Apply f and update accumulator (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (local.set $acc (local.get $r)) ;; (d) Advance each list (cdr) (local.set $i (i32.const 0)) (block $cdrs_done (loop $cdrs (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cdrs_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $lists) (local.get $i) (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cdrs))) (br $loop)) (unreachable)) (func $foldr (type $Prim>=3) (param $proc (ref eq)) ;; procedure (param $init (ref eq)) ;; initial value (param $xs (ref eq)) ;; first list (param $rest (ref eq)) ;; remaining lists (result (ref eq)) (local $pair (ref $Pair)) (local $ys (ref eq)) (local $rest2 (ref eq)) (local $xss (ref eq)) ;; Combine first list with remaining lists for n-ary case. ;; Use cons for the same reason as in foldl. (local.set $xss (call $cons (local.get $xs) (local.get $rest))) ;; Single list case (if (ref.eq (local.get $rest) (global.get $null)) (then (return (call $foldr/1 (local.get $proc) (local.get $init) (local.get $xs))))) ;; Two-list case (if (i32.eqz (ref.test (ref $Pair) (local.get $rest))) (then (call $raise-pair-expected (local.get $rest)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $rest))) (local.set $ys (struct.get $Pair $a (local.get $pair))) (local.set $rest2 (struct.get $Pair $d (local.get $pair))) (if (ref.eq (local.get $rest2) (global.get $null)) (then (return (call $foldr/2 (local.get $proc) (local.get $init) (local.get $xs) (local.get $ys))))) ;; N-ary case (call $foldr/n (local.get $proc) (local.get $init) (local.get $xss))) (func $foldr/1-loop (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (param $init (ref eq)) (param $xs (ref eq)) (result (ref eq)) (local $pair (ref $Pair)) (local $call (ref $Args)) (local $rest (ref eq)) (if (ref.eq (local.get $xs) (global.get $null)) (then (return (local.get $init)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $xs))) (local.set $rest (call $foldr/1-loop (local.get $f) (local.get $finv) (local.get $init) (struct.get $Pair $d (local.get $pair)))) (local.set $call (array.new_fixed $Args 2 (struct.get $Pair $a (local.get $pair)) (local.get $rest))) (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (func $foldr/1 (param $proc (ref eq)) ;; procedure (param $init (ref eq)) ;; initial value (param $xs (ref eq)) ;; list (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (call $foldr/1-loop (local.get $f) (local.get $finv) (local.get $init) (local.get $xs))) (func $foldr/2-loop (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (param $init (ref eq)) (param $xs (ref eq)) (param $ys (ref eq)) (result (ref eq)) (local $px (ref $Pair)) (local $py (ref $Pair)) (local $call (ref $Args)) (local $rest (ref eq)) (if (i32.or (ref.eq (local.get $xs) (global.get $null)) (ref.eq (local.get $ys) (global.get $null))) (then (return (local.get $init)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $xs))) (then (call $raise-pair-expected (local.get $xs)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $ys))) (then (call $raise-pair-expected (local.get $ys)) (unreachable))) (local.set $px (ref.cast (ref $Pair) (local.get $xs))) (local.set $py (ref.cast (ref $Pair) (local.get $ys))) (local.set $rest (call $foldr/2-loop (local.get $f) (local.get $finv) (local.get $init) (struct.get $Pair $d (local.get $px)) (struct.get $Pair $d (local.get $py)))) (local.set $call (array.new_fixed $Args 3 (struct.get $Pair $a (local.get $px)) (struct.get $Pair $a (local.get $py)) (local.get $rest))) (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (func $foldr/2 (param $proc (ref eq)) ;; procedure (param $init (ref eq)) ;; initial value (param $xs (ref eq)) ;; list X (param $ys (ref eq)) ;; list Y (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (call $foldr/2-loop (local.get $f) (local.get $finv) (local.get $init) (local.get $xs) (local.get $ys))) (func $foldr/n-loop (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (param $lists (ref $Args)) (param $nlists i32) (param $init (ref eq)) (result (ref eq)) (local $i i32) (local $cur (ref eq)) (local $tails (ref $Args)) (local $call (ref $Args)) (local $pair (ref $Pair)) (local $rest (ref eq)) ;; Check for empty lists (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done))) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $init)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; Build tails and call arrays (local.set $tails (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (i32.add (local.get $nlists) (i32.const 1)))) (local.set $i (i32.const 0)) (block $build_done (loop $build (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $build_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $tails) (local.get $i) (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $build))) ;; Recursive call on tails (local.set $rest (call $foldr/n-loop (local.get $f) (local.get $finv) (local.get $tails) (local.get $nlists) (local.get $init))) (array.set $Args (local.get $call) (local.get $nlists) (local.get $rest)) (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (func $foldr/n (param $proc (ref eq)) ;; procedure (param $init (ref eq)) ;; initial value (param $xss (ref eq)) ;; list of lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $outer (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $nlists i32) (local $lists (ref $Args)) (local $i i32) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (if (ref.eq (local.get $xss) (global.get $null)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:foldr)) (i32.const 1)) (unreachable))) ;; Count lists (local.set $nlists (i32.const 0)) (local.set $outer (local.get $xss)) (block $count_done (loop $count (if (ref.eq (local.get $outer) (global.get $null)) (then (br $count_done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $outer))) (then (call $raise-pair-expected (local.get $outer)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $outer))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (i32.or (ref.eq (local.get $elem) (global.get $null)) (ref.test (ref $Pair) (local.get $elem)))) (then (call $raise-pair-expected (local.get $elem)) (unreachable))) (local.set $nlists (i32.add (local.get $nlists) (i32.const 1))) (local.set $outer (struct.get $Pair $d (local.get $pair))) (br $count))) (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $outer (local.get $xss)) (local.set $i (i32.const 0)) (block $seed_done (loop $seed (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $seed_done))) (local.set $pair (ref.cast (ref $Pair) (local.get $outer))) (array.set $Args (local.get $lists) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $outer (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $seed))) (call $foldr/n-loop (local.get $f) (local.get $finv) (local.get $lists) (local.get $nlists) (local.get $init))) (func $filter (type $Prim2) (param $proc (ref eq)) ;; predicate (param $xs (ref eq)) ;; list (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $call (ref $Args)) (local $r (ref eq)) (local $acc (ref eq)) (local $res (ref eq)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Prepare argument array for predicate (local.set $call (array.new $Args (global.get $null) (i32.const 1))) ;; 3) Iterate through list, building reversed accumulator (local.set $cur (local.get $xs)) (local.set $acc (global.get $null)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then ;; Reverse accumulator and return (local.set $res (global.get $null)) (local.set $cur (local.get $acc)) (loop $rev (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $res)))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $res (call $cons (struct.get $Pair $a (local.get $pair)) (local.get $res))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $rev)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $call) (i32.const 0) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (if (ref.eq (local.get $r) (global.get $false)) (then (nop)) (else (local.set $acc (call $cons (local.get $elem) (local.get $acc))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) (func $filter-map (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $xs0 (ref eq)) ;; first list (param $rest (ref eq)) ;; rest lists (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $outer (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $nlists i32) (local $lists (ref $Args)) ;; cursors for each list (local $call (ref $Args)) ;; args for f (length = nlists) (local $i i32) (local $cur (ref eq)) (local $stop i32) (local $acc (ref eq)) ;; reversed accumulator (local $res (ref eq)) ;; final result (local $r (ref eq)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Ensure $xs0 is a list head; walk $rest to count #lists and validate each head (if (i32.eqz (i32.or (ref.eq (local.get $xs0) (global.get $null)) (ref.test (ref $Pair) (local.get $xs0)))) (then (call $raise-pair-expected (local.get $xs0)) (unreachable))) (local.set $nlists (i32.const 1)) (local.set $outer (local.get $rest)) (block $count_done (loop $count (if (ref.eq (local.get $outer) (global.get $null)) (then (br $count_done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $outer))) (then (call $raise-pair-expected (local.get $outer)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $outer))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (i32.or (ref.eq (local.get $elem) (global.get $null)) (ref.test (ref $Pair) (local.get $elem)))) (then (call $raise-pair-expected (local.get $elem)) (unreachable))) (local.set $nlists (i32.add (local.get $nlists) (i32.const 1))) (local.set $outer (struct.get $Pair $d (local.get $pair))) (br $count))) ;; 3) Allocate arrays for list cursors and call arguments; seed list cursors (local.set $lists (array.new $Args (global.get $null) (local.get $nlists))) (local.set $call (array.new $Args (global.get $null) (local.get $nlists))) (array.set $Args (local.get $lists) (i32.const 0) (local.get $xs0)) (local.set $outer (local.get $rest)) (local.set $i (i32.const 1)) (block $seed_done (loop $seed (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $seed_done))) (local.set $pair (ref.cast (ref $Pair) (local.get $outer))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $lists) (local.get $i) (local.get $elem)) (local.set $outer (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $seed))) ;; 4) Main loop: stop at the shortest list (local.set $acc (global.get $null)) (loop $loop ;; (a) Check state of all lists; determine if we stop (local.set $stop (i32.const 0)) (local.set $i (i32.const 0)) (block $check_done (loop $check (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $check_done))) (local.set $cur (array.get $Args (local.get $lists) (local.get $i))) (if (ref.eq (local.get $cur) (global.get $null)) (then (local.set $stop (i32.const 1))) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $check))) ;; If any list is empty → finish by reversing $acc (if (i32.ne (local.get $stop) (i32.const 0)) (then (local.set $res (global.get $null)) (local.set $cur (local.get $acc)) (loop $rev (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $res)))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $res (call $cons (struct.get $Pair $a (local.get $pair)) (local.get $res))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $rev)))) ;; (b) Build call args for f: cars of each list (local.set $i (i32.const 0)) (block $cars_done (loop $cars (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cars_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $call) (local.get $i) (struct.get $Pair $a (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cars))) ;; (c) Apply f to those cars (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) ;; (d) cons the result onto the accumulator if non-#f (if (ref.eq (local.get $r) (global.get $false)) (then (nop)) (else (local.set $acc (call $cons (local.get $r) (local.get $acc))))) ;; (e) Advance each list (cdr) (local.set $i (i32.const 0)) (block $cdrs_done (loop $cdrs (if (i32.ge_u (local.get $i) (local.get $nlists)) (then (br $cdrs_done))) (local.set $pair (ref.cast (ref $Pair) (array.get $Args (local.get $lists) (local.get $i)))) (array.set $Args (local.get $lists) (local.get $i) (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $cdrs))) (br $loop)) (unreachable)) ;; Like filter, but keeps elements for which the predicate returns false (func $filter-not (type $Prim2) (param $proc (ref eq)) ;; predicate (param $xs (ref eq)) ;; list (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $call (ref $Args)) (local $r (ref eq)) (local $acc (ref eq)) (local $res (ref eq)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Prepare argument array for predicate (local.set $call (array.new $Args (global.get $null) (i32.const 1))) ;; 3) Iterate through list, building reversed accumulator (local.set $cur (local.get $xs)) (local.set $acc (global.get $null)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then ;; Reverse accumulator and return (local.set $res (global.get $null)) (local.set $cur (local.get $acc)) (loop $rev (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $res)))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $res (call $cons (struct.get $Pair $a (local.get $pair)) (local.get $res))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $rev)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $call) (i32.const 0) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (if (ref.eq (local.get $r) (global.get $false)) (then (local.set $acc (call $cons (local.get $elem) (local.get $acc))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) (func $shuffle (type $Prim1) (param $xs (ref eq)) ;; list (result (ref eq)) (local $arr (ref $Array)) (local $len i32) (local $i i32) (local $j i32) (local $tmp (ref eq)) (local $elem (ref eq)) (local $res (ref eq)) ;; 1) Convert list to array (validates list) (local.set $arr (call $list->array (local.get $xs))) (local.set $len (array.len (local.get $arr))) ;; 2) Fisher-Yates shuffle (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $j (i32.rem_u (call $random-u32) (i32.add (local.get $i) (i32.const 1)))) (if (i32.ne (local.get $j) (local.get $i)) (then (local.set $tmp (array.get $Array (local.get $arr) (local.get $i))) (local.set $elem (array.get $Array (local.get $arr) (local.get $j))) (array.set $Array (local.get $arr) (local.get $i) (local.get $elem)) (array.set $Array (local.get $arr) (local.get $j) (local.get $tmp)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; 3) Convert array back to list (local.set $res (global.get $null)) (local.set $i (i32.sub (local.get $len) (i32.const 1))) (block $rev-done (loop $rev (br_if $rev-done (i32.lt_s (local.get $i) (i32.const 0))) (local.set $elem (array.get $Array (local.get $arr) (local.get $i))) (local.set $res (struct.new $Pair (i32.const 0) (local.get $elem) (local.get $res))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $rev))) (local.get $res)) (func $partition (type $Prim2) (param $proc (ref eq)) ;; predicate (param $xs (ref eq)) ;; list (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $call (ref $Args)) (local $r (ref eq)) (local $acc-t (ref eq)) (local $acc-f (ref eq)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Prepare argument array for predicate (local.set $call (array.new $Args (global.get $null) (i32.const 1))) ;; 3) Iterate through list, building two reversed accumulators (local.set $cur (local.get $xs)) (local.set $acc-t (global.get $null)) (local.set $acc-f (global.get $null)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (array.new_fixed $Values 2 (call $reverse (local.get $acc-t)) (call $reverse (local.get $acc-f)))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $call) (i32.const 0) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (if (ref.eq (local.get $r) (global.get $false)) (then (local.set $acc-f (call $cons (local.get $elem) (local.get $acc-f)))) (else (local.set $acc-t (call $cons (local.get $elem) (local.get $acc-t))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) (func $remove (type $Prim23) (param $v (ref eq)) ;; value to remove (param $lst (ref eq)) ;; list (param $proc (ref eq)) ;; optional comparator (result (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $acc (ref eq)) (local $res (ref eq)) (local $r (ref eq)) (local $tail (ref eq)) (local $args (ref $Args)) (local $use-proc i32) ;; 1) Handle optional comparator (fail early + flag) (if (ref.eq (local.get $proc) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else ;; Type check $proc (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable)) (else)) (local.set $use-proc (i32.const 1)))) ;; 2) Iterate through list until match found (local.set $cur (local.get $lst)) (local.set $acc (global.get $null)) (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $lst))) (else)) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable)) (else)) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (local.set $tail (struct.get $Pair $d (local.get $pair))) (block $found (if (i32.eqz (local.get $use-proc)) (then ;; No comparator: use equal? (if (ref.eq (call $equal? (local.get $v) (local.get $elem)) (global.get $false)) (then (local.set $acc (call $cons (local.get $elem) (local.get $acc))) (local.set $cur (local.get $tail)) (br $loop)) (else (br $found)))) (else ;; With comparator: build args and call via invoker, all on the stack. ;; args := [v, elem] (local.set $r (call_ref $ProcedureInvoker ;; f : (ref $Procedure) (ref.cast (ref $Procedure) (local.get $proc)) ;; call args : (ref $Args) (size 2) (block (result (ref $Args)) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (array.set $Args (local.get $args) (i32.const 0) (local.get $v)) (array.set $Args (local.get $args) (i32.const 1) (local.get $elem)) (local.get $args)) ;; finv : (ref $ProcedureInvoker) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $proc))))) (if (ref.eq (local.get $r) (global.get $false)) (then (local.set $acc (call $cons (local.get $elem) (local.get $acc))) (local.set $cur (local.get $tail)) (br $loop)) (else (br $found)))))) ;; found match, fallthrough: rebuild with accumulator (local.set $cur (local.get $tail)) (local.set $res (local.get $cur)) (local.set $cur (local.get $acc)) (loop $rev (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $res))) (else)) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $res (call $cons (struct.get $Pair $a (local.get $pair)) (local.get $res))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $rev))) (unreachable)) (func $remf (type $Prim2) ;; removes first element that satisfies predicate (param $proc (ref eq)) ;; predicate (param $lst (ref eq)) ;; list (result (ref eq)) (return_call $remf/impl (local.get $proc) (local.get $lst) (i32.const 0))) (func $remf* (type $Prim2) ;; removes first element that satisfies predicate (param $proc (ref eq)) ;; predicate (param $lst (ref eq)) ;; list (result (ref eq)) (return_call $remf/impl (local.get $proc) (local.get $lst) (i32.const 1))) ;; Remove list elements using predicate (func $remf/impl (param $proc (ref eq)) ;; predicate (param $lst (ref eq)) ;; list (param $all i32) ;; non-zero => remove all matches (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $tail (ref eq)) (local $acc (ref eq)) (local $res (ref eq)) (local $args (ref $Args)) (local $r (ref eq)) (local $found i32) ;; Ensure proc is a procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) ;; Iterate through list (local.set $cur (local.get $lst)) (local.set $acc (global.get $null)) (local.set $found (i32.const 0)) (block $done (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (br $done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (local.set $tail (struct.get $Pair $d (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $r) (global.get $false)) (then (local.set $acc (call $cons (local.get $elem) (local.get $acc))) (local.set $cur (local.get $tail)) (br $loop)) (else (local.set $found (i32.const 1)) (local.set $cur (local.get $tail)) (if (i32.eqz (local.get $all)) (then (br $done)) (else (br $loop))))))) (if (i32.eqz (local.get $found)) (then (return (local.get $lst)))) (local.set $res (local.get $cur)) (local.set $cur (local.get $acc)) (loop $rev (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $res)))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $res (call $cons (struct.get $Pair $a (local.get $pair)) (local.get $res))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $rev)) (unreachable)) ;; $remq, $remv, and $remw implemented via remove ,@(let ([ops '(($remq $prim:eq?) ($remv $prim:eqv?) ($remw $prim:equal-always?))]) (for/list ([p ops]) (define name (car p)) (define cmp (cadr p)) `(func ,name (type $Prim2) (param $v (ref eq)) (param $lst (ref eq)) (result (ref eq)) (return_call $remove (local.get $v) (local.get $lst) (global.get ,cmp))))) (func $remove* (type $Prim23) (param $v-lst (ref eq)) ;; list of values (param $lst (ref eq)) ;; list (param $proc (ref eq)) ;; optional comparator, defaults to equal? (result (ref eq)) ;; Handle optional comparator (if (ref.eq (local.get $proc) (global.get $missing)) (then (local.set $proc (global.get $prim:equal?))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))))) ;; If v-lst is empty, return original list (if (ref.eq (local.get $v-lst) (global.get $null)) (then (return (local.get $lst)))) (return_call $remove*/checked (local.get $v-lst) (local.get $lst) (local.get $proc))) ;; Iterate through the value list, removing each one in turn (func $remove*/checked (param $v-lst (ref eq)) (param $lst (ref eq)) (param $proc (ref eq)) (result (ref eq)) (local $vlcur (ref eq)) (local $vlpair (ref $Pair)) (local $v (ref eq)) (local $res (ref eq)) (local.set $vlcur (local.get $v-lst)) (local.set $res (local.get $lst)) (loop $loop (if (ref.eq (local.get $vlcur) (global.get $null)) (then (return (local.get $res)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $vlcur))) (then (call $raise-pair-expected (local.get $vlcur)) (unreachable))) (local.set $vlpair (ref.cast (ref $Pair) (local.get $vlcur))) (local.set $v (struct.get $Pair $a (local.get $vlpair))) (local.set $vlcur (struct.get $Pair $d (local.get $vlpair))) (local.set $res (call $remove*/checked/1 (local.get $v) (local.get $res) (local.get $proc))) (br $loop)) (unreachable)) ;; Remove all occurrences of a single value from a list using the comparator (func $remove*/checked/1 (param $v (ref eq)) (param $lst (ref eq)) (param $proc (ref eq)) (result (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $acc (ref eq)) (local $res (ref eq)) (local $tail (ref eq)) (local $args (ref $Args)) (local $r (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $modified i32) ;; Prepare comparator call (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (array.set $Args (local.get $args) (i32.const 0) (local.get $v)) (local.set $cur (local.get $lst)) (local.set $acc (global.get $null)) (local.set $modified (i32.const 0)) (block $done (loop $loop (if (ref.eq (local.get $cur) (global.get $null)) (then (br $done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (local.set $tail (struct.get $Pair $d (local.get $pair))) (array.set $Args (local.get $args) (i32.const 1) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (local.get $r) (global.get $false)) (then (local.set $acc (call $cons (local.get $elem) (local.get $acc)))) (else (local.set $modified (i32.const 1)))) (local.set $cur (local.get $tail)) (br $loop))) (if (i32.eqz (local.get $modified)) (then (return (local.get $lst)))) ;; Rebuild reversed accumulator (local.set $cur (local.get $acc)) (local.set $res (global.get $null)) (loop $rev (if (ref.eq (local.get $cur) (global.get $null)) (then (return (local.get $res)))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $res (call $cons (struct.get $Pair $a (local.get $pair)) (local.get $res))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $rev)) (unreachable)) ;; $remq*, $remv*, and $remw* implemented via remove* ,@(let ([ops '(($remq* $prim:eq?) ($remv* $prim:eqv?) ($remw* $prim:equal-always?))]) (for/list ([p ops]) (define name (car p)) (define cmp (cadr p)) `(func ,name (type $Prim2) (param $v-lst (ref eq)) (param $lst (ref eq)) (result (ref eq)) (return_call $remove* (local.get $v-lst) (local.get $lst) (global.get ,cmp))))) (func $raise-take:bad-length (param $xs (ref eq)) (param $n i32) (param $len i32) (unreachable)) (func $raise-take-right:bad-length (param $xs (ref eq)) (param $n i32) (param $len i32) (unreachable)) (func $take (type $Prim2) (param $xs (ref eq)) (param $n/fx (ref eq)) (result (ref eq)) (local $count i32) (local $orig-count i32) (local $taken i32) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $acc (ref eq)) (if (ref.test (ref i31) (local.get $n/fx)) (then (local.set $count (i31.get_u (ref.cast (ref i31) (local.get $n/fx)))) (if (i32.ne (i32.and (local.get $count) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $n/fx)))) (local.set $count (i32.shr_u (local.get $count) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $n/fx)))) (local.set $orig-count (local.get $count)) (if (i32.eqz (local.get $count)) (then (return (global.get $null)))) (local.set $cur (local.get $xs)) (local.set $pair (global.get $dummy-pair)) (local.set $acc (global.get $null)) (local.set $taken (i32.const 0)) (loop $loop (if (i32.eqz (local.get $count)) (then (return (call $reverse (local.get $acc))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-take:bad-length (local.get $xs) (local.get $orig-count) (local.get $taken)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (local.set $acc (call $cons (local.get $elem) (local.get $acc))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (local.set $count (i32.sub (local.get $count) (i32.const 1))) (local.set $taken (i32.add (local.get $taken) (i32.const 1))) (br $loop)) (unreachable)) (func $take-right (type $Prim2) (param $xs (ref eq)) (param $n/fx (ref eq)) (result (ref eq)) (local $count i32) (local $orig-count i32) (local $remaining i32) (local $available i32) (local $lead (ref eq)) (local $lag (ref eq)) (local $pair (ref $Pair)) (local $lag-pair (ref $Pair)) (if (ref.test (ref i31) (local.get $n/fx)) (then (local.set $count (i31.get_u (ref.cast (ref i31) (local.get $n/fx)))) (if (i32.ne (i32.and (local.get $count) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $n/fx)))) (local.set $count (i32.shr_u (local.get $count) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $n/fx)))) (local.set $orig-count (local.get $count)) (if (i32.eqz (local.get $count)) (then (return (local.get $xs)))) (local.set $lead (local.get $xs)) (local.set $remaining (local.get $count)) (local.set $available (i32.const 0)) (local.set $pair (global.get $dummy-pair)) (local.set $lag-pair (global.get $dummy-pair)) (block $advance-done (loop $advance (if (i32.eqz (local.get $remaining)) (then (br $advance-done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lead))) (then (call $raise-take-right:bad-length (local.get $xs) (local.get $orig-count) (local.get $available)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $lead))) (local.set $lead (struct.get $Pair $d (local.get $pair))) (local.set $remaining (i32.sub (local.get $remaining) (i32.const 1))) (local.set $available (i32.add (local.get $available) (i32.const 1))) (br $advance))) (local.set $lag (local.get $xs)) (loop $slide (if (i32.eqz (ref.test (ref $Pair) (local.get $lead))) (then (return (local.get $lag)))) (local.set $pair (ref.cast (ref $Pair) (local.get $lead))) (local.set $lag-pair (ref.cast (ref $Pair) (local.get $lag))) (local.set $lead (struct.get $Pair $d (local.get $pair))) (local.set $lag (struct.get $Pair $d (local.get $lag-pair))) (br $slide)) (unreachable)) (func $takef (type $Prim2) (param $xs (ref eq)) (param $proc (ref eq)) (result (ref eq)) (local $f (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $res (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $acc (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $inv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $cur (local.get $xs)) (local.set $pair (global.get $dummy-pair)) (local.set $acc (global.get $null)) (loop $loop (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (return (call $reverse (local.get $acc))))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $inv))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (call $reverse (local.get $acc))))) (local.set $acc (call $cons (local.get $elem) (local.get $acc))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) (func $takef-right (type $Prim2) (param $xs (ref eq)) (param $proc (ref eq)) (result (ref eq)) (local $f (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $res (ref eq)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $suffix (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $inv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) (local.set $cur (local.get $xs)) (local.set $pair (global.get $dummy-pair)) (local.set $suffix (local.get $xs)) (loop $loop (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (return (local.get $suffix)))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $inv))) (if (ref.eq (local.get $res) (global.get $false)) (then (local.set $suffix (struct.get $Pair $d (local.get $pair))))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $loop)) (unreachable)) (func $list-prefix? (type $Prim23) (param $l (ref eq)) ; list (param $r (ref eq)) ; list (param $same? (ref eq)) ; optional, defaults to equal? (result (ref eq)) (local $lcur (ref eq)) (local $rcur (ref eq)) (local $lpair (ref $Pair)) (local $rpair (ref $Pair)) (local $lelem (ref eq)) (local $relem (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local $use-proc i32) ;; Handle optional comparator (if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable)) (else)) (local.set $use-proc (i32.const 1)))) (if (ref.eq (call $list? (local.get $l)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:list-prefix?) (global.get $string:list?) (local.get $l)) (unreachable))) (if (ref.eq (call $list? (local.get $r)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:list-prefix?) (global.get $string:list?) (local.get $r)) (unreachable))) ;; Iterate through lists (local.set $lcur (local.get $l)) (local.set $rcur (local.get $r)) (loop $loop (if (ref.eq (local.get $lcur) (global.get $null)) (then (return (global.get $true)))) (if (ref.eq (local.get $rcur) (global.get $null)) (then (return (global.get $false)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lcur))) (then (call $raise-pair-expected (local.get $lcur)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $rcur))) (then (call $raise-pair-expected (local.get $rcur)) (unreachable))) (local.set $lpair (ref.cast (ref $Pair) (local.get $lcur))) (local.set $rpair (ref.cast (ref $Pair) (local.get $rcur))) (local.set $lelem (struct.get $Pair $a (local.get $lpair))) (local.set $relem (struct.get $Pair $a (local.get $rpair))) (block $same (if (i32.eqz (local.get $use-proc)) (then (if (ref.eq (call $equal? (local.get $lelem) (local.get $relem)) (global.get $false)) (then (return (global.get $false))) (else (br $same)))) (else (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (block (result (ref $Args)) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (array.set $Args (local.get $args) (i32.const 0) (local.get $lelem)) (array.set $Args (local.get $args) (i32.const 1) (local.get $relem)) (local.get $args)) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false))) (else (br $same)))))) (local.set $lcur (struct.get $Pair $d (local.get $lpair))) (local.set $rcur (struct.get $Pair $d (local.get $rpair))) (br $loop)) (unreachable)) (func $take-common-prefix (type $Prim23) (param $l (ref eq)) ; list (param $r (ref eq)) ; list (param $same? (ref eq)) ; optional, defaults to equal? (result (ref eq)) (local $lcur (ref eq)) (local $rcur (ref eq)) (local $lpair (ref $Pair)) (local $rpair (ref $Pair)) (local $lelem (ref eq)) (local $relem (ref eq)) (local $acc (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local $use-proc i32) ;; Handle optional comparator (if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable)) (else)) (local.set $use-proc (i32.const 1)))) (if (ref.eq (call $list? (local.get $l)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:take-common-prefix) (global.get $string:list?) (local.get $l)) (unreachable))) (if (ref.eq (call $list? (local.get $r)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:take-common-prefix) (global.get $string:list?) (local.get $r)) (unreachable))) ;; Iterate through lists, building accumulator (local.set $lcur (local.get $l)) (local.set $rcur (local.get $r)) (local.set $acc (global.get $null)) (loop $loop (if (ref.eq (local.get $lcur) (global.get $null)) (then (return (call $reverse (local.get $acc))))) (if (ref.eq (local.get $rcur) (global.get $null)) (then (return (call $reverse (local.get $acc))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lcur))) (then (call $raise-pair-expected (local.get $lcur)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $rcur))) (then (call $raise-pair-expected (local.get $rcur)) (unreachable))) (local.set $lpair (ref.cast (ref $Pair) (local.get $lcur))) (local.set $rpair (ref.cast (ref $Pair) (local.get $rcur))) (local.set $lelem (struct.get $Pair $a (local.get $lpair))) (local.set $relem (struct.get $Pair $a (local.get $rpair))) (block $same (if (i32.eqz (local.get $use-proc)) (then (if (ref.eq (call $equal? (local.get $lelem) (local.get $relem)) (global.get $false)) (then (return (call $reverse (local.get $acc)))) (else (br $same)))) (else (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (block (result (ref $Args)) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (array.set $Args (local.get $args) (i32.const 0) (local.get $lelem)) (array.set $Args (local.get $args) (i32.const 1) (local.get $relem)) (local.get $args)) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (call $reverse (local.get $acc)))) (else (br $same)))))) (local.set $acc (call $cons (local.get $lelem) (local.get $acc))) (local.set $lcur (struct.get $Pair $d (local.get $lpair))) (local.set $rcur (struct.get $Pair $d (local.get $rpair))) (br $loop)) (unreachable)) (func $drop-common-prefix (type $Prim23) (param $l (ref eq)) ; list (param $r (ref eq)) ; list (param $same? (ref eq)) ; optional, defaults to equal? (result (ref eq)) (local $lcur (ref eq)) (local $rcur (ref eq)) (local $lpair (ref $Pair)) (local $rpair (ref $Pair)) (local $lelem (ref eq)) (local $relem (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local $use-proc i32) ;; Handle optional comparator (if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable)) (else)) (local.set $use-proc (i32.const 1)))) (if (ref.eq (call $list? (local.get $l)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:drop-common-prefix) (global.get $string:list?) (local.get $l)) (unreachable))) (if (ref.eq (call $list? (local.get $r)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:drop-common-prefix) (global.get $string:list?) (local.get $r)) (unreachable))) ;; Iterate through lists, dropping common prefix (local.set $lcur (local.get $l)) (local.set $rcur (local.get $r)) (loop $loop (if (ref.eq (local.get $lcur) (global.get $null)) (then (return (array.new_fixed $Values 2 (local.get $lcur) (local.get $rcur))))) (if (ref.eq (local.get $rcur) (global.get $null)) (then (return (array.new_fixed $Values 2 (local.get $lcur) (local.get $rcur))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lcur))) (then (call $raise-pair-expected (local.get $lcur)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $rcur))) (then (call $raise-pair-expected (local.get $rcur)) (unreachable))) (local.set $lpair (ref.cast (ref $Pair) (local.get $lcur))) (local.set $rpair (ref.cast (ref $Pair) (local.get $rcur))) (local.set $lelem (struct.get $Pair $a (local.get $lpair))) (local.set $relem (struct.get $Pair $a (local.get $rpair))) (block $same (if (i32.eqz (local.get $use-proc)) (then (if (ref.eq (call $equal? (local.get $lelem) (local.get $relem)) (global.get $false)) (then (return (array.new_fixed $Values 2 (local.get $lcur) (local.get $rcur)))) (else (br $same)))) (else (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (block (result (ref $Args)) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (array.set $Args (local.get $args) (i32.const 0) (local.get $lelem)) (array.set $Args (local.get $args) (i32.const 1) (local.get $relem)) (local.get $args)) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (array.new_fixed $Values 2 (local.get $lcur) (local.get $rcur)))) (else (br $same)))))) (local.set $lcur (struct.get $Pair $d (local.get $lpair))) (local.set $rcur (struct.get $Pair $d (local.get $rpair))) (br $loop)) (unreachable)) (func $split-common-prefix (type $Prim23) (param $l (ref eq)) ; list (param $r (ref eq)) ; list (param $same? (ref eq)) ; optional, defaults to equal? (result (ref eq)) (local $lcur (ref eq)) (local $rcur (ref eq)) (local $lpair (ref $Pair)) (local $rpair (ref $Pair)) (local $lelem (ref eq)) (local $relem (ref eq)) (local $acc (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local $use-proc i32) ;; Handle optional comparator (if (ref.eq (local.get $same?) (global.get $missing)) (then (local.set $use-proc (i32.const 0))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable)) (else)) (local.set $use-proc (i32.const 1)))) (if (ref.eq (call $list? (local.get $l)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:split-common-prefix) (global.get $string:list?) (local.get $l)) (unreachable))) (if (ref.eq (call $list? (local.get $r)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:split-common-prefix) (global.get $string:list?) (local.get $r)) (unreachable))) ;; Iterate through lists, building accumulator and returning tails (local.set $lcur (local.get $l)) (local.set $rcur (local.get $r)) (local.set $acc (global.get $null)) (loop $loop (if (ref.eq (local.get $lcur) (global.get $null)) (then (return (array.new_fixed $Values 3 (call $reverse (local.get $acc)) (local.get $lcur) (local.get $rcur))))) (if (ref.eq (local.get $rcur) (global.get $null)) (then (return (array.new_fixed $Values 3 (call $reverse (local.get $acc)) (local.get $lcur) (local.get $rcur))))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lcur))) (then (call $raise-pair-expected (local.get $lcur)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $rcur))) (then (call $raise-pair-expected (local.get $rcur)) (unreachable))) (local.set $lpair (ref.cast (ref $Pair) (local.get $lcur))) (local.set $rpair (ref.cast (ref $Pair) (local.get $rcur))) (local.set $lelem (struct.get $Pair $a (local.get $lpair))) (local.set $relem (struct.get $Pair $a (local.get $rpair))) (block $same (if (i32.eqz (local.get $use-proc)) (then (if (ref.eq (call $equal? (local.get $lelem) (local.get $relem)) (global.get $false)) (then (return (array.new_fixed $Values 3 (call $reverse (local.get $acc)) (local.get $lcur) (local.get $rcur)))) (else (br $same)))) (else (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (block (result (ref $Args)) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (array.set $Args (local.get $args) (i32.const 0) (local.get $lelem)) (array.set $Args (local.get $args) (i32.const 1) (local.get $relem)) (local.get $args)) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (array.new_fixed $Values 3 (call $reverse (local.get $acc)) (local.get $lcur) (local.get $rcur)))) (else (br $same)))))) (local.set $acc (call $cons (local.get $lelem) (local.get $acc))) (local.set $lcur (struct.get $Pair $d (local.get $lpair))) (local.set $rcur (struct.get $Pair $d (local.get $rpair))) (br $loop)) (unreachable)) ;; Note: Only implements the two-argument form of Racket's add-between. ;; Keyword arguments such as #:before-first are not supported. (func $add-between (type $Prim2) (param $xs (ref eq)) ; list (param $v (ref eq)) ; separator value (result (ref eq)) (local $acc (ref eq)) (local $p (ref $Pair)) (local.set $acc (global.get $null)) (block $done (loop $loop (if (ref.eq (local.get $xs) (global.get $null)) (then (br $done))) (if (ref.test (ref $Pair) (local.get $xs)) (then (local.set $p (ref.cast (ref $Pair) (local.get $xs))) (local.set $acc (call $cons (struct.get $Pair $a (local.get $p)) (local.get $acc))) (local.set $xs (struct.get $Pair $d (local.get $p))) (if (ref.eq (local.get $xs) (global.get $null)) (then (br $loop)) (else (local.set $acc (call $cons (local.get $v) (local.get $acc))) (br $loop)))) (else (call $raise-pair-expected (local.get $xs)) (unreachable))))) (call $reverse (local.get $acc))) ; Note: Simplified version of sort with two arguments only. ; Internally it uses a merge sort. (func $sort (type $Prim2) (param $xs (ref eq)) (param $proc (ref eq)) (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $call (ref $Args)) ;; 1) Check that $proc is a procedure and fetch its invoker (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $call (array.new $Args (global.get $null) (i32.const 2))) (return_call $sort:merge-sort (local.get $xs) (local.get $f) (local.get $finv) (local.get $call))) (func $sort:merge (param $a (ref eq)) (param $b (ref eq)) (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (param $call (ref $Args)) (result (ref eq)) (local $apair (ref $Pair)) (local $bpair (ref $Pair)) (local $x (ref eq)) (local $y (ref eq)) (local $r (ref eq)) (if (ref.eq (local.get $a) (global.get $null)) (then (return (local.get $b)))) (if (ref.eq (local.get $b) (global.get $null)) (then (return (local.get $a)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $a))) (then (call $raise-pair-expected (local.get $a)) (unreachable))) (if (i32.eqz (ref.test (ref $Pair) (local.get $b))) (then (call $raise-pair-expected (local.get $b)) (unreachable))) (local.set $apair (ref.cast (ref $Pair) (local.get $a))) (local.set $bpair (ref.cast (ref $Pair) (local.get $b))) (local.set $x (struct.get $Pair $a (local.get $apair))) (local.set $y (struct.get $Pair $a (local.get $bpair))) (array.set $Args (local.get $call) (i32.const 0) (local.get $y)) (array.set $Args (local.get $call) (i32.const 1) (local.get $x)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (if (result (ref eq)) (ref.eq (local.get $r) (global.get $false)) (then (call $cons (local.get $x) (call $sort:merge (struct.get $Pair $d (local.get $apair)) (local.get $b) (local.get $f) (local.get $finv) (local.get $call)))) (else (call $cons (local.get $y) (call $sort:merge (local.get $a) (struct.get $Pair $d (local.get $bpair)) (local.get $f) (local.get $finv) (local.get $call)))))) (func $sort:split (param $lst (ref eq)) (result (ref $Values)) (local $slow (ref eq)) (local $fast (ref eq)) (local $acc (ref eq)) (local $pair (ref $Pair)) (local $fastcdr (ref eq)) (local $slowpair (ref $Pair)) (local.set $slow (local.get $lst)) (local.set $fast (local.get $lst)) (local.set $acc (global.get $null)) (block $done (loop $loop (if (ref.eq (local.get $fast) (global.get $null)) (then (br $done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $fast))) (then (call $raise-pair-expected (local.get $fast)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $fast))) (local.set $fastcdr (struct.get $Pair $d (local.get $pair))) (if (ref.eq (local.get $fastcdr) (global.get $null)) (then (br $done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $slow))) (then (call $raise-pair-expected (local.get $slow)) (unreachable))) (local.set $slowpair (ref.cast (ref $Pair) (local.get $slow))) (local.set $acc (call $cons (struct.get $Pair $a (local.get $slowpair)) (local.get $acc))) (local.set $slow (struct.get $Pair $d (local.get $slowpair))) (if (i32.eqz (ref.test (ref $Pair) (local.get $fastcdr))) (then (call $raise-pair-expected (local.get $fastcdr)) (unreachable))) (local.set $fast (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $fastcdr)))) (br $loop))) (array.new_fixed $Values 2 (call $reverse (local.get $acc)) (local.get $slow))) (func $sort:merge-sort (param $lst (ref eq)) (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (param $call (ref $Args)) (result (ref eq)) (local $pair (ref $Pair)) (local $rest (ref eq)) (local $vals (ref $Values)) (local $a (ref eq)) (local $b (ref eq)) (local $sa (ref eq)) (local $sb (ref eq)) (if (ref.eq (local.get $lst) (global.get $null)) (then (return (local.get $lst)))) (if (i32.eqz (ref.test (ref $Pair) (local.get $lst))) (then (call $raise-pair-expected (local.get $lst)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $lst))) (local.set $rest (struct.get $Pair $d (local.get $pair))) (if (ref.eq (local.get $rest) (global.get $null)) (then (return (local.get $lst)))) (local.set $vals (call $sort:split (local.get $lst))) (local.set $a (array.get $Values (local.get $vals) (i32.const 0))) (local.set $b (array.get $Values (local.get $vals) (i32.const 1))) (local.set $sa (call $sort:merge-sort (local.get $a) (local.get $f) (local.get $finv) (local.get $call))) (local.set $sb (call $sort:merge-sort (local.get $b) (local.get $f) (local.get $finv) (local.get $call))) (return_call $sort:merge (local.get $sa) (local.get $sb) (local.get $f) (local.get $finv) (local.get $call))) ;;; ;;; 4.11 Mutable Pairs and lists ;;; ;; https://docs.racket-lang.org/reference/mpairs.html ;; Mutable pairs are similar to pairs but allow their fields ;; to be updated. These operations parallel their immutable ;; counterparts above but use the `$MPair` type. ;; mpair? : any/c -> boolean? (func $raise-mpair-expected (param $x (ref eq)) (unreachable)) (func $mpair? (type $Prim1) ,@(make-predicate-body '$MPair)) ;; mcons : any/c any/c -> mpair? (func $mcons (type $Prim2) (param $a (ref eq)) ;; car value (param $d (ref eq)) ;; cdr value (result (ref eq)) (struct.new $MPair (i32.const 0) (local.get $a) (local.get $d))) ;; mcar : mpair? -> any/c (func $mcar (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $MPair) (local.get $v)) (then (struct.get $MPair $a (ref.cast (ref $MPair) (local.get $v)))) (else (call $raise-mpair-expected (local.get $v)) (unreachable)))) ;; mcdr : mpair? -> any/c (func $mcdr (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $MPair) (local.get $v)) (then (struct.get $MPair $d (ref.cast (ref $MPair) (local.get $v)))) (else (call $raise-mpair-expected (local.get $v)) (unreachable)))) ;; set-mcar! : mpair? any/c -> void? (func $set-mcar! (type $Prim2) (param $p (ref eq)) ;; mpair to mutate (param $v (ref eq)) ;; new car value (result (ref eq)) (if (result (ref eq)) (ref.test (ref $MPair) (local.get $p)) (then (struct.set $MPair $a (ref.cast (ref $MPair) (local.get $p)) (local.get $v)) (global.get $void)) (else (call $raise-mpair-expected (local.get $p)) (unreachable)))) ;; set-mcdr! : mpair? any/c -> void? (func $set-mcdr! (type $Prim2) (param $p (ref eq)) ;; mpair to mutate (param $v (ref eq)) ;; new cdr value (result (ref eq)) (if (result (ref eq)) (ref.test (ref $MPair) (local.get $p)) (then (struct.set $MPair $d (ref.cast (ref $MPair) (local.get $p)) (local.get $v)) (global.get $void)) (else (call $raise-mpair-expected (local.get $p)) (unreachable)))) ;;; ;;; 4.12 Vectors ;;; ;; https://docs.racket-lang.org/reference/vectors.html ;; (type $Vector (sub $Heap ;; (struct ;; (field $hash (mut i32)) ;; (field (ref $Array)))))) ; The global $dummy-vector is needed when a non-nullable local variable needs ; initialization to a default. (global $dummy-array (ref $Array) (array.new $Array (global.get $false) (i32.const 0))) (global $dummy-vector (ref $Vector) (struct.new $Vector (i32.const 0) ;; hash (i32.const 0) ;; mutable (global.get $dummy-array))) ;; Vector related exceptions (func $raise-check-vector (param $x (ref eq)) (unreachable)) (func $raise-check-fixnum (param $x (ref eq)) (unreachable)) (func $raise-immutable-vector (param $x (ref eq)) (unreachable)) (func $raise-bad-vector-ref-index (param $v (ref $Vector)) (param $i i32) (param $len i32) (unreachable)) (func $raise-bad-vector-copy-range (param (ref $Vector)) (param i32) (param (ref $Vector)) (param i32) (param i32) (unreachable)) (func $raise-bad-vector-take-index (param (ref $Vector)) (param i32) (param i32) (unreachable)) (func $raise-make-vector:bad-length (unreachable)) (func $make-vector (type $Prim12) (param $k-fx (ref eq)) ;; fixnum (param $val (ref eq)) ;; optional, defaults to 0 (result (ref eq)) (local $k i32) (local $v (ref eq)) ;; possibly rewritten value ;; --- Type check for fixnum --- (if (i32.eqz (ref.test (ref i31) (local.get $k-fx))) (then (call $raise-make-vector:bad-length))) ;; --- Decode fixnum to i32 --- (local.set $k (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $k-fx))) (i32.const 1))) ;; --- Substitute default value if missing --- (local.set $v (if (result (ref eq)) (ref.eq (local.get $val) (global.get $missing)) (then (global.get $zero)) (else (local.get $val)))) ;; --- Delegate to checked version --- (call $make-vector/checked (local.get $k) (local.get $v))) (func $make-vector/checked (param $k i32) ;; number of elements (param $val (ref eq)) ;; initial value (result (ref $Vector)) (local $arr (ref $Array)) ;; Create the array (local.set $arr (array.new $Array (local.get $val) (local.get $k))) ;; Construct and return the vector (struct.new $Vector (i32.const 0) ;; hash = 0 (i32.const 0) ;; mutable (local.get $arr))) (func $vector (type $Prim>=0) (param $args (ref eq)) (result (ref eq)) (local $as (ref $Args)) (local $len i32) (local $arr (ref $Array)) (local $use-args? i32) (local $list (ref eq)) (local $node (ref eq)) (local $pair (ref $Pair)) (local $i i32) (local $x (ref eq)) ;; Initialize non-defaultable locals (local.set $as (array.new $Args (global.get $null) (i32.const 0))) (local.set $x (global.get $false)) (local.set $list (global.get $null)) (local.set $node (global.get $null)) ;; Determine whether we received an $Args array or a list of rest arguments. (local.set $use-args? (ref.test (ref $Args) (local.get $args))) (if (local.get $use-args?) (then (local.set $as (ref.cast (ref $Args) (local.get $args))) (local.set $len (array.len (local.get $as)))) (else (local.set $list (local.get $args)) (local.set $node (local.get $list)) (local.set $len (call $length/i32 (local.get $list))))) ;; Allocate backing array (local.set $arr (call $make-array (local.get $len) (global.get $false))) (local.set $i (i32.const 0)) ;; Copy arguments from either $Args or list form (if (local.get $use-args?) (then (block $done-args (loop $loop-args (br_if $done-args (i32.ge_u (local.get $i) (local.get $len))) (local.set $x (array.get $Args (local.get $as) (local.get $i))) (array.set $Array (local.get $arr) (local.get $i) (local.get $x)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop-args)))) (else (local.set $node (local.get $list)) (block $done-list (loop $loop-list (br_if $done-list (i32.ge_u (local.get $i) (local.get $len))) (if (ref.test (ref $Pair) (local.get $node)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $node))) (local.set $x (struct.get $Pair $a (local.get $pair))) (local.set $node (struct.get $Pair $d (local.get $pair)))) (else (call $raise-pair-expected (local.get $node)) (unreachable))) (array.set $Array (local.get $arr) (local.get $i) (local.get $x)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop-list))))) ;; Wrap in a mutable vector structure (struct.new $Vector (i32.const 0) ;; hash (i32.const 0) ;; mutable (local.get $arr))) (func $vector-immutable (type $Prim>=0) (param $args (ref eq)) (result (ref eq)) (local $as (ref $Args)) (local $len i32) (local $arr (ref $Array)) (local $use-args? i32) (local $list (ref eq)) (local $node (ref eq)) (local $pair (ref $Pair)) (local $i i32) (local $x (ref eq)) ;; Initialize non-defaultable locals (local.set $as (array.new $Args (global.get $null) (i32.const 0))) (local.set $x (global.get $false)) (local.set $list (global.get $null)) (local.set $node (global.get $null)) ;; Determine whether we received an $Args array or a list of rest arguments. (local.set $use-args? (ref.test (ref $Args) (local.get $args))) (if (local.get $use-args?) (then (local.set $as (ref.cast (ref $Args) (local.get $args))) (local.set $len (array.len (local.get $as)))) (else (local.set $list (local.get $args)) (local.set $node (local.get $list)) (local.set $len (call $length/i32 (local.get $list))))) ;; Allocate backing array (local.set $arr (call $make-array (local.get $len) (global.get $false))) (local.set $i (i32.const 0)) ;; Copy arguments from either $Args or list form (if (local.get $use-args?) (then (block $done-args (loop $loop-args (br_if $done-args (i32.ge_u (local.get $i) (local.get $len))) (local.set $x (array.get $Args (local.get $as) (local.get $i))) (array.set $Array (local.get $arr) (local.get $i) (local.get $x)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop-args)))) (else (local.set $node (local.get $list)) (block $done-list (loop $loop-list (br_if $done-list (i32.ge_u (local.get $i) (local.get $len))) (if (ref.test (ref $Pair) (local.get $node)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $node))) (local.set $x (struct.get $Pair $a (local.get $pair))) (local.set $node (struct.get $Pair $d (local.get $pair)))) (else (call $raise-pair-expected (local.get $node)) (unreachable))) (array.set $Array (local.get $arr) (local.get $i) (local.get $x)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop-list))))) ;; Wrap in a mutable vector structure (struct.new $Vector (i32.const 0) ;; hash (i32.const 1) ;; immutable (local.get $arr))) (func $build-vector (type $Prim2) (param $n-raw (ref eq)) ;; exact-nonnegative integer (param $proc (ref eq)) ;; (exact-nonnegative-integer? . -> . any/c) (result (ref eq)) (local $n i32) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) ;; --- Check and unwrap n --- (if (i32.eqz (ref.test (ref i31) (local.get $n-raw))) (then (call $raise-expected-fixnum (local.get $n-raw)))) (local.set $n (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $n-raw))) (i32.const 1))) (if (i32.lt_s (local.get $n) (i32.const 0)) (then (call $raise-argument-error (local.get $n-raw)))) ;; --- Check procedure --- (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Delegate (call $build-vector/checked (local.get $n) (local.get $f) (local.get $finv))) (func $build-vector/checked (param $n i32) (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (result (ref $Vector)) (local $args (ref $Args)) (local $arr (ref $Array)) (local $i i32) (local $res (ref eq)) ;; Prepare array and argument holder (local.set $arr (call $make-array (local.get $n) (global.get $false))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) ;; Iterate from 0 to n-1 (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $n))) ;; Set argument to current index as fixnum (array.set $Args (local.get $args) (i32.const 0) (ref.i31 (i32.shl (local.get $i) (i32.const 1)))) ;; Call procedure and store result (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (array.set $Array (local.get $arr) (local.get $i) (local.get $res)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Construct vector (struct.new $Vector (i32.const 0) ;; hash (i32.const 0) ;; mutable (local.get $arr))) (func $vector-set/copy (type $Prim3) ;; Returns a fresh mutable vector with VAL at index POS. (param $v (ref eq)) ;; vector (param $pos (ref eq)) ;; fixnum (param $val (ref eq)) ;; any (result (ref eq)) (local $vec (ref $Vector)) (local $idx i32) (local $len i32) ;; --- Validate vector --- (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) ;; --- Validate index --- (if (ref.test (ref i31) (local.get $pos)) (then (local.set $idx (i31.get_u (ref.cast (ref i31) (local.get $pos)))) (if (i32.and (local.get $idx) (i32.const 1)) (then (call $raise-check-fixnum (local.get $pos)))) (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $pos)))) ;; --- Bounds check --- (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.lt_u (local.get $idx) (local.get $len)) (then (return (struct.new $Vector (i32.const 0) (i32.const 0) (call $array-set/copy (struct.get $Vector $arr (local.get $vec)) (local.get $idx) (local.get $val))))) (else (call $raise-bad-vector-ref-index (local.get $vec) (local.get $idx) (local.get $len)) (unreachable))) (unreachable)) (func $vector-length (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (if (result (ref eq)) (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (ref.i31 (i32.shl (array.len (struct.get $Vector $arr (local.get $vec))) (i32.const 1)))) (else (call $raise-check-vector (local.get $v)) (unreachable)))) (func $vector-length/i32 (param $v (ref eq)) (result i32) (local $vec (ref $Vector)) (if (result i32) (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (array.len (struct.get $Vector $arr (local.get $vec)))) (else (call $raise-check-vector (local.get $v)) (unreachable)))) (func $vector-length/checked/i32 (param $v (ref $Vector)) (result i32) (array.len (struct.get $Vector $arr (local.get $v)))) (func $vector?/i32 (param $a (ref eq)) (result i32) (ref.test (ref $Vector) (local.get $a))) (func $vector? (type $Prim1) (param $a (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Vector) (local.get $a)) (then (global.get $true)) (else (global.get $false)))) (func $vector-ref/checked (param $a (ref $Vector)) (param $i i32) (result (ref eq)) (local $len i32) ;; get length (local.set $len (array.len (struct.get $Vector $arr (local.get $a)))) ;; bounds check (if (result (ref eq)) (i32.lt_u (local.get $i) (local.get $len)) (then (array.get $Array (struct.get $Vector $arr (local.get $a)) (local.get $i))) (else (call $raise-bad-vector-ref-index (local.get $a) (local.get $i) (local.get $len)) (unreachable)))) (func $vector-ref (type $Prim2) (param $v (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local $idx i32) (local $len i32) ;; Initialize vec to dummy to satisfy non-nullable default requirement (local.set $vec (global.get $dummy-vector)) ;; Check that $v is a vector (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) ;; Check that $i is an i31 and decode fixnum (if (ref.test (ref i31) (local.get $i)) (then (local.set $idx (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $idx) (i32.const 1))) (then (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) ;; Get array length (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) ;; Bounds check (if (result (ref eq)) (i32.lt_u (local.get $idx) (local.get $len)) (then (return (array.get $Array (struct.get $Vector $arr (local.get $vec)) (local.get $idx)))) (else (call $raise-bad-vector-ref-index (local.get $vec) (local.get $idx) (local.get $len)) (unreachable)))) (func $vector-set!/checked (param $vec (ref $Vector)) (param $i i32) (param $val (ref eq)) (local $len i32) (if (i32.ne (struct.get $Vector $immutable (local.get $vec)) (i32.const 0)) (then (call $raise-immutable-vector (local.get $vec)) (unreachable))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.lt_u (local.get $i) (local.get $len)) (then (array.set $Array (struct.get $Vector $arr (local.get $vec)) (local.get $i) (local.get $val))) (else (call $raise-bad-vector-ref-index (local.get $vec) (local.get $i) (local.get $len)) (unreachable)))) (func $vector-set! (type $Prim3) (param $v (ref eq)) (param $i (ref eq)) (param $val (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local $idx i32) (local $len i32) ;; Initialize $vec with dummy to satisfy non-nullable restriction (local.set $vec (global.get $dummy-vector)) ;; 1. Check $v is a vector (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) ;; 2. Check $i is a fixnum (if (ref.test (ref i31) (local.get $i)) (then (local.set $idx (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $idx) (i32.const 1))) (then (local.set $idx (i32.shr_u (local.get $idx) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) ;; 3. Check mutability and get length (if (i32.ne (struct.get $Vector $immutable (local.get $vec)) (i32.const 0)) (then (call $raise-immutable-vector (local.get $v)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) ;; 4. Bounds check and set (if (result (ref eq)) (i32.lt_u (local.get $idx) (local.get $len)) (then (array.set $Array (struct.get $Vector $arr (local.get $vec)) (local.get $idx) (local.get $val)) (global.get $void)) (else (call $raise-bad-vector-ref-index (local.get $vec) (local.get $idx) (local.get $len)) (unreachable)))) (func $vector-fill! (type $Prim2) (param $v (ref eq)) (param $x (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) (if (i32.ne (struct.get $Vector $immutable (local.get $vec)) (i32.const 0)) (then (call $raise-immutable-vector (local.get $v)))) (call $array-fill! (struct.get $Vector $arr (local.get $vec)) (local.get $x)) (global.get $void)) (func $vector-copy! (param $dest (ref eq)) (param $dest-start (ref eq)) ;; fixnum (param $src (ref eq)) (param $src-start (ref eq)) ;; fixnum or $missing, default: 0) (param $src-end (ref eq)) ;; fixnum or $missing, default: (vector-length src) (result (ref eq)) (local $d (ref $Vector)) (local $s (ref $Vector)) (local $ds i32) (local $ss i32) (local $se i32) (local $src-len i32) ;; --- Validate $dest --- (if (i32.eqz (ref.test (ref $Vector) (local.get $dest))) (then (call $raise-check-vector (local.get $dest)))) ;; --- Validate $src --- (if (i32.eqz (ref.test (ref $Vector) (local.get $src))) (then (call $raise-check-vector (local.get $src)))) ;; --- Validate $dest-start --- (if (i32.eqz (ref.test (ref i31) (local.get $dest-start))) (then (call $raise-check-fixnum (local.get $dest-start)))) (if (i32.and (i31.get_u (ref.cast (ref i31) (local.get $dest-start))) (i32.const 1)) (then (call $raise-check-fixnum (local.get $dest-start)))) ;; --- Validate $src-start --- (if (i32.eqz (ref.eq (local.get $src-start) (global.get $missing))) (then (if (i32.eqz (ref.test (ref i31) (local.get $src-start))) (then (call $raise-check-fixnum (local.get $src-start)))) (if (i32.and (i31.get_u (ref.cast (ref i31) (local.get $src-start))) (i32.const 1)) (then (call $raise-check-fixnum (local.get $src-start)))))) ;; --- Validate $src-end --- (if (i32.eqz (ref.eq (local.get $src-end) (global.get $missing))) (then (if (i32.eqz (ref.test (ref i31) (local.get $src-end))) (then (call $raise-check-fixnum (local.get $src-end)))) (if (i32.and (i31.get_u (ref.cast (ref i31) (local.get $src-end))) (i32.const 1)) (then (call $raise-check-fixnum (local.get $src-end)))))) ;; --- Cast and decode after validation --- (local.set $d (ref.cast (ref $Vector) (local.get $dest))) (local.set $s (ref.cast (ref $Vector) (local.get $src))) (if (i32.ne (struct.get $Vector $immutable (local.get $d)) (i32.const 0)) (then (call $raise-immutable-vector (local.get $dest)))) (local.set $ds (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $dest-start))) (i32.const 1))) (local.set $src-len (array.len (struct.get $Vector $arr (local.get $s)))) (if (ref.eq (local.get $src-start) (global.get $missing)) (then (local.set $ss (i32.const 0))) (else (local.set $ss (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $src-start))) (i32.const 1))))) (if (ref.eq (local.get $src-end) (global.get $missing)) (then (local.set $se (local.get $src-len))) (else (local.set $se (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $src-end))) (i32.const 1))))) ;; --- Delegate to checked copy --- (call $vector-copy!/checked (local.get $d) (local.get $ds) (local.get $s) (local.get $ss) (local.get $se))) (func $vector-copy!/checked (param $dest (ref $Vector)) (param $ds i32) (param $src (ref $Vector)) (param $ss i32) (param $se i32) (result (ref eq)) (local $src-len i32) (local $dest-len i32) (if (i32.ne (struct.get $Vector $immutable (local.get $dest)) (i32.const 0)) (then (call $raise-immutable-vector (local.get $dest)) (unreachable))) (local.set $src-len (array.len (struct.get $Vector $arr (local.get $src)))) (local.set $dest-len (array.len (struct.get $Vector $arr (local.get $dest)))) (if (i32.or (i32.or (i32.gt_u (local.get $ss) (local.get $src-len)) (i32.gt_u (local.get $se) (local.get $src-len))) (i32.gt_u (i32.add (local.get $ds) (i32.sub (local.get $se) (local.get $ss))) (local.get $dest-len))) (then (call $raise-bad-vector-copy-range (local.get $dest) (local.get $ds) (local.get $src) (local.get $ss) (local.get $se)) (unreachable))) (call $array-copy! (struct.get $Vector $arr (local.get $dest)) (local.get $ds) (struct.get $Vector $arr (local.get $src)) (local.get $ss) (local.get $se)) (global.get $void)) (func $vector-copy (type $Prim13) (param $v (ref eq)) (param $start (ref eq)) ;; fixnum or $missing, default: 0 (param $end (ref eq)) ;; fixnum or $missing, default: (vector-length v) (result (ref eq)) (local $vec (ref $Vector)) (local $ss i32) (local $se i32) (local $len i32) ;; --- Validate vector --- (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) ;; --- Decode $start --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $ss (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $ss (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.and (local.get $ss) (i32.const 1)) (then (call $raise-check-fixnum (local.get $start)))) (local.set $ss (i32.shr_u (local.get $ss) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)))))) ;; --- Decode $end --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $se (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $se (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.and (local.get $se) (i32.const 1)) (then (call $raise-check-fixnum (local.get $end)))) (local.set $se (i32.shr_u (local.get $se) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)))))) ;; --- Bounds check: start <= end <= len --- (if (i32.or (i32.gt_u (local.get $ss) (local.get $se)) (i32.gt_u (local.get $se) (local.get $len))) (then (call $raise-bad-vector-copy-range (local.get $vec) (i32.const 0) (local.get $vec) (local.get $ss) (local.get $se)) (unreachable))) ;; --- Allocate and return new mutable vector --- (struct.new $Vector (i32.const 0) (i32.const 0) (call $array-copy (struct.get $Vector $arr (local.get $vec)) (local.get $ss) (local.get $se)))) (func $vector->immutable-vector (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) ;; --- Validate vector --- (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) ;; --- If already immutable, return as-is --- (if (result (ref eq)) (i32.eq (struct.get $Vector $immutable (local.get $vec)) (i32.const 1)) (then (local.get $vec)) (else ;; Otherwise, allocate immutable copy (struct.new $Vector (struct.get $Vector $hash (local.get $vec)) (i32.const 1) (call $array-copy (struct.get $Vector $arr (local.get $vec)) (i32.const 0) (array.len (struct.get $Vector $arr (local.get $vec)))))))) (func $vector->values (type $Prim13) (param $v (ref eq)) (param $start (ref eq)) ;; fixnum or $missing, default: 0 (param $end (ref eq)) ;; fixnum or $missing, default: (vector-length v) (result (ref eq)) (local $vec (ref $Vector)) (local $arr (ref $Array)) (local $ss i32) (local $se i32) (local $len i32) (local $n i32) (local $vals (ref $Values)) ;; --- Validate vector --- (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $len (array.len (local.get $arr))) ;; --- Decode $start --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $ss (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $ss (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.and (local.get $ss) (i32.const 1)) (then (call $raise-check-fixnum (local.get $start)))) (local.set $ss (i32.shr_u (local.get $ss) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)))))) ;; --- Decode $end --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $se (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $se (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.and (local.get $se) (i32.const 1)) (then (call $raise-check-fixnum (local.get $end)))) (local.set $se (i32.shr_u (local.get $se) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)))))) ;; --- Bounds check: start <= end <= len --- (if (i32.or (i32.gt_u (local.get $ss) (local.get $se)) (i32.gt_u (local.get $se) (local.get $len))) (then (call $raise-bad-vector-copy-range (local.get $vec) (i32.const 0) (local.get $vec) (local.get $ss) (local.get $se)) (unreachable))) (local.set $n (i32.sub (local.get $se) (local.get $ss))) (if (i32.eq (local.get $n) (i32.const 1)) (then (return (array.get $Array (local.get $arr) (local.get $ss))))) (local.set $vals (array.new $Values (global.get $null) (local.get $n))) (array.copy $Values $Array (local.get $vals) (i32.const 0) (local.get $arr) (local.get $ss) (local.get $n)) (local.get $vals)) (func $raise-vector-extend:bad-length (param (ref $Vector)) (param i32) (param i32) (unreachable)) ; TODO: ; Error message in original vector-extend ; vector-extend: new length is shorter than existing length ; new length: 3 ; existing length: 7 (func $vector-extend (type $Prim23) (param $v (ref eq)) ;; vector (param $new-size (ref eq)) ;; fixnum (param $val (ref eq)) ;; optional, defaults to 0 (result (ref eq)) (local $vec (ref $Vector)) (local $ns i32) (local $len i32) (local $fill (ref eq)) (local.set $vec (global.get $dummy-vector)) (local.set $fill (global.get $zero)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) (if (ref.test (ref i31) (local.get $new-size)) (then (local.set $ns (i31.get_s (ref.cast (ref i31) (local.get $new-size)))) (if (i32.and (local.get $ns) (i32.const 1)) (then (call $raise-check-fixnum (local.get $new-size)))) (local.set $ns (i32.shr_s (local.get $ns) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $new-size)))) (local.set $fill (if (result (ref eq)) (ref.eq (local.get $val) (global.get $missing)) (then (global.get $zero)) (else (local.get $val)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.lt_s (local.get $ns) (local.get $len)) (then (call $raise-vector-extend:bad-length (local.get $vec) (local.get $ns) (local.get $len)) (unreachable))) (struct.new $Vector (i32.const 0) (i32.const 0) (call $array-extend (struct.get $Vector $arr (local.get $vec)) (local.get $ns) (local.get $fill)))) (func $vector-empty? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local $len i32) (if (result (ref eq)) (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (result (ref eq)) (i32.eqz (local.get $len)) (then (global.get $true)) (else (global.get $false)))) (else (call $raise-check-vector (local.get $v)) (unreachable)))) (func $vector-append (type $Prim>=0) (param $xs (ref eq)) ;; list of vectors (result (ref eq)) (local $n i32) (local $node (ref $Pair)) (local $vec (ref $Vector)) (local $v (ref eq)) (local $orig (ref eq)) (local $total i32) (local $len i32) (local $arr (ref $Array)) (local $pos i32) ;; initialize non-defaultable refs (local.set $vec (global.get $dummy-vector)) ;; Preserve original list (local.set $orig (local.get $xs)) ;; Determine number of arguments (local.set $n (call $length/i32 (local.get $xs))) ;; Zero arguments -> empty vector (if (i32.eqz (local.get $n)) (then (return (struct.new $Vector (i32.const 0) (i32.const 0) (call $make-array (i32.const 0) (global.get $false)))))) ;; Extract and check first argument (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) ;; Single argument -> copy to ensure fresh vector (if (i32.eq (local.get $n) (i32.const 1)) (then (return (call $vector-copy (local.get $v) (global.get $missing) (global.get $missing))))) ;; Compute total length (local.set $total (array.len (struct.get $Vector $arr (local.get $vec)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (block $done1 (loop $loop1 (br_if $done1 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (local.set $total (i32.add (local.get $total) (local.get $len)))) (else (call $raise-check-vector (local.get $v)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop1))) ;; Allocate result array (local.set $arr (call $make-array (local.get $total) (global.get $false))) ;; Copy vectors into result array (local.set $xs (local.get $orig)) (local.set $pos (i32.const 0)) (block $done2 (loop $loop2 (br_if $done2 (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $vec (ref.cast (ref $Vector) (struct.get $Pair $a (local.get $node)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (call $array-copy! (local.get $arr) (local.get $pos) (struct.get $Vector $arr (local.get $vec)) (i32.const 0) (local.get $len)) (local.set $pos (i32.add (local.get $pos) (local.get $len))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop2))) (struct.new $Vector (i32.const 0) (i32.const 0) (local.get $arr))) (func $vector-append/2 (param $a (ref eq)) (param $b (ref eq)) (result (ref eq)) (local $va (ref $Vector)) (local $vb (ref $Vector)) (local.set $va (global.get $dummy-vector)) (local.set $vb (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $a)) (then (local.set $va (ref.cast (ref $Vector) (local.get $a)))) (else (call $raise-check-vector (local.get $a)))) (if (ref.test (ref $Vector) (local.get $b)) (then (local.set $vb (ref.cast (ref $Vector) (local.get $b)))) (else (call $raise-check-vector (local.get $b)))) (struct.new $Vector (i32.const 0) (i32.const 0) (call $array-append (struct.get $Vector $arr (local.get $va)) (struct.get $Vector $arr (local.get $vb))))) (func $vector-take (type $Prim2) (param $v (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local $ix i32) (local $len i32) (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) (if (ref.test (ref i31) (local.get $i)) (then (local.set $ix (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $ix) (i32.const 1))) (then (local.set $ix (i32.shr_u (local.get $ix) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.gt_u (local.get $ix) (local.get $len)) (then (call $raise-bad-vector-take-index (local.get $vec) (local.get $ix) (local.get $len)) (unreachable))) (struct.new $Vector (i32.const 0) (i32.const 0) (call $array-take (struct.get $Vector $arr (local.get $vec)) (local.get $ix)))) (func $vector-drop (type $Prim2) (param $v (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local $ix i32) (local $len i32) (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) (if (ref.test (ref i31) (local.get $i)) (then (local.set $ix (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $ix) (i32.const 1))) (then (local.set $ix (i32.shr_u (local.get $ix) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.gt_u (local.get $ix) (local.get $len)) (then (call $raise-bad-vector-take-index (local.get $vec) (local.get $ix) (local.get $len)) (unreachable))) (struct.new $Vector (i32.const 0) (i32.const 0) (call $array-drop (struct.get $Vector $arr (local.get $vec)) (local.get $ix)))) (func $vector-drop-right (type $Prim2) (param $v (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local $ix i32) (local $len i32) (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) (if (ref.test (ref i31) (local.get $i)) (then (local.set $ix (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $ix) (i32.const 1))) (then (local.set $ix (i32.shr_u (local.get $ix) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.gt_u (local.get $ix) (local.get $len)) (then (call $raise-bad-vector-take-index (local.get $vec) (local.get $ix) (local.get $len)) (unreachable))) (struct.new $Vector (i32.const 0) (i32.const 0) (call $array-drop-right (struct.get $Vector $arr (local.get $vec)) (local.get $ix)))) (func $vector-take-right (type $Prim2) (param $v (ref eq)) (param $i (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local $ix i32) (local $len i32) (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) (if (ref.test (ref i31) (local.get $i)) (then (local.set $ix (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $ix) (i32.const 1))) (then (local.set $ix (i32.shr_u (local.get $ix) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.gt_u (local.get $ix) (local.get $len)) (then (call $raise-bad-vector-take-index (local.get $vec) (local.get $ix) (local.get $len)) (unreachable))) (struct.new $Vector (i32.const 0) (i32.const 0) (call $array-take-right (struct.get $Vector $arr (local.get $vec)) (local.get $ix)))) (func $vector-split-at (type $Prim2) (param $v (ref eq)) (param $i (ref eq)) (result (ref eq)) ; returns two values (local $vec (ref $Vector)) (local $ix i32) (local $len i32) (local $res (ref $Array)) ; 1. Check $v is a vector (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) ; 2. Check $i is a fixnum (if (ref.test (ref i31) (local.get $i)) (then (local.set $ix (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $ix) (i32.const 1))) (then (local.set $ix (i32.shr_u (local.get $ix) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) ; 3. Range check (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.gt_u (local.get $ix) (local.get $len)) (then (call $raise-bad-vector-take-index (local.get $vec) (local.get $ix) (local.get $len)) (unreachable))) ; 4. Split the vector (local.set $res (call $array-split-at (struct.get $Vector $arr (local.get $vec)) (local.get $ix))) (array.new_fixed $Values 2 (struct.new $Vector (i32.const 0) ; hash (i32.const 0) ; mutable (ref.cast (ref $Array) (array.get $Array (local.get $res) (i32.const 0)))) (struct.new $Vector (i32.const 0) ; hash (i32.const 0) ; mutable (ref.cast (ref $Array) (array.get $Array (local.get $res) (i32.const 1)))))) (func $vector-split-at-right (type $Prim2) (param $v (ref eq)) ; vec (param $i (ref eq)) ; count (result (ref eq)) ; returns two values (local $vec (ref $Vector)) (local $ix i32) (local $len i32) (local $take (ref $Array)) (local $drop (ref $Array)) ; 1. Check $v is a vector (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)))) ; 2. Check $i is a fixnum (if (ref.test (ref i31) (local.get $i)) (then (local.set $ix (i31.get_u (ref.cast (ref i31) (local.get $i)))) (if (i32.eqz (i32.and (local.get $ix) (i32.const 1))) (then (local.set $ix (i32.shr_u (local.get $ix) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $i))))) (else (call $raise-check-fixnum (local.get $i)))) ; 3. Range check (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (i32.gt_u (local.get $ix) (local.get $len)) (then (call $raise-bad-vector-take-index (local.get $vec) (local.get $ix) (local.get $len)) (unreachable))) ; 4. Split the vector from the right (local.set $take (call $array-take-right (struct.get $Vector $arr (local.get $vec)) (local.get $ix))) (local.set $drop (call $array-drop-right (struct.get $Vector $arr (local.get $vec)) (local.get $ix))) (array.new_fixed $Values 2 (struct.new $Vector (i32.const 0) ; hash (i32.const 0) ; mutable (local.get $take)) (struct.new $Vector (i32.const 0) ; hash (i32.const 0) ; mutable (local.get $drop)))) (func $raise-expected-vector (unreachable)) (func $list->vector (type $Prim1) (param $xs (ref eq)) (result (ref eq)) (local $arr (ref $Array)) (local.set $arr (call $list->array (local.get $xs))) (struct.new $Vector (i32.const 0) ; hash (i32.const 0) ; immutable (local.get $arr))) (func $vector->list (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (i32.eqz (ref.test (ref $Vector) (local.get $v))) (then (call $raise-expected-vector (local.get $v)) (unreachable))) (call $vector->list/checked (ref.cast (ref $Vector) (local.get $v)))) (func $vector->list/checked (param $v (ref $Vector)) (result (ref eq)) (local $arr (ref $Array)) (local $i i32) ;; current index (starts from len - 1 and decrements) (local $x (ref eq)) (local $xs (ref eq)) ;; Extract backing array and initialize (local.set $arr (struct.get $Vector $arr (local.get $v))) (local.set $i (i32.sub (array.len (local.get $arr)) (i32.const 1))) (local.set $xs (global.get $null)) ;; Loop backwards (block $done (loop $loop (br_if $done (i32.lt_s (local.get $i) (i32.const 0))) (local.set $x (array.get $Array (local.get $arr) (local.get $i))) (local.set $xs (struct.new $Pair (i32.const 0) (local.get $x) (local.get $xs))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $loop))) (return (local.get $xs))) (func $vector-map (type $Prim>=2) (param $proc (ref eq)) (param $v0 (ref eq)) (param $rest (ref eq)) (result (ref eq)) ;; Copy first vector and delegate to vector-map! (call $vector-map! (local.get $proc) (call $vector-copy (local.get $v0) (global.get $missing) (global.get $missing)) (local.get $rest))) (func $vector-map! (type $Prim>=2) (param $proc (ref eq)) (param $v0 (ref eq)) (param $rest (ref eq)) (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $vec0 (ref $Vector)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $vec (ref $Vector)) (local $vectors (ref $Args)) (local $call (ref $Args)) (local $nvecs i32) (local $len i32) (local $i i32) (local $j i32) (local $elem (ref eq)) (local $r (ref eq)) (local $tmp (ref eq)) ;; 1) Validate procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Validate first vector (local.set $vec0 (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v0)) (then (local.set $vec0 (ref.cast (ref $Vector) (local.get $v0)))) (else (call $raise-check-vector (local.get $v0)) (unreachable))) (if (i32.ne (struct.get $Vector $immutable (local.get $vec0)) (i32.const 0)) (then (call $raise-immutable-vector (local.get $v0)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec0)))) ;; 3) Walk rest vectors (local.set $nvecs (i32.const 1)) (local.set $cur (local.get $rest)) (block $count-done (loop $count (if (ref.eq (local.get $cur) (global.get $null)) (then (br $count-done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) ;; Load car as (ref eq), test, then cast before putting into $vec (local.set $tmp (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $Vector) (local.get $tmp))) (then (call $raise-check-vector (local.get $tmp)) (unreachable))) (local.set $vec (ref.cast (ref $Vector) (local.get $tmp))) (if (i32.ne (array.len (struct.get $Vector $arr (local.get $vec))) (local.get $len)) (then (call $raise-argument-error (local.get $vec)) (unreachable))) (local.set $nvecs (i32.add (local.get $nvecs) (i32.const 1))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $count))) ;; 4) Allocate arrays and seed (local.set $vectors (array.new $Args (global.get $null) (local.get $nvecs))) (local.set $call (array.new $Args (global.get $null) (local.get $nvecs))) (array.set $Args (local.get $vectors) (i32.const 0) (local.get $vec0)) (local.set $cur (local.get $rest)) (local.set $i (i32.const 1)) (block $seed-done (loop $seed (if (i32.ge_u (local.get $i) (local.get $nvecs)) (then (br $seed-done))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (array.set $Args (local.get $vectors) (local.get $i) (ref.cast (ref $Vector) (struct.get $Pair $a (local.get $pair)))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $seed))) ;; 5) Iterate and mutate first vector (local.set $j (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $j) (local.get $len))) (local.set $i (i32.const 0)) (block $args-done (loop $args (if (i32.ge_u (local.get $i) (local.get $nvecs)) (then (br $args-done))) (local.set $vec (ref.cast (ref $Vector) (array.get $Args (local.get $vectors) (local.get $i)))) (local.set $elem (array.get $Array (struct.get $Vector $arr (local.get $vec)) (local.get $j))) (array.set $Args (local.get $call) (local.get $i) (local.get $elem)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $args))) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (array.set $Array (struct.get $Vector $arr (local.get $vec0)) (local.get $j) (local.get $r)) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $loop))) (local.get $vec0)) (func $vector-count (type $Prim>=2) (param $proc (ref eq)) ;; procedure (param $v0 (ref eq)) ;; first vector (param $rest (ref eq)) ;; list of vectors (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $vec0 (ref $Vector)) (local $cur (ref eq)) (local $pair (ref $Pair)) (local $vec (ref $Vector)) (local $vectors (ref $Args)) (local $call (ref $Args)) (local $nvecs i32) (local $len i32) (local $i i32) (local $j i32) (local $elem (ref eq)) (local $r (ref eq)) (local $tmp (ref eq)) (local $cnt i32) ;; 1) Validate procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Validate first vector (local.set $vec0 (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v0)) (then (local.set $vec0 (ref.cast (ref $Vector) (local.get $v0)))) (else (call $raise-check-vector (local.get $v0)) (unreachable))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vec0)))) ;; 3) Walk rest vectors (local.set $nvecs (i32.const 1)) (local.set $cur (local.get $rest)) (block $count-done (loop $count (if (ref.eq (local.get $cur) (global.get $null)) (then (br $count-done))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cur))) (then (call $raise-pair-expected (local.get $cur)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) ;; Load car as (ref eq), test, then cast before putting into $vec (local.set $tmp (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $Vector) (local.get $tmp))) (then (call $raise-check-vector (local.get $tmp)) (unreachable))) (local.set $vec (ref.cast (ref $Vector) (local.get $tmp))) (if (i32.ne (array.len (struct.get $Vector $arr (local.get $vec))) (local.get $len)) (then (call $raise-argument-error (local.get $vec)) (unreachable))) (local.set $nvecs (i32.add (local.get $nvecs) (i32.const 1))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (br $count))) ;; 4) Allocate arrays and seed (local.set $vectors (array.new $Args (global.get $null) (local.get $nvecs))) (local.set $call (array.new $Args (global.get $null) (local.get $nvecs))) (array.set $Args (local.get $vectors) (i32.const 0) (local.get $vec0)) (local.set $cur (local.get $rest)) (local.set $i (i32.const 1)) (block $seed-done (loop $seed (if (i32.ge_u (local.get $i) (local.get $nvecs)) (then (br $seed-done))) (local.set $pair (ref.cast (ref $Pair) (local.get $cur))) (array.set $Args (local.get $vectors) (local.get $i) (ref.cast (ref $Vector) (struct.get $Pair $a (local.get $pair)))) (local.set $cur (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $seed))) ;; 5) Iterate and count (local.set $cnt (i32.const 0)) (local.set $j (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $j) (local.get $len))) (local.set $i (i32.const 0)) (block $args-done (loop $args (if (i32.ge_u (local.get $i) (local.get $nvecs)) (then (br $args-done))) (local.set $vec (ref.cast (ref $Vector) (array.get $Args (local.get $vectors) (local.get $i)))) (local.set $elem (array.get $Array (struct.get $Vector $arr (local.get $vec)) (local.get $j))) (array.set $Args (local.get $call) (local.get $i) (local.get $elem)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $args))) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (if (i32.eqz (ref.eq (local.get $r) (global.get $false))) (then (local.set $cnt (i32.add (local.get $cnt) (i32.const 1)))) (else)) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $loop))) (ref.i31 (i32.shl (local.get $cnt) (i32.const 1)))) ,@(let () (define (gen-vector-argminmax $name cmp) `((func ,$name (type $Prim2) (param $proc (ref eq)) ;; procedure (param $vec (ref eq)) ;; vector (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $vector (ref $Vector)) (local $args (ref $Args)) (local $len i32) (local $i i32) (local $elem (ref eq)) (local $best (ref eq)) (local $bestv (ref eq)) (local $val (ref eq)) (local.set $vector (global.get $dummy-vector)) ;; Validate procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Validate vector (if (ref.test (ref $Vector) (local.get $vec)) (then (local.set $vector (ref.cast (ref $Vector) (local.get $vec)))) (else (call $raise-check-vector (local.get $vec)) (unreachable))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vector)))) ;; non-empty vector (if (i32.eqz (local.get $len)) (then (call $raise-argument-error (local.get $vec)) (unreachable))) ;; Prepare argument array (local.set $args (array.new $Args (global.get $null) (i32.const 1))) ;; Seed with first element (local.set $best (array.get $Array (struct.get $Vector $arr (local.get $vector)) (i32.const 0))) (array.set $Args (local.get $args) (i32.const 0) (local.get $best)) (local.set $bestv (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (local.set $i (i32.const 1)) ;; Iterate remaining elements (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $elem (array.get $Array (struct.get $Vector $arr (local.get $vector)) (local.get $i))) (array.set $Args (local.get $args) (i32.const 0) (local.get $elem)) (local.set $val (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (if (ref.eq (call ,cmp (local.get $bestv) (local.get $val)) (global.get $true)) (then (local.set $best (local.get $elem)) (local.set $bestv (local.get $val)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $best)))) (append (gen-vector-argminmax '$vector-argmax '$/2))) (func $vector-filter/template (param $proc (ref eq)) ;; predicate, must accept one argument (param $vec (ref eq)) ;; vector (param $not? i32) ;; 1 => keep elements where predicate returns #f (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $v (ref $Vector)) (local $len i32) (local $call (ref $Args)) (local $i i32) (local $elem (ref eq)) (local $r (ref eq)) (local $ga (ref $GrowableArray)) (local $arr (ref $Array)) ;; 1) Validate procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; 2) Validate vector (local.set $v (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $vec)) (then (local.set $v (ref.cast (ref $Vector) (local.get $vec)))) (else (call $raise-check-vector (local.get $vec)) (unreachable))) (local.set $len (array.len (struct.get $Vector $arr (local.get $v)))) ;; 3) Prepare argument array and growable result array (local.set $call (array.new $Args (global.get $null) (i32.const 1))) (local.set $ga (call $make-growable-array (local.get $len))) ;; 4) Single pass over elements (local.set $i (i32.const 0)) (block $loop-done (loop $loop (br_if $loop-done (i32.ge_u (local.get $i) (local.get $len))) (local.set $elem (array.get $Array (struct.get $Vector $arr (local.get $v)) (local.get $i))) (array.set $Args (local.get $call) (i32.const 0) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (if (i32.eqz (local.get $not?)) (then (if (ref.eq (local.get $r) (global.get $false)) (then (nop)) (else (call $growable-array-add! (local.get $ga) (local.get $elem))))) (else (if (ref.eq (local.get $r) (global.get $false)) (then (call $growable-array-add! (local.get $ga) (local.get $elem)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; 5) Turn growable array into vector (local.set $arr (call $growable-array->array (local.get $ga))) (struct.new $Vector (i32.const 0) (i32.const 0) (local.get $arr))) (func $vector-filter (type $Prim2) (param $proc (ref eq)) ;; predicate (param $vec (ref eq)) ;; vector (result (ref eq)) (call $vector-filter/template (local.get $proc) (local.get $vec) (i32.const 0))) (func $vector-filter-not (type $Prim2) (param $proc (ref eq)) ;; predicate (param $vec (ref eq)) ;; vector (result (ref eq)) (call $vector-filter/template (local.get $proc) (local.get $vec) (i32.const 1))) ,@(for/list ([name '($vector-memq $vector-memv)] [cmp '((ref.eq (local.get $needle) (local.get $elem)) (ref.eq (call $eqv? (local.get $needle) (local.get $elem)) (global.get $true)))]) `(func ,name (type $Prim2) (param $needle (ref eq)) ;; value to find (param $v (ref eq)) ;; vector to search (result (ref eq)) ;; fixnum index or #f (local $vec (ref $Vector)) (local $arr (ref $Array)) (local $len i32) (local $i i32) (local $elem (ref eq)) ;; Ensure $v is a vector (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)) (unreachable))) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $len (array.len (local.get $arr))) (local.set $i (i32.const 0)) (loop $loop (if (i32.ge_u (local.get $i) (local.get $len)) (then (return (global.get $false)))) (local.set $elem (array.get $Array (local.get $arr) (local.get $i))) (if ,cmp (then (return (ref.i31 (i32.shl (local.get $i) (i32.const 1)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)) (unreachable))) (func $vector-member/2 (param $v (ref eq)) ;; value to find (param $vec (ref eq)) ;; vector to search (result (ref eq)) (local $vecv (ref $Vector)) (local $len i32) (local $i i32) (local $elem (ref eq)) (local.set $vecv (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $vec)) (then (local.set $vecv (ref.cast (ref $Vector) (local.get $vec)))) (else (call $raise-check-vector (local.get $vec)))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vecv)))) (local.set $i (i32.const 0)) (loop $loop (if (i32.ge_u (local.get $i) (local.get $len)) (then (return (global.get $false)))) (local.set $elem (array.get $Array (struct.get $Vector $arr (local.get $vecv)) (local.get $i))) (if (ref.eq (call $equal? (local.get $v) (local.get $elem)) (global.get $true)) (then (return (ref.i31 (i32.shl (local.get $i) (i32.const 1)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)) (unreachable)) (func $vector-member (type $Prim23) (param $v (ref eq)) ;; value to find (param $vec (ref eq)) ;; vector to search (param $same? (ref eq)) ;; optional comparator, default equal? (result (ref eq)) (local $vecv (ref $Vector)) (local $len i32) (local $i i32) (local $elem (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (if (ref.eq (local.get $same?) (global.get $missing)) (then (return (call $vector-member/2 (local.get $v) (local.get $vec))))) (local.set $vecv (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $vec)) (then (local.set $vecv (ref.cast (ref $Vector) (local.get $vec)))) (else (call $raise-check-vector (local.get $vec)))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $same?))) (then (call $raise-argument-error:procedure-expected (local.get $same?)) (unreachable))) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (local.set $len (array.len (struct.get $Vector $arr (local.get $vecv)))) (local.set $i (i32.const 0)) (loop $loop (if (i32.ge_u (local.get $i) (local.get $len)) (then (return (global.get $false)))) (local.set $elem (array.get $Array (struct.get $Vector $arr (local.get $vecv)) (local.get $i))) (array.set $Args (local.get $args) (i32.const 0) (local.get $v)) (array.set $Args (local.get $args) (i32.const 1) (local.get $elem)) (local.set $res (call_ref $ProcedureInvoker (ref.cast (ref $Procedure) (local.get $same?)) (local.get $args) (struct.get $Procedure $invoke (ref.cast (ref $Procedure) (local.get $same?))))) (if (ref.eq (local.get $res) (global.get $true)) (then (return (ref.i31 (i32.shl (local.get $i) (i32.const 1)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)) (unreachable)) ;;; Vector Sorting ;; Quicksort with median-of-three pivot and insertion sort cutoff. ;; #:key and #:cache-keys? arguments are not supported. (func $vector-sort!:swap! (param $vec (ref $Vector)) ;; vector (param $i i32) ;; index (param $j i32) ;; index (local $arr (ref $Array)) (local $tmp (ref eq)) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $tmp (array.get $Array (local.get $arr) (local.get $i))) (array.set $Array (local.get $arr) (local.get $i) (array.get $Array (local.get $arr) (local.get $j))) (array.set $Array (local.get $arr) (local.get $j) (local.get $tmp))) (func $vector-sort!:lt-idx? (param $vec (ref $Vector)) ;; vector (param $f (ref $Procedure)) ;; less-than (param $finv (ref $ProcedureInvoker)) ;; invoker (param $call (ref $Args)) ;; args array (param $i i32) ;; index (param $j i32) ;; index (result i32) (local $arr (ref $Array)) (local $x (ref eq)) (local $y (ref eq)) (local $r (ref eq)) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $x (array.get $Array (local.get $arr) (local.get $i))) (local.set $y (array.get $Array (local.get $arr) (local.get $j))) (array.set $Args (local.get $call) (i32.const 0) (local.get $x)) (array.set $Args (local.get $call) (i32.const 1) (local.get $y)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (i32.eqz (ref.eq (local.get $r) (global.get $false)))) (func $vector-sort!:median-of-three->end! (param $vec (ref $Vector)) ;; vector (param $f (ref $Procedure)) ;; less-than (param $finv (ref $ProcedureInvoker)) ;; invoker (param $call (ref $Args)) ;; args array (param $lo i32) ;; start index (param $hi i32) ;; end index (exclusive) (local $i i32) (local $k i32) (local $m i32) (local.set $i (local.get $lo)) (local.set $k (i32.sub (local.get $hi) (i32.const 1))) (local.set $m (i32.add (local.get $lo) (i32.shr_u (i32.sub (local.get $hi) (local.get $lo)) (i32.const 1)))) (if (i32.ne (call $vector-sort!:lt-idx? (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (local.get $k) (local.get $i)) (i32.const 0)) (then (call $vector-sort!:swap! (local.get $vec) (local.get $k) (local.get $i)))) (if (i32.ne (call $vector-sort!:lt-idx? (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (local.get $m) (local.get $i)) (i32.const 0)) (then (call $vector-sort!:swap! (local.get $vec) (local.get $m) (local.get $i)))) (if (i32.ne (call $vector-sort!:lt-idx? (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (local.get $k) (local.get $m)) (i32.const 0)) (then (call $vector-sort!:swap! (local.get $vec) (local.get $k) (local.get $m)))) (call $vector-sort!:swap! (local.get $vec) (local.get $m) (local.get $k))) (func $vector-sort!:partition! (param $vec (ref $Vector)) ;; vector (param $f (ref $Procedure)) ;; less-than (param $finv (ref $ProcedureInvoker)) ;; invoker (param $call (ref $Args)) ;; args array (param $lo i32) ;; start index (param $hi i32) ;; end index (exclusive) (result i32) (local $arr (ref $Array)) (local $pivot (ref eq)) (local $i i32) (local $j i32) (local $elem (ref eq)) (local $r (ref eq)) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $pivot (array.get $Array (local.get $arr) (i32.sub (local.get $hi) (i32.const 1)))) (local.set $i (local.get $lo)) (local.set $j (local.get $lo)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $j) (i32.sub (local.get $hi) (i32.const 1)))) (local.set $elem (array.get $Array (local.get $arr) (local.get $j))) (array.set $Args (local.get $call) (i32.const 0) (local.get $elem)) (array.set $Args (local.get $call) (i32.const 1) (local.get $pivot)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (if (i32.eqz (ref.eq (local.get $r) (global.get $false))) (then (call $vector-sort!:swap! (local.get $vec) (local.get $i) (local.get $j)) (local.set $i (i32.add (local.get $i) (i32.const 1))))) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $loop))) (call $vector-sort!:swap! (local.get $vec) (local.get $i) (i32.sub (local.get $hi) (i32.const 1))) (local.get $i)) (func $vector-sort!:insertion-sort! (param $vec (ref $Vector)) ;; vector (param $f (ref $Procedure)) ;; less-than (param $finv (ref $ProcedureInvoker)) ;; invoker (param $call (ref $Args)) ;; args array (param $lo i32) ;; start index (param $hi i32) ;; end index (exclusive) (local $arr (ref $Array)) (local $i i32) (local $j i32) (local $key (ref eq)) (local $elem (ref eq)) (local $r (ref eq)) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $i (i32.add (local.get $lo) (i32.const 1))) (block $outer-done (loop $outer (br_if $outer-done (i32.ge_u (local.get $i) (local.get $hi))) (local.set $key (array.get $Array (local.get $arr) (local.get $i))) (local.set $j (i32.sub (local.get $i) (i32.const 1))) (block $inner-done (loop $inner (br_if $inner-done (i32.lt_s (local.get $j) (local.get $lo))) (local.set $elem (array.get $Array (local.get $arr) (local.get $j))) (array.set $Args (local.get $call) (i32.const 0) (local.get $key)) (array.set $Args (local.get $call) (i32.const 1) (local.get $elem)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (br_if $inner-done (ref.eq (local.get $r) (global.get $false))) (array.set $Array (local.get $arr) (i32.add (local.get $j) (i32.const 1)) (local.get $elem)) (local.set $j (i32.sub (local.get $j) (i32.const 1))) (br $inner))) (array.set $Array (local.get $arr) (i32.add (local.get $j) (i32.const 1)) (local.get $key)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $outer)))) (func $vector-sort!:qs! (param $vec (ref $Vector)) ;; vector (param $f (ref $Procedure)) ;; less-than (param $finv (ref $ProcedureInvoker)) ;; invoker (param $call (ref $Args)) ;; args array (param $lo i32) ;; start index (param $hi i32) ;; end index (exclusive) (local $p i32) (local $left i32) (local $right i32) (block $done (loop $loop ; If the range to sort is shorted than 16, then use insertion sort. ; Otherwise, ... (if (i32.le_u (i32.sub (local.get $hi) (local.get $lo)) (i32.const 16)) (then (call $vector-sort!:insertion-sort! (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (local.get $lo) (local.get $hi)) (br $done))) (call $vector-sort!:median-of-three->end! (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (local.get $lo) (local.get $hi)) (local.set $p (call $vector-sort!:partition! (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (local.get $lo) (local.get $hi))) (local.set $left (i32.sub (local.get $p) (local.get $lo))) (local.set $right (i32.sub (local.get $hi) (i32.add (local.get $p) (i32.const 1)))) (if (i32.lt_u (local.get $left) (local.get $right)) (then (call $vector-sort!:qs! (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (local.get $lo) (local.get $p)) (local.set $lo (i32.add (local.get $p) (i32.const 1))) (br $loop)) (else (call $vector-sort!:qs! (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (i32.add (local.get $p) (i32.const 1)) (local.get $hi)) (local.set $hi (local.get $p)) (br $loop)))))) (func $vector-sort! (type $Prim24) (param $v (ref eq)) ;; vector (param $proc (ref eq)) ;; less-than procedure (param $start (ref eq)) ;; fixnum or $missing, default: 0 (param $end (ref eq)) ;; fixnum or $missing, default: (vector-length v) (result (ref eq)) (local $vec (ref $Vector)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $call (ref $Args)) (local $lo i32) (local $hi i32) (local $len i32) ;; Validate vector (local.set $vec (global.get $dummy-vector)) (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v)))) (else (call $raise-check-vector (local.get $v)) (unreachable))) (if (i32.ne (struct.get $Vector $immutable (local.get $vec)) (i32.const 0)) (then (call $raise-immutable-vector (local.get $v)) (unreachable))) ;; Validate procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $call (array.new $Args (global.get $null) (i32.const 2))) ;; Validate start (if (i32.eqz (ref.eq (local.get $start) (global.get $missing))) (then (if (i32.eqz (ref.test (ref i31) (local.get $start))) (then (call $raise-check-fixnum (local.get $start)) (unreachable))) (if (i32.and (i31.get_u (ref.cast (ref i31) (local.get $start))) (i32.const 1)) (then (call $raise-check-fixnum (local.get $start)) (unreachable))))) ;; Validate end (if (i32.eqz (ref.eq (local.get $end) (global.get $missing))) (then (if (i32.eqz (ref.test (ref i31) (local.get $end))) (then (call $raise-check-fixnum (local.get $end)) (unreachable))) (if (i32.and (i31.get_u (ref.cast (ref i31) (local.get $end))) (i32.const 1)) (then (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; Decode start/end and check range (local.set $len (array.len (struct.get $Vector $arr (local.get $vec)))) (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $lo (i32.const 0))) (else (local.set $lo (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $start))) (i32.const 1))))) (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $hi (local.get $len))) (else (local.set $hi (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $end))) (i32.const 1))))) (if (i32.or (i32.or (i32.gt_u (local.get $lo) (local.get $len)) (i32.gt_u (local.get $hi) (local.get $len))) (i32.gt_u (local.get $lo) (local.get $hi))) (then (call $raise-bad-vector-copy-range (local.get $vec) (local.get $lo) (local.get $vec) (local.get $lo) (local.get $hi)) (unreachable))) ;; Sort when range has more than one element (if (i32.lt_u (i32.add (local.get $lo) (i32.const 1)) (local.get $hi)) (then (call $vector-sort!:qs! (local.get $vec) (local.get $f) (local.get $finv) (local.get $call) (local.get $lo) (local.get $hi)))) (local.get $vec)) (func $vector-sort (type $Prim24) (param $v (ref eq)) ;; vector (param $proc (ref eq)) ;; less-than procedure (param $start (ref eq)) ;; fixnum or $missing, default: 0 (param $end (ref eq)) ;; fixnum or $missing, default: (vector-length v) (result (ref eq)) (local $copy (ref eq)) ;; Validate procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; Copy the vector or subvector (local.set $copy (call $vector-copy (local.get $v) (local.get $start) (local.get $end))) ;; Sort the copy and return it (call $vector-sort! (local.get $copy) (local.get $proc) (global.get $missing) (global.get $missing))) ;;; ;;; Boxed (for assignable variables) ;;; ;; We use `boxed`, `set-boxed!` and `unboxed` for assignable variables. ;; These "boxes" are not the same as the Racket datatype `box`. ;; See next section. (func $boxed (type $Prim1) (param $v (ref eq)) (result (ref eq)) (struct.new $Boxed (local.get $v))) (func $unboxed (type $Prim1) (param $b (ref eq)) (result (ref eq)) (local $B (ref $Boxed)) (local $v (ref eq)) (local.set $B (block $ok (result (ref $Boxed)) (br_on_cast $ok (ref eq) (ref $Boxed) (local.get $b)) (unreachable))) (local.set $v (struct.get $Boxed $v (local.get $B))) ;; Safe code must not observe letrec's internal placeholder. (if (ref.eq (local.get $v) (global.get $unsafe-undefined)) (then (unreachable))) (local.get $v)) (func $set-boxed! (type $Prim2) ; todo: make this return no values ; problem: set-boxed! is currently wrapped by drop in ; the code generator. Add an rule that avoids ; the drop for set-boxed!. (param $b (ref eq)) (param $v (ref eq)) (result (ref eq)) ; 1. Cast $b into a (ref $Box) (local $B (ref $Boxed)) (local.set $B (block $ok (result (ref $Boxed)) (br_on_cast $ok (ref eq) (ref $Boxed) (local.get $b)) (return (global.get $error)))) (if (ref.eq (struct.get $Boxed $v (local.get $B)) (global.get $unsafe-undefined)) (then (unreachable))) ; 2. Set the contents (struct.set $Boxed $v (local.get $B) (local.get $v)) ; 3. Return `void` (global.get $void)) (func $initialize-boxed! (type $Prim2) (param $b (ref eq)) (param $v (ref eq)) (result (ref eq)) (local $B (ref $Boxed)) (local.set $B (block $ok (result (ref $Boxed)) (br_on_cast $ok (ref eq) (ref $Boxed) (local.get $b)) (return (global.get $error)))) (struct.set $Boxed $v (local.get $B) (local.get $v)) (global.get $void)) (func $boxed? (type $Prim1) ,@(make-predicate-body '$Boxed)) ;;; ;;; 4.14 Boxes ;;; ;; https://docs.racket-lang.org/reference/boxes.html (func $box? (type $Prim1) ,@(make-predicate-body '$Box)) (func $box (type $Prim1) (param $v (ref eq)) (result (ref eq)) (struct.new $Box (i32.const 0) ;; $hash (i32.const 0) ;; $immutable (mutable = not immmutable) (local.get $v))) (func $box-immutable (type $Prim1) (param $v (ref eq)) (result (ref eq)) (struct.new $Box (i32.const 0) ;; $hash (i32.const 1) ;; $immutable (local.get $v))) (func $unbox (type $Prim1) (param $b (ref eq)) (result (ref eq)) (struct.get $Box $v (block $ok (result (ref $Box)) (br_on_cast $ok (ref eq) (ref $Box) (local.get $b)) (return (global.get $error))))) (func $raise-immutable-box (param $x (ref eq)) (unreachable)) (func $set-box! (type $Prim2) ; todo: should this invalidate the hash code? (param $b (ref eq)) (param $v (ref eq)) (result (ref eq)) ; 1. Cast $b into a (ref $Box) (local $B (ref $Box)) (local.set $B (block $ok (result (ref $Box)) (br_on_cast $ok (ref eq) (ref $Box) (local.get $b)) (return (global.get $error)))) ; 2. Check mutability (if (i32.ne (struct.get $Box $immutable (local.get $B)) (i32.const 0)) (then (call $raise-immutable-box (local.get $B)) (unreachable))) ; 3. Set the contents (struct.set $Box $v (local.get $B) (local.get $v)) ; 4. Return `void` (global.get $void)) ;;; ;;; Memory Map ;;; ;; Currently (June 2005) the proposal for multiple memories aren't supported ;; by Safari (WebKit). Therefore, we are forced to using a single memory only. ;; The following memory map is used to segment the linear memory. ;; Static host bridge scratch map: ;; 0..1MiB VFS file transfer buffer ;; +4KiB VFS path buffer ;; +64KiB callback/FASL/string scratch buffer (global $memory-map:vfs-file-buffer-base (mut i32) (i32.const 0)) (global $memory-map:vfs-file-buffer-length (mut i32) (i32.const 1048576)) (global $memory-map:vfs-path-buffer-base (mut i32) (i32.const 1048576)) (global $memory-map:vfs-path-buffer-length (mut i32) (i32.const 4096)) (global $memory-map:callback-buffer-base (mut i32) (i32.const 1052672)) (global $memory-map:callback-buffer-length (mut i32) (i32.const 65536)) (global $memory-map:string-buffer-base (mut i32) (i32.const 1052672)) (global $memory-map:string-buffer-length (mut i32) (i32.const 4096)) ;;; ;;; Data segment strings ;;; ;; The current (June 2005) support for strings in WebAssembly is *very* limited. ;; The data segment can be used to embed byte sequences in the source. ;; However in order to access the data, one must copy them to the linear memory first. (func $raise-string-buffer-overflow (unreachable)) ; Turns out that the `memory.init` instruction requires a fixed ; segment number. So since we can't pass the segment number as an argument, ; we can't use these functions. Sigh. #;(func $data:codepoints->string (param $segment i32) ;; index of the passive data segment (param $count i32) ;; number of UTF-32 codepoints (result (ref $String)) (local $base i32) (local $i i32) (local $arr (ref $I32Array)) (local $str (ref $String)) (local $byte-size i32) ;; 1. Get base address for string buffer (local.set $base (global.get $memory-map:string-buffer-base)) ;; 2. Compute byte size = count * 4 (local.set $byte-size (i32.mul (local.get $count) (i32.const 4))) ;; 3. Bounds check (optional but recommended) (if (i32.gt_u (local.get $byte-size) (global.get $memory-map:string-buffer-length)) (then (call $raise-string-buffer-overflow))) ;; 4. Copy data segment to string buffer (memory.init (local.get $segment) (local.get $base) (i32.const 0) (local.get $byte-size)) ;; 5. Allocate I32Array (local.set $arr (array.new_default $I32Array (local.get $count))) ;; 6. Copy from memory buffer into array (local.set $i (i32.const 0)) (loop $copy (array.set $I32Array (local.get $arr) (local.get $i) (i32.load (i32.add (local.get $base) (i32.mul (local.get $i) (i32.const 4))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br_if $copy (i32.lt_u (local.get $i) (local.get $count)))) ;; 7. Convert to string (local.set $str (call $i32array->string (local.get $arr))) (local.get $str)) #;(func $data:bytes->string (param $segment i32) ;; passive data segment index (param $byte-count i32) ;; number of UTF-8 bytes (result (ref $String)) (local $base i32) (local $i i32) (local $arr (ref $I8Array)) (local $bs (ref $Bytes)) (local $str (ref $String)) ;; 1. Get base address for string buffer (local.set $base (global.get $memory-map:string-buffer-base)) ;; 2. Bounds check (if (i32.gt_u (local.get $byte-count) (global.get $memory-map:string-buffer-length)) (then (call $raise-string-buffer-overflow))) ;; 3. Copy from data segment into memory buffer (memory.init (local.get $segment) (local.get $base) (i32.const 0) (local.get $byte-count)) ;; 4. Allocate and fill I8Array (local.set $arr (array.new_default $I8Array (local.get $byte-count))) (local.set $i (i32.const 0)) (loop $copy (array.set $I8Array (local.get $arr) (local.get $i) (i32.load8_u (i32.add (local.get $base) (local.get $i)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br_if $copy (i32.lt_u (local.get $i) (local.get $byte-count)))) ;; 5. Wrap I8Array in Bytes struct (local.set $bs (struct.new $Bytes (i32.const 0) ;; hash = 0 (unset) (local.get $byte-count) (local.get $arr))) ;; 6. Decode UTF-8 (local.set $str (call $bytes->string/utf-8/checked (local.get $bs))) (local.get $str)) ;;; ;;; HASH TABLES ;;; ;; Racket contains quite a few types of hash tables. ;; ;; eq?/eqv?/equal?/equal-always? mutable/immutable strong/weak/ephemerons ;; ;; Currently `webracket` supports mutable, strong hash tables only. (func $hash? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Hash) (local.get $v)) (then (global.get $true)) (else (global.get $false)))) ,@(for/list ([pred '($hash-eq? $hash-eqv? $hash-equal? $hash-equal-always?)] [type '($HashEq $HashEqv $HashEqual $HashEqualAlways)]) `(func ,pred (type $Prim1) (param $ht (ref eq)) ;; hash table (result (ref eq)) ;; Validate argument is a hash table (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) (if (result (ref eq)) (ref.test (ref ,type) (local.get $ht)) (then (global.get $true)) (else (global.get $false))))) ;;; ;;; MUTABLE HASHEQ ;;; ; We'll use an open-addressing hash table with linear probing for simplicity. ; A load of 50% leads to fast lookup - but uses some more memory. ; Theory: https://thenumb.at/Hashtables/ ,@(for/list ([name '($make-empty-hasheq $make-empty-hasheqv $make-empty-hash $make-empty-hashalw)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,name (type $Prim0) (result (ref eq)) ; an (ref $HashEqMutable) (local $entries (ref $Array)) ;; Step 1: Allocate an array with 2 × capacity (key/value pairs) ;; Capacity = 16 entries → 32 elements (local.set $entries (array.new $Array (global.get $missing) (i32.const 32))) ;; Step 2: Construct and return the hashtable struct (struct.new ,type (i32.const 0) ;; hash = 0 (placeholder or unused) (global.get $true) ;; mutable? = #t (local.get $entries) ;; entries array (i32.const 0)))) ;; count = 0 (func $raise-argument-error:pair-expected (unreachable)) (func $raise-argument-error:pair-expected1 (unreachable)) (func $raise-argument-error:pair-expected2 (unreachable)) ,@(for/list ([make-hash '($make-hasheq $make-hasheqv $make-hash $make-hashalw)] [make-empty '($make-empty-hasheq $make-empty-hasheqv $make-empty-hash $make-empty-hashalw)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [set!/checked '($hasheq-set!/mutable/checked $hasheqv-set!/mutable/checked $hash-set!/mutable/checked $hashalw-set!/mutable/checked)]) `(func ,make-hash (type $Prim1) ; (make-hasheq [assocs]) - optional without default (param $assocs (ref eq)) ;; Either $missing or an alist of key/value pairs (result (ref eq)) ;; an (ref $HashEqMutable) (local $alist (ref eq)) (local $pair (ref $Pair)) (local $key (ref eq)) (local $val (ref eq)) (local $first (ref eq)) ; of the alist (local $rest (ref eq)) ; of the alist (local $ht (ref ,type)) ;; Case 1: No argument => make empty table (if (ref.eq (local.get $assocs) (global.get $missing)) (then (return (call ,make-empty)))) ;; Case 2: Provided association list (local.set $ht (ref.cast (ref ,type) (call ,make-empty))) (local.set $alist (local.get $assocs)) (block $done (loop $walk ;; Stop when list is null (br_if $done (ref.eq (local.get $alist) (global.get $null))) ;; Must be a pair (if (i32.eqz (ref.test (ref $Pair) (local.get $alist))) (then (call $raise-argument-error:pair-expected1 (local.get $alist)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $alist))) ;; Extract car and cdr of current pair (local.set $first (struct.get $Pair $a (local.get $pair))) ;; first = key/value pair (local.set $rest (struct.get $Pair $d (local.get $pair))) ;; rest = remaining pairings ;; Validate first is a pair (key . value) (if (i32.eqz (ref.test (ref $Pair) (local.get $first))) (then (call $raise-argument-error:pair-expected2 (local.get $first)) (unreachable))) ;; Extract key and value from nested pair (local.set $pair (ref.cast (ref $Pair) (local.get $first))) (local.set $key (struct.get $Pair $a (local.get $pair))) ;; key (local.set $val (struct.get $Pair $d (local.get $pair))) ;; value ;; Insert into table (call ,set!/checked (local.get $ht) (local.get $key) (local.get $val)) ;; Move to next element in alist (local.set $alist (local.get $rest)) (br $walk))) (local.get $ht))) ; NOTE - TODO - These "weak" hash tables are dummies. ; - They simply call the constructors of ; the non-weak hash table constructors. (func $make-weak-hash (type $Prim01) ; one optional argument (param $assocs (ref eq)) (result (ref eq)) (if (ref.eq (local.get $assocs) (global.get $missing)) (then (local.set $assocs (global.get $null)))) (call $make-hash (local.get $assocs))) (func $make-weak-hasheq (type $Prim01) ; one optional argument (param $assocs (ref eq)) (result (ref eq)) (if (ref.eq (local.get $assocs) (global.get $missing)) (then (local.set $assocs (global.get $null)))) (call $make-hasheq (local.get $assocs))) (func $make-weak-hasheqv (type $Prim01) ; one optional argument (param $assocs (ref eq)) (result (ref eq)) (if (ref.eq (local.get $assocs) (global.get $missing)) (then (local.set $assocs (global.get $null)))) (call $make-hasheqv (local.get $assocs))) (func $make-weak-hashalw (type $Prim01) ; one optional argument (param $assocs (ref eq)) (result (ref eq)) (if (ref.eq (local.get $assocs) (global.get $missing)) (then (local.set $assocs (global.get $null)))) (call $make-hashalw (local.get $assocs))) (func $raise-hash-ref-key-not-found (param $key (ref eq)) (unreachable)) (func $hash-ref (param $ht (ref eq)) ;; must be a mutable hasheq (param $key (ref eq)) (param $failure (ref eq)) ;; failure result (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Mutable or immutable? (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-ref (local.get $ht) (local.get $key) (local.get $failure))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-ref (local.get $ht) (local.get $key) (local.get $failure))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-ref (local.get $ht) (local.get $key) (local.get $failure))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-ref (local.get $ht) (local.get $key) (local.get $failure))))) (unreachable)) ,@(for/list ([hash-ref '($hasheq-ref $hasheqv-ref $hashequal-ref $hashalw-ref)] [hash-ref/plain '($hasheq-ref/plain $hasheqv-ref/plain $hashequal-ref/plain $hashalw-ref/plain)]) `(func ,hash-ref (type $Prim23) ; todo (param $ht (ref eq)) ;; hasheq (param $key (ref eq)) ;; lookup key (param $failure (ref eq)) ;; value to return if not found (result (ref eq)) (return_call ,hash-ref/plain (local.get $ht) (local.get $key) (local.get $failure)))) ,@(for/list ([raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,raise-expected (unreachable))) ,@(for/list ([hash-ref/plain '($hasheq-ref/plain $hasheqv-ref/plain $hashequal-ref/plain $hashalw-ref/plain)] [hash-ref/plain/checked '($hasheq-ref/plain/checked $hasheqv-ref/plain/checked $hashequal-ref/plain/checked $hashalw-ref/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,hash-ref/plain (param $ht (ref eq)) ;; hasheq (param $key (ref eq)) ;; lookup key (param $failure (ref eq)) ;; value to return if not found (result (ref eq)) (local $table (ref ,type)) ;; Check that ht is a mutable hasheq (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate to checked implementation (call ,hash-ref/plain/checked (local.get $table) (local.get $key) (local.get $failure)))) ,@(for/list ([hash-ref/plain/checked '($hasheq-ref/plain/checked $hasheqv-ref/plain/checked $hashequal-ref/plain/checked $hashalw-ref/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,hash-ref/plain/checked (param $table (ref ,type)) (param $key (ref eq)) (param $fail (ref eq)) (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $index i32) (local $step i32) (local $hash i32) (local $k (ref eq)) (local $slot i32) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $noargs (ref $Args)) ;; Get entries and compute capacity (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) ;; Hash key (identity hash) (local.set $hash (call $eq-hash/i32 (local.get $key))) (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) (local.set $step (i32.const 0)) (block $not-found (loop $probe ;; Stop probing if we've checked all slots (br_if $not-found (i32.ge_u (local.get $step) (local.get $capacity))) ;; slot = 2 * ((index + step) % capacity) (local.set $slot (i32.shl (i32.rem_u (i32.add (local.get $index) (local.get $step)) (local.get $capacity)) (i32.const 1))) ;; Get key at slot (local.set $k (array.get $Array (local.get $entries) (local.get $slot))) ;; Empty slot means not found (br_if $not-found (ref.eq (local.get $k) (global.get $missing))) ;; Tombstone? — skip and continue probing (if (ref.eq (local.get $k) (global.get $tombstone)) (then (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Match? — return value at slot + 1 (if (ref.eq (local.get $k) (local.get $key)) (then (return (array.get $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)))))) ;; Continue probing (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Not found — handle failure result (if (result (ref eq)) (ref.eq (local.get $fail) (global.get $missing)) (then (call $raise-hash-ref-key-not-found (local.get $key)) (unreachable)) (else (if (result (ref eq)) (ref.test (ref $Procedure) (local.get $fail)) (then (local.set $proc (ref.cast (ref $Procedure) (local.get $fail))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $noargs (array.new $Args (global.get $null) (i32.const 0))) (return_call_ref $ProcedureInvoker (local.get $proc) (local.get $noargs) (local.get $inv))) (else (local.get $fail))))))) (func $hash-set! (type $Prim3) (param $ht (ref eq)) ;; hash table (param $key (ref eq)) ;; key (param $val (ref eq)) ;; value (result (ref eq)) ;; return void (an immediate) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-set! (local.get $ht) (local.get $key) (local.get $val))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-set! (local.get $ht) (local.get $key) (local.get $val))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-set! (local.get $ht) (local.get $key) (local.get $val))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-set! (local.get $ht) (local.get $key) (local.get $val))))) (unreachable)) ,@(for/list ([hash-set '($hasheq-set! $hasheqv-set! $hashequal-set! $hashalw-set!)] [set!/checked '($hasheq-set!/mutable/checked $hasheqv-set!/mutable/checked $hash-set!/mutable/checked $hashalw-set!/mutable/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `(func ,hash-set (type $Prim3) (param $ht (ref eq)) ;; table (param $key (ref eq)) ;; key (param $val (ref eq)) ;; value (result (ref eq)) ;; return void (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate (call ,set!/checked (local.get $table) (local.get $key) (local.get $val)) ; return void (global.get $void))) ,@(for/list ([raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `(func ,raise-expected (unreachable))) (func $raise-hash-insert:table-full (unreachable)) ,@(for/list ([set!/checked '($hasheq-set!/mutable/checked $hasheqv-set!/mutable/checked $hash-set!/mutable/checked $hashalw-set!/mutable/checked)] [make-empty '($make-empty-hasheq $make-empty-hasheqv $make-empty-hash $make-empty-hashalw)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [maybe-resize '($maybe-resize-hasheq $maybe-resize-hasheqv $maybe-resize-hashequal $maybe-resize-hashalw)]) `(func ,set!/checked (param $table (ref ,type)) (param $key (ref eq)) (param $val (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $hash i32) (local $index i32) (local $step i32) (local $slot i32) (local $k (ref eq)) (local $first-tombstone i32) ;; Maybe resize (call ,maybe-resize (local.get $table)) ;; Get entries and capacity (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) ;; Compute initial hash/index (local.set $hash (call $eq-hash/i32 (local.get $key))) ; XXX (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) (local.set $step (i32.const 0)) ;; No tombstone seen yet (local.set $first-tombstone (i32.const -1)) (block $done (block $full (loop $probe (br_if $full (i32.ge_u (local.get $step) (local.get $capacity))) ;; Compute probe slot = 2 * ((index + step) % capacity) (local.set $slot (i32.shl (i32.rem_u (i32.add (local.get $index) (local.get $step)) (local.get $capacity)) (i32.const 1))) ;; Load key at slot (local.set $k (array.get $Array (local.get $entries) (local.get $slot))) ;; First tombstone — record slot (if (ref.eq (local.get $k) (global.get $tombstone)) (then (if (i32.eq (local.get $first-tombstone) (i32.const -1)) (then (local.set $first-tombstone (local.get $slot)))))) ;; Empty — insert into tombstone if available, else here (if (ref.eq (local.get $k) (global.get $missing)) (then (local.set $slot (select (local.get $slot) (local.get $first-tombstone) (i32.eq (local.get $first-tombstone) (i32.const -1)))) (array.set $Array (local.get $entries) (local.get $slot) (local.get $key)) (array.set $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)) (local.get $val)) (struct.set ,type $count (local.get $table) (i32.add (struct.get ,type $count (local.get $table)) (i32.const 1))) (br $done))) ;; Key match — overwrite value (if (ref.eq (local.get $k) (local.get $key)) (then (array.set $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)) (local.get $val)) (br $done))) ;; Next probe (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Table full — but insert into tombstone if one was found (if (i32.ne (local.get $first-tombstone) (i32.const -1)) (then (local.set $slot (local.get $first-tombstone)) (array.set $Array (local.get $entries) (local.get $slot) (local.get $key)) (array.set $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)) (local.get $val)) (struct.set ,type $count (local.get $table) (i32.add (struct.get ,type $count (local.get $table)) (i32.const 1))) (br $done))) (call $raise-hash-insert:table-full))) ) (func $hash-ref! (type $Prim3) (param $ht (ref eq)) ;; hash table (param $key (ref eq)) ;; lookup key (param $to-set (ref eq)) ;; value to insert when missing (optional, default = raises error) (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-ref! (local.get $ht) (local.get $key) (local.get $to-set))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-ref! (local.get $ht) (local.get $key) (local.get $to-set))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-ref! (local.get $ht) (local.get $key) (local.get $to-set))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-ref! (local.get $ht) (local.get $key) (local.get $to-set))))) (unreachable)) ,@(for/list ([hash-ref! '($hasheq-ref! $hasheqv-ref! $hashequal-ref! $hashalw-ref!)] [hash-ref!/plain '($hasheq-ref!/plain $hasheqv-ref!/plain $hashequal-ref!/plain $hashalw-ref!/plain)]) `(func ,hash-ref! (param $ht (ref eq)) ;; hash table (param $key (ref eq)) ;; lookup key (param $to-set (ref eq)) ;; value to insert when missing (optional, default = raises error) (result (ref eq)) (return_call ,hash-ref!/plain (local.get $ht) (local.get $key) (local.get $to-set)))) ,@(for/list ([hash-ref!/plain '($hasheq-ref!/plain $hasheqv-ref!/plain $hashequal-ref!/plain $hashalw-ref!/plain)] [hash-ref!/plain/checked '($hasheq-ref!/plain/checked $hasheqv-ref!/plain/checked $hashequal-ref!/plain/checked $hashalw-ref!/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `(func ,hash-ref!/plain (param $ht (ref eq)) ;; hash table (param $key (ref eq)) ;; lookup key (param $to-set (ref eq)) ;; value to insert when missing (optional, default = raises error) (result (ref eq)) (local $table (ref ,type)) ;; Check that ht is the expected mutable hash table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate to checked implementation (call ,hash-ref!/plain/checked (local.get $table) (local.get $key) (local.get $to-set)))) ,@(for/list ([hash-ref!/plain/checked '($hasheq-ref!/plain/checked $hasheqv-ref!/plain/checked $hashequal-ref!/plain/checked $hashalw-ref!/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [set!/checked '($hasheq-set!/mutable/checked $hasheqv-set!/mutable/checked $hash-set!/mutable/checked $hashalw-set!/mutable/checked)]) `(func ,hash-ref!/plain/checked (param $table (ref ,type)) (param $key (ref eq)) (param $to-set (ref eq)) ;; value to insert when missing (optional, default = raises error) (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $index i32) (local $step i32) (local $hash i32) (local $k (ref eq)) (local $slot i32) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $noargs (ref $Args)) (local $value (ref eq)) ;; Get entries and compute capacity (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) ;; Hash key (identity hash) (local.set $hash (call $eq-hash/i32 (local.get $key))) (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) (local.set $step (i32.const 0)) (block $not-found (loop $probe ;; Stop probing if we've checked all slots (br_if $not-found (i32.ge_u (local.get $step) (local.get $capacity))) ;; slot = 2 * ((index + step) % capacity) (local.set $slot (i32.shl (i32.rem_u (i32.add (local.get $index) (local.get $step)) (local.get $capacity)) (i32.const 1))) ;; Get key at slot (local.set $k (array.get $Array (local.get $entries) (local.get $slot))) ;; Empty slot means not found (br_if $not-found (ref.eq (local.get $k) (global.get $missing))) ;; Tombstone? — skip and continue probing (if (ref.eq (local.get $k) (global.get $tombstone)) (then (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Match? — return value at slot + 1 (if (ref.eq (local.get $k) (local.get $key)) (then (return (array.get $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)))))) ;; Continue probing (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Missing entry — compute value to insert (if (ref.eq (local.get $to-set) (global.get $missing)) (then (call $raise-hash-ref-key-not-found (local.get $key)) (unreachable))) (local.set $value (if (result (ref eq)) (ref.test (ref $Procedure) (local.get $to-set)) (then (local.set $proc (ref.cast (ref $Procedure) (local.get $to-set))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $noargs (array.new $Args (global.get $null) (i32.const 0))) (call_ref $ProcedureInvoker (local.get $proc) (local.get $noargs) (local.get $inv))) (else (local.get $to-set)))) ;; Insert computed value and return it (call ,set!/checked (local.get $table) (local.get $key) (local.get $value)) (local.get $value))) (func $hash-update! (type $Prim34) (param $ht (ref eq)) ;; hash table (param $key (ref eq)) ;; key (param $proc (ref eq)) ;; updater procedure (param $fail (ref eq)) ;; optional failure result (default = raises error) (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-update! (local.get $ht) (local.get $key) (local.get $proc) (local.get $fail))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-update! (local.get $ht) (local.get $key) (local.get $proc) (local.get $fail))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-update! (local.get $ht) (local.get $key) (local.get $proc) (local.get $fail))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-update! (local.get $ht) (local.get $key) (local.get $proc) (local.get $fail))))) (unreachable)) ,@(for/list ([hash-update '($hasheq-update! $hasheqv-update! $hashequal-update! $hashalw-update!)] [hash-update/plain '($hasheq-update!/plain $hasheqv-update!/plain $hashequal-update!/plain $hashalw-update!/plain)]) `(func ,hash-update #;(type $Prim34) ;todo (param $ht (ref eq)) (param $key (ref eq)) (param $proc (ref eq)) (param $fail (ref eq)) (result (ref eq)) (return_call ,hash-update/plain (local.get $ht) (local.get $key) (local.get $proc) (local.get $fail)))) ,@(for/list ([hash-update/plain '($hasheq-update!/plain $hasheqv-update!/plain $hashequal-update!/plain $hashalw-update!/plain)] [hash-update/plain/checked '($hasheq-update!/plain/checked $hasheqv-update!/plain/checked $hashequal-update!/plain/checked $hashalw-update!/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `(func ,hash-update/plain (param $ht (ref eq)) (param $key (ref eq)) (param $proc (ref eq)) (param $fail (ref eq)) (result (ref eq)) (local $table (ref ,type)) (local $f (ref $Procedure)) ;; Check that ht is the expected mutable hash table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Ensure updater is a procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) ;; Delegate to checked implementation (call ,hash-update/plain/checked (local.get $table) (local.get $key) (local.get $f) (local.get $fail)))) ,@(for/list ([hash-update/plain/checked '($hasheq-update!/plain/checked $hasheqv-update!/plain/checked $hashequal-update!/plain/checked $hashalw-update!/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [set!/checked '($hasheq-set!/mutable/checked $hasheqv-set!/mutable/checked $hash-set!/mutable/checked $hashalw-set!/mutable/checked)]) `(func ,hash-update/plain/checked (param $table (ref ,type)) (param $key (ref eq)) (param $proc (ref $Procedure)) (param $fail (ref eq)) (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $hash i32) (local $index i32) (local $step i32) (local $slot i32) (local $k (ref eq)) (local $old-value (ref eq)) (local $new-value (ref eq)) (local $default (ref eq)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $fail-proc (ref $Procedure)) (local $fail-inv (ref $ProcedureInvoker)) (local $noargs (ref $Args)) ;; Initialize locals used across branches (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $finv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.const 1))) ;; Hash key (identity hash) (local.set $hash (call $eq-hash/i32 (local.get $key))) (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) (local.set $step (i32.const 0)) (block $not-found (loop $probe ;; Stop probing if we've checked all slots (br_if $not-found (i32.ge_u (local.get $step) (local.get $capacity))) ;; slot = 2 * ((index + step) % capacity) (local.set $slot (i32.shl (i32.rem_u (i32.add (local.get $index) (local.get $step)) (local.get $capacity)) (i32.const 1))) ;; Get key at slot (local.set $k (array.get $Array (local.get $entries) (local.get $slot))) ;; Empty slot means not found (br_if $not-found (ref.eq (local.get $k) (global.get $missing))) ;; Tombstone? — skip and continue probing (if (ref.eq (local.get $k) (global.get $tombstone)) (then (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Match? — compute new value and overwrite existing entry (if (ref.eq (local.get $k) (local.get $key)) (then (local.set $old-value (array.get $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)))) (array.set $Args (local.get $args) (i32.const 0) (local.get $old-value)) (local.set $new-value (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $finv))) (array.set $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)) (local.get $new-value)) (return (global.get $void)))) ;; Continue probing (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Missing entry — compute default (if (ref.eq (local.get $fail) (global.get $missing)) (then (call $raise-hash-ref-key-not-found (local.get $key)) (unreachable))) (local.set $default (if (result (ref eq)) (ref.test (ref $Procedure) (local.get $fail)) (then (local.set $fail-proc (ref.cast (ref $Procedure) (local.get $fail))) (local.set $fail-inv (struct.get $Procedure $invoke (local.get $fail-proc))) (local.set $noargs (array.new $Args (global.get $null) (i32.const 0))) (call_ref $ProcedureInvoker (local.get $fail-proc) (local.get $noargs) (local.get $fail-inv))) (else (local.get $fail)))) ;; Apply updater to default and insert mapping (array.set $Args (local.get $args) (i32.const 0) (local.get $default)) (local.set $new-value (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $finv))) (call ,set!/checked (local.get $table) (local.get $key) (local.get $new-value)) (global.get $void))) (func $raise-argument-error:hasheq-mutable-expected (unreachable)) ;; (func $hash-remove! (type $Prim2) ;; (param $ht (ref eq)) ;; (param $key (ref eq)) ;; (result (ref eq)) ;; (call $hash-remove!/mutable ;; (local.get $ht) (local.get $key))) ;; (func $hash-remove!/mutable ;; (param $ht (ref eq)) ;; (param $key (ref eq)) ;; (result (ref eq)) ;; (local $table (ref $HashEqMutable)) ;; ;; --- Type checks --- ;; (if (i32.eqz (ref.test (ref $HashEqMutable) (local.get $ht))) ;; (then ;; (call $raise-argument-error:hasheq-mutable-expected (local.get $ht)) ;; (unreachable))) ;; ;; --- Decode --- ;; (local.set $table (ref.cast (ref $HashEqMutable) (local.get $ht))) ;; ;; --- Delegate --- ;; (call $hash-remove!/mutable/checked ;; (local.get $table) ;; (local.get $key)) ;; (global.get $void)) ;; (func $hash-remove!/mutable/checked ;; ; Note: (global $tombstone) must be different from valid keys and $missing. ;; (param $ht (ref $HashEqMutable)) ;; (param $key (ref eq)) ;; (local $entries (ref $Array)) ;; (local $capacity i32) ;; (local $hash i32) ;; (local $index i32) ;; (local $step i32) ;; (local $k (ref eq)) ;; (local $slot i32) ;; ;; Get entries and capacity ;; (local.set $entries (struct.get $HashEqMutable $entries (local.get $ht))) ;; (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) ;; ;; Compute hash and initial index ;; (local.set $hash (call $eq-hash/i32 (local.get $key))) ;; (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) ;; (local.set $step (i32.const 0)) ;; ;; Probe loop ;; (block $done ;; (loop $probe ;; ;; Stop if probing exceeds capacity ;; (br_if $done (i32.ge_u (local.get $step) (local.get $capacity))) ;; ;; Compute probe slot (2 * index) ;; (local.set $slot (i32.shl ;; (i32.rem_u (i32.add (local.get $index) (local.get $step)) ;; (local.get $capacity)) ;; (i32.const 1))) ;; ;; Load key from slot ;; (local.set $k (array.get $Array (local.get $entries) (local.get $slot))) ;; ;; Stop if slot is missing ;; (br_if $done (ref.eq (local.get $k) (global.get $missing))) ;; ;; If key matches, remove ;; (if (ref.eq (local.get $k) (local.get $key)) ;; (then ;; ;; Replace key and value with tombstone ;; (array.set $Array (local.get $entries) (local.get $slot) (global.get $tombstone)) ;; (array.set $Array (local.get $entries) ;; (i32.add (local.get $slot) (i32.const 1)) ;; (global.get $tombstone)) ;; ;; Decrement count ;; (struct.set $HashEqMutable $count ;; (local.get $ht) ;; (i32.sub (struct.get $HashEqMutable $count (local.get $ht)) (i32.const 1))) ;; (br $done))) ;; ;; Step to next slot ;; (local.set $step (i32.add (local.get $step) (i32.const 1))) ;; (br $probe)))) (func $hash-remove! (type $Prim2) (param $ht (ref eq)) (param $key (ref eq)) (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-remove! (local.get $ht) (local.get $key))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-remove! (local.get $ht) (local.get $key))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-remove! (local.get $ht) (local.get $key))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-remove! (local.get $ht) (local.get $key))))) (unreachable)) ,@(for/list ([hash-remove '($hasheq-remove! $hasheqv-remove! $hashequal-remove! $hashalw-remove!)] [remove!/checked '($hasheq-remove!/mutable/checked $hasheqv-remove!/mutable/checked $hash-remove!/mutable/checked $hashalw-remove!/mutable/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `(func ,hash-remove (param $ht (ref eq)) (param $key (ref eq)) (result (ref eq)) (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate (call ,remove!/checked (local.get $table) (local.get $key)) ;; Return void (global.get $void))) ,@(for/list ([remove!/checked '($hasheq-remove!/mutable/checked $hasheqv-remove!/mutable/checked $hash-remove!/mutable/checked $hashalw-remove!/mutable/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,remove!/checked ;; Note: (global $tombstone) must be different from valid keys and $missing. (param $ht (ref ,type)) (param $key (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $hash i32) (local $index i32) (local $step i32) (local $k (ref eq)) (local $slot i32) ;; Get entries and capacity (local.set $entries (struct.get ,type $entries (local.get $ht))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) ;; Compute hash and initial index (local.set $hash (call $eq-hash/i32 (local.get $key))) ; XXX (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) (local.set $step (i32.const 0)) ;; Probe loop (block $done (loop $probe ;; Stop if probing exceeds capacity (br_if $done (i32.ge_u (local.get $step) (local.get $capacity))) ;; Compute probe slot (2 * index) (local.set $slot (i32.shl (i32.rem_u (i32.add (local.get $index) (local.get $step)) (local.get $capacity)) (i32.const 1))) ;; Load key from slot (local.set $k (array.get $Array (local.get $entries) (local.get $slot))) ;; Stop if slot is missing (br_if $done (ref.eq (local.get $k) (global.get $missing))) ;; If key matches, remove (if (ref.eq (local.get $k) (local.get $key)) (then ;; Replace key and value with tombstone (array.set $Array (local.get $entries) (local.get $slot) (global.get $tombstone)) (array.set $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)) (global.get $tombstone)) ;; Decrement count (struct.set ,type $count (local.get $ht) (i32.sub (struct.get ,type $count (local.get $ht)) (i32.const 1))) (br $done))) ;; Step to next slot (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))))) ,@(for/list ([type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [resize '($hasheq-resize $hasheqv-resize $hashequal-resize $hashalw-resize)] [set!/checked '($hasheq-set!/mutable/checked $hasheqv-set!/mutable/checked $hash-set!/mutable/checked $hashalw-set!/mutable/checked)]) `(func ,resize (param $table (ref ,type)) (local $old-entries (ref $Array)) (local $old-cap i32) (local $new-cap i32) (local $new-array (ref $Array)) (local $i i32) (local $len i32) (local $key (ref eq)) (local $val (ref eq)) ;; Get old table size and entries (local.set $old-entries (struct.get ,type $entries (local.get $table))) (local.set $old-cap (i32.div_u (array.len (local.get $old-entries)) (i32.const 2))) (local.set $new-cap (i32.mul (local.get $old-cap) (i32.const 2))) ;; Allocate new entries array (2 * new-capacity), all set to $missing (local.set $new-array (array.new $Array (global.get $missing) (i32.mul (local.get $new-cap) (i32.const 2)))) ;; Replace storage in-place and reset metadata (struct.set ,type $entries (local.get $table) (local.get $new-array)) (struct.set ,type $count (local.get $table) (i32.const 0)) ;; Reinsert valid key-value pairs (local.set $i (i32.const 0)) (local.set $len (array.len (local.get $old-entries))) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) ;; Load key and value (local.set $key (array.get $Array (local.get $old-entries) (local.get $i))) (local.set $val (array.get $Array (local.get $old-entries) (i32.add (local.get $i) (i32.const 1)))) ;; Reinsert only if key is not $missing or $tombstone (if (i32.eqz (ref.eq (local.get $key) (global.get $missing))) (then (if (i32.eqz (ref.eq (local.get $key) (global.get $tombstone))) (then (call ,set!/checked (local.get $table) (local.get $key) (local.get $val)))))) ;; Next pair (local.set $i (i32.add (local.get $i) (i32.const 2))) (br $loop))))) ,@(for/list ([type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [resize '($hasheq-resize $hasheqv-resize $hashequal-resize $hashalw-resize)] [maybe-resize '($maybe-resize-hasheq $maybe-resize-hasheqv $maybe-resize-hashequal $maybe-resize-hashalw)]) `(func ,maybe-resize (param $table (ref ,type)) (local $entries (ref $Array)) (local $capacity i32) (local $count i32) ;; Get fields (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $count (struct.get ,type $count (local.get $table))) ;; Resize if count ≥ capacity / 2 (if (i32.ge_u (local.get $count) (i32.shr_u (local.get $capacity) (i32.const 1))) (then (call ,resize (local.get $table)))))) ; General hash-has-key? (func $hash-has-key? (type $Prim2) (param $ht (ref eq)) (param $key (ref eq)) (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-has-key? (local.get $ht) (local.get $key))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-has-key? (local.get $ht) (local.get $key))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-has-key? (local.get $ht) (local.get $key))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-has-key? (local.get $ht) (local.get $key))))) (unreachable)) ; Specialized hash-has-key? ,@(for/list ([hash-has-key '($hasheq-has-key? $hasheqv-has-key? $hashequal-has-key? $hashalw-has-key?)] [hash-has-key/plain '($hasheq-has-key?/plain $hasheqv-has-key?/plain $hashequal-has-key?/plain $hashalw-has-key?/plain)]) `(func ,hash-has-key (param $ht (ref eq)) ;; hash table (param $key (ref eq)) ;; key (result (ref eq)) ;; boolean result (return_call ,hash-has-key/plain (local.get $ht) (local.get $key)))) ,@(for/list ([hash-has-key/plain '($hasheq-has-key?/plain $hasheqv-has-key?/plain $hashequal-has-key?/plain $hashalw-has-key?/plain)] [hash-has-key/plain/checked '($hasheq-has-key?/plain/checked $hasheqv-has-key?/plain/checked $hashequal-has-key?/plain/checked $hashalw-has-key?/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,hash-has-key/plain (param $ht (ref eq)) (param $key (ref eq)) (result (ref eq)) (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate to checked implementation (call ,hash-has-key/plain/checked (local.get $table) (local.get $key)))) ,@(for/list ([hash-has-key/plain/checked '($hasheq-has-key?/plain/checked $hasheqv-has-key?/plain/checked $hashequal-has-key?/plain/checked $hashalw-has-key?/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,hash-has-key/plain/checked (param $table (ref ,type)) (param $key (ref eq)) (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $index i32) (local $step i32) (local $hash i32) (local $k (ref eq)) (local $slot i32) ;; Get entries and capacity (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) ;; Hash key and compute initial index (local.set $hash (call $eq-hash/i32 (local.get $key))) (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) (local.set $step (i32.const 0)) (block $not-found (loop $probe ;; Stop if we've probed the full table (br_if $not-found (i32.ge_u (local.get $step) (local.get $capacity))) ;; slot = 2 * ((index + step) % capacity) (local.set $slot (i32.shl (i32.rem_u (i32.add (local.get $index) (local.get $step)) (local.get $capacity)) (i32.const 1))) ;; Load key from slot (local.set $k (array.get $Array (local.get $entries) (local.get $slot))) ;; Empty slot: key is not in the table (br_if $not-found (ref.eq (local.get $k) (global.get $missing))) ;; Tombstone: skip (if (ref.eq (local.get $k) (global.get $tombstone)) (then (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Match: return #t (if (ref.eq (local.get $k) (local.get $key)) (then (return (global.get $true)))) ;; Otherwise try next slot (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Not found: return #f (global.get $false))) (func $hash-clear! (type $Prim1) (param $ht (ref eq)) (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-clear! (local.get $ht))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-clear! (local.get $ht))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-clear! (local.get $ht))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-clear! (local.get $ht))))) (unreachable)) ,@(for/list ([hash-clear '($hasheq-clear! $hasheqv-clear! $hashequal-clear! $hashalw-clear!)] [clear!/checked '($hasheq-clear!/mutable/checked $hasheqv-clear!/mutable/checked $hash-clear!/mutable/checked $hashalw-clear!/mutable/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `(func ,hash-clear (param $ht (ref eq)) ;; table (result (ref eq)) ;; return void (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate (call ,clear!/checked (local.get $table)) ;; Return void (global.get $void))) ,@(for/list ([clear!/checked '($hasheq-clear!/mutable/checked $hasheqv-clear!/mutable/checked $hash-clear!/mutable/checked $hashalw-clear!/mutable/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,clear!/checked (param $ht (ref ,type)) (local $new-entries (ref $Array)) ;; Allocate fresh array of default size (16 entries = 32 slots) (local.set $new-entries (array.new $Array (global.get $missing) (i32.const 32))) ;; Replace entries array (struct.set ,type $entries (local.get $ht) (local.get $new-entries)) ;; Reset count to 0 (struct.set ,type $count (local.get $ht) (i32.const 0)))) ;; hash-empty? (func $hash-empty? (type $Prim1) (param $ht (ref eq)) (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-empty? (local.get $ht))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-empty? (local.get $ht))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-empty? (local.get $ht))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-empty? (local.get $ht))))) (unreachable)) ,@(for/list ([hash-empty '($hasheq-empty? $hasheqv-empty? $hashequal-empty? $hashalw-empty?)] [empty?/checked '($hasheq-empty?/mutable/checked $hasheqv-empty?/mutable/checked $hash-empty?/mutable/checked $hashalw-empty?/mutable/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,hash-empty (param $ht (ref eq)) ;; table (result (ref eq)) ;; boolean result (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate (call ,empty?/checked (local.get $table)))) ,@(for/list ([empty?/checked '($hasheq-empty?/mutable/checked $hasheqv-empty?/mutable/checked $hash-empty?/mutable/checked $hashalw-empty?/mutable/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,empty?/checked (param $ht (ref ,type)) ;; hash table (result (ref eq)) ;; boolean result (if (result (ref eq)) (i32.eqz (struct.get ,type $count (local.get $ht))) (then (global.get $true)) (else (global.get $false))))) (func $raise-mutable-hash-iterate-bad-index (param $pos (ref eq)) ;; iteration position (unreachable)) ;; mutable-hash-iterate-first (func $mutable-hash-iterate-first (type $Prim1) (param $ht (ref eq)) ;; mutable hash table (result (ref eq)) ;; first iteration position or #f ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-iterate-first (local.get $ht))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-iterate-first (local.get $ht))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-iterate-first (local.get $ht))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-iterate-first (local.get $ht))))) (call $raise-argument-error:mutable-hash-expected (local.get $ht)) (unreachable)) ,@(for/list ([iter-first '($hasheq-iterate-first $hasheqv-iterate-first $hashequal-iterate-first $hashalw-iterate-first)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `(func ,iter-first (type $Prim1) (param $ht (ref eq)) ;; mutable hash table (result (ref eq)) (local $table (ref ,type)) (local $entries (ref $Array)) (local $capacity i32) (local $i i32) (local $key (ref eq)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) (local.set $table (ref.cast (ref ,type) (local.get $ht))) (if (i32.eqz (struct.get ,type $count (local.get $table))) (then (return (global.get $false)))) (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) ;; Skip empty or tombstone slots (if (ref.eq (local.get $key) (global.get $missing)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (return (ref.i31 (i32.shl (local.get $i) (i32.const 1)))) )) (global.get $false))) ;; mutable-hash-iterate-next (func $mutable-hash-iterate-next (type $Prim12) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; current position (result (ref eq)) ;; next position or #f ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-iterate-next (local.get $ht) (local.get $pos))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-iterate-next (local.get $ht) (local.get $pos))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-iterate-next (local.get $ht) (local.get $pos))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-iterate-next (local.get $ht) (local.get $pos))))) (call $raise-argument-error:mutable-hash-expected (local.get $ht)) (unreachable)) ,@(for/list ([iter-next '($hasheq-iterate-next $hasheqv-iterate-next $hashequal-iterate-next $hashalw-iterate-next)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `(func ,iter-next (type $Prim12) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; current iteration position (result (ref eq)) ;; next iteration position or #f (local $table (ref ,type)) (local $entries (ref $Array)) (local $capacity i32) (local $i i32) (local $key (ref eq)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode and validate position (if (i32.eqz (ref.test (ref i31) (local.get $pos))) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $i (i31.get_u (ref.cast (ref i31) (local.get $pos)))) (if (i32.and (local.get $i) (i32.const 1)) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.add (i32.shr_u (local.get $i) (i32.const 1)) (i32.const 1))) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) ;; Skip empty or tombstone slots (if (ref.eq (local.get $key) (global.get $missing)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (return (ref.i31 (i32.shl (local.get $i) (i32.const 1)))) )) (global.get $false))) ;; mutable-hash-iterate-key (func $mutable-hash-iterate-key (type $Prim13) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; iteration position (param $bad-index (ref eq)) ;; optional bad-index value, default = raise (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-iterate-key (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-iterate-key (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-iterate-key (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-iterate-key (local.get $ht) (local.get $pos) (local.get $bad-index))))) (call $raise-argument-error:mutable-hash-expected (local.get $ht)) (unreachable)) ;; mutable-hash-iterate-value (func $mutable-hash-iterate-value (type $Prim13) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; iteration position (param $bad-index (ref eq)) ;; optional bad-index value, default = raise (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-iterate-value (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-iterate-value (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-iterate-value (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-iterate-value (local.get $ht) (local.get $pos) (local.get $bad-index))))) (call $raise-argument-error:mutable-hash-expected (local.get $ht)) (unreachable)) ;; mutable-hash-iterate-pair (func $mutable-hash-iterate-pair (type $Prim13) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; iteration position (param $bad-index (ref eq)) ;; optional bad-index value, default = raise (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-iterate-pair (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-iterate-pair (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-iterate-pair (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-iterate-pair (local.get $ht) (local.get $pos) (local.get $bad-index))))) (call $raise-argument-error:mutable-hash-expected (local.get $ht)) (unreachable)) ;; mutable-hash-iterate-key+value (func $mutable-hash-iterate-key+value (type $Prim13) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; iteration position (param $bad-index (ref eq)) ;; optional bad-index value, default = raise (result (ref eq)) ;; returns two values encoded in $Values ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-iterate-key+value (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-iterate-key+value (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-iterate-key+value (local.get $ht) (local.get $pos) (local.get $bad-index))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-iterate-key+value (local.get $ht) (local.get $pos) (local.get $bad-index))))) (call $raise-argument-error:mutable-hash-expected (local.get $ht)) (unreachable)) ,@(append* (for/list ([iter-key '($hasheq-iterate-key $hasheqv-iterate-key $hashequal-iterate-key $hashalw-iterate-key)] [iter-value '($hasheq-iterate-value $hasheqv-iterate-value $hashequal-iterate-value $hashalw-iterate-value)] [iter-pair '($hasheq-iterate-pair $hasheqv-iterate-pair $hashequal-iterate-pair $hashalw-iterate-pair)] [iter-key+value '($hasheq-iterate-key+value $hasheqv-iterate-key+value $hashequal-iterate-key+value $hashalw-iterate-key+value)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:mutable-hasheq-expected $raise-argument-error:mutable-hasheqv-expected $raise-argument-error:mutable-hash-expected $raise-argument-error:mutable-hashalw-expected)]) `((func ,iter-key (type $Prim13) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; iteration position (param $bad-index (ref eq)) ;; optional bad-index value, default = raise (result (ref eq)) (local $table (ref ,type)) (local $entries (ref $Array)) (local $capacity i32) (local $index i32) (local $key (ref eq)) (block $bad-index ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode and validate position (if (i32.eqz (ref.test (ref i31) (local.get $pos))) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $index (i31.get_u (ref.cast (ref i31) (local.get $pos)))) (if (i32.and (local.get $index) (i32.const 1)) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $index (i32.shr_u (local.get $index) (i32.const 1))) (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (if (i32.ge_u (local.get $index) (local.get $capacity)) (then (br $bad-index))) (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $index) (i32.const 1)))) (if (ref.eq (local.get $key) (global.get $missing)) (then (br $bad-index))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (br $bad-index))) (return (local.get $key))) (if (ref.eq (local.get $bad-index) (global.get $missing)) (then (call $raise-mutable-hash-iterate-bad-index (local.get $pos)) (unreachable))) (return (local.get $bad-index))) (func ,iter-value (type $Prim13) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; iteration position (param $bad-index (ref eq)) ;; optional bad-index value, default = raise (result (ref eq)) (local $table (ref ,type)) (local $entries (ref $Array)) (local $capacity i32) (local $index i32) (local $key (ref eq)) (block $bad-index ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode and validate position (if (i32.eqz (ref.test (ref i31) (local.get $pos))) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $index (i31.get_u (ref.cast (ref i31) (local.get $pos)))) (if (i32.and (local.get $index) (i32.const 1)) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $index (i32.shr_u (local.get $index) (i32.const 1))) (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (if (i32.ge_u (local.get $index) (local.get $capacity)) (then (br $bad-index))) (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $index) (i32.const 1)))) (if (ref.eq (local.get $key) (global.get $missing)) (then (br $bad-index))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (br $bad-index))) (return (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $index) (i32.const 1)) (i32.const 1))))) (if (ref.eq (local.get $bad-index) (global.get $missing)) (then (call $raise-mutable-hash-iterate-bad-index (local.get $pos)) (unreachable))) (return (local.get $bad-index))) (func ,iter-pair (type $Prim13) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; iteration position (param $bad-index (ref eq)) ;; optional bad-index value, default = raise (result (ref eq)) (local $table (ref ,type)) (local $entries (ref $Array)) (local $capacity i32) (local $index i32) (local $key (ref eq)) (local $val (ref eq)) (block $bad-index ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode and validate position (if (i32.eqz (ref.test (ref i31) (local.get $pos))) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $index (i31.get_u (ref.cast (ref i31) (local.get $pos)))) (if (i32.and (local.get $index) (i32.const 1)) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $index (i32.shr_u (local.get $index) (i32.const 1))) (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (if (i32.ge_u (local.get $index) (local.get $capacity)) (then (br $bad-index))) (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $index) (i32.const 1)))) (if (ref.eq (local.get $key) (global.get $missing)) (then (br $bad-index))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (br $bad-index))) (local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $index) (i32.const 1)) (i32.const 1)))) (return (call $cons (local.get $key) (local.get $val)))) (if (ref.eq (local.get $bad-index) (global.get $missing)) (then (call $raise-mutable-hash-iterate-bad-index (local.get $pos)) (unreachable))) (return (call $cons (local.get $bad-index) (local.get $bad-index)))) (func ,iter-key+value (type $Prim13) (param $ht (ref eq)) ;; mutable hash table (param $pos (ref eq)) ;; iteration position (param $bad-index (ref eq)) ;; optional bad-index value, default = raise (result (ref eq)) ;; returns two values encoded in $Values (local $table (ref ,type)) (local $entries (ref $Array)) (local $capacity i32) (local $index i32) (local $key (ref eq)) (local $val (ref eq)) (block $bad-index ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode and validate position (if (i32.eqz (ref.test (ref i31) (local.get $pos))) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $index (i31.get_u (ref.cast (ref i31) (local.get $pos)))) (if (i32.and (local.get $index) (i32.const 1)) (then (call $raise-check-fixnum (local.get $pos)) (unreachable))) (local.set $index (i32.shr_u (local.get $index) (i32.const 1))) (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (if (i32.ge_u (local.get $index) (local.get $capacity)) (then (br $bad-index))) (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $index) (i32.const 1)))) (if (ref.eq (local.get $key) (global.get $missing)) (then (br $bad-index))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (br $bad-index))) (local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $index) (i32.const 1)) (i32.const 1)))) (return (array.new_fixed $Values 2 (local.get $key) (local.get $val)))) (if (ref.eq (local.get $bad-index) (global.get $missing)) (then (call $raise-mutable-hash-iterate-bad-index (local.get $pos)) (unreachable))) (return (array.new_fixed $Values 2 (local.get $bad-index) (local.get $bad-index))))))) ; General hash-count (func $hash-count (type $Prim1) (param $ht (ref eq)) (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-count (local.get $ht))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-count (local.get $ht))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-count (local.get $ht))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-count (local.get $ht))))) (unreachable)) ,@(for/list ([hash-count '($hasheq-count $hasheqv-count $hashequal-count $hashalw-count)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,hash-count (param $ht (ref eq)) ;; table (result (ref eq)) ;; fixnum count (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Return fixnum of count (ref.i31 (i32.shl (struct.get ,type $count (local.get $table)) (i32.const 1))))) ;; General hash->list (func $hash->list (type $Prim2) (param $ht (ref eq)) ;; hash table (param $try (ref eq)) ;; optional try-order? (default #f) (result (ref eq)) ;; list of pairs ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq->list (local.get $ht) (local.get $try))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv->list (local.get $ht) (local.get $try))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal->list (local.get $ht) (local.get $try))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw->list (local.get $ht) (local.get $try))))) (unreachable)) ,@(for/list ([hash->list '($hasheq->list $hasheqv->list $hashequal->list $hashalw->list)] [hash->list/plain '($hasheq->list/plain $hasheqv->list/plain $hashequal->list/plain $hashalw->list/plain)]) `(func ,hash->list (param $ht (ref eq)) (param $try (ref eq)) (result (ref eq)) (return_call ,hash->list/plain (local.get $ht) (local.get $try)))) ,@(for/list ([hash->list/plain '($hasheq->list/plain $hasheqv->list/plain $hashequal->list/plain $hashalw->list/plain)] [hash->list/plain/checked '($hasheq->list/plain/checked $hasheqv->list/plain/checked $hashequal->list/plain/checked $hashalw->list/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,hash->list/plain (param $ht (ref eq)) (param $try (ref eq)) (result (ref eq)) (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate to checked implementation (call ,hash->list/plain/checked (local.get $table)))) ,@(for/list ([hash->list/plain/checked '($hasheq->list/plain/checked $hasheqv->list/plain/checked $hashequal->list/plain/checked $hashalw->list/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,hash->list/plain/checked (param $table (ref ,type)) (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $i i32) (local $key (ref eq)) (local $val (ref eq)) (local $pair (ref eq)) (local $acc (ref eq)) ;; Initialize locals (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.const 0)) (local.set $acc (global.get $null)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) ;; Load key (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) ;; Skip empty or tombstone slots (if (ref.eq (local.get $key) (global.get $missing)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Load value and cons onto result (local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $i) (i32.const 1)) (i32.const 1)))) (local.set $pair (call $cons (local.get $key) (local.get $val))) (local.set $acc (call $cons (local.get $pair) (local.get $acc))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $acc))) ;; hash-for-each : apply proc to each key/value in a hash table. ;; The try-order? argument is accepted but ignored. (func $hash-for-each (type $Prim23) (param $ht (ref eq)) ;; hash table (param $proc (ref eq)) ;; procedure (param $try (ref eq)) ;; optional try-order? (default #f) (result (ref eq)) ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Optional for try-order (if (ref.eq (local.get $try) (global.get $missing)) (then (local.set $try (global.get $false)))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-for-each (local.get $ht) (local.get $proc))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-for-each (local.get $ht) (local.get $proc))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-for-each (local.get $ht) (local.get $proc))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-for-each (local.get $ht) (local.get $proc))))) (unreachable)) ,@(for/list ([hash-for-each '($hasheq-for-each $hasheqv-for-each $hashequal-for-each $hashalw-for-each)] [hash-for-each/checked '($hasheq-for-each/checked $hasheqv-for-each/checked $hashequal-for-each/checked $hashalw-for-each/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,hash-for-each (param $ht (ref eq)) (param $proc (ref eq)) (result (ref eq)) (local $table (ref ,type)) (local $f (ref $Procedure)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Ensure proc is a procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) ;; Delegate to checked implementation (return_call ,hash-for-each/checked (local.get $table) (local.get $f)))) ,@(for/list ([hash-for-each/checked '($hasheq-for-each/checked $hasheqv-for-each/checked $hashequal-for-each/checked $hashalw-for-each/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,hash-for-each/checked (param $table (ref ,type)) (param $f (ref $Procedure)) (result (ref eq)) (local $finv (ref $ProcedureInvoker)) (local $entries (ref $Array)) (local $capacity i32) (local $i i32) (local $key (ref eq)) (local $val (ref eq)) (local $call (ref $Args)) ;; Fetch procedure invoker and table fields (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.const 0)) (local.set $call (array.new_fixed $Args 2 (global.get $null) (global.get $null))) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) ;; Load key (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) ;; Skip empty or tombstone slots (if (ref.eq (local.get $key) (global.get $missing)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Load value (local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $i) (i32.const 1)) (i32.const 1)))) ;; Prepare call arguments (array.set $Args (local.get $call) (i32.const 0) (local.get $key)) (array.set $Args (local.get $call) (i32.const 1) (local.get $val)) ;; Apply procedure and drop result (drop (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (global.get $void))) ;; General hash-map (func $hash-map (type $Prim23) (param $ht (ref eq)) ;; hash table (param $proc (ref eq)) ;; procedure (param $try (ref eq)) ;; optional try-order? (default #f) (result (ref eq)) ;; list of results ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Validate procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; Optional try-order? (if (ref.eq (local.get $try) (global.get $missing)) (then (local.set $try (global.get $false)))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-map (local.get $ht) (local.get $proc) (local.get $try))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-map (local.get $ht) (local.get $proc) (local.get $try))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-map (local.get $ht) (local.get $proc) (local.get $try))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-map (local.get $ht) (local.get $proc) (local.get $try))))) (unreachable)) ,@(for/list ([hash-map '($hasheq-map $hasheqv-map $hashequal-map $hashalw-map)] [hash-map/plain '($hasheq-map/plain $hasheqv-map/plain $hashequal-map/plain $hashalw-map/plain)]) `(func ,hash-map (param $ht (ref eq)) (param $proc (ref eq)) (param $try (ref eq)) (result (ref eq)) (return_call ,hash-map/plain (local.get $ht) (local.get $proc) (local.get $try)))) ,@(for/list ([hash-map/plain '($hasheq-map/plain $hasheqv-map/plain $hashequal-map/plain $hashalw-map/plain)] [hash-map/plain/checked '($hasheq-map/plain/checked $hasheqv-map/plain/checked $hashequal-map/plain/checked $hashalw-map/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,hash-map/plain (param $ht (ref eq)) (param $proc (ref eq)) (param $try (ref eq)) ;; try-order?, default #f (result (ref eq)) (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate to checked implementation (call ,hash-map/plain/checked (local.get $table) (local.get $proc)))) ,@(for/list ([hash-map/plain/checked '($hasheq-map/plain/checked $hasheqv-map/plain/checked $hashequal-map/plain/checked $hashalw-map/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)]) `(func ,hash-map/plain/checked (param $table (ref ,type)) (param $proc (ref eq)) (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $entries (ref $Array)) (local $capacity i32) (local $i i32) (local $key (ref eq)) (local $val (ref eq)) (local $args (ref $Args)) (local $r (ref eq)) (local $acc (ref eq)) ;; Validate procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) ;; Initialize locals (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.const 0)) (local.set $acc (global.get $null)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) ;; Load key (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) ;; Skip empty or tombstone slots (if (ref.eq (local.get $key) (global.get $missing)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Load value (local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $i) (i32.const 1)) (i32.const 1)))) ;; Prepare arguments and invoke procedure (array.set $Args (local.get $args) (i32.const 0) (local.get $key)) (array.set $Args (local.get $args) (i32.const 1) (local.get $val)) (local.set $r (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) ;; Accumulate result (local.set $acc (call $cons (local.get $r) (local.get $acc))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $acc))) ;; ------------------------------------------------------------------- ;; hash-map/copy ;; Applies `proc` to each key/value in `ht`, producing a new hash. ;; The optional `kind` argument defaults to #f and is currently ignored ;; (only mutable hashes are supported). ;; Note: The original Racket `hash-map/copy` uses keyword arguments. ;; ------------------------------------------------------------------- (func $hash-map/copy (type $Prim23) (param $ht (ref eq)) ;; hash table (param $proc (ref eq)) ;; (key value -> (values key value)) (param $kind (ref eq)) ;; optional kind, default #f (result (ref eq)) ;; Validate argument is a hash table (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-map/copy (local.get $ht) (local.get $proc) (local.get $kind))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-map/copy (local.get $ht) (local.get $proc) (local.get $kind))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-map/copy (local.get $ht) (local.get $proc) (local.get $kind))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-map/copy (local.get $ht) (local.get $proc) (local.get $kind))))) (unreachable)) ,@(for/list ([hash-map/copy '($hasheq-map/copy $hasheqv-map/copy $hashequal-map/copy $hashalw-map/copy)] [hash-map/plain '($hasheq-map/copy/plain $hasheqv-map/copy/plain $hashequal-map/copy/plain $hashalw-map/copy/plain)]) `(func ,hash-map/copy (param $ht (ref eq)) (param $proc (ref eq)) (param $kind (ref eq)) (result (ref eq)) (return_call ,hash-map/plain (local.get $ht) (local.get $proc) (local.get $kind)))) ,@(for/list ([hash-map/plain '($hasheq-map/copy/plain $hasheqv-map/copy/plain $hashequal-map/copy/plain $hashalw-map/copy/plain)] [hash-map/checked '($hasheq-map/copy/plain/checked $hasheqv-map/copy/plain/checked $hashequal-map/copy/plain/checked $hashalw-map/copy/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise-expected '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,hash-map/plain (param $ht (ref eq)) (param $proc (ref eq)) (param $kind (ref eq)) (result (ref eq)) (local $table (ref ,type)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) ;; Check hash table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise-expected (local.get $ht)) (unreachable))) ;; Check procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; Decode and fetch invoker (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Delegate (call ,hash-map/checked (local.get $table) (local.get $f) (local.get $finv) (local.get $kind)))) ,@(for/list ([hash-map/checked '($hasheq-map/copy/plain/checked $hasheqv-map/copy/plain/checked $hashequal-map/copy/plain/checked $hashalw-map/copy/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [make-empty '($make-empty-hasheq $make-empty-hasheqv $make-empty-hash $make-empty-hashalw)] [set!/checked '($hasheq-set!/mutable/checked $hasheqv-set!/mutable/checked $hash-set!/mutable/checked $hashalw-set!/mutable/checked)]) `(func ,hash-map/checked (param $table (ref ,type)) (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (param $kind (ref eq)) ;; optional kind, ignored (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $i i32) (local $key (ref eq)) (local $val (ref eq)) (local $nkey (ref eq)) (local $nval (ref eq)) (local $dst (ref ,type)) (local $call (ref $Args)) (local $res (ref eq)) (local $vals (ref $Values)) ;; Initialize non-defaultable locals. (local.set $kind (global.get $false)) ; todo: remove, when kind support is added. (local.set $nkey (global.get $zero)) (local.set $nval (global.get $zero)) ;; Create destination hash. Only mutable hashes are supported. (local.set $dst (ref.cast (ref ,type) (call ,make-empty))) ;; Prepare iteration (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.const 0)) (local.set $call (array.new_fixed $Args 2 (global.get $null) (global.get $null))) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) ;; Load key (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) ;; Skip empty or tombstone (if (ref.eq (local.get $key) (global.get $missing)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Load value (local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $i) (i32.const 1)) (i32.const 1)))) ;; Call procedure (array.set $Args (local.get $call) (i32.const 0) (local.get $key)) (array.set $Args (local.get $call) (i32.const 1) (local.get $val)) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $call) (local.get $finv))) ;; Expect two values (if (ref.test (ref $Values) (local.get $res)) (then (local.set $vals (ref.cast (ref $Values) (local.get $res))) (if (i32.ne (array.len (local.get $vals)) (i32.const 2)) (then (call $raise-wrong-number-of-values-received) (unreachable))) (local.set $nkey (array.get $Values (local.get $vals) (i32.const 0))) (local.set $nval (array.get $Values (local.get $vals) (i32.const 1)))) (else (call $raise-wrong-number-of-values-received) (unreachable))) ;; Insert into destination table (call ,set!/checked (local.get $dst) (local.get $nkey) (local.get $nval)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $dst))) ;; ------------------------------------------------------------------- ;; hash-filter, hash-filter-keys, hash-filter-values ;; ------------------------------------------------------------------- ,@(for/list ([prim '($hash-filter $hash-filter-keys $hash-filter-values)] [mode '(0 1 2)]) `(func ,prim (type $Prim2) (param $ht (ref eq)) ;; hash table (param $proc (ref eq)) ;; predicate (result (ref eq)) ;; filtered hash ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Validate predicate (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call $hasheq-filter (local.get $ht) (local.get $proc) (i32.const ,mode))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call $hasheqv-filter (local.get $ht) (local.get $proc) (i32.const ,mode))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call $hashequal-filter (local.get $ht) (local.get $proc) (i32.const ,mode))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call $hashalw-filter (local.get $ht) (local.get $proc) (i32.const ,mode))))) (unreachable))) ,@(for/list ([filter '($hasheq-filter $hasheqv-filter $hashequal-filter $hashalw-filter)] [plain '($hasheq-filter/plain $hasheqv-filter/plain $hashequal-filter/plain $hashalw-filter/plain)]) `(func ,filter (param $ht (ref eq)) (param $proc (ref eq)) (param $mode i32) (result (ref eq)) (return_call ,plain (local.get $ht) (local.get $proc) (local.get $mode)))) ,@(for/list ([plain '($hasheq-filter/plain $hasheqv-filter/plain $hashequal-filter/plain $hashalw-filter/plain)] [checked '($hasheq-filter/plain/checked $hasheqv-filter/plain/checked $hashequal-filter/plain/checked $hashalw-filter/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) `(func ,plain (param $ht (ref eq)) (param $proc (ref eq)) (param $mode i32) (result (ref eq)) (local $table (ref ,type)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise (local.get $ht)) (unreachable))) ;; Validate predicate (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) ;; Delegate to checked implementation (call ,checked (local.get $table) (local.get $f) (local.get $finv) (local.get $mode)))) ,@(for/list ([checked '($hasheq-filter/plain/checked $hasheqv-filter/plain/checked $hashequal-filter/plain/checked $hashalw-filter/plain/checked)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [make-empty '($make-empty-hasheq $make-empty-hasheqv $make-empty-hash $make-empty-hashalw)] [set!/checked '($hasheq-set!/mutable/checked $hasheqv-set!/mutable/checked $hash-set!/mutable/checked $hashalw-set!/mutable/checked)]) `(func ,checked (param $table (ref ,type)) (param $f (ref $Procedure)) (param $finv (ref $ProcedureInvoker)) (param $mode i32) (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $i i32) (local $key (ref eq)) (local $val (ref eq)) (local $dst (ref ,type)) (local $args (ref $Args)) (local $res (ref eq)) ;; Create destination hash (local.set $dst (ref.cast (ref ,type) (call ,make-empty))) ;; Prepare iteration (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.const 0)) (local.set $args (if (result (ref $Args)) (i32.eq (local.get $mode) (i32.const 0)) (then (array.new $Args (global.get $null) (i32.const 2))) (else (array.new $Args (global.get $null) (i32.const 1))))) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) ;; Load key (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) ;; Skip empty or tombstone slots (if (ref.eq (local.get $key) (global.get $missing)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Load value (local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $i) (i32.const 1)) (i32.const 1)))) ;; Prepare predicate arguments based on mode (if (i32.eq (local.get $mode) (i32.const 0)) (then (array.set $Args (local.get $args) (i32.const 0) (local.get $key)) (array.set $Args (local.get $args) (i32.const 1) (local.get $val))) (else (if (i32.eq (local.get $mode) (i32.const 1)) (then (array.set $Args (local.get $args) (i32.const 0) (local.get $key))) (else (array.set $Args (local.get $args) (i32.const 0) (local.get $val)))))) ;; Invoke predicate (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) ;; Insert when predicate result is not #f (if (ref.eq (local.get $res) (global.get $false)) (then (nop)) (else (call ,set!/checked (local.get $dst) (local.get $key) (local.get $val)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $dst))) ;; General hash-keys and hash-values ;; The try-order? argument is accepted but ignored. ,@(for/list ([prim '($hash-keys $hash-values)] [hasheq-prim '($hasheq-keys $hasheq-values)] [hasheqv-prim '($hasheqv-keys $hasheqv-values)] [hashequal-prim '($hashequal-keys $hashequal-values)] [hashalw-prim '($hashalw-keys $hashalw-values)]) `(func ,prim (type $Prim2) (param $ht (ref eq)) ;; hash table (param $try (ref eq)) ;; optional try-order? (default #f) (result (ref eq)) ;; list of keys/values ;; Check type: must be (ref $Hash) (if (i32.eqz (ref.test (ref $Hash) (local.get $ht))) (then (call $raise-argument-error:hash-expected) (unreachable))) ;; Dispatch on table type (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return (call ,hasheq-prim (local.get $ht) (local.get $try))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return (call ,hasheqv-prim (local.get $ht) (local.get $try))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return (call ,hashequal-prim (local.get $ht) (local.get $try))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return (call ,hashalw-prim (local.get $ht) (local.get $try))))) (unreachable))) ,@(append* (for/list ([spec '("keys" "values")] [offset '(0 1)]) (let* ([name spec] [off offset] [mk (λ (base) (string->symbol (format "$~a-~a" base name)))] [mkp (λ (base suf) (string->symbol (format "~a/~a" (symbol->string (mk base)) suf)))]) (append* (for/list ([base '("hasheq" "hasheqv" "hashequal" "hashalw")] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [raise '($raise-argument-error:hasheq-expected $raise-argument-error:hasheqv-expected $raise-argument-error:hash-expected $raise-argument-error:hashalw-expected)]) (let* ([fun (mk base)] [plain (mkp base "plain")] [checked (mkp base "plain/checked")]) `((func ,fun (param $ht (ref eq)) (param $try (ref eq)) (result (ref eq)) (return_call ,plain (local.get $ht) (local.get $try))) (func ,plain (param $ht (ref eq)) (param $try (ref eq)) (result (ref eq)) (local $table (ref ,type)) ;; Check that ht is expected table type (if (i32.eqz (ref.test (ref ,type) (local.get $ht))) (then (call ,raise (local.get $ht)) (unreachable))) ;; Decode (local.set $table (ref.cast (ref ,type) (local.get $ht))) ;; Delegate to checked implementation (call ,checked (local.get $table))) (func ,checked (param $table (ref ,type)) (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $i i32) (local $key (ref eq)) (local $val (ref eq)) (local $acc (ref eq)) ;; Initialize locals (local.set $entries (struct.get ,type $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.const 0)) (local.set $acc (global.get $null)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) ;; Load key (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) ;; Skip empty or tombstone slots (if (ref.eq (local.get $key) (global.get $missing)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (ref.eq (local.get $key) (global.get $tombstone)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ,@(if (zero? off) '() '((local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $i) (i32.const 1)) (i32.const 1)))))) (local.set $acc (call $cons ,(if (zero? off) '(local.get $key) '(local.get $val)) (local.get $acc))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $acc))))))))) ;;; ;;; HASH CODES ;;; (global $next-hash-state (mut i32) (i32.const 0x9e3779b9)) ;; initial seed, can be randomized or fixed (func $splitmix32 (result i32) (local $z i32) ;; z = state += 0x9E3779B9 (local.set $z (i32.add (global.get $next-hash-state) (i32.const 0x9E3779B9))) (global.set $next-hash-state (local.get $z)) ;; z ^= (z >> 16) (local.set $z (i32.xor (local.get $z) (i32.shr_u (local.get $z) (i32.const 16)))) ;; z *= 0x85EBCA6B (local.set $z (i32.mul (local.get $z) (i32.const 0x85EBCA6B))) ;; z ^= (z >> 13) (local.set $z (i32.xor (local.get $z) (i32.shr_u (local.get $z) (i32.const 13)))) ;; z *= 0xC2B2AE35 (local.set $z (i32.mul (local.get $z) (i32.const 0xC2B2AE35))) ;; z ^= (z >> 16) (local.set $z (i32.xor (local.get $z) (i32.shr_u (local.get $z) (i32.const 16)))) (local.get $z)) (func $eq-hash-code (type $Prim1) (param $v (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (call $eq-hash/i32 (local.get $v)) (i32.const 1)))) (func $eq-hash/i32 (param $v (ref eq)) (result i32) (local $v-i31 i32) (local $heap (ref $Heap)) (local $h i32) (local $f (ref $Flonum)) (local $bits i64) (local $low i32) (local $high i32) (local $x i32) (if (result i32) (ref.test (ref i31) (local.get $v)) ;; Immediates (then ;; --- Mix i31 immediate using Murmur3-style scramble --- (local.set $v-i31 (i31.get_u (ref.cast (ref i31) (local.get $v)))) (i32.mul (i32.rotl (i32.mul (local.get $v-i31) (i32.const 0xcc9e2d51)) (i32.const 15)) (i32.const 0x1b873593))) ;; Notes: ;; If the special case is commented out, then eq-hash-code ;; will always produce the same hash code for the same flonum value. ;; But that is not enough to make flonums work with $HashEQ since, ;; we are using (ref.eq ...) to test for equality. ;; (else ;; ;; --- Special case: flonum --- ;; (if (result i32) ;; (ref.test (ref $Flonum) (local.get $v)) ;; (then ;; (local.set $f (ref.cast (ref $Flonum) (local.get $v))) ;; (local.set $bits (i64.reinterpret_f64 ;; (struct.get $Flonum $v (local.get $f)))) ;; (local.set $low (i32.wrap_i64 (local.get $bits))) ;; (local.set $high (i32.wrap_i64 (i64.shr_u (local.get $bits) (i64.const 32)))) ;; (local.set $x (i32.xor (local.get $low) (local.get $high))) ;; ;; Murmur3 mix again ;; (i32.mul ;; (i32.rotl ;; (i32.mul (local.get $x) (i32.const 0xcc9e2d51)) ;; (i32.const 15)) ;; (i32.const 0x1b873593))) (else ;; --- Heap object: return or assign hash without mixing for now --- (local.set $heap (ref.cast (ref $Heap) (local.get $v))) (local.set $h (struct.get $Heap $hash (local.get $heap))) (if (result i32) (i32.eqz (local.get $h)) ;; If the current stored hash value is zero, ;; it means the hash value hasn't been computed yet. (then (local.set $h (call $splitmix32)) ;; If $splitmix32 happens to return 0, we need a different ;; value - since 0 means "not computed yet". (if (i32.eqz (local.get $h)) (then (local.set $h (i32.const 2)))) ; Store we newly computed hash value. (struct.set $Heap $hash (local.get $heap) (local.get $h)) (local.get $h)) ;; Just use the existing hash value. (else (local.get $h)))))) (func $eqv-hash-code (type $Prim1) (param $v (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (call $eqv-hash/i32 (local.get $v)) (i32.const 1)))) (func $eqv-hash/i32 (param $v (ref eq)) (result i32) (local $v-i31 i32) (local $heap (ref $Heap)) (local $h i32) (local $f (ref $Flonum)) (local $bits i64) (local $low i32) (local $high i32) (local $x i32) (if (result i32) (ref.test (ref i31) (local.get $v)) (then ;; --- Mix i31 immediate using Murmur3-style scramble --- (local.set $v-i31 (i31.get_u (ref.cast (ref i31) (local.get $v)))) (i32.mul (i32.rotl (i32.mul (local.get $v-i31) (i32.const 0xcc9e2d51)) (i32.const 15)) (i32.const 0x1b873593))) (else ;; --- Special case: flonum hashed by value --- (if (result i32) (ref.test (ref $Flonum) (local.get $v)) (then (local.set $f (ref.cast (ref $Flonum) (local.get $v))) (local.set $bits (i64.reinterpret_f64 (struct.get $Flonum $v (local.get $f)))) (local.set $low (i32.wrap_i64 (local.get $bits))) (local.set $high (i32.wrap_i64 (i64.shr_u (local.get $bits) (i64.const 32)))) (local.set $x (i32.xor (local.get $low) (local.get $high))) ;; Murmur3 mix again (i32.mul (i32.rotl (i32.mul (local.get $x) (i32.const 0xcc9e2d51)) (i32.const 15)) (i32.const 0x1b873593))) (else ;; --- Heap object: return or assign hash without mixing for now --- (local.set $heap (ref.cast (ref $Heap) (local.get $v))) (local.set $h (struct.get $Heap $hash (local.get $heap))) (if (result i32) (i32.eqz (local.get $h)) ;; If the current stored hash value is zero, ;; it means the hash value hasn't been computed yet. (then (local.set $h (call $splitmix32)) ;; If $splitmix32 happens to return 0, we need a different ;; value - since 0 means "not computed yet". (if (i32.eqz (local.get $h)) (then (local.set $h (i32.const 2)))) ; Store the newly computed hash value. (struct.set $Heap $hash (local.get $heap) (local.get $h)) (local.get $h)) ;; Just use the existing hash value. (else (local.get $h)))))))) ;;; Equal Hash Code ;; equal-hash-code -- computes a hash code consistent with equal? ;; Supports struct customization via prop:equal+hash. ;; Note: currently does not support user-defined extensions via ;; gen:equal+hash or gen:equal-mode+hash. (func $equal-hash-code (type $Prim1) (param $v (ref eq)) (result (ref eq)) (ref.i31 (i32.shl (call $equal-hash/i32 (local.get $v)) (i32.const 1)))) (func $equal-hash/i32 (param $v (ref eq)) (result i32) (local $str (ref $String)) (local $bs (ref $Bytes)) (local $kw (ref $Keyword)) (local $h i32) ;; immediates and numbers fall back to eqv-hash (if (ref.test (ref i31) (local.get $v)) (then (return (call $eqv-hash/i32 (local.get $v))))) (if (ref.test (ref $Flonum) (local.get $v)) (then (return (call $eqv-hash/i32 (local.get $v))))) ;; strings (if (ref.test (ref $String) (local.get $v)) (then (local.set $str (ref.cast (ref $String) (local.get $v))) (local.set $h (call $string-hash/i32 (local.get $str))) ;; reset memoized hash so eq-hash-code can still assign (struct.set $String $hash (local.get $str) (i32.const 0)) (return (local.get $h)))) ;; keywords (if (ref.test (ref $Keyword) (local.get $v)) (then (local.set $kw (ref.cast (ref $Keyword) (local.get $v))) (local.set $str (struct.get $Keyword $str (local.get $kw))) (local.set $h (call $string-hash/i32 (local.get $str))) ;; reset memoized hashes so eq-hash-code can still assign (struct.set $String $hash (local.get $str) (i32.const 0)) (struct.set $Keyword $hash (local.get $kw) (i32.const 0)) (return (local.get $h)))) ;; bytes (if (ref.test (ref $Bytes) (local.get $v)) (then (local.set $bs (ref.cast (ref $Bytes) (local.get $v))) (return (call $bytes-hash/i32 (local.get $bs))))) ;; pair (if (ref.test (ref $Pair) (local.get $v)) (then (return_call $equal-hash/pair (ref.cast (ref $Pair) (local.get $v))))) ;; mpair (if (ref.test (ref $MPair) (local.get $v)) (then (return_call $equal-hash/mpair (ref.cast (ref $MPair) (local.get $v))))) ;; box (if (ref.test (ref $Box) (local.get $v)) (then (return_call $equal-hash/box (ref.cast (ref $Box) (local.get $v))))) ;; vector (if (ref.test (ref $Vector) (local.get $v)) (then (return_call $equal-hash/vector (ref.cast (ref $Vector) (local.get $v))))) ;; struct (if (ref.test (ref $Struct) (local.get $v)) (then (return_call $equal-hash/struct (ref.cast (ref $Struct) (local.get $v))))) ;; hash table (if (ref.test (ref $Hash) (local.get $v)) (then (return_call $equal-hash/hash (ref.cast (ref $Hash) (local.get $v))))) ;; fallback to eqv-hash for other heap objects (symbols, etc.) (return (call $eqv-hash/i32 (local.get $v)))) (func $equal-hash/pair (param $p (ref $Pair)) (result i32) (local $heap (ref $Heap)) (local $h i32) (local $ha i32) (local $hd i32) (local.set $heap (ref.cast (ref $Heap) (local.get $p))) (local.set $h (struct.get $Heap $hash (local.get $heap))) ;; cycle detection (if (i32.eq (local.get $h) (i32.const -2147483648)) (then (return (i32.const 0)))) (struct.set $Heap $hash (local.get $heap) (i32.const -2147483648)) (local.set $ha (call $equal-hash/i32 (struct.get $Pair $a (local.get $p)))) (local.set $hd (call $equal-hash/i32 (struct.get $Pair $d (local.get $p)))) (struct.set $Heap $hash (local.get $heap) (i32.const 0)) (local.set $h (i32.add (i32.mul (local.get $ha) (i32.const 33)) (local.get $hd))) (local.get $h)) (func $equal-hash/mpair (param $p (ref $MPair)) (result i32) (local $heap (ref $Heap)) (local $h i32) (local $ha i32) (local $hd i32) (local.set $heap (ref.cast (ref $Heap) (local.get $p))) (local.set $h (struct.get $Heap $hash (local.get $heap))) ;; cycle detection (if (i32.eq (local.get $h) (i32.const -2147483648)) (then (return (i32.const 0)))) (struct.set $Heap $hash (local.get $heap) (i32.const -2147483648)) (local.set $ha (call $equal-hash/i32 (struct.get $MPair $a (local.get $p)))) (local.set $hd (call $equal-hash/i32 (struct.get $MPair $d (local.get $p)))) (struct.set $Heap $hash (local.get $heap) (i32.const 0)) (local.set $h (i32.add (i32.mul (local.get $ha) (i32.const 33)) (local.get $hd))) (local.get $h)) (func $equal-hash/box (param $b (ref $Box)) (result i32) (local $heap (ref $Heap)) (local $h i32) (local.set $heap (ref.cast (ref $Heap) (local.get $b))) (local.set $h (struct.get $Heap $hash (local.get $heap))) (if (i32.eq (local.get $h) (i32.const -2147483648)) (then (return (i32.const 0)))) (struct.set $Heap $hash (local.get $heap) (i32.const -2147483648)) (local.set $h (call $equal-hash/i32 (struct.get $Box $v (local.get $b)))) (struct.set $Heap $hash (local.get $heap) (i32.const 0)) (i32.add (i32.const 1) (local.get $h))) (func $equal-hash/vector (param $v (ref $Vector)) (result i32) (local $heap (ref $Heap)) (local $arr (ref $Array)) (local $len i32) (local $i i32) (local $h i32) (local $elem (ref eq)) (local.set $heap (ref.cast (ref $Heap) (local.get $v))) (local.set $h (struct.get $Heap $hash (local.get $heap))) (if (i32.eq (local.get $h) (i32.const -2147483648)) (then (return (i32.const 0)))) (struct.set $Heap $hash (local.get $heap) (i32.const -2147483648)) (local.set $arr (struct.get $Vector $arr (local.get $v))) (local.set $len (array.len (local.get $arr))) (local.set $i (i32.const 0)) (local.set $h (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $elem (array.get $Array (local.get $arr) (local.get $i))) (local.set $h (i32.add (i32.mul (local.get $h) (i32.const 33)) (call $equal-hash/i32 (local.get $elem)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (struct.set $Heap $hash (local.get $heap) (i32.const 0)) (local.get $h)) (func $equal-hash/struct (param $s (ref $Struct)) (result i32) (local $heap (ref $Heap)) (local $type (ref $StructType)) (local $fields (ref $Array)) (local $len i32) (local $i i32) (local $h i32) (local $elem (ref eq)) ; for prop:equal+hash if present (local $prop-name (ref $Symbol)) (local $prop-sentinel (ref eq)) (local $prop-val (ref eq)) (local $prop-info (ref $Array)) (local.set $heap (ref.cast (ref $Heap) (local.get $s))) (local.set $h (struct.get $Heap $hash (local.get $heap))) (if (i32.eq (local.get $h) (i32.const -2147483648)) (then (return (i32.const 0)))) (struct.set $Heap $hash (local.get $heap) (i32.const -2147483648)) (local.set $type (struct.get $Struct $type (local.get $s))) (local.set $prop-name (ref.cast (ref $Symbol) (global.get $symbol:prop:equal+hash))) (local.set $prop-sentinel (call $cons (global.get $false) (global.get $false))) (local.set $prop-val (call $struct-type-property-lookup-by-name (local.get $type) (local.get $prop-name) (local.get $prop-sentinel))) (if (i32.eqz (ref.eq (local.get $prop-val) (local.get $prop-sentinel))) (then (local.set $prop-info (ref.cast (ref $Array) (local.get $prop-val))) (local.set $h (call $struct-equal+hash-hash (local.get $prop-info) (local.get $s) (i32.const 1))) (struct.set $Heap $hash (local.get $heap) (i32.const 0)) (return (local.get $h)))) (local.set $h (call $eqv-hash/i32 (local.get $type))) (local.set $fields (struct.get $Struct $fields (local.get $s))) (local.set $len (array.len (local.get $fields))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $elem (array.get $Array (local.get $fields) (local.get $i))) (local.set $h (i32.add (i32.mul (local.get $h) (i32.const 33)) (call $equal-hash/i32 (local.get $elem)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (struct.set $Heap $hash (local.get $heap) (i32.const 0)) (local.get $h)) (func $equal-hash/hash (param $ht (ref eq)) ;; hash table (result i32) (if (ref.test (ref $HashEqMutable) (local.get $ht)) (then (return_call $equal-hash/hasheq-mutable (ref.cast (ref $HashEqMutable) (local.get $ht))))) (if (ref.test (ref $HashEqvMutable) (local.get $ht)) (then (return_call $equal-hash/hasheqv-mutable (ref.cast (ref $HashEqvMutable) (local.get $ht))))) (if (ref.test (ref $HashEqualMutable) (local.get $ht)) (then (return_call $equal-hash/hashequal-mutable (ref.cast (ref $HashEqualMutable) (local.get $ht))))) (if (ref.test (ref $HashEqualAlwaysMutable) (local.get $ht)) (then (return_call $equal-hash/hashalw-mutable (ref.cast (ref $HashEqualAlwaysMutable) (local.get $ht))))) (return (call $eqv-hash/i32 (local.get $ht)))) (func $equal-hash/hash-core (param $heap (ref $Heap)) (param $entries (ref $Array)) (param $count i32) (param $salt i32) (result i32) (local $h i32) (local $capacity i32) (local $i i32) (local $key (ref eq)) (local $val (ref eq)) (local $entry-h i32) (local $key-h i32) (local $val-h i32) (local.set $h (struct.get $Heap $hash (local.get $heap))) (if (i32.eq (local.get $h) (i32.const -2147483648)) (then (return (i32.const 0)))) (struct.set $Heap $hash (local.get $heap) (i32.const -2147483648)) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $i (i32.const 0)) (local.set $h (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $capacity))) (local.set $key (array.get $Array (local.get $entries) (i32.shl (local.get $i) (i32.const 1)))) (if (i32.or (ref.eq (local.get $key) (global.get $missing)) (ref.eq (local.get $key) (global.get $tombstone))) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.set $val (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $i) (i32.const 1)) (i32.const 1)))) (local.set $key-h (call $equal-hash/i32 (local.get $key))) (local.set $entry-h (i32.mul (i32.rotl (i32.mul (local.get $key-h) (i32.const 0xcc9e2d51)) (i32.const 15)) (i32.const 0x1b873593))) (local.set $val-h (call $equal-hash/i32 (local.get $val))) (local.set $entry-h (i32.xor (local.get $entry-h) (local.get $val-h))) (local.set $entry-h (i32.mul (i32.rotl (i32.mul (local.get $entry-h) (i32.const 0xcc9e2d51)) (i32.const 15)) (i32.const 0x1b873593))) (local.set $h (i32.add (local.get $h) (local.get $entry-h))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.set $h (i32.add (local.get $h) (local.get $count))) (local.set $h (i32.add (local.get $h) (local.get $salt))) (local.set $h (i32.mul (i32.rotl (i32.mul (local.get $h) (i32.const 0xcc9e2d51)) (i32.const 15)) (i32.const 0x1b873593))) (struct.set $Heap $hash (local.get $heap) (i32.const 0)) (local.get $h)) ,@(for/list ([name '($equal-hash/hasheq-mutable $equal-hash/hasheqv-mutable $equal-hash/hashequal-mutable $equal-hash/hashalw-mutable)] [type '($HashEqMutable $HashEqvMutable $HashEqualMutable $HashEqualAlwaysMutable)] [salt '(0 1 2 3)]) `(func ,name (param $ht (ref ,type)) (result i32) (return_call $equal-hash/hash-core (ref.cast (ref $Heap) (local.get $ht)) (struct.get ,type $entries (local.get $ht)) (struct.get ,type $count (local.get $ht)) (i32.const ,salt)))) (func $bytes-hash/i32 ; 32‑bit FNV‑1a hash algorithm (param $b (ref $Bytes)) (result i32) (local $hash i32) (local $arr (ref $I8Array)) (local $len i32) (local $i i32) (local $byte i32) (local.set $hash (i32.const 2166136261)) (local.set $arr (struct.get $Bytes $bs (local.get $b))) (local.set $len (array.len (local.get $arr))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $i))) (local.set $hash (i32.mul (i32.xor (local.get $hash) (local.get $byte)) (i32.const 16777619))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $hash)) ;;; ;;; SYMBOL TABLE ;;; ; We'll use an open-addressing hash table with linear probing for simplicity. ; A load of 50% leads to fast lookup - but uses some more memory. ; Theory: https://thenumb.at/Hashtables/ ; We do not support deleting symbols from the symbol table, so we do not ; need to handle tombstones. (type $SymbolTable (struct (field $entries (mut (ref $Array))) ;; flat array: key0, val0, key1, val1, ... (field $count (mut i32)))) ;; number of symbols currently stored ; The $Array is a flat array: [key0 val0 key1 val1 ...]. ; Capacity is half the array length (since entries are key-value pairs). ; Keys are (ref $String), values are (ref $Symbol). ; Count is number of active entries (not tombstones). (func $make-symbol-table (result (ref $SymbolTable)) (local $entries (ref $Array)) ;; Initial capacity is 16, so total entries = 2 * 16 = 32 (local.set $entries (array.new $Array (global.get $missing) (i32.const 1024))) ; todo: was 32 - was 1024 (struct.new $SymbolTable (local.get $entries) ;; $entries (i32.const 0))) ;; $count (func $symbol-table-find ; returns $missing for "not found" (param $table (ref $SymbolTable)) (param $key (ref $String)) (result (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $hash i32) (local $index i32) (local $step i32) (local $slot i32) (local $k (ref eq)) ;; Get the entries array and capacity (local.set $entries (struct.get $SymbolTable $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) ;; Compute hash and initial index (local.set $hash (call $string-hash/i32 (local.get $key))) (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) (local.set $step (i32.const 0)) (block $not-found (loop $probe ;; Stop probing if we’ve gone through every slot (br_if $not-found (i32.ge_u (local.get $step) (local.get $capacity))) ;; Compute probe slot: (index + step) % capacity (local.set $slot (i32.rem_u (i32.add (local.get $index) (local.get $step)) (local.get $capacity))) ;; Load key at 2 * slot (local.set $k (array.get $Array (local.get $entries) (i32.shl (local.get $slot) (i32.const 1)))) ;; If slot is unused, stop (br_if $not-found (ref.eq (local.get $k) (global.get $missing))) ;; If match, return value at index + 1 (if #;(or (ref.eq (local.get $k) (local.get $key)) (call $string=?/i32 (local.get $k) (local.get $key))) (call $string=?/i32 (local.get $k) (local.get $key)) (then (return ; returns value at slot 2*slot+1. (array.get $Array (local.get $entries) (i32.add (i32.shl (local.get $slot) (i32.const 1)) (i32.const 1)))))) ;; Try next slot (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Not found (global.get $missing)) (func $raise-symbol-table-insert:table-full (unreachable)) (func $symbol-table-insert (param $table (ref $SymbolTable)) (param $key (ref $String)) ;; must be interned or immutable (param $val (ref eq)) (local $entries (ref $Array)) (local $capacity i32) (local $hash i32) (local $index i32) (local $step i32) (local $k (ref eq)) (local $slot i32) ;; Possibly resize before inserting (local.set $table (call $maybe-resize-symbol-table (local.get $table))) ;; Get updated entries array and capacity (local.set $entries (struct.get $SymbolTable $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) ;; Compute hash and initial index (local.set $hash (call $string-hash/i32 (local.get $key))) ; (local.set $index (i32.rem_u (local.get $hash) (local.get $capacity))) (local.set $step (i32.const 0)) (block $done (block $full (loop $probe ;; Stop if probing exceeds capacity (should never happen if resize worked) (br_if $full (i32.ge_u (local.get $step) (local.get $capacity))) ;; Compute probe index and actual slot (2 * index) (local.set $slot (i32.shl (i32.rem_u (i32.add (local.get $index) (local.get $step)) (local.get $capacity)) (i32.const 1))) ;; multiply by 2 ;; Load key from slot (local.set $k (array.get $Array (local.get $entries) (local.get $slot))) ;; Empty slot — insert (if (ref.eq (local.get $k) (global.get $missing)) (then (array.set $Array (local.get $entries) (local.get $slot) (local.get $key)) (array.set $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)) (local.get $val)) (struct.set $SymbolTable $count (local.get $table) (i32.add (struct.get $SymbolTable $count (local.get $table)) (i32.const 1))) (br $done))) ;; Key match — update value (if (call $string=?/i32 (local.get $k) (local.get $key)) (then (array.set $Array (local.get $entries) (i32.add (local.get $slot) (i32.const 1)) (local.get $val)) (br $done))) ;; Otherwise, try next probe (local.set $step (i32.add (local.get $step) (i32.const 1))) (br $probe))) ;; Failed to insert — table full (should not happen if resizing is correct) (call $raise-symbol-table-insert:table-full))) (func $symbol-table-resize (param $table (ref $SymbolTable)) (result (ref $SymbolTable)) (local $old-entries (ref $Array)) (local $old-cap i32) (local $new-cap i32) (local $new-array (ref $Array)) (local $new-table (ref $SymbolTable)) (local $i i32) (local $len i32) (local $key (ref eq)) (local $val (ref eq)) ;; Get old table size and entries (local.set $old-entries (struct.get $SymbolTable $entries (local.get $table))) (local.set $old-cap (i32.div_u (array.len (local.get $old-entries)) (i32.const 2))) (local.set $new-cap (i32.mul (local.get $old-cap) (i32.const 2))) ;; Allocate new entries array with (2 * new-capacity) slots filled with $missing (local.set $new-array (array.new $Array (global.get $missing) (i32.mul (local.get $new-cap) (i32.const 2)))) ;; Create new empty table with count = 0 (local.set $new-table (struct.new $SymbolTable (local.get $new-array) (i32.const 0))) ;; Reinsert old entries (local.set $i (i32.const 0)) (local.set $len (array.len (local.get $old-entries))) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) ;; Get key and value (local.set $key (array.get $Array (local.get $old-entries) (local.get $i))) (local.set $val (array.get $Array (local.get $old-entries) (i32.add (local.get $i) (i32.const 1)))) ;; Insert if key is not $missing (i.e. dummy) (if (ref.test (ref $String) (local.get $key)) (then (call $symbol-table-insert (local.get $new-table) (ref.cast (ref $String) (local.get $key)) (local.get $val)))) ;; Step to next pair (local.set $i (i32.add (local.get $i) (i32.const 2))) (br $loop))) ; Store changes in the original table (struct.set $SymbolTable $entries (local.get $table) (local.get $new-array)) (struct.set $SymbolTable $count (local.get $table) (struct.get $SymbolTable $count (local.get $new-table))) (local.get $table)) (func $maybe-resize-symbol-table (param $table (ref $SymbolTable)) (result (ref $SymbolTable)) (local $entries (ref $Array)) (local $capacity i32) (local $count i32) ;; Get fields (local.set $entries (struct.get $SymbolTable $entries (local.get $table))) (local.set $capacity (i32.div_u (array.len (local.get $entries)) (i32.const 2))) (local.set $count (struct.get $SymbolTable $count (local.get $table))) ;; Resize if count >= capacity / 2 (if (i32.ge_u (local.get $count) (i32.shr_u (local.get $capacity) (i32.const 1))) (then (return (call $symbol-table-resize (local.get $table))))) ;; Otherwise return unchanged (local.get $table)) ; Fowler–Noll–Vo hash function ; Note: Since 0 means "hash not computed yet", $string-hash/i32 must ; return a non-zero value. (func $string-hash/i32 (param $s (ref $String)) (result i32) (local $hash i32) (local $arr (ref $I32Array)) (local $len i32) (local $i i32) (local $cp i32) ;; Check if already memoized (local.set $hash (struct.get $String $hash (local.get $s))) (if (result i32) (i32.ne (local.get $hash) (i32.const 0)) (then (return (local.get $hash))) ;; Already cached ;; Compute FNV-1a hash (else (local.set $hash (i32.const 2166136261)) ; offset (local.set $arr (struct.get $String $codepoints (local.get $s))) (local.set $len (array.len (local.get $arr))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $cp (array.get $I32Array (local.get $arr) (local.get $i))) (local.set $hash (i32.mul (i32.xor (local.get $hash) (local.get $cp)) (i32.const 16777619))) ; prime (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Ensure non-zero before memoizing (if (i32.eqz (local.get $hash)) (then (local.set $hash (i32.const 2)))) ;; Memoize (struct.set $String $hash (local.get $s) (local.get $hash)) (local.get $hash)))) ;;; ;;; LOCATION ;;; ;; When counting lines, Racket treats linefeed, return, and ;; return-linefeed combinations as a line terminator and as a single ;; position (on all platforms). ;; Each tab advances the column count to one before the next multiple ;; of 8. ;; When a sequence of bytes in the range 128 to 253 forms a ;; UTF-8 encoding of a character, the position/column is incremented ;; once for each byte, and then decremented appropriately ;; when a complete encoding sequence is discovered. ;; (array line column pos) ;; pos and line counts from 1 ;; column counts from 0 (func $make-initial-location (result (ref eq)) ;; (location 1 0 1) (struct.new $Location (i32.const 0) ;; hash (placeholder) (global.get $one) ;; line (global.get $zero) ;; col (global.get $one))) ;; pos ;;; ;;; 13.1.6 STRING PORT ;;; ;; https://docs.racket-lang.org/reference/ports.html ;; A string port reads or writes from a byte string. An input ;; string port can be created from either a byte string or a string; ;; in the latter case, the string is effectively converted to a byte ;; string using string->bytes/utf-8. An output string port collects ;; output into a byte string, but get-output-string conveniently converts ;; the accumulated bytes to a string. ;; Note: the index `idx` and the location position may be different, ;; since the #\return#\newline combination counts as a ;; single position. ;; [x] string-port? ;; [x] open-input-bytes ;; [x] open-input-string ;; [x] open-output-bytes ;; [x] open-output-string ;; [x] get-output-bytes ;; [x] get-output-string ;; [ ] write-byte (func $raise-check-string-port (param $x (ref eq)) (unreachable)) (func $raise-check-port-or-false (param $x (ref eq)) (unreachable)) (func $string-port? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (i32.or (ref.test (ref $InputStringPort) (local.get $v)) (ref.test (ref $OutputStringPort) (local.get $v))) (then (global.get $true)) (else (global.get $false)))) ;; Note: ;; Currently WebRacket only supports string ports. Distinguish ;; between input and output ports based on the mutability of the ;; underlying byte string: input ports store immutable bytes, while ;; output ports keep a mutable byte buffer that grows as data is ;; written. (func $port? (type $Prim1) ,@(make-predicate-body '$Port)) (func $input-port? (type $Prim1) ,@(make-predicate-body '$InputPort)) (func $output-port? (type $Prim1) ,@(make-predicate-body '$OutputPort)) (func $port-closed? (type $Prim1) (param $p (ref eq)) ;; port? (result (ref eq)) (local $port (ref $Port)) (if (i32.eqz (ref.test (ref $Port) (local.get $p))) (then (call $raise-argument-error1 (global.get $symbol:port-closed?) (global.get $string:port?) (local.get $p)) (unreachable))) (local.set $port (ref.cast (ref $Port) (local.get $p))) (if (result (ref eq)) (struct.get $Port $closed (local.get $port)) (then (global.get $true)) (else (global.get $false)))) ;; close-input-port : input-port? -> void? ;; Call a custom input port's close thunk once, then mark the port closed. (func $close-input-port (type $Prim1) (param $p (ref eq)) ;; input-port? (result (ref eq)) (local $port (ref $InputPort)) (local $custom (ref $CustomInputPort)) (local $close-proc (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (if (i32.eqz (ref.test (ref $InputPort) (local.get $p))) (then (call $raise-argument-error1 (global.get $symbol:close-input-port) (global.get $string:input-port?) (local.get $p)) (unreachable))) (local.set $port (ref.cast (ref $InputPort) (local.get $p))) (if (struct.get $InputPort $closed (local.get $port)) (then (return (global.get $void)))) (if (ref.test (ref $CustomInputPort) (local.get $p)) (then (local.set $custom (ref.cast (ref $CustomInputPort) (local.get $p))) (local.set $close-proc (struct.get $CustomInputPort $close-proc (local.get $custom))) (if (ref.test (ref $Procedure) (local.get $close-proc)) (then (local.set $f (ref.cast (ref $Procedure) (local.get $close-proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new_fixed $Args 0)) (drop (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))))))) (struct.set $InputPort $closed (local.get $port) (i32.const 1)) (global.get $void)) ;; current-input-port : [input-port?] -> input-port? or void? ;; Get or set the runtime current input port. (func $current-input-port (type $Prim01) (param $p (ref eq)) ;; optional input-port? (result (ref eq)) (if (ref.eq (local.get $p) (global.get $missing)) (then (return (global.get $current-input-port-value)))) (if (i32.eqz (ref.test (ref $InputPort) (local.get $p))) (then (call $raise-argument-error1 (global.get $symbol:current-input-port) (global.get $string:input-port?) (local.get $p)) (unreachable))) (global.set $current-input-port-value (local.get $p)) (global.get $void)) ;; current-output-port : [output-port?] -> output-port? or void? ;; Get or set the runtime current output port. (func $current-output-port (type $Prim01) (param $p (ref eq)) ;; optional output-port? (result (ref eq)) (if (ref.eq (local.get $p) (global.get $missing)) (then (return (global.get $current-output-port-value)))) (if (i32.eqz (ref.test (ref $OutputPort) (local.get $p))) (then (call $raise-argument-error1 (global.get $symbol:current-output-port) (global.get $string:output-port?) (local.get $p)) (unreachable))) (global.set $current-output-port-value (local.get $p)) (global.get $void)) ;; current-error-port : [output-port?] -> output-port? or void? ;; Get or set the runtime current error port. (func $current-error-port (type $Prim01) (param $p (ref eq)) ;; optional output-port? (result (ref eq)) (if (ref.eq (local.get $p) (global.get $missing)) (then (return (global.get $current-error-port-value)))) (if (i32.eqz (ref.test (ref $OutputPort) (local.get $p))) (then (call $raise-argument-error1 (global.get $symbol:current-error-port) (global.get $string:output-port?) (local.get $p)) (unreachable))) (global.set $current-error-port-value (local.get $p)) (global.get $void)) ;; reset-current-input-port! : -> void? ;; Reset the runtime current input port to an empty string port. (func $reset-current-input-port! (type $Prim0) (result (ref eq)) (global.set $current-input-port-value (call $open-input-string (global.get $string:empty) (global.get $missing))) (global.get $void)) ;; reset-current-output-port! : -> void? ;; Reset the runtime current output port to a fresh output string port. (func $reset-current-output-port! (type $Prim0) (result (ref eq)) (global.set $current-output-port-value (call $open-output-string (global.get $missing))) (global.get $void)) ;; reset-current-error-port! : -> void? ;; Reset the runtime current error port to a fresh output string port. (func $reset-current-error-port! (type $Prim0) (result (ref eq)) (global.set $current-error-port-value (call $open-output-string (global.get $missing))) (global.get $void)) ;; flush-output : [output-port?] -> void? ;; Flush a VFS output file port; string and bytes output ports are no-ops. (func $flush-output (type $Prim01) (param $p (ref eq)) ;; optional output-port?, default = current output port (result (ref eq)) (local $port (ref $OutputPort)) (local $file-port (ref $OutputFilePort)) (if (ref.eq (local.get $p) (global.get $missing)) (then (local.set $p (call $current-output-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $OutputPort) (local.get $p))) (then (call $raise-argument-error1 (global.get $symbol:flush-output) (global.get $string:output-port?) (local.get $p)) (unreachable))) (local.set $port (ref.cast (ref $OutputPort) (local.get $p))) (if (struct.get $OutputPort $closed (local.get $port)) (then (call $raise-output-port-closed (global.get $string:flush-output:output-port-closed)) (unreachable))) (if (ref.test (ref $OutputFilePort) (local.get $p)) (then (local.set $file-port (ref.cast (ref $OutputFilePort) (local.get $p))) (drop (call $webracket-vfs-write-file (struct.get $OutputFilePort $path (local.get $file-port)) (call $get-output-bytes (local.get $p)))))) (global.get $void)) ;; close-output-port : output-port? -> void? ;; Flush a VFS file output port if needed, then mark the port closed. (func $close-output-port (type $Prim1) (param $p (ref eq)) ;; output-port? (result (ref eq)) (local $port (ref $OutputPort)) (if (i32.eqz (ref.test (ref $OutputPort) (local.get $p))) (then (call $raise-argument-error1 (global.get $symbol:close-output-port) (global.get $string:output-port?) (local.get $p)) (unreachable))) (local.set $port (ref.cast (ref $OutputPort) (local.get $p))) (if (struct.get $OutputPort $closed (local.get $port)) (then (return (global.get $void)))) (drop (call $flush-output (local.get $p))) (struct.set $OutputPort $closed (local.get $port) (i32.const 1)) (global.get $void)) ;; Note: ;; WebRacket's current string ports always track line and column ;; information, so enabling line counting is a no-op. ;; ;; For now the primitives port-count-lines! and port-counts-lines? ;; exists to preserve compatibility with Racket ;; programs that explicitly request line counting. ;; ;; When support for file ports is added, these functions needs ;; to be revised. (func $port-count-lines! (type $Prim1) (param $p (ref eq)) (result (ref eq)) (global.get $void)) ;; Note: ;; See previous note. (func $port-counts-lines? (type $Prim1) (param $p (ref eq)) (result (ref eq)) (global.get $true)) (func $port-next-location (type $Prim1) (param $p (ref eq)) (result (ref eq)) (local $port (ref null $Port)) (local $loc (ref null $Location)) ;; 1. Check and cast $p to (ref $Port) (if (ref.test (ref $Port) (local.get $p)) (then (local.set $port (ref.cast (ref $Port) (local.get $p)))) (else (return (global.get $false)))) ;; 2. Get location struct from the port (local.set $loc (struct.get $Port $loc (local.get $port))) ;; 3. Extract and return as fixed array of 3 elements (array.new_fixed $Values 3 (struct.get $Location $line (local.get $loc)) (struct.get $Location $col (local.get $loc)) (struct.get $Location $pos (local.get $loc)))) (func $open-input-bytes (type $Prim12) (param $bstr-raw (ref eq)) ;; bytes? (param $name (ref eq)) ;; optional any/c, default = 'string (result (ref eq)) (local $bstr (ref $Bytes)) (local $port-bs (ref $Bytes)) (local $name-val (ref eq)) (local $src-arr (ref $I8Array)) (local $arr (ref $I8Array)) (local $len i32) (local $loc (ref $Location)) (local $immutable i32) ;; --- Initialize non-defaultable locals --- (local.set $bstr (ref.cast (ref $Bytes) (global.get $bytes:empty))) (local.set $port-bs (ref.cast (ref $Bytes) (global.get $bytes:empty))) ;; --- Validate byte string argument --- (if (i32.eqz (ref.test (ref $Bytes) (local.get $bstr-raw))) (then (call $raise-check-bytes (local.get $bstr-raw)))) (local.set $bstr (ref.cast (ref $Bytes) (local.get $bstr-raw))) ;; --- Determine port name, honoring optional argument --- (local.set $name-val (if (result (ref eq)) (ref.eq (local.get $name) (global.get $missing)) (then (global.get $symbol:string)) (else (local.get $name)))) ;; --- Ensure the port holds an immutable copy of the bytes --- (local.set $immutable (struct.get $Bytes $immutable (local.get $bstr))) (local.set $src-arr (struct.get $Bytes $bs (local.get $bstr))) (if (i32.eq (local.get $immutable) (i32.const 1)) (then (local.set $port-bs (local.get $bstr))) (else (local.set $port-bs (struct.new $Bytes (struct.get $Bytes $hash (local.get $bstr)) (i32.const 1) (call $i8array-copy (local.get $src-arr) (i32.const 0) (call $i8array-length (local.get $src-arr))))))) ;; --- Cache backing array and byte length --- (local.set $arr (struct.get $Bytes $bs (local.get $port-bs))) (local.set $len (array.len (local.get $arr))) ;; --- Initialize location and construct the port --- (local.set $loc (ref.cast (ref $Location) (call $make-initial-location))) (struct.new $InputStringPort (i32.const 0) ;; $hash (local.get $name-val) ;; $name (i32.const 0) ;; $closed (local.get $port-bs) ;; $bytes (local.get $len) ;; $len (i32.const 0) ;; $idx (local.get $loc) ;; $loc (i32.const 0) ;; $utf8-len (i32.const 0) ;; $utf8-left (i32.const 0))) ;; $utf8-bytes (func $open-input-string (type $Prim12) (param $str-raw (ref eq)) ;; string? (param $name (ref eq)) ;; optional any/c, default = 'string (result (ref eq)) (local $str (ref $String)) (local $name-val (ref eq)) (local $bytes (ref $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $loc (ref $Location)) ;; --- Validate string argument --- (if (i32.eqz (ref.test (ref $String) (local.get $str-raw))) (then (call $raise-check-string (local.get $str-raw)))) (local.set $str (ref.cast (ref $String) (local.get $str-raw))) ;; --- Determine port name, honoring optional argument --- (local.set $name-val (if (result (ref eq)) (ref.eq (local.get $name) (global.get $missing)) (then (global.get $symbol:string)) (else (local.get $name)))) ;; --- Convert string contents to immutable UTF-8 bytes --- (local.set $bytes (ref.cast (ref $Bytes) (call $string->bytes/utf-8 (local.get $str) (global.get $false) (global.get $false) (global.get $false)))) (local.set $arr (struct.get $Bytes $bs (local.get $bytes))) (local.set $len (array.len (local.get $arr))) ;; --- Initialize location and construct the port --- (local.set $loc (ref.cast (ref $Location) (call $make-initial-location))) (struct.new $InputStringPort (i32.const 0) ;; $hash (local.get $name-val) ;; $name (i32.const 0) ;; $closed (local.get $bytes) ;; $bytes (local.get $len) ;; $len (byte length) (i32.const 0) ;; $idx (local.get $loc) ;; $loc (i32.const 0) ;; $utf8-len (i32.const 0) ;; $utf8-left (i32.const 0))) ;; $utf8-bytes (func $open-output-bytes (type $Prim01) (param $name (ref eq)) ;; optional any/c, default = 'string (result (ref eq)) (local $bs (ref $Bytes)) (local $loc (ref $Location)) (local $name-val (ref eq)) (local $port (ref $OutputStringPort)) ;; Step 1: Allocate the backing I8Array with capacity 32 and fill with 0 (local.set $bs (struct.new $Bytes (i32.const 0) ;; hash = 0 (i32.const 0) ;; mutable = false (call $i8make-array (i32.const 32) (i32.const 0)))) ;; backing array ;; Step 2: Make initial location: (line 1, col 0, pos 1) (local.set $loc (ref.cast (ref $Location) (call $make-initial-location))) ;; Step 3: Determine the port name, defaulting to 'string (local.set $name-val (if (result (ref eq)) (ref.eq (local.get $name) (global.get $missing)) (then (global.get $symbol:string)) (else (local.get $name)))) ;; Step 4: Construct and return the StringPort (local.set $port (struct.new $OutputStringPort (i32.const 0) ;; $hash (local.get $name-val) ;; $name : (ref eq) (i32.const 0) ;; $closed (local.get $bs) ;; $bytes : (ref $Bytes) (i32.const 32) ;; $len : i32 (i32.const 0) ;; $idx : i32 (local.get $loc) ;; $loc : (ref $Location) (i32.const 0) ;; $utf8-len (i32.const 0) ;; $utf8-left (i32.const 0))) ;; $utf8-bytes (local.get $port)) (func $open-output-string (type $Prim01) (param $name (ref eq)) ;; optional any/c, default = 'string (result (ref eq)) (local $port (ref $OutputStringPort)) (local $name-val (ref eq)) ;; Determine the port name, defaulting to 'string when omitted (local.set $name-val (if (result (ref eq)) (ref.eq (local.get $name) (global.get $missing)) (then (global.get $symbol:string)) (else (local.get $name)))) ;; Reuse the byte-backed string port and update its name field (local.set $port (ref.cast (ref $OutputStringPort) (call $open-output-bytes (global.get $missing)))) (struct.set $OutputStringPort $name (local.get $port) (local.get $name-val)) (local.get $port)) (func $get-output-bytes (type $Prim1) (param $out (ref eq)) (result (ref eq)) (local $sp (ref null $OutputStringPort)) (local $bs (ref $Bytes)) (local $idx i32) (local $src (ref $I8Array)) (local $dest (ref $I8Array)) (local $res (ref $Bytes)) ;; 1. Check that $out is a StringPort (if (ref.test (ref $OutputStringPort) (local.get $out)) (then (local.set $sp (ref.cast (ref $OutputStringPort) (local.get $out)))) (else (call $raise-check-string-port (local.get $out)) (unreachable))) ;; 2. Get internal byte string and index (local.set $bs (struct.get $OutputStringPort $bytes (local.get $sp))) (local.set $idx (struct.get $OutputStringPort $idx (local.get $sp))) ;; 3. Extract the I8Array from the Bytes object (local.set $src (struct.get $Bytes $bs (local.get $bs))) ;; 4. Allocate a new array of length $idx (local.set $dest (call $i8make-array (local.get $idx) (i32.const 0))) ;; 5. Copy contents into the new array (drop (call $i8array-copy!/error (local.get $dest) ;; dest (i32.const 0) ;; dest-start (local.get $src) ;; src (i32.const 0) ;; src-start (local.get $idx))) ;; count ;; 6. Construct new Bytes object (local.set $res (struct.new $Bytes (i32.const 0) ;; hash (i32.const 1) ;; immutable (local.get $dest))) ;; backing array ;; 7. Return the new Bytes (local.get $res)) (func $get-output-string (type $Prim1) (param $out (ref eq)) (result (ref eq)) (local $bs (ref $Bytes)) (local.set $bs (ref.cast (ref $Bytes) (call $get-output-bytes (local.get $out)))) (call $bytes->string/utf-8 (local.get $bs) ;; Replacement character #\uFFFD (ref.i31 (i32.or (i32.shl (i32.const 65533) (i32.const ,char-shift)) (i32.const ,char-tag))) (global.get $missing) (global.get $missing))) ;; NOTE: Procedure-based custom ports currently support only immediate ;; byte operations without events or special results. (func $make-input-port (type $Prim>=4) (param $name (ref eq)) ;; any/c (param $read (ref eq)) ;; input-port? or procedure? (param $peek (ref eq)) ;; input-port? or #f (param $close (ref eq)) ;; procedure? (param $rest (ref eq)) ;; list of optional arguments (get-progress-evt commit get-location count-lines! init-position buffer-mode) (result (ref eq)) (local $args (ref eq)) (local $node (ref $Pair)) (local $arg (ref eq)) (local $count i32) (local $progress-evt (ref eq)) (local $commit-proc (ref eq)) (local $get-location (ref eq)) (local $count-lines! (ref eq)) (local $init-position (ref eq)) (local $buffer-mode (ref eq)) (local $loc (ref $Location)) (local $bytes (ref $Bytes)) ;; --- Initialize optional arguments with their defaults --- (local.set $progress-evt (global.get $false)) (local.set $commit-proc (global.get $false)) (local.set $get-location (global.get $false)) (local.set $count-lines! (global.get $void)) (local.set $init-position (ref.i31 (i32.shl (i32.const 1) (i32.const 1)))) (local.set $buffer-mode (global.get $false)) ;; --- Decode optional rest arguments --- (local.set $args (local.get $rest)) (local.set $count (i32.const 0)) (block $done (loop $loop (if (ref.eq (local.get $args) (global.get $null)) (then (br $done))) (local.set $node (ref.cast (ref $Pair) (local.get $args))) (local.set $arg (struct.get $Pair $a (local.get $node))) (local.set $args (struct.get $Pair $d (local.get $node))) (local.set $count (i32.add (local.get $count) (i32.const 1))) (if (i32.gt_u (local.get $count) (i32.const 6)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:make-input-port)) (local.get $count)) (unreachable))) (if (i32.eq (local.get $count) (i32.const 1)) (then (local.set $progress-evt (local.get $arg))) (else (if (i32.eq (local.get $count) (i32.const 2)) (then (local.set $commit-proc (local.get $arg))) (else (if (i32.eq (local.get $count) (i32.const 3)) (then (local.set $get-location (local.get $arg))) (else (if (i32.eq (local.get $count) (i32.const 4)) (then (local.set $count-lines! (local.get $arg))) (else (if (i32.eq (local.get $count) (i32.const 5)) (then (local.set $init-position (local.get $arg))) (else (local.set $buffer-mode (local.get $arg)))))))))))) (br $loop))) ;; --- Ensure read-in is either an input port or a procedure --- (if (i32.eqz (i32.or (ref.test (ref $InputPort) (local.get $read)) (ref.test (ref $Procedure) (local.get $read)))) ; TODO: raise exception (then (return (global.get $false)))) ;; --- Ensure peek-in is #f, an input port, or a procedure --- (if (i32.eqz (ref.eq (local.get $peek) (global.get $false))) (then (if (i32.eqz (i32.or (ref.test (ref $InputPort) (local.get $peek)) (ref.test (ref $Procedure) (local.get $peek)))) ; TODO: raise exception (then (return (global.get $false)))))) (local.set $bytes (ref.cast (ref $Bytes) (global.get $bytes:empty))) (local.set $loc (ref.cast (ref $Location) (call $make-initial-location))) (struct.new $CustomInputPort (i32.const 0) ;; $hash (local.get $name) ;; $name (i32.const 0) ;; $closed (local.get $bytes) ;; $bytes (scratch buffer) (i32.const 0) ;; $len placeholder (i32.const 0) ;; $idx placeholder (local.get $loc) ;; $loc (local.get $read) ;; $read-proc (local.get $peek) ;; $peek-proc (local.get $close) ;; $close-proc (local.get $progress-evt) ;; $get-progress-evt (local.get $commit-proc) ;; $commit-proc (local.get $get-location) ;; $get-location-proc (local.get $count-lines!) ;; $count-lines-proc (local.get $init-position) ;; $init-position-arg (local.get $buffer-mode))) ;; $buffer-mode-arg (func $eof-object? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v) (global.get $eof)) (then (global.get $true)) (else (global.get $false)))) (func $call-with-output-string (type $Prim1) (param $proc (ref eq)) ;; procedure? (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $port (ref $OutputStringPort)) (local $args (ref $Args)) ;; 1. Ensure the argument is a procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; 2. Open a fresh output string port (local.set $port (ref.cast (ref $OutputStringPort) (call $open-output-string (global.get $missing)))) ;; 3. Prepare the procedure invocation with the port argument (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new_fixed $Args 1 (local.get $port))) ;; 4. Invoke the procedure and ignore its result (drop (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) ;; 5. Return the accumulated output as a string (call $get-output-string (local.get $port))) ;;; ;;; 13.2 Byte and String Input ;;; ;; NOTE: WebRacket currently lacks progress events, so report #f. (func $progress-evt? (type $Prim12) (param $v (ref eq)) ;; any/c (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (result (ref eq)) (global.get $false)) (func $read-byte/custom ; read byte from custom input port (param $port (ref $CustomInputPort)) (result (ref eq)) (local $delegate (ref eq)) (local $buffer (ref $Bytes)) (local $arr (ref $I8Array)) (local $buf-len i32) (local $idx i32) (local $len i32) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $result (ref eq)) (local $count-raw i32) (local $count i32) (local $byte i32) (if (struct.get $CustomInputPort $closed (local.get $port)) (then (call $raise-input-port-closed (global.get $string:read-byte:input-port-closed)) (unreachable))) (local.set $delegate (struct.get $CustomInputPort $read-proc (local.get $port))) ;; Delegate to another input port when provided. (if (ref.test (ref $InputPort) (local.get $delegate)) (then (return (call $read-byte (local.get $delegate))))) ;; Procedure-based handler: maintain scratch buffer and cached bytes. ;; - the delegate is passed a mutable byte string to return the read bytes (local.set $buffer (struct.get $CustomInputPort $bytes (local.get $port))) (local.set $arr (struct.get $Bytes $bs (local.get $buffer))) (local.set $buf-len (call $i8array-length (local.get $arr))) (local.set $idx (struct.get $CustomInputPort $idx (local.get $port))) (local.set $len (struct.get $CustomInputPort $len (local.get $port))) (if (i32.ge_u (local.get $idx) (local.get $len)) (then ;; Ensure scratch buffer has capacity. (if (i32.eqz (local.get $buf-len)) (then (local.set $buffer (ref.cast (ref $Bytes) (call $make-bytes (ref.i31 (i32.shl (i32.const 4096) (i32.const 1))) (global.get $missing)))) (struct.set $CustomInputPort $bytes (local.get $port) (local.get $buffer)) (local.set $arr (struct.get $Bytes $bs (local.get $buffer))) (local.set $buf-len (call $i8array-length (local.get $arr))))) ;; Require a procedure to produce fresh bytes. (if (i32.eqz (ref.test (ref $Procedure) (local.get $delegate))) (then (return (global.get $false)))) (local.set $proc (ref.cast (ref $Procedure) (local.get $delegate))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new_fixed $Args 1 (local.get $buffer))) (local.set $result (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv))) ;; Interpret procedure result. (if (ref.eq (local.get $result) (global.get $eof)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $eof)))) (if (i32.eqz (ref.test (ref i31) (local.get $result))) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (local.set $count-raw (i31.get_s (ref.cast (ref i31) (local.get $result)))) (if (i32.and (local.get $count-raw) (i32.const 1)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (local.set $count (i32.shr_s (local.get $count-raw) (i32.const 1))) (if (i32.lt_s (local.get $count) (i32.const 0)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (if (i32.gt_u (local.get $count) (local.get $buf-len)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (struct.set $CustomInputPort $len (local.get $port) (local.get $count)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (local.set $idx (i32.const 0)) (local.set $len (local.get $count)) (if (i32.eqz (local.get $count)) (then (return (global.get $false)))))) ;; Deliver next buffered byte. (if (i32.ge_u (local.get $idx) (local.get $len)) (then (return (global.get $false)))) (local.set $buffer (struct.get $CustomInputPort $bytes (local.get $port))) (local.set $arr (struct.get $Bytes $bs (local.get $buffer))) (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $idx))) (struct.set $CustomInputPort $idx (local.get $port) (i32.add (local.get $idx) (i32.const 1))) (ref.i31 (i32.shl (local.get $byte) (i32.const 1)))) (func $peek-byte/custom ;; peek byte from custom input port (param $port (ref $CustomInputPort)) (param $skip (ref eq)) ;; exact-nonnegative-integer?, default = 0 (result (ref eq)) (local $delegate (ref eq)) (local $skip-arg (ref eq)) (local $skip-count i32) (local $skip-raw i32) (local $buffer (ref $Bytes)) (local $arr (ref $I8Array)) (local $buf-len i32) (local $idx i32) (local $len i32) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $result (ref eq)) (local $count-raw i32) (local $count i32) (local $byte i32) (if (struct.get $CustomInputPort $closed (local.get $port)) (then (call $raise-input-port-closed (global.get $string:peek-byte:input-port-closed)) (unreachable))) ;; Normalize skip argument for procedure-based handlers. (local.set $skip-count (i32.const 0)) (local.set $skip-arg (local.get $skip)) (if (ref.eq (local.get $skip-arg) (global.get $missing)) (then (local.set $skip-arg (ref.i31 (i32.const 0)))) (else (if (i32.eqz (ref.test (ref i31) (local.get $skip-arg))) (then (return (global.get $false)))) (local.set $skip-raw (i31.get_u (ref.cast (ref i31) (local.get $skip-arg)))) (if (i32.and (local.get $skip-raw) (i32.const 1)) (then (return (global.get $false)))) (local.set $skip-count (i32.shr_u (local.get $skip-raw) (i32.const 1))))) ;; Look up delegate (local.set $delegate (struct.get $CustomInputPort $peek-proc (local.get $port))) ;; TODO: If delegate is #f, then peeking should be done via read-byte. (if (ref.eq (local.get $delegate) (global.get $false)) (then (return (global.get $false)))) ;; If delegate is an input port, just forward to peek-byte (if (ref.test (ref $InputPort) (local.get $delegate)) (then (return (call $peek-byte (local.get $delegate) (local.get $skip))))) ;; Procedure-based handler. Only skip = 0 is currently supported. (if (i32.ne (local.get $skip-count) (i32.const 0)) (then (return (global.get $false)))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $delegate))) (then (return (global.get $false)))) (local.set $buffer (struct.get $CustomInputPort $bytes (local.get $port))) (local.set $arr (struct.get $Bytes $bs (local.get $buffer))) (local.set $buf-len (call $i8array-length (local.get $arr))) (local.set $idx (struct.get $CustomInputPort $idx (local.get $port))) (local.set $len (struct.get $CustomInputPort $len (local.get $port))) ;; Use buffered data when available. (if (i32.lt_u (local.get $idx) (local.get $len)) (then (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $idx))) (return (ref.i31 (i32.shl (local.get $byte) (i32.const 1)))))) ;; Ensure scratch buffer exists before invoking procedure. (if (i32.eqz (local.get $buf-len)) (then (local.set $buffer (ref.cast (ref $Bytes) (call $make-bytes (ref.i31 (i32.shl (i32.const 4096) (i32.const 1))) (global.get $missing)))) (struct.set $CustomInputPort $bytes (local.get $port) (local.get $buffer)) (local.set $arr (struct.get $Bytes $bs (local.get $buffer))) (local.set $buf-len (call $i8array-length (local.get $arr))))) (local.set $proc (ref.cast (ref $Procedure) (local.get $delegate))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new_fixed $Args 3 (local.get $buffer) (local.get $skip-arg) (global.get $false))) (local.set $result (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv))) (if (ref.eq (local.get $result) (global.get $false)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (if (ref.eq (local.get $result) (global.get $eof)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $eof)))) (if (i32.eqz (ref.test (ref i31) (local.get $result))) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (local.set $count-raw (i31.get_s (ref.cast (ref i31) (local.get $result)))) (if (i32.and (local.get $count-raw) (i32.const 1)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (local.set $count (i32.shr_s (local.get $count-raw) (i32.const 1))) (if (i32.lt_s (local.get $count) (i32.const 0)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (if (i32.gt_u (local.get $count) (local.get $buf-len)) (then (struct.set $CustomInputPort $len (local.get $port) (i32.const 0)) (struct.set $CustomInputPort $idx (local.get $port) (i32.const 0)) (return (global.get $false)))) (if (i32.eqz (local.get $count)) (then (return (global.get $false)))) (local.set $buffer (struct.get $CustomInputPort $bytes (local.get $port))) (local.set $arr (struct.get $Bytes $bs (local.get $buffer))) (local.set $byte (array.get_u $I8Array (local.get $arr) (i32.const 0))) (ref.i31 (i32.shl (local.get $byte) (i32.const 1)))) (func $read-byte (type $Prim01) (param $in (ref eq)) ;; optional input-port?, default = (current-input-port) (result (ref eq)) (local $sp (ref null $InputStringPort)) (local $bs (ref eq)) (local $arr (ref $I8Array)) (local $idx i32) (local $limit i32) (local $byte i32) (local $loc (ref $Location)) (local $pos (ref eq)) (local $line (ref eq)) (local $col (ref eq)) (local $int-pos i32) (local $int-line i32) (local $int-col i32) (local $len i32) (local $left i32) (local $seen i32) (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) ;; Ensure the input is a string port. (if (ref.test (ref $InputStringPort) (local.get $in)) (then (local.set $sp (ref.cast (ref $InputStringPort) (local.get $in)))) (else (if (ref.test (ref $CustomInputPort) (local.get $in)) (then (return (call $read-byte/custom (ref.cast (ref $CustomInputPort) (local.get $in))))) (else (return (global.get $false)))))) (if (struct.get $InputStringPort $closed (local.get $sp)) (then (call $raise-input-port-closed (global.get $string:read-byte:input-port-closed)) (unreachable))) (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $limit (struct.get $InputStringPort $len (local.get $sp))) ;; Return the EOF object when no more bytes remain. (if (i32.ge_u (local.get $idx) (local.get $limit)) (then (return (ref.i31 (i32.const ,eof-value))))) (local.set $bs (struct.get $InputStringPort $bytes (local.get $sp))) (local.set $arr (struct.get $Bytes $bs (ref.cast (ref $Bytes) (local.get $bs)))) (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $idx))) (struct.set $InputStringPort $idx (local.get $sp) (i32.add (local.get $idx) (i32.const 1))) ;; Load the existing location information. (local.set $loc (struct.get $InputStringPort $loc (local.get $sp))) (local.set $pos (struct.get $Location $pos (local.get $loc))) (local.set $line (struct.get $Location $line (local.get $loc))) (local.set $col (struct.get $Location $col (local.get $loc))) (local.set $int-pos (if (result i32) (ref.test (ref i31) (local.get $pos)) (then ,(Half `(i31.get_u (ref.cast (ref i31) (local.get $pos))))) (else (i32.const 0)))) (local.set $int-line (if (result i32) (ref.test (ref i31) (local.get $line)) (then ,(Half `(i31.get_u (ref.cast (ref i31) (local.get $line))))) (else (i32.const 0)))) (local.set $int-col (if (result i32) (ref.test (ref i31) (local.get $col)) (then ,(Half `(i31.get_u (ref.cast (ref i31) (local.get $col))))) (else (i32.const 0)))) ;; Update UTF-8 state and column tracking using the read byte. (local.set $len (struct.get $InputStringPort $utf8-len (local.get $sp))) (local.set $left (struct.get $InputStringPort $utf8-left (local.get $sp))) (local.set $seen (struct.get $InputStringPort $utf8-bytes (local.get $sp))) (if (i32.eqz (local.get $len)) (then ;; Start of UTF-8 sequence (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (local.set $int-col (i32.add (local.get $int-col) (i32.const 1)))) (else (if (i32.and (i32.ge_u (local.get $byte) (i32.const 192)) (i32.le_u (local.get $byte) (i32.const 253))) (then (block (local.set $len (i32.clz (i32.xor (i32.shl (local.get $byte) (i32.const 24)) (i32.const 0xFF000000)))) (local.set $len (select (local.get $len) (i32.const 4) (i32.lt_u (local.get $len) (i32.const 4)))) (struct.set $InputStringPort $utf8-len (local.get $sp) (local.get $len)) (struct.set $InputStringPort $utf8-left (local.get $sp) (i32.sub (local.get $len) (i32.const 1))) (struct.set $InputStringPort $utf8-bytes (local.get $sp) (i32.const 1)))))))) (else (block (local.set $seen (i32.add (local.get $seen) (i32.const 1))) (local.set $left (i32.sub (local.get $left) (i32.const 1))) (struct.set $InputStringPort $utf8-left (local.get $sp) (local.get $left)) (struct.set $InputStringPort $utf8-bytes (local.get $sp) (local.get $seen)) (if (i32.eqz (local.get $left)) (then ;; UTF-8 sequence complete: count one character column. (local.set $int-col (i32.add (local.get $int-col) (i32.const 1))) (struct.set $InputStringPort $utf8-len (local.get $sp) (i32.const 0)) (struct.set $InputStringPort $utf8-left (local.get $sp) (i32.const 0)) (struct.set $InputStringPort $utf8-bytes (local.get $sp) (i32.const 0))))))) ;; Handle newline, return, and tab characters. (if (i32.eq (local.get $byte) (i32.const 10)) (then (local.set $int-line (i32.add (local.get $int-line) (i32.const 1))) (local.set $int-col (i32.const 0)))) (if (i32.eq (local.get $byte) (i32.const 13)) (then (local.set $int-line (i32.add (local.get $int-line) (i32.const 1))) (local.set $int-col (i32.const 0)))) (if (i32.eq (local.get $byte) (i32.const 9)) (then (local.set $int-col (i32.add (local.get $int-col) (i32.sub (i32.const 8) (i32.rem_u (local.get $int-col) (i32.const 8))))))) ;; Always advance the absolute position. (local.set $int-pos (i32.add (local.get $int-pos) (i32.const 1))) ;; Store updated location back into the port. (struct.set $InputStringPort $loc (local.get $sp) (struct.new $Location (i32.const 0) (ref.i31 (i32.shl (local.get $int-line) (i32.const 1))) (ref.i31 (i32.shl (local.get $int-col) (i32.const 1))) (ref.i31 (i32.shl (local.get $int-pos) (i32.const 1))))) ;; Return the read byte as a fixnum. (ref.i31 (i32.shl (local.get $byte) (i32.const 1)))) (func $read-char (type $Prim01) (param $in (ref eq)) ;; optional input-port?, default = (current-input-port) (result (ref eq)) (local $first (ref eq)) (local $next (ref eq)) (local $byte i32) (local $need i32) (local $initial-need i32) (local $acc i32) (local $cp i32) (local $cont i32) (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) ;; Ensure the input is an input port. (if (i32.eqz (ref.test (ref $InputPort) (local.get $in))) (then (return (global.get $false)))) (local.set $first (call $read-byte (local.get $in))) (if (ref.eq (local.get $first) (global.get $eof)) (then (return (global.get $eof)))) (if (i32.eqz (ref.test (ref i31) (local.get $first))) (then (return (global.get $false)))) (local.set $byte (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $first))) (i32.const 1))) ;; ASCII fast path. (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (return (ref.i31 (i32.or (i32.shl (local.get $byte) (i32.const ,char-shift)) (i32.const ,char-tag)))))) ;; Determine continuation byte count and initial accumulator bits. (call $bytes->string/utf-8:determine-utf-8-sequence (local.get $byte)) (local.set $acc) (local.set $need) (local.set $initial-need (local.get $need)) ;; Reject invalid lead bytes. (if (i32.lt_s (local.get $need) (i32.const 0)) (then (return (global.get $false)))) (local.set $cp (local.get $acc)) ;; Consume required continuation bytes. (block $done (loop $loop (br_if $done (i32.eqz (local.get $need))) (local.set $next (call $read-byte (local.get $in))) (if (ref.eq (local.get $next) (global.get $eof)) (then (return (global.get $eof)))) (if (i32.eqz (ref.test (ref i31) (local.get $next))) (then (return (global.get $false)))) (local.set $cont (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $next))) (i32.const 1))) ;; Continuation bytes must have the form #b10xxxxxx. (if (i32.or (i32.lt_u (local.get $cont) (i32.const 128)) (i32.ge_u (local.get $cont) (i32.const 192))) (then (return (global.get $false)))) (local.set $cp (i32.or (i32.shl (local.get $cp) (i32.const 6)) (i32.and (local.get $cont) (i32.const 63)))) (local.set $need (i32.sub (local.get $need) (i32.const 1))) (br $loop))) ;; Validate resulting code point: reject surrogates, overlong encodings, and out-of-range values. (if (i32.gt_u (local.get $cp) (i32.const #x10FFFF)) (then (return (global.get $false)))) (if (i32.and (i32.ge_u (local.get $cp) (i32.const #xD800)) (i32.le_u (local.get $cp) (i32.const #xDFFF))) (then (return (global.get $false)))) (if (i32.eq (local.get $initial-need) (i32.const 1)) (then (if (i32.lt_u (local.get $cp) (i32.const #x80)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 2)) (then (if (i32.lt_u (local.get $cp) (i32.const #x800)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 3)) (then (if (i32.lt_u (local.get $cp) (i32.const #x10000)) (then (return (global.get $false)))))) ;; Construct and return the character immediate. (ref.i31 (i32.or (i32.shl (local.get $cp) (i32.const ,char-shift)) (i32.const ,char-tag)))) (func $read-bytes! (type $Prim14) (param $bstr (ref eq)) ;; bytes? (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (param $start (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $end (ref eq)) ;; exact-nonnegative-integer? (optional, default = (bytes-length bstr)) (result (ref eq)) (local $bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $from i32) (local $to i32) (local $count i32) (local $i i32) (local $res (ref eq)) (local $byte i32) (local $dest-idx i32) ;; --- Validate byte string argument --- (if (i32.eqz (ref.test (ref $Bytes) (local.get $bstr))) (then (call $raise-check-bytes (local.get $bstr)) (unreachable))) (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr))) ;; Reject immutable byte strings (if (i32.eq (struct.get $Bytes $immutable (local.get $bs)) (i32.const 1)) (then (call $raise-expected-mutable-bytes (local.get $bstr)) (unreachable))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (call $i8array-length (local.get $arr))) ;; --- Determine input port --- (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $InputPort) (local.get $in))) (then (call $raise-check-string-port (local.get $in)) (unreachable))) ;; --- Decode optional start index --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $from (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $from (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.eqz (i32.and (local.get $from) (i32.const 1))) (then (local.set $from (i32.shr_u (local.get $from) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable))))) ;; --- Decode optional end index --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $to (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $to (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.eqz (i32.and (local.get $to) (i32.const 1))) (then (local.set $to (i32.shr_u (local.get $to) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; --- Bounds checks --- (if (i32.gt_u (local.get $from) (local.get $to)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $from) (local.get $to)) (unreachable))) (if (i32.gt_u (local.get $to) (local.get $len)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $from) (local.get $to)) (unreachable))) (local.set $count (i32.sub (local.get $to) (local.get $from))) (local.set $i (i32.const 0)) ;; --- Read bytes into destination --- (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $count))) (local.set $res (call $read-byte (local.get $in))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $res) (global.get $eof)) (then (if (i32.eqz (local.get $i)) (then (return (global.get $eof)))) (br $done))) (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (return (global.get $false)))) (local.set $byte (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $res))) (i32.const 1))) (local.set $dest-idx (i32.add (local.get $from) (local.get $i))) (call $i8array-set! (local.get $arr) (local.get $dest-idx) (local.get $byte)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; --- Report number of bytes read --- (ref.i31 (i32.shl (local.get $i) (i32.const 1)))) ;; NOTE: Like Racket's read-bytes-avail!, but currently only string ports are supported ;; and the runtime does not yet produce special results. (func $read-bytes-avail! (type $Prim14) (param $bstr (ref eq)) ;; bytes? (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (param $start (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $end (ref eq)) ;; exact-nonnegative-integer? (optional, default = (bytes-length bstr)) (result (ref eq)) (call $read-bytes! (local.get $bstr) (local.get $in) (local.get $start) (local.get $end))) ;; NOTE: Like Racket's read-bytes-avail!*, but currently only string ports are supported ;; and the runtime does not yet produce special results. (func $read-bytes-avail!* (type $Prim14) (param $bstr (ref eq)) ;; bytes? (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (param $start (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $end (ref eq)) ;; exact-nonnegative-integer? (optional, default = (bytes-length bstr)) (result (ref eq)) (call $read-bytes-avail! (local.get $bstr) (local.get $in) (local.get $start) (local.get $end))) (func $read-string! (type $Prim14) (param $str (ref eq)) ;; string? (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (param $start (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $end (ref eq)) ;; exact-nonnegative-integer? (optional, default = (string-length str)) (result (ref eq)) (local $s (ref $String)) (local $arr (ref $I32Array)) (local $len i32) (local $from i32) (local $to i32) (local $count i32) (local $i i32) (local $res (ref eq)) (local $tagged i32) (local $cp i32) (local $dest-idx i32) ;; --- Validate string argument --- (if (i32.eqz (ref.test (ref $String) (local.get $str))) (then (call $raise-check-string (local.get $str)) (unreachable))) (local.set $s (ref.cast (ref $String) (local.get $str))) ;; Reject immutable strings (if (i32.ne (struct.get $String $immutable (local.get $s)) (i32.const 0)) (then (call $raise-immutable-string (local.get $str)) (unreachable))) (local.set $arr (struct.get $String $codepoints (local.get $s))) (local.set $len (call $i32array-length (local.get $arr))) ;; --- Determine input port --- (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $InputPort) (local.get $in))) (then (call $raise-check-string-port (local.get $in)) (unreachable))) ;; --- Decode optional start index --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $from (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $from (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.eqz (i32.and (local.get $from) (i32.const 1))) (then (local.set $from (i32.shr_u (local.get $from) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable))))) ;; --- Decode optional end index --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $to (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $to (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.eqz (i32.and (local.get $to) (i32.const 1))) (then (local.set $to (i32.shr_u (local.get $to) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; --- Bounds checks --- (if (i32.gt_u (local.get $from) (local.get $to)) (then (call $raise-bad-string-range (local.get $str) (local.get $from) (local.get $to)) (unreachable))) (if (i32.gt_u (local.get $to) (local.get $len)) (then (call $raise-bad-string-range (local.get $str) (local.get $from) (local.get $to)) (unreachable))) (local.set $count (i32.sub (local.get $to) (local.get $from))) (local.set $i (i32.const 0)) ;; --- Read characters into destination --- (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $count))) (local.set $res (call $read-char (local.get $in))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $res) (global.get $eof)) (then (if (i32.eqz (local.get $i)) (then (return (global.get $eof)))) (br $done))) (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (return (global.get $false)))) (local.set $tagged (i31.get_u (ref.cast (ref i31) (local.get $res)))) (if (i32.ne (i32.and (local.get $tagged) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (return (global.get $false)))) (local.set $cp (i32.shr_u (local.get $tagged) (i32.const ,char-shift))) (local.set $dest-idx (i32.add (local.get $from) (local.get $i))) (call $i32array-set! (local.get $arr) (local.get $dest-idx) (local.get $cp)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; --- Report number of characters read --- (ref.i31 (i32.shl (local.get $i) (i32.const 1)))) ;; Like Racket's read-bytes, but currently only string ports are supported (func $read-bytes (type $Prim12) (param $amt (ref eq)) ;; exact-nonnegative-integer? (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (result (ref eq)) (local $count-i31 (ref i31)) (local $count i32) (local $buf (ref $Bytes)) (local $res (ref eq)) (local $read i32) (local $arr (ref $I8Array)) (local $new-arr (ref $I8Array)) ;; --- Decode amount --- (if (i32.eqz (ref.test (ref i31) (local.get $amt))) (then (call $raise-check-fixnum (local.get $amt)) (unreachable))) (local.set $count-i31 (ref.cast (ref i31) (local.get $amt))) (local.set $count (i31.get_u (local.get $count-i31))) (if (i32.ne (i32.and (local.get $count) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $amt)) (unreachable))) (local.set $count (i32.shr_u (local.get $count) (i32.const 1))) ;; --- Handle zero-length read --- (if (i32.eqz (local.get $count)) (then (return (global.get $bytes:empty)))) ;; --- Determine input port --- (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $InputPort) (local.get $in))) (then (call $raise-check-string-port (local.get $in)) (unreachable))) ;; --- Allocate destination buffer --- (local.set $buf (ref.cast (ref $Bytes) (call $make-bytes (ref.i31 (i32.shl (local.get $count) (i32.const 1))) (global.get $missing)))) ;; --- Fill buffer using read-bytes! --- (local.set $res (call $read-bytes! (local.get $buf) (local.get $in) (global.get $missing) (global.get $missing))) ;; Propagate failure conditions (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $res) (global.get $eof)) (then (return (global.get $eof)))) (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (return (global.get $false)))) (local.set $read (i31.get_u (ref.cast (ref i31) (local.get $res)))) (if (i32.ne (i32.and (local.get $read) (i32.const 1)) (i32.const 0)) (then (return (global.get $false)))) (local.set $read (i32.shr_u (local.get $read) (i32.const 1))) ;; Shrink buffer on partial read (if (i32.lt_u (local.get $read) (local.get $count)) (then (local.set $arr (struct.get $Bytes $bs (local.get $buf))) (local.set $new-arr (call $i8array-copy (local.get $arr) (i32.const 0) (local.get $read))) (struct.set $Bytes $bs (local.get $buf) (local.get $new-arr)))) ;; Freeze buffer to make result immutable #;(struct.set $Bytes $immutable (local.get $buf) (i32.const 1)) (local.get $buf)) ;; Like Racket's read-string, but currently only string ports are supported (func $read-string (type $Prim12) (param $amt (ref eq)) ;; exact-nonnegative-integer? (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (result (ref eq)) (local $count-i31 (ref i31)) (local $count i32) (local $buf (ref $String)) (local $res (ref eq)) (local $read i32) (local $arr (ref $I32Array)) (local $new-arr (ref $I32Array)) ;; --- Decode amount --- (if (i32.eqz (ref.test (ref i31) (local.get $amt))) (then (call $raise-check-fixnum (local.get $amt)) (unreachable))) (local.set $count-i31 (ref.cast (ref i31) (local.get $amt))) (local.set $count (i31.get_u (local.get $count-i31))) (if (i32.ne (i32.and (local.get $count) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $amt)) (unreachable))) (local.set $count (i32.shr_u (local.get $count) (i32.const 1))) ;; --- Handle zero-length read --- (if (i32.eqz (local.get $count)) (then (return (global.get $string:empty)))) ;; --- Determine input port --- (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $InputPort) (local.get $in))) (then (call $raise-check-string-port (local.get $in)) (unreachable))) ;; --- Allocate destination string --- (local.set $buf (call $make-string/checked (local.get $count) (i32.const 0))) ;; --- Fill buffer using read-string! --- (local.set $res (call $read-string! (local.get $buf) (local.get $in) (global.get $missing) (global.get $missing))) ;; Propagate failure conditions (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $res) (global.get $eof)) (then (return (global.get $eof)))) (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (return (global.get $false)))) (local.set $read (i31.get_u (ref.cast (ref i31) (local.get $res)))) (if (i32.ne (i32.and (local.get $read) (i32.const 1)) (i32.const 0)) (then (return (global.get $false)))) (local.set $read (i32.shr_u (local.get $read) (i32.const 1))) (local.set $arr (struct.get $String $codepoints (local.get $buf))) ;; Shrink buffer on partial read (if (i32.lt_u (local.get $read) (local.get $count)) (then (local.set $new-arr (call $i32array-copy (local.get $arr) (i32.const 0) (local.get $read))) (struct.set $String $codepoints (local.get $buf) (local.get $new-arr)))) (struct.set $String $hash (local.get $buf) (i32.const 0)) (local.get $buf)) (func $byte-ready? (type $Prim01) (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (result (ref eq)) (local $sp (ref null $InputStringPort)) (local $idx i32) (local $len i32) (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) ;; Ensure the input is a string port. (if (ref.test (ref $InputStringPort) (local.get $in)) (then (local.set $sp (ref.cast (ref $InputStringPort) (local.get $in)))) (else (return (global.get $false)))) (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $len (struct.get $InputStringPort $len (local.get $sp))) (if (result (ref eq)) (i32.lt_u (local.get $idx) (local.get $len)) (then (global.get $true)) (else (global.get $false)))) (func $char-ready? (type $Prim01) (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (result (ref eq)) (local $sp (ref null $InputStringPort)) (local $bs (ref eq)) (local $arr (ref $I8Array)) (local $idx i32) (local $len i32) (local $byte i32) (local $need i32) (local $acc i32) (local $initial i32) (local $available i32) (local $cp i32) (local $i i32) (local $scan i32) (local $cont i32) (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) ;; Ensure the input is a string port. (if (ref.test (ref $InputStringPort) (local.get $in)) (then (local.set $sp (ref.cast (ref $InputStringPort) (local.get $in)))) (else (return (global.get $false)))) (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $len (struct.get $InputStringPort $len (local.get $sp))) ;; No characters remain when at EOF. (if (i32.ge_u (local.get $idx) (local.get $len)) (then (return (global.get $false)))) (local.set $bs (struct.get $InputStringPort $bytes (local.get $sp))) (local.set $arr (struct.get $Bytes $bs (ref.cast (ref $Bytes) (local.get $bs)))) (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $idx))) ;; ASCII characters are always ready when a byte is available. (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (return (global.get $true)))) ;; Determine continuation requirements for the UTF-8 sequence. (call $bytes->string/utf-8:determine-utf-8-sequence (local.get $byte)) (local.set $acc) (local.set $need) (local.set $initial (local.get $need)) ;; Reject invalid lead bytes. (if (i32.lt_s (local.get $need) (i32.const 0)) (then (return (global.get $false)))) ;; Ensure enough bytes remain to finish the character. (local.set $available (i32.sub (local.get $len) (local.get $idx))) (if (i32.lt_u (local.get $available) (i32.add (local.get $need) (i32.const 1))) (then (return (global.get $false)))) (local.set $cp (local.get $acc)) (local.set $i (i32.const 0)) (local.set $scan (local.get $idx)) ;; Validate continuation bytes and build the code point. (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $need))) (local.set $scan (i32.add (local.get $scan) (i32.const 1))) (local.set $cont (array.get_u $I8Array (local.get $arr) (local.get $scan))) (if (i32.or (i32.lt_u (local.get $cont) (i32.const 128)) (i32.ge_u (local.get $cont) (i32.const 192))) (then (return (global.get $false)))) (local.set $cp (i32.or (i32.shl (local.get $cp) (i32.const 6)) (i32.and (local.get $cont) (i32.const 63)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Reject invalid Unicode scalars and overlong encodings. (if (i32.gt_u (local.get $cp) (i32.const #x10FFFF)) (then (return (global.get $false)))) (if (i32.and (i32.ge_u (local.get $cp) (i32.const #xD800)) (i32.le_u (local.get $cp) (i32.const #xDFFF))) (then (return (global.get $false)))) (if (i32.eq (local.get $initial) (i32.const 1)) (then (if (i32.lt_u (local.get $cp) (i32.const #x80)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial) (i32.const 2)) (then (if (i32.lt_u (local.get $cp) (i32.const #x800)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial) (i32.const 3)) (then (if (i32.lt_u (local.get $cp) (i32.const #x10000)) (then (return (global.get $false)))))) (global.get $true)) (func $raise-read-line:bad-mode (param $mode (ref eq)) (unreachable)) (func $read-line/custom (param $port (ref $CustomInputPort)) (param $mode-code i32) (result (ref eq)) (local $buf (ref $I32GrowableArray)) (local $count i32) (local $res (ref eq)) (local $tagged i32) (local $cp i32) (local $append i32) (local $stop i32) (local $consume i32) (local $skip (ref eq)) (local $peek (ref eq)) (local $peek-tagged i32) (local $peek-byte i32) (local.set $buf (call $make-i32growable-array (i32.const 32))) (local.set $count (i32.const 0)) (block $done (loop $loop (local.set $res (call $read-char (local.get $port))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $res) (global.get $eof)) (then (if (i32.eqz (local.get $count)) (then (return (global.get $eof))) (else (br $done))))) (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (return (global.get $false)))) (local.set $tagged (i31.get_u (ref.cast (ref i31) (local.get $res)))) (if (i32.ne (i32.and (local.get $tagged) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (return (global.get $false)))) (local.set $cp (i32.shr_u (local.get $tagged) (i32.const ,char-shift))) (local.set $append (i32.const 1)) (local.set $stop (i32.const 0)) (local.set $consume (i32.const 0)) (if (i32.eq (local.get $mode-code) (i32.const 0)) (then (if (i32.eq (local.get $cp) (i32.const 10)) (then (local.set $append (i32.const 0)) (local.set $stop (i32.const 1)))))) (if (i32.eq (local.get $mode-code) (i32.const 1)) (then (if (i32.eq (local.get $cp) (i32.const 13)) (then (local.set $append (i32.const 0)) (local.set $stop (i32.const 1)))))) (if (i32.eq (local.get $mode-code) (i32.const 3)) (then (if (i32.eq (local.get $cp) (i32.const 10)) (then (local.set $append (i32.const 0)) (local.set $stop (i32.const 1)))))) (if (i32.eq (local.get $mode-code) (i32.const 4)) (then (if (i32.or (i32.eq (local.get $cp) (i32.const 10)) (i32.eq (local.get $cp) (i32.const 13))) (then (local.set $append (i32.const 0)) (local.set $stop (i32.const 1)))))) (if (i32.or (i32.eq (local.get $mode-code) (i32.const 2)) (i32.eq (local.get $mode-code) (i32.const 3))) (then (if (i32.eq (local.get $cp) (i32.const 13)) (then (local.set $append (i32.const 0)) (local.set $stop (i32.const 1)) (local.set $peek (call $peek-byte (local.get $port) (global.get $missing))) (if (ref.eq (local.get $peek) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $peek) (global.get $eof)) (then) (else (if (i32.eqz (ref.test (ref i31) (local.get $peek))) (then (return (global.get $false)))) (local.set $peek-tagged (i31.get_u (ref.cast (ref i31) (local.get $peek)))) (if (i32.ne (i32.and (local.get $peek-tagged) (i32.const 1)) (i32.const 0)) (then (return (global.get $false)))) (local.set $peek-byte (i32.shr_u (local.get $peek-tagged) (i32.const 1))) (if (i32.eq (local.get $peek-byte) (i32.const 10)) (then (local.set $consume (i32.const 1)))))))))) (if (i32.eq (local.get $append) (i32.const 1)) (then (call $i32growable-array-add! (local.get $buf) (local.get $cp)) (local.set $count (i32.add (local.get $count) (i32.const 1))))) (if (i32.eq (local.get $stop) (i32.const 1)) (then (if (i32.eq (local.get $consume) (i32.const 1)) (then (local.set $skip (call $read-char (local.get $port))) (if (ref.eq (local.get $skip) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $skip) (global.get $eof)) (then (return (global.get $false)))))) (br $done))) (br $loop))) (local.set $res (call $i32growable-array->immutable-string (local.get $buf))) (local.get $res)) (func $read-line (type $Prim02) (param $in (ref eq)) ;; optional input-port?, default = (current-input-port) (param $mode (ref eq)) ;; optional read-line-mode?, default = 'linefeed (result (ref eq)) (local $sp (ref null $InputStringPort)) (local $bytes (ref null $Bytes)) (local $arr (ref null $I8Array)) (local $mode-val (ref eq)) (local $mode-code i32) (local $buf (ref $I32GrowableArray)) (local $res (ref eq)) (local $skip (ref eq)) (local $tagged i32) (local $cp i32) (local $count i32) (local $append i32) (local $break i32) (local $consume i32) (local $idx i32) (local $len i32) (local $next-byte i32) (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) ;; Determine the mode symbol, defaulting to 'linefeed. (local.set $mode-val (if (result (ref eq)) (ref.eq (local.get $mode) (global.get $missing)) (then (global.get $symbol:linefeed)) (else (local.get $mode)))) ;; Decode the mode into an internal tag. (local.set $mode-code (i32.const -1)) (block (if (ref.eq (local.get $mode-val) (global.get $symbol:linefeed)) (then (local.set $mode-code (i32.const 0)) (br 1))) (if (ref.eq (local.get $mode-val) (global.get $symbol:return)) (then (local.set $mode-code (i32.const 1)) (br 1))) (if (ref.eq (local.get $mode-val) (global.get $symbol:return-linefeed)) (then (local.set $mode-code (i32.const 2)) (br 1))) (if (ref.eq (local.get $mode-val) (global.get $symbol:any)) (then (local.set $mode-code (i32.const 3)) (br 1))) (if (ref.eq (local.get $mode-val) (global.get $symbol:any-one)) (then (local.set $mode-code (i32.const 4)) (br 1))) (call $raise-read-line:bad-mode (local.get $mode-val)) (unreachable)) ;; Dispatch based on the input port type. (if (ref.test (ref $InputStringPort) (local.get $in)) (then (local.set $sp (ref.cast (ref $InputStringPort) (local.get $in))) (local.set $bytes (ref.cast (ref $Bytes) (struct.get $InputStringPort $bytes (local.get $sp)))) (local.set $arr (struct.get $Bytes $bs (local.get $bytes)))) (else (if (ref.test (ref $CustomInputPort) (local.get $in)) (then (return (call $read-line/custom (ref.cast (ref $CustomInputPort) (local.get $in)) (local.get $mode-code)))) (else (call $raise-check-string-port (local.get $in)) (unreachable))))) (local.set $buf (call $make-i32growable-array (i32.const 32))) (local.set $count (i32.const 0)) (block $done (loop $loop (local.set $res (call $read-char (local.get $in))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $res) (global.get $eof)) (then (if (i32.eqz (local.get $count)) (then (return (global.get $eof))) (else (br $done))))) (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (return (global.get $false)))) (local.set $tagged (i31.get_u (ref.cast (ref i31) (local.get $res)))) (if (i32.ne (i32.and (local.get $tagged) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (return (global.get $false)))) (local.set $cp (i32.shr_u (local.get $tagged) (i32.const ,char-shift))) (local.set $append (i32.const 1)) (local.set $break (i32.const 0)) (local.set $consume (i32.const 0)) (if (i32.eq (local.get $mode-code) (i32.const 0)) (then (if (i32.eq (local.get $cp) (i32.const 10)) (then (local.set $append (i32.const 0))) (else (local.set $break (i32.const 1)))))) (if (i32.eq (local.get $mode-code) (i32.const 1)) (then (if (i32.eq (local.get $cp) (i32.const 13)) (then (local.set $append (i32.const 0))) (else (local.set $break (i32.const 1)))))) (if (i32.eq (local.get $mode-code) (i32.const 2)) (then (if (i32.eq (local.get $cp) (i32.const 13)) (then (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $len (struct.get $InputStringPort $len (local.get $sp))) (if (i32.lt_u (local.get $idx) (local.get $len)) (then (local.set $next-byte (array.get_u $I8Array (local.get $arr) (local.get $idx))) (if (i32.eq (local.get $next-byte) (i32.const 10)) (then (local.set $append (i32.const 0)) (local.set $break (i32.const 1)) (local.set $consume (i32.const 1)))))))))) (if (i32.eq (local.get $mode-code) (i32.const 3)) (then (if (i32.eq (local.get $cp) (i32.const 10)) (then (local.set $append (i32.const 0)) (local.set $break (i32.const 1))) (else (if (i32.eq (local.get $cp) (i32.const 13)) (then (local.set $append (i32.const 0)) (local.set $break (i32.const 1)) (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $len (struct.get $InputStringPort $len (local.get $sp))) (if (i32.lt_u (local.get $idx) (local.get $len)) (then (local.set $next-byte (array.get_u $I8Array (local.get $arr) (local.get $idx))) (if (i32.eq (local.get $next-byte) (i32.const 10)) (then (local.set $consume (i32.const 1)))))))))))) (if (i32.eq (local.get $mode-code) (i32.const 4)) (then (if (i32.or (i32.eq (local.get $cp) (i32.const 10)) (i32.eq (local.get $cp) (i32.const 13))) (then (local.set $append (i32.const 0))) (else (local.set $break (i32.const 1)))))) (if (i32.eq (local.get $append) (i32.const 1)) (then (call $i32growable-array-add! (local.get $buf) (local.get $cp)) (local.set $count (i32.add (local.get $count) (i32.const 1))))) (if (i32.eq (local.get $break) (i32.const 1)) (then (if (i32.eq (local.get $consume) (i32.const 1)) (then (local.set $skip (call $read-char (local.get $in))) (if (ref.eq (local.get $skip) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $skip) (global.get $eof)) (then (return (global.get $false))))))) (else (br $done))) (br $loop))) (local.set $res (call $i32growable-array->immutable-string (local.get $buf))) (local.get $res)) (func $peek-bytes! (type $Prim25) (param $bstr (ref eq)) ;; bytes? (param $skip (ref eq)) ;; exact-nonnegative-integer? (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (param $start (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $end (ref eq)) ;; exact-nonnegative-integer? (optional, default = (bytes-length bstr)) (result (ref eq)) (local $bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $from i32) (local $to i32) (local $count i32) (local $skip-count i32) (local $sp (ref null $InputStringPort)) (local $port-bytes (ref $Bytes)) (local $port-arr (ref $I8Array)) (local $idx i32) (local $limit i32) (local $remaining i32) (local $peek-start i32) (local $copy-count i32) (local $i i32) (local $dest-idx i32) (local $byte i32) ;; --- Validate byte string argument --- (if (i32.eqz (ref.test (ref $Bytes) (local.get $bstr))) (then (call $raise-check-bytes (local.get $bstr)) (unreachable))) (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr))) ;; Reject immutable byte strings (if (i32.eq (struct.get $Bytes $immutable (local.get $bs)) (i32.const 1)) (then (call $raise-expected-mutable-bytes (local.get $bstr)) (unreachable))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (call $i8array-length (local.get $arr))) ;; --- Decode skip amount --- (if (i32.eqz (ref.test (ref i31) (local.get $skip))) (then (call $raise-check-fixnum (local.get $skip)) (unreachable))) (local.set $skip-count (i31.get_u (ref.cast (ref i31) (local.get $skip)))) (if (i32.eqz (i32.and (local.get $skip-count) (i32.const 1))) (then (local.set $skip-count (i32.shr_u (local.get $skip-count) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $skip)) (unreachable))) ;; --- Determine input port --- (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $InputStringPort) (local.get $in))) (then (call $raise-check-string-port (local.get $in)) (unreachable))) (local.set $sp (ref.cast (ref $InputStringPort) (local.get $in))) ;; --- Decode optional start index --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $from (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $from (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.eqz (i32.and (local.get $from) (i32.const 1))) (then (local.set $from (i32.shr_u (local.get $from) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable))))) ;; --- Decode optional end index --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $to (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $to (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.eqz (i32.and (local.get $to) (i32.const 1))) (then (local.set $to (i32.shr_u (local.get $to) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; --- Bounds checks --- (if (i32.gt_u (local.get $from) (local.get $to)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $from) (local.get $to)) (unreachable))) (if (i32.gt_u (local.get $to) (local.get $len)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $from) (local.get $to)) (unreachable))) (local.set $count (i32.sub (local.get $to) (local.get $from))) (if (i32.eqz (local.get $count)) (then (return (ref.i31 (i32.const 0))))) ;; --- Determine peek window --- (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $limit (struct.get $InputStringPort $len (local.get $sp))) (local.set $remaining (i32.sub (local.get $limit) (local.get $idx))) (if (i32.le_u (local.get $remaining) (local.get $skip-count)) (then (return (global.get $eof)))) (local.set $peek-start (i32.add (local.get $idx) (local.get $skip-count))) (local.set $remaining (i32.sub (local.get $limit) (local.get $peek-start))) ;; --- Determine number of bytes to copy --- (local.set $copy-count (local.get $count)) (if (i32.lt_u (local.get $remaining) (local.get $copy-count)) (then (local.set $copy-count (local.get $remaining)))) (local.set $port-bytes (struct.get $InputStringPort $bytes (local.get $sp))) (local.set $port-arr (struct.get $Bytes $bs (local.get $port-bytes))) (local.set $i (i32.const 0)) ;; --- Copy bytes without consuming the port --- (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $copy-count))) (local.set $dest-idx (i32.add (local.get $from) (local.get $i))) (local.set $byte (array.get_u $I8Array (local.get $port-arr) (i32.add (local.get $peek-start) (local.get $i)))) (call $i8array-set! (local.get $arr) (local.get $dest-idx) (local.get $byte)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; --- Report number of bytes peeked --- (ref.i31 (i32.shl (local.get $copy-count) (i32.const 1)))) (func $peek-bytes-avail!:progress-not-supported (unreachable)) ;; NOTE: Like Racket's peek-bytes-avail!, but currently only string ports are supported ;; and the progress argument must be #f. ;; Takes at least 1 argument and at most 6. ;; Instead of introducing a new shaped ($Prim16) we reuse $Prim>=1 (func $peek-bytes-avail! (type $Prim>=1) (param $bstr (ref eq)) ;; bytes? (param $rest (ref eq)) ;; remaining arguments as a list (result (ref eq)) (local $args (ref eq)) (local $node (ref $Pair)) (local $arg (ref eq)) (local $count i32) (local $skip (ref eq)) (local $progress (ref eq)) (local $in (ref eq)) (local $start (ref eq)) (local $end (ref eq)) ;; Initialize optional arguments to "missing" (local.set $skip (global.get $missing)) (local.set $progress (global.get $missing)) (local.set $in (global.get $missing)) (local.set $start (global.get $missing)) (local.set $end (global.get $missing)) ;; Decode rest arguments (skip, progress, in, start, end) (local.set $args (local.get $rest)) (local.set $count (i32.const 0)) (block $done (loop $loop (if (ref.eq (local.get $args) (global.get $null)) (then (br $done))) (local.set $node (ref.cast (ref $Pair) (local.get $args))) (local.set $arg (struct.get $Pair $a (local.get $node))) (local.set $args (struct.get $Pair $d (local.get $node))) (local.set $count (i32.add (local.get $count) (i32.const 1))) (if (i32.gt_u (local.get $count) (i32.const 5)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:peek-bytes-avail!)) (local.get $count)) (unreachable))) (if (i32.eq (local.get $count) (i32.const 1)) (then (local.set $skip (local.get $arg))) (else (if (i32.eq (local.get $count) (i32.const 2)) (then (local.set $progress (local.get $arg))) (else (if (i32.eq (local.get $count) (i32.const 3)) (then (local.set $in (local.get $arg))) (else (if (i32.eq (local.get $count) (i32.const 4)) (then (local.set $start (local.get $arg))) (else (local.set $end (local.get $arg)))))))))) (br $loop))) ;; Require the skip argument (if (ref.eq (local.get $skip) (global.get $missing)) (then (call $primitive-invoke:raise-arity-error (ref.cast (ref $PrimitiveProcedure) (global.get $prim:peek-bytes-avail!)) (local.get $count)) (unreachable))) ;; Progress defaults to #f and must be #f (if (ref.eq (local.get $progress) (global.get $missing)) (then (local.set $progress (global.get $false)))) (if (i32.eqz (ref.eq (local.get $progress) (global.get $false))) (then (call $peek-bytes-avail!:progress-not-supported) (unreachable))) (call $peek-bytes! (local.get $bstr) (local.get $skip) (local.get $in) (local.get $start) (local.get $end))) ;; NOTE: Like Racket's peek-bytes-avail!*, but currently only string ports are supported ;; and the progress argument must be #f. ;; Takes at least 1 argument and at most 6. (func $peek-bytes-avail!* (type $Prim>=1) (param $bstr (ref eq)) (param $rest (ref eq)) (result (ref eq)) (call $peek-bytes-avail! (local.get $bstr) (local.get $rest))) (func $peek-string! (type $Prim25) (param $str (ref eq)) ;; string? (param $skip (ref eq)) ;; exact-nonnegative-integer? (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (param $start (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $end (ref eq)) ;; exact-nonnegative-integer? (optional, default = (string-length str)) (result (ref eq)) (local $s (ref $String)) (local $arr (ref $I32Array)) (local $len i32) (local $from i32) (local $to i32) (local $count i32) (local $skip-count i32) (local $sp (ref null $InputStringPort)) (local $port-bytes (ref $Bytes)) (local $src (ref $I8Array)) (local $idx i32) (local $limit i32) (local $peek-start i32) (local $skipped i32) (local $written i32) (local $byte i32) (local $need i32) (local $initial-need i32) (local $acc i32) (local $cp i32) (local $cont i32) (local $dest-idx i32) ;; --- Validate string argument --- (if (i32.eqz (ref.test (ref $String) (local.get $str))) (then (call $raise-check-string (local.get $str)) (unreachable))) (local.set $s (ref.cast (ref $String) (local.get $str))) ;; Reject immutable strings (if (i32.ne (struct.get $String $immutable (local.get $s)) (i32.const 0)) (then (call $raise-immutable-string (local.get $str)) (unreachable))) (local.set $arr (struct.get $String $codepoints (local.get $s))) (local.set $len (call $i32array-length (local.get $arr))) ;; --- Decode skip amount --- (if (i32.eqz (ref.test (ref i31) (local.get $skip))) (then (call $raise-check-fixnum (local.get $skip)) (unreachable))) (local.set $skip-count (i31.get_u (ref.cast (ref i31) (local.get $skip)))) (if (i32.eqz (i32.and (local.get $skip-count) (i32.const 1))) (then (local.set $skip-count (i32.shr_u (local.get $skip-count) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $skip)) (unreachable))) ;; --- Determine input port --- (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $InputStringPort) (local.get $in))) (then (call $raise-check-string-port (local.get $in)) (unreachable))) (local.set $sp (ref.cast (ref $InputStringPort) (local.get $in))) ;; --- Decode optional start index --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $from (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $from (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.eqz (i32.and (local.get $from) (i32.const 1))) (then (local.set $from (i32.shr_u (local.get $from) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable))))) ;; --- Decode optional end index --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $to (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $to (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.eqz (i32.and (local.get $to) (i32.const 1))) (then (local.set $to (i32.shr_u (local.get $to) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; --- Bounds checks --- (if (i32.gt_u (local.get $from) (local.get $to)) (then (call $raise-bad-string-range (local.get $str) (local.get $from) (local.get $to)) (unreachable))) (if (i32.gt_u (local.get $to) (local.get $len)) (then (call $raise-bad-string-range (local.get $str) (local.get $from) (local.get $to)) (unreachable))) (local.set $count (i32.sub (local.get $to) (local.get $from))) (if (i32.eqz (local.get $count)) (then (return (ref.i31 (i32.const 0))))) ;; --- Determine peek window and skip characters --- (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $limit (struct.get $InputStringPort $len (local.get $sp))) (local.set $port-bytes (struct.get $InputStringPort $bytes (local.get $sp))) (local.set $src (struct.get $Bytes $bs (local.get $port-bytes))) (local.set $peek-start (local.get $idx)) (local.set $skipped (i32.const 0)) (block $skip-done (loop $skip (br_if $skip-done (i32.ge_u (local.get $skipped) (local.get $skip-count))) (if (i32.ge_u (local.get $peek-start) (local.get $limit)) (then (return (global.get $eof)))) (local.set $byte (array.get_u $I8Array (local.get $src) (local.get $peek-start))) (local.set $peek-start (i32.add (local.get $peek-start) (i32.const 1))) (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (local.set $skipped (i32.add (local.get $skipped) (i32.const 1)))) (else (call $bytes->string/utf-8:determine-utf-8-sequence (local.get $byte)) (local.set $acc) (local.set $need) (local.set $initial-need (local.get $need)) (if (i32.lt_s (local.get $need) (i32.const 0)) (then (return (global.get $false)))) (local.set $cp (local.get $acc)) (block $skip-cont-done (loop $skip-cont (br_if $skip-cont-done (i32.eqz (local.get $need))) (if (i32.ge_u (local.get $peek-start) (local.get $limit)) (then (return (global.get $eof)))) (local.set $cont (array.get_u $I8Array (local.get $src) (local.get $peek-start))) (local.set $peek-start (i32.add (local.get $peek-start) (i32.const 1))) (if (i32.or (i32.lt_u (local.get $cont) (i32.const 128)) (i32.ge_u (local.get $cont) (i32.const 192))) (then (return (global.get $false)))) (local.set $cp (i32.or (i32.shl (local.get $cp) (i32.const 6)) (i32.and (local.get $cont) (i32.const 63)))) (local.set $need (i32.sub (local.get $need) (i32.const 1))) (br $skip-cont))) (if (i32.gt_u (local.get $cp) (i32.const #x10FFFF)) (then (return (global.get $false)))) (if (i32.and (i32.ge_u (local.get $cp) (i32.const #xD800)) (i32.le_u (local.get $cp) (i32.const #xDFFF))) (then (return (global.get $false)))) (if (i32.eq (local.get $initial-need) (i32.const 1)) (then (if (i32.lt_u (local.get $cp) (i32.const #x80)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 2)) (then (if (i32.lt_u (local.get $cp) (i32.const #x800)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 3)) (then (if (i32.lt_u (local.get $cp) (i32.const #x10000)) (then (return (global.get $false)))))) (local.set $skipped (i32.add (local.get $skipped) (i32.const 1))))) (br $skip))) (local.set $written (i32.const 0)) ;; --- Decode characters without consuming the port --- (block $done (loop $loop (br_if $done (i32.ge_u (local.get $written) (local.get $count))) (if (i32.ge_u (local.get $peek-start) (local.get $limit)) (then (if (i32.eqz (local.get $written)) (then (return (global.get $eof))) (else (br $done))))) (local.set $byte (array.get_u $I8Array (local.get $src) (local.get $peek-start))) (local.set $peek-start (i32.add (local.get $peek-start) (i32.const 1))) (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (local.set $cp (local.get $byte)) (local.set $initial-need (i32.const 0))) (else (call $bytes->string/utf-8:determine-utf-8-sequence (local.get $byte)) (local.set $acc) (local.set $need) (local.set $initial-need (local.get $need)) (if (i32.lt_s (local.get $need) (i32.const 0)) (then (return (global.get $false)))) (local.set $cp (local.get $acc)) (block $cont-done (loop $cont (br_if $cont-done (i32.eqz (local.get $need))) (if (i32.ge_u (local.get $peek-start) (local.get $limit)) (then (if (i32.eqz (local.get $written)) (then (return (global.get $eof))) (else (br $done))))) (local.set $cont (array.get_u $I8Array (local.get $src) (local.get $peek-start))) (local.set $peek-start (i32.add (local.get $peek-start) (i32.const 1))) (if (i32.or (i32.lt_u (local.get $cont) (i32.const 128)) (i32.ge_u (local.get $cont) (i32.const 192))) (then (return (global.get $false)))) (local.set $cp (i32.or (i32.shl (local.get $cp) (i32.const 6)) (i32.and (local.get $cont) (i32.const 63)))) (local.set $need (i32.sub (local.get $need) (i32.const 1))) (br $cont))) (if (i32.gt_u (local.get $cp) (i32.const #x10FFFF)) (then (return (global.get $false)))) (if (i32.and (i32.ge_u (local.get $cp) (i32.const #xD800)) (i32.le_u (local.get $cp) (i32.const #xDFFF))) (then (return (global.get $false)))) (if (i32.eq (local.get $initial-need) (i32.const 1)) (then (if (i32.lt_u (local.get $cp) (i32.const #x80)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 2)) (then (if (i32.lt_u (local.get $cp) (i32.const #x800)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 3)) (then (if (i32.lt_u (local.get $cp) (i32.const #x10000)) (then (return (global.get $false)))))))) (local.set $dest-idx (i32.add (local.get $from) (local.get $written))) (call $i32array-set! (local.get $arr) (local.get $dest-idx) (local.get $cp)) (local.set $written (i32.add (local.get $written) (i32.const 1))) (br $loop))) ;; --- Report number of characters peeked --- (ref.i31 (i32.shl (local.get $written) (i32.const 1)))) (func $peek-byte (type $Prim02) (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (param $skip (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (result (ref eq)) (local $sp (ref null $InputStringPort)) (local $bs (ref eq)) (local $arr (ref $I8Array)) (local $idx i32) (local $limit i32) (local $remaining i32) (local $skip-count i32) (local $peek-idx i32) (local $byte i32) (local $skip-arg (ref eq)) (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) ;; Decode optional skip amount, defaulting to 0. (local.set $skip-count (i32.const 0)) (local.set $skip-arg (ref.i31 (i32.const 0))) (if (ref.eq (local.get $skip) (global.get $missing)) (then) (else (if (i32.eqz (ref.test (ref i31) (local.get $skip))) (then (call $raise-check-fixnum (local.get $skip)) (unreachable))) (local.set $skip-count (i31.get_u (ref.cast (ref i31) (local.get $skip)))) (if (i32.eqz (i32.and (local.get $skip-count) (i32.const 1))) (then (local.set $skip-count (i32.shr_u (local.get $skip-count) (i32.const 1))) (local.set $skip-arg (ref.i31 (i32.shl (local.get $skip-count) (i32.const 1))))) (else (call $raise-check-fixnum (local.get $skip)) (unreachable))))) ;; Ensure the input is a supported port and dispatch accordingly. (if (ref.test (ref $InputStringPort) (local.get $in)) (then (local.set $sp (ref.cast (ref $InputStringPort) (local.get $in)))) (else (if (ref.test (ref $CustomInputPort) (local.get $in)) (then (return (call $peek-byte/custom (ref.cast (ref $CustomInputPort) (local.get $in)) (local.get $skip-arg)))) (else (return (global.get $false)))))) (if (struct.get $InputStringPort $closed (local.get $sp)) (then (call $raise-input-port-closed (global.get $string:peek-byte:input-port-closed)) (unreachable))) (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $limit (struct.get $InputStringPort $len (local.get $sp))) (local.set $remaining (i32.sub (local.get $limit) (local.get $idx))) (if (i32.ge_u (local.get $skip-count) (local.get $remaining)) (then (return (global.get $eof)))) (local.set $peek-idx (i32.add (local.get $idx) (local.get $skip-count))) (local.set $bs (struct.get $InputStringPort $bytes (local.get $sp))) (local.set $arr (struct.get $Bytes $bs (ref.cast (ref $Bytes) (local.get $bs)))) (local.set $byte (array.get_u $I8Array (local.get $arr) (local.get $peek-idx))) (ref.i31 (i32.shl (local.get $byte) (i32.const 1)))) (func $peek-char (type $Prim02) (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (param $skip (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (result (ref eq)) (local $sp (ref null $InputStringPort)) (local $port-bytes (ref $Bytes)) (local $src (ref $I8Array)) (local $idx i32) (local $limit i32) (local $skip-count i32) (local $peek-start i32) (local $skipped i32) (local $byte i32) (local $need i32) (local $initial-need i32) (local $acc i32) (local $cp i32) (local $cont i32) (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) ;; Ensure the input is a string port. (if (i32.eqz (ref.test (ref $InputStringPort) (local.get $in))) (then (return (global.get $false)))) (local.set $sp (ref.cast (ref $InputStringPort) (local.get $in))) ;; Decode optional skip amount, defaulting to 0. (local.set $skip-count (i32.const 0)) (if (ref.eq (local.get $skip) (global.get $missing)) (then) (else (if (i32.eqz (ref.test (ref i31) (local.get $skip))) (then (call $raise-check-fixnum (local.get $skip)) (unreachable))) (local.set $skip-count (i31.get_u (ref.cast (ref i31) (local.get $skip)))) (if (i32.eqz (i32.and (local.get $skip-count) (i32.const 1))) (then (local.set $skip-count (i32.shr_u (local.get $skip-count) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $skip)) (unreachable))))) (local.set $idx (struct.get $InputStringPort $idx (local.get $sp))) (local.set $limit (struct.get $InputStringPort $len (local.get $sp))) (local.set $port-bytes (struct.get $InputStringPort $bytes (local.get $sp))) (local.set $src (struct.get $Bytes $bs (local.get $port-bytes))) (local.set $peek-start (local.get $idx)) (local.set $skipped (i32.const 0)) ;; Skip the requested number of characters without consuming the port. (block $skip-done (loop $skip-loop (br_if $skip-done (i32.ge_u (local.get $skipped) (local.get $skip-count))) (if (i32.ge_u (local.get $peek-start) (local.get $limit)) (then (return (global.get $eof)))) (local.set $byte (array.get_u $I8Array (local.get $src) (local.get $peek-start))) (local.set $peek-start (i32.add (local.get $peek-start) (i32.const 1))) (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (local.set $skipped (i32.add (local.get $skipped) (i32.const 1)))) (else (call $bytes->string/utf-8:determine-utf-8-sequence (local.get $byte)) (local.set $acc) (local.set $need) (local.set $initial-need (local.get $need)) (if (i32.lt_s (local.get $need) (i32.const 0)) (then (return (global.get $false)))) (local.set $cp (local.get $acc)) (block $skip-cont-done (loop $skip-cont (br_if $skip-cont-done (i32.eqz (local.get $need))) (if (i32.ge_u (local.get $peek-start) (local.get $limit)) (then (return (global.get $eof)))) (local.set $cont (array.get_u $I8Array (local.get $src) (local.get $peek-start))) (local.set $peek-start (i32.add (local.get $peek-start) (i32.const 1))) (if (i32.or (i32.lt_u (local.get $cont) (i32.const 128)) (i32.ge_u (local.get $cont) (i32.const 192))) (then (return (global.get $false)))) (local.set $cp (i32.or (i32.shl (local.get $cp) (i32.const 6)) (i32.and (local.get $cont) (i32.const 63)))) (local.set $need (i32.sub (local.get $need) (i32.const 1))) (br $skip-cont))) (if (i32.gt_u (local.get $cp) (i32.const #x10FFFF)) (then (return (global.get $false)))) (if (i32.and (i32.ge_u (local.get $cp) (i32.const #xD800)) (i32.le_u (local.get $cp) (i32.const #xDFFF))) (then (return (global.get $false)))) (if (i32.eq (local.get $initial-need) (i32.const 1)) (then (if (i32.lt_u (local.get $cp) (i32.const #x80)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 2)) (then (if (i32.lt_u (local.get $cp) (i32.const #x800)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 3)) (then (if (i32.lt_u (local.get $cp) (i32.const #x10000)) (then (return (global.get $false)))))) (local.set $skipped (i32.add (local.get $skipped) (i32.const 1))))) (br $skip-loop))) ;; Peek the next character without consuming the port. (if (i32.ge_u (local.get $peek-start) (local.get $limit)) (then (return (global.get $eof)))) (local.set $byte (array.get_u $I8Array (local.get $src) (local.get $peek-start))) (local.set $peek-start (i32.add (local.get $peek-start) (i32.const 1))) (if (i32.lt_u (local.get $byte) (i32.const 128)) (then (local.set $cp (local.get $byte))) (else (call $bytes->string/utf-8:determine-utf-8-sequence (local.get $byte)) (local.set $acc) (local.set $need) (local.set $initial-need (local.get $need)) (if (i32.lt_s (local.get $need) (i32.const 0)) (then (return (global.get $false)))) (local.set $cp (local.get $acc)) (block $cont-done (loop $cont-loop (br_if $cont-done (i32.eqz (local.get $need))) (if (i32.ge_u (local.get $peek-start) (local.get $limit)) (then (return (global.get $eof)))) (local.set $cont (array.get_u $I8Array (local.get $src) (local.get $peek-start))) (local.set $peek-start (i32.add (local.get $peek-start) (i32.const 1))) (if (i32.or (i32.lt_u (local.get $cont) (i32.const 128)) (i32.ge_u (local.get $cont) (i32.const 192))) (then (return (global.get $false)))) (local.set $cp (i32.or (i32.shl (local.get $cp) (i32.const 6)) (i32.and (local.get $cont) (i32.const 63)))) (local.set $need (i32.sub (local.get $need) (i32.const 1))) (br $cont-loop))) (if (i32.gt_u (local.get $cp) (i32.const #x10FFFF)) (then (return (global.get $false)))) (if (i32.and (i32.ge_u (local.get $cp) (i32.const #xD800)) (i32.le_u (local.get $cp) (i32.const #xDFFF))) (then (return (global.get $false)))) (if (i32.eq (local.get $initial-need) (i32.const 1)) (then (if (i32.lt_u (local.get $cp) (i32.const #x80)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 2)) (then (if (i32.lt_u (local.get $cp) (i32.const #x800)) (then (return (global.get $false)))))) (if (i32.eq (local.get $initial-need) (i32.const 3)) (then (if (i32.lt_u (local.get $cp) (i32.const #x10000)) (then (return (global.get $false)))))))) (ref.i31 (i32.or (i32.shl (local.get $cp) (i32.const ,char-shift)) (i32.const ,char-tag)))) ;; Like Racket's peek-bytes, but currently only string ports are supported. ;; The optional skip argument defaults to 0. (func $peek-bytes (type $Prim23) (param $amt (ref eq)) ;; exact-nonnegative-integer? (param $skip (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (result (ref eq)) (local $count-i31 (ref i31)) (local $count i32) (local $skip-arg (ref eq)) (local $skip-val i32) (local $buf (ref $Bytes)) (local $res (ref eq)) (local $peeked i32) (local $arr (ref $I8Array)) (local $new-arr (ref $I8Array)) ;; --- Decode amount --- (if (i32.eqz (ref.test (ref i31) (local.get $amt))) (then (call $raise-check-fixnum (local.get $amt)) (unreachable))) (local.set $count-i31 (ref.cast (ref i31) (local.get $amt))) (local.set $count (i31.get_u (local.get $count-i31))) (if (i32.ne (i32.and (local.get $count) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $amt)) (unreachable))) (local.set $count (i32.shr_u (local.get $count) (i32.const 1))) ;; --- Decode optional skip amount --- (local.set $skip-arg (ref.i31 (i32.const 0))) (local.set $skip-val (i32.const 0)) (if (ref.eq (local.get $skip) (global.get $missing)) (then (local.set $skip-arg (ref.i31 (i32.const 0))) (local.set $skip-val (i32.const 0))) (else (if (i32.eqz (ref.test (ref i31) (local.get $skip))) (then (call $raise-check-fixnum (local.get $skip)) (unreachable))) (local.set $skip-val (i31.get_u (ref.cast (ref i31) (local.get $skip)))) (if (i32.eqz (i32.and (local.get $skip-val) (i32.const 1))) (then (local.set $skip-val (i32.shr_u (local.get $skip-val) (i32.const 1))) (local.set $skip-arg (local.get $skip))) (else (call $raise-check-fixnum (local.get $skip)) (unreachable))))) ;; --- Determine input port --- (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $InputStringPort) (local.get $in))) (then (call $raise-check-string-port (local.get $in)) (unreachable))) ;; --- Handle zero-length peek --- (if (i32.eqz (local.get $count)) (then (return (global.get $bytes:empty)))) ;; --- Allocate destination buffer --- (local.set $buf (ref.cast (ref $Bytes) (call $make-bytes (ref.i31 (i32.shl (local.get $count) (i32.const 1))) (global.get $missing)))) ;; --- Fill buffer using peek-bytes! --- (local.set $res (call $peek-bytes! (local.get $buf) (local.get $skip-arg) (local.get $in) (global.get $missing) (global.get $missing))) ;; Propagate failure conditions (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $res) (global.get $eof)) (then (return (global.get $eof)))) (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (return (global.get $false)))) (local.set $peeked (i31.get_u (ref.cast (ref i31) (local.get $res)))) (if (i32.ne (i32.and (local.get $peeked) (i32.const 1)) (i32.const 0)) (then (return (global.get $false)))) (local.set $peeked (i32.shr_u (local.get $peeked) (i32.const 1))) ;; When no characters were peeked, signal EOF (if (i32.eqz (local.get $peeked)) (then (return (global.get $eof)))) ;; Shrink buffer on partial peek (if (i32.lt_u (local.get $peeked) (local.get $count)) (then (local.set $arr (struct.get $Bytes $bs (local.get $buf))) (local.set $new-arr (call $i8array-copy (local.get $arr) (i32.const 0) (local.get $peeked))) (struct.set $Bytes $bs (local.get $buf) (local.get $new-arr)))) (local.get $buf)) ;; Like Racket's peek-string, but currently only string ports are supported. ;; The optional skip argument defaults to 0. (func $peek-string (type $Prim23) (param $amt (ref eq)) ;; exact-nonnegative-integer? (param $skip (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $in (ref eq)) ;; input-port? (optional, default = (current-input-port)) (result (ref eq)) (local $count-i31 (ref i31)) (local $count i32) (local $skip-arg (ref eq)) (local $skip-val i32) (local $buf (ref $String)) (local $res (ref eq)) (local $peeked i32) (local $arr (ref $I32Array)) (local $new-arr (ref $I32Array)) ;; --- Decode amount --- (if (i32.eqz (ref.test (ref i31) (local.get $amt))) (then (call $raise-check-fixnum (local.get $amt)) (unreachable))) (local.set $count-i31 (ref.cast (ref i31) (local.get $amt))) (local.set $count (i31.get_u (local.get $count-i31))) (if (i32.ne (i32.and (local.get $count) (i32.const 1)) (i32.const 0)) (then (call $raise-check-fixnum (local.get $amt)) (unreachable))) (local.set $count (i32.shr_u (local.get $count) (i32.const 1))) ;; --- Decode optional skip amount --- (local.set $skip-arg (ref.i31 (i32.const 0))) (local.set $skip-val (i32.const 0)) (if (ref.eq (local.get $skip) (global.get $missing)) (then (local.set $skip-arg (ref.i31 (i32.const 0))) (local.set $skip-val (i32.const 0))) (else (if (i32.eqz (ref.test (ref i31) (local.get $skip))) (then (call $raise-check-fixnum (local.get $skip)) (unreachable))) (local.set $skip-val (i31.get_u (ref.cast (ref i31) (local.get $skip)))) (if (i32.eqz (i32.and (local.get $skip-val) (i32.const 1))) (then (local.set $skip-val (i32.shr_u (local.get $skip-val) (i32.const 1))) (local.set $skip-arg (local.get $skip))) (else (call $raise-check-fixnum (local.get $skip)) (unreachable))))) ;; --- Determine input port --- (if (ref.eq (local.get $in) (global.get $missing)) (then (local.set $in (call $current-input-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $InputStringPort) (local.get $in))) (then (call $raise-check-string-port (local.get $in)) (unreachable))) ;; --- Handle zero-length peek --- (if (i32.eqz (local.get $count)) (then (return (global.get $string:empty)))) ;; --- Allocate destination string --- (local.set $buf (call $make-string/checked (local.get $count) (i32.const 0))) ;; --- Fill buffer using peek-string! --- (local.set $res (call $peek-string! (local.get $buf) (local.get $skip-arg) (local.get $in) (global.get $missing) (global.get $missing))) ;; Propagate failure conditions (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (if (ref.eq (local.get $res) (global.get $eof)) (then (return (global.get $eof)))) (if (i32.eqz (ref.test (ref i31) (local.get $res))) (then (return (global.get $false)))) (local.set $peeked (i31.get_u (ref.cast (ref i31) (local.get $res)))) (if (i32.ne (i32.and (local.get $peeked) (i32.const 1)) (i32.const 0)) (then (return (global.get $false)))) (local.set $peeked (i32.shr_u (local.get $peeked) (i32.const 1))) ;; Return EOF when no characters were peeked. (if (i32.eqz (local.get $peeked)) (then (return (global.get $eof)))) (local.set $arr (struct.get $String $codepoints (local.get $buf))) ;; Shrink buffer on partial peek (if (i32.lt_u (local.get $peeked) (local.get $count)) (then (local.set $new-arr (call $i32array-copy (local.get $arr) (i32.const 0) (local.get $peeked))) (struct.set $String $codepoints (local.get $buf) (local.get $new-arr)))) (struct.set $String $hash (local.get $buf) (i32.const 0)) (local.get $buf)) ;;; ;;; 13.3 Byte and String Output ;;; ;; (type $InputStringPort ;; (sub $InputPort ;; (struct ;; (field $name (ref eq)) ; the port name (string) ;; (field $bytes (ref eq)) ; the byte string (bytes) ;; (field $len i32) ; the length of the string ;; (field $idx i32) ; the current index into the string ;; (field $loc (ref $Location)))) ; the current location (func $write-byte (type $Prim12) (param $byte (ref eq)) (param $out (ref eq)) ;; optional output-port?, default = current-output-port (result (ref eq)) (local $b i32) (local $sp (ref null $OutputStringPort)) (local $bs (ref eq)) (local $idx i32) (local $loc (ref $Location)) (local $pos (ref eq)) (local $line (ref eq)) (local $col (ref eq)) (local $int-pos i32) (local $int-line i32) (local $int-col i32) (local $old-len i32) (local $new-len i32) (local $new-bytes (ref $I8Array)) (local $len i32) (local $left i32) (local $seen i32) ;; 1. Cast byte to i31 and extract i32 (if (ref.test (ref i31) (local.get $byte)) (then (local.set $b (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $byte))) (i32.const 1)))) (else (return (global.get $false)))) ;; 2. Cast output to $OutputStringPort (if (ref.eq (local.get $out) (global.get $missing)) (then (local.set $out (call $current-output-port (global.get $missing))))) (if (ref.test (ref $OutputStringPort) (local.get $out)) (then (local.set $sp (ref.cast (ref $OutputStringPort) (local.get $out)))) (else (return (global.get $false)))) (if (struct.get $OutputStringPort $closed (local.get $sp)) (then (call $raise-output-port-closed (global.get $string:write-byte:output-port-closed)) (unreachable))) ;; 3. Get buffer and current index (local.set $bs (struct.get $OutputStringPort $bytes (local.get $sp))) (local.set $idx (struct.get $OutputStringPort $idx (local.get $sp))) ;; 4. Resize buffer if needed (local.set $old-len (struct.get $OutputStringPort $len (local.get $sp))) (if (i32.eq (local.get $idx) (local.get $old-len)) (then (local.set $new-len (i32.shl (local.get $old-len) (i32.const 1))) (local.set $new-bytes (call $i8array-extend (struct.get $Bytes $bs (ref.cast (ref $Bytes) (local.get $bs))) (local.get $new-len) (i32.const 0))) (struct.set $OutputStringPort $bytes (local.get $sp) (struct.new $Bytes (i32.const 0) (i32.const 0) (local.get $new-bytes))) (struct.set $OutputStringPort $len (local.get $sp) (local.get $new-len)) (local.set $bs (struct.get $OutputStringPort $bytes (local.get $sp))))) ;; 5. Write byte into array (call $i8array-set! (struct.get $Bytes $bs (ref.cast (ref $Bytes) (local.get $bs))) (local.get $idx) (local.get $b)) (struct.set $OutputStringPort $idx (local.get $sp) (i32.add (local.get $idx) (i32.const 1))) ;; 6. Load old location fields (local.set $loc (struct.get $OutputStringPort $loc (local.get $sp))) (local.set $pos (struct.get $Location $pos (local.get $loc))) (local.set $line (struct.get $Location $line (local.get $loc))) (local.set $col (struct.get $Location $col (local.get $loc))) (local.set $int-pos (if (result i32) (ref.test (ref i31) (local.get $pos)) (then ,(Half `(i31.get_u (ref.cast (ref i31) (local.get $pos))))) (else (i32.const 0)))) (local.set $int-line (if (result i32) (ref.test (ref i31) (local.get $line)) (then ,(Half `(i31.get_u (ref.cast (ref i31) (local.get $line))))) (else (i32.const 0)))) (local.set $int-col (if (result i32) (ref.test (ref i31) (local.get $col)) (then ,(Half `(i31.get_u (ref.cast (ref i31) (local.get $col))))) (else (i32.const 0)))) ;; 7. Decode UTF-8 byte (local.set $len (struct.get $OutputStringPort $utf8-len (local.get $sp))) (local.set $left (struct.get $OutputStringPort $utf8-left (local.get $sp))) (local.set $seen (struct.get $OutputStringPort $utf8-bytes (local.get $sp))) ;; Start of UTF-8 sequence? (if (i32.eqz (local.get $len)) (then ;; Determine length from lead byte (if (i32.lt_u (local.get $b) (i32.const 128)) ;; ASCII (then (local.set $int-col (i32.add (local.get $int-col) (i32.const 1)))) (else (if (i32.and (i32.ge_u (local.get $b) (i32.const 192)) (i32.le_u (local.get $b) (i32.const 253))) (then (block ;; Count leading 1s to determine length (local.set $len (i32.clz (i32.xor (i32.shl (local.get $b) (i32.const 24)) (i32.const 0xFF000000)))) ;; Limit to 4 (local.set $len ; minimum of $length and 5 (select (local.get $len) (i32.const 4) (i32.lt_u (local.get $len) (i32.const 4)))) (struct.set $OutputStringPort $utf8-len (local.get $sp) (local.get $len)) (struct.set $OutputStringPort $utf8-left (local.get $sp) (i32.sub (local.get $len) (i32.const 1))) (struct.set $OutputStringPort $utf8-bytes (local.get $sp) (i32.const 1))))))) ;; Inside a sequence (else (block (local.set $seen (i32.add (local.get $seen) (i32.const 1))) (local.set $left (i32.sub (local.get $left) (i32.const 1))) (struct.set $OutputStringPort $utf8-left (local.get $sp) (local.get $left)) (struct.set $OutputStringPort $utf8-bytes (local.get $sp) (local.get $seen)) (if (i32.eqz (local.get $left)) (then ;; UTF-8 sequence complete: count one character column. (local.set $int-col (i32.add (local.get $int-col) (i32.const 1))) (struct.set $OutputStringPort $utf8-len (local.get $sp) (i32.const 0)) (struct.set $OutputStringPort $utf8-left (local.get $sp) (i32.const 0)) (struct.set $OutputStringPort $utf8-bytes (local.get $sp) (i32.const 0)))))))) ;; 8. Handle line/column updates (if (i32.eq (local.get $b) (i32.const 10)) ;; '\n' (then (local.set $int-line (i32.add (local.get $int-line) (i32.const 1))) (local.set $int-col (i32.const 0)))) (if (i32.eq (local.get $b) (i32.const 13)) ;; '\r' (then (local.set $int-line (i32.add (local.get $int-line) (i32.const 1))) (local.set $int-col (i32.const 0)))) (if (i32.eq (local.get $b) (i32.const 9)) ;; '\t' (then (local.set $int-col (i32.add (local.get $int-col) (i32.sub (i32.const 8) (i32.rem_u (local.get $int-col) (i32.const 8))))))) ;; Always increment position (local.set $int-pos (i32.add (local.get $int-pos) (i32.const 1))) ;; 9. Store new location (struct.set $OutputStringPort $loc (local.get $sp) (struct.new $Location (i32.const 0) ;; hash (ref.i31 (i32.shl (local.get $int-line) (i32.const 1))) (ref.i31 (i32.shl (local.get $int-col) (i32.const 1))) (ref.i31 (i32.shl (local.get $int-pos) (i32.const 1))))) ;; 10. Return void (global.get $void)) (func $write-char (type $Prim12) (param $char (ref eq)) ;; character? (param $out (ref eq)) ;; optional output-port?, default = (current-output-port) (result (ref eq)) (local $cp i32) (local $byte i32) (local $res (ref eq)) (if (ref.eq (local.get $out) (global.get $missing)) (then (local.set $out (call $current-output-port (global.get $missing))))) ;; Decode the character argument to a Unicode scalar value. (local.set $cp (call $char->integer/i32 (local.get $char))) ;; Fast path for ASCII characters (1-byte UTF-8 sequence). (if (i32.le_u (local.get $cp) (i32.const 0x7f)) (then (local.set $byte (local.get $cp)) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (return (global.get $void)))) ;; Two-byte UTF-8 sequence for U+0080 .. U+07FF. (if (i32.le_u (local.get $cp) (i32.const 0x7ff)) (then (local.set $byte (i32.or (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0xc0))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (local.set $byte (i32.or (i32.and (local.get $cp) (i32.const 0x3f)) (i32.const 0x80))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (return (global.get $void)))) ;; Three-byte UTF-8 sequence for U+0800 .. U+FFFF. (if (i32.le_u (local.get $cp) (i32.const 0xffff)) (then (local.set $byte (i32.or (i32.shr_u (local.get $cp) (i32.const 12)) (i32.const 0xe0))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (local.set $byte (i32.or (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3f)) (i32.const 0x80))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (local.set $byte (i32.or (i32.and (local.get $cp) (i32.const 0x3f)) (i32.const 0x80))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (return (global.get $void)))) ;; Four-byte UTF-8 sequence for U+10000 .. U+10FFFF. (local.set $byte (i32.or (i32.shr_u (local.get $cp) (i32.const 18)) (i32.const 0xf0))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (local.set $byte (i32.or (i32.and (i32.shr_u (local.get $cp) (i32.const 12)) (i32.const 0x3f)) (i32.const 0x80))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (local.set $byte (i32.or (i32.and (i32.shr_u (local.get $cp) (i32.const 6)) (i32.const 0x3f)) (i32.const 0x80))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (local.set $byte (i32.or (i32.and (local.get $cp) (i32.const 0x3f)) (i32.const 0x80))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (return (global.get $void)) ;; Should be unreachable because all cases return above. (global.get $void)) (func $newline (type $Prim01) (param $out (ref eq)) ;; optional output-port?, default = (current-output-port) (result (ref eq)) (local $res (ref eq)) (if (ref.eq (local.get $out) (global.get $missing)) (then (local.set $out (call $current-output-port (global.get $missing))))) ;; Delegate to write-char with the newline character. (call $write-char ,(Imm #\newline) (local.get $out))) ;; Like Racket's write-bytes, but currently only string ports are supported ;; as output destinations. (func $write-bytes (type $Prim14) (param $bstr (ref eq)) ;; bytes? (param $out (ref eq)) ;; output-port? (optional, default = (current-output-port)) (param $start (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $end (ref eq)) ;; exact-nonnegative-integer? (optional, default = (bytes-length bstr)) (result (ref eq)) (local $bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $from i32) (local $to i32) (local $count i32) (local $idx i32) (local $byte i32) (local $i i32) (local $res (ref eq)) ;; --- Validate byte string argument --- (if (i32.eqz (ref.test (ref $Bytes) (local.get $bstr))) (then (call $raise-check-bytes (local.get $bstr)) (unreachable))) (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (call $i8array-length (local.get $arr))) ;; --- Determine output port --- (if (ref.eq (local.get $out) (global.get $missing)) (then (local.set $out (call $current-output-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $OutputStringPort) (local.get $out))) (then (call $raise-check-string-port (local.get $out)) (unreachable))) ;; --- Decode optional start index --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $from (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $from (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.eqz (i32.and (local.get $from) (i32.const 1))) (then (local.set $from (i32.shr_u (local.get $from) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable))))) ;; --- Decode optional end index --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $to (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $to (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.eqz (i32.and (local.get $to) (i32.const 1))) (then (local.set $to (i32.shr_u (local.get $to) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; --- Bounds checks --- (if (i32.gt_u (local.get $from) (local.get $to)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $from) (local.get $to)) (unreachable))) (if (i32.gt_u (local.get $to) (local.get $len)) (then (call $raise-bad-bytes-range (local.get $bstr) (local.get $from) (local.get $to)) (unreachable))) (local.set $count (i32.sub (local.get $to) (local.get $from))) ;; --- Write the requested slice byte by byte --- (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $count))) (local.set $idx (i32.add (local.get $from) (local.get $i))) (local.set $byte (call $i8array-ref (local.get $arr) (local.get $idx))) (local.set $res (call $write-byte (ref.i31 (i32.shl (local.get $byte) (i32.const 1))) (local.get $out))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; --- Report number of bytes written --- (ref.i31 (i32.shl (local.get $count) (i32.const 1)))) (func $raise-bad-string-range (param $x (ref eq)) (param i32) (param i32) (unreachable)) ;; Like Racket's write-string, but currently only string ports are supported ;; as output destinations. The port argument must be provided explicitly. (func $write-string (type $Prim14) (param $str (ref eq)) ;; string? (param $out (ref eq)) ;; output-port? (optional, default = (current-output-port)) (param $start (ref eq)) ;; exact-nonnegative-integer? (optional, default = 0) (param $end (ref eq)) ;; exact-nonnegative-integer? (optional, default = (string-length str)) (result (ref eq)) (local $s (ref $String)) (local $arr (ref $I32Array)) (local $len i32) (local $from i32) (local $to i32) (local $count i32) (local $bytes (ref $Bytes)) (local $res (ref eq)) ;; --- Validate string argument --- (if (i32.eqz (ref.test (ref $String) (local.get $str))) (then (call $raise-check-string (local.get $str)) (unreachable))) (local.set $s (ref.cast (ref $String) (local.get $str))) (local.set $arr (struct.get $String $codepoints (local.get $s))) (local.set $len (call $i32array-length (local.get $arr))) ;; --- Determine output port --- (if (ref.eq (local.get $out) (global.get $missing)) (then (local.set $out (call $current-output-port (global.get $missing))))) (if (i32.eqz (ref.test (ref $OutputStringPort) (local.get $out))) (then (call $raise-check-string-port (local.get $out)) (unreachable))) ;; --- Decode optional start index --- (if (ref.eq (local.get $start) (global.get $missing)) (then (local.set $from (i32.const 0))) (else (if (ref.test (ref i31) (local.get $start)) (then (local.set $from (i31.get_u (ref.cast (ref i31) (local.get $start)))) (if (i32.eqz (i32.and (local.get $from) (i32.const 1))) (then (local.set $from (i32.shr_u (local.get $from) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable)))) (else (call $raise-check-fixnum (local.get $start)) (unreachable))))) ;; --- Decode optional end index --- (if (ref.eq (local.get $end) (global.get $missing)) (then (local.set $to (local.get $len))) (else (if (ref.test (ref i31) (local.get $end)) (then (local.set $to (i31.get_u (ref.cast (ref i31) (local.get $end)))) (if (i32.eqz (i32.and (local.get $to) (i32.const 1))) (then (local.set $to (i32.shr_u (local.get $to) (i32.const 1)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable)))) (else (call $raise-check-fixnum (local.get $end)) (unreachable))))) ;; --- Bounds checks --- (if (i32.gt_u (local.get $from) (local.get $to)) (then (call $raise-bad-string-range (local.get $str) (local.get $from) (local.get $to)) (unreachable))) (if (i32.gt_u (local.get $to) (local.get $len)) (then (call $raise-bad-string-range (local.get $str) (local.get $from) (local.get $to)) (unreachable))) (local.set $count (i32.sub (local.get $to) (local.get $from))) ;; --- Convert requested slice to bytes --- (local.set $bytes (ref.cast (ref $Bytes) (call $string->bytes/utf-8 (local.get $str) (global.get $false) (ref.i31 (i32.shl (local.get $from) (i32.const 1))) (ref.i31 (i32.shl (local.get $to) (i32.const 1)))))) ;; --- Delegate to write-bytes --- (local.set $res (call $write-bytes (local.get $bytes) (local.get $out) (global.get $missing) (global.get $missing))) (if (ref.eq (local.get $res) (global.get $false)) (then (return (global.get $false)))) ;; --- Report number of characters written --- (ref.i31 (i32.shl (local.get $count) (i32.const 1)))) ;;; ;;; FFI Helpers ;;; (func $js-log (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $len i32) (global.set $result-bytes (call $s-exp->fasl (local.get $v) (global.get $false))) #;(local.set $len (call $copy_bytes_to_memory (global.get $memory-map:callback-buffer-base))) (local.set $len (call $copy-bytes-to-callback-buffer (global.get $result-bytes))) (call $js_print_fasl (global.get $memory-map:callback-buffer-base) (local.get $len)) (global.get $void)) (func $copy-bytes-to-memory ;; Copy a Racket $Bytes object into linear memory at $ptr. (param $bs-any (ref eq)) ;; source: expected (ref $Bytes) (param $ptr i32) ;; destination address in linear memory (result i32) ;; number of bytes copied (local $bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $i i32) (local $val i32) ;; 1) Type-check: ensure $bs-any is a $Bytes (if (i32.eqz (ref.test (ref $Bytes) (local.get $bs-any))) (then (call $raise-expected-bytes (local.get $bs-any)) (unreachable))) (local.set $bs (ref.cast (ref $Bytes) (local.get $bs-any))) ;; 2) Get backing array and its length (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (array.len (local.get $arr))) ;; 3) Copy loop (local.set $i (i32.const 0)) (block $done (loop $copy (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $val (array.get_u $I8Array (local.get $arr) (local.get $i))) (i32.store8 (i32.add (local.get $ptr) (local.get $i)) (local.get $val)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $copy))) ;; 4) Return number of bytes copied (local.get $len)) (func $memory-range->immutable-bytes ;; Copy linear memory into an immutable byte string. (param $ptr i32) ;; source address in linear memory (param $len i32) ;; byte count (result (ref $Bytes)) (local $arr (ref $I8Array)) (local $i i32) (local.set $arr (array.new_default $I8Array (local.get $len))) (local.set $i (i32.const 0)) (block $done (loop $copy (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (array.set $I8Array (local.get $arr) (local.get $i) (i32.load8_u (i32.add (local.get $ptr) (local.get $i)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $copy))) (struct.new $Bytes (i32.const 0) ;; hash (i32.const 1) ;; immutable (local.get $arr))) (func $linear-memory-range-available? ;; Check whether ptr..ptr+len is inside the current memory. (param $ptr i32) (param $len i32) (result i32) (local $mem-bytes i32) (local $end i32) (local.set $mem-bytes (i32.mul (memory.size) (i32.const 65536))) (local.set $end (i32.add (local.get $ptr) (local.get $len))) (if (i32.lt_u (local.get $end) (local.get $ptr)) (then (return (i32.const 0)))) (i32.le_u (local.get $end) (local.get $mem-bytes))) (func $copy-bytes-to-callback-buffer ;; Copy encoded callback/FASL bytes into their static region. (param $bs-any (ref eq)) (result i32) (local $bs (ref $Bytes)) (local $len i32) (if (i32.eqz (ref.test (ref $Bytes) (local.get $bs-any))) (then (call $raise-expected-bytes (local.get $bs-any)) (unreachable))) (local.set $bs (ref.cast (ref $Bytes) (local.get $bs-any))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $bs)))) (if (i32.gt_u (local.get $len) (global.get $memory-map:callback-buffer-length)) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:callback-buffer-base) (global.get $memory-map:callback-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (call $copy-bytes-to-memory (local.get $bs) (global.get $memory-map:callback-buffer-base))) #;(func $copy_bytes_to_memory (export "copy_bytes_to_memory") (param $ptr i32) ;; destination address in linear memory (result i32) ;; number of bytes copied (local $i i32) (local $val i32) (local $len i32) (local $bs (ref $Bytes)) (local $arr (ref $I8Array)) ;; 1. Cast global to (ref $Bytes) (local.set $bs (ref.cast (ref $Bytes) (global.get $result-bytes))) ;; 2. Get backing array (local.set $arr (struct.get $Bytes $bs (local.get $bs))) ;; 3. Get length of array (local.set $len (array.len (local.get $arr))) ;; 4. Loop to copy each byte (local.set $i (i32.const 0)) (block $done (loop $copy ;; if i >= len, break (br_if $done (i32.ge_u (local.get $i) (local.get $len))) ;; val = arr[i] (local.set $val (array.get_u $I8Array (local.get $arr) (local.get $i))) ;; memory[ptr + i] = val (i32.store8 (i32.add (local.get $ptr) (local.get $i)) (local.get $val)) ;; i++ (local.set $i (i32.add (local.get $i) (i32.const 1))) ;; loop again (br $copy))) ;; 5. Return total bytes copied (local.get $len)) ;;; ;;; CALLBACKS (Calling webracket from js) ;;; ;; callback-result->bridge-payload : any/c boolean? -> any/c ;; Convert a callback result into a host-serializable bridge payload. (func $callback-result->bridge-payload (param $v (ref eq)) (param $success? i32) (result (ref eq)) (if (result (ref eq)) (local.get $success?) (then (local.get $v)) (else (if (result (ref eq)) (ref.eq (call $exn? (local.get $v)) (global.get $false)) (then (call $format/display (local.get $v))) (else (call $exn-message (local.get $v))))))) ;; callback-result->bridge-vector : boolean? any/c -> vector? ;; Build the tagged callback bridge result returned to JS. (func $callback-result->bridge-vector (param $success? i32) (param $payload (ref eq)) (result (ref $Vector)) (struct.new $Vector (i32.const 0) (i32.const 1) (array.new_fixed $Array 2 (if (result (ref eq)) (local.get $success?) (then (global.get $true)) (else (global.get $false))) (local.get $payload)))) ;; callback-non-fasl-return-message : any/c -> string? ;; Explain that a callback returned a value with no JS/FASL encoding. (func $callback-non-fasl-return-message (param $v (ref eq)) (result (ref $String)) (local $out (ref $GrowableArray)) (local.set $out (call $make-growable-array (i32.const 2))) (call $growable-array-add! (local.get $out) (global.get $string:callback:no-js-equivalent)) (call $growable-array-add! (local.get $out) (call $format/display (local.get $v))) (call $array-of-strings->string (call $growable-array->array (local.get $out)))) ;;; ;;; Browser Console Bridge (globalThis.WR) ;;; (func $wr-result->bridge-vector (param $success? (ref eq)) (param $kind (ref eq)) (param $value (ref eq)) (param $printed (ref eq)) (param $error (ref eq)) (result (ref $Vector)) (struct.new $Vector (i32.const 0) (i32.const 1) (array.new_fixed $Array 5 (local.get $success?) (local.get $kind) (local.get $value) (local.get $printed) (local.get $error)))) (func $wr-copy-result (param $v (ref eq)) (result i32) (global.set $result-bytes (call $s-exp->fasl (local.get $v) (global.get $false))) (call $copy-bytes-to-callback-buffer (ref.cast (ref $Bytes) (global.get $result-bytes)))) (func $wr-bridge-error-message (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (call $exn? (local.get $v)) (global.get $false)) (then (call $format/display (local.get $v))) (else (call $exn-message (local.get $v))))) (func $wr-success (param $v (ref eq)) (result (ref $Vector)) (call $wr-result->bridge-vector (global.get $true) (global.get $string:wr-kind:value) (local.get $v) (call $format/display (local.get $v)) (global.get $false))) (func $wr-failure (param $kind (ref eq)) (param $printed (ref eq)) (param $error (ref eq)) (result (ref $Vector)) (call $wr-result->bridge-vector (global.get $false) (local.get $kind) (global.get $false) (local.get $printed) (local.get $error))) (func $wr-binding-value (param $sym (ref $Symbol)) (result (ref eq) i32) ,(let loop ([bindings console-bridge-bindings]) (cond [(null? bindings) `(return (global.get $false) (i32.const 0))] [else (match-define (cons binding rest) bindings) (match binding [(list 'top _ const-name x mutable? _ _) `(if (ref.eq (local.get $sym) (global.get ,(console-bridge-symbol-global const-name))) (then (return ,(if mutable? `(struct.get $Boxed $v (ref.cast (ref $Boxed) (struct.get $Boxed $v (ref.cast (ref $Boxed) (global.get ,(Var x)))))) `(struct.get $Boxed $v (ref.cast (ref $Boxed) (global.get ,(Var x))))) (i32.const 1))) (else ,(loop rest)))] [(list 'primitive pr const-name _) `(if (ref.eq (local.get $sym) (global.get ,(console-bridge-symbol-global const-name))) (then (return (global.get ,($ (prim: pr))) (i32.const 1))) (else ,(loop rest)))])])) (unreachable)) (func $wr-top-level-names (result (ref $Vector)) (struct.new $Vector (i32.const 0) (i32.const 1) (array.new_fixed $Array ,(length console-bridge-bindings) ,@(for/list ([binding (in-list console-bridge-bindings)]) (match binding [(list 'top _ const-name _ _ _ _) `(call $symbol->string (global.get ,(console-bridge-symbol-global const-name)))] [(list 'primitive _ const-name _) `(call $symbol->string (global.get ,(console-bridge-symbol-global const-name)))]))))) (func $wr-top-level-names-detailed (result (ref $Vector)) (struct.new $Vector (i32.const 0) (i32.const 1) (array.new_fixed $Array ,(length console-bridge-bindings) ,@(for/list ([binding (in-list console-bridge-bindings)]) (match binding [(list 'top _ const-name _ mutable? origin-kind-const-name source-path-const-name) `(struct.new $Vector (i32.const 0) (i32.const 1) (array.new_fixed $Array 5 (call $symbol->string (global.get ,(console-bridge-symbol-global const-name))) (global.get ,($ (string->symbol (~a "string:" origin-kind-const-name)))) ,(if mutable? `(global.get $true) `(global.get $false)) (global.get $string:wr-console-bridge-kind) ,(if source-path-const-name `(global.get ,($ (string->symbol (~a "string:" source-path-const-name)))) `(global.get $false))))]))))) (func $wr-name->symbol (param $name (ref eq)) (result (ref $Symbol)) (if (ref.test (ref $Symbol) (local.get $name)) (then (return (ref.cast (ref $Symbol) (local.get $name))))) (if (ref.test (ref $String) (local.get $name)) (then (return (ref.cast (ref $Symbol) (call $string->symbol (ref.cast (ref $String) (local.get $name))))))) (call $raise-argument-error1 (global.get $symbol:wr-ref) (global.get $string:string-or-symbol?) (local.get $name)) (unreachable)) (func $wr-ref (export "wr-ref") (param $fasl i32) (result i32) (local $name (ref eq)) (local $sym (ref $Symbol)) (local $val (ref eq)) (local $found? i32) (local $bridge (ref $Vector)) (local $bridge-exn (ref eq)) (local $bridge-sentinel (ref eq)) (local $len i32) (local.set $name (call $fasl-memory->s-exp (local.get $fasl))) (local.set $sym (call $wr-name->symbol (local.get $name))) (call $wr-binding-value (local.get $sym)) (local.set $found?) (local.set $val) (local.set $bridge (if (result (ref $Vector)) (i32.eqz (local.get $found?)) (then (call $wr-failure (global.get $string:wr-kind:missing-binding) (global.get $false) (call $string-append/2 (global.get $string:missing-binding) (call $symbol->string (local.get $sym))))) (else (call $wr-success (local.get $val))))) (local.set $bridge-sentinel (call $cons (global.get $false) (global.get $false))) (local.set $bridge-exn (block $wr-ref-encode-failed (result (ref eq)) (try_table (result (ref eq)) (catch $exn $wr-ref-encode-failed) (global.set $result-bytes (call $s-exp->fasl (local.get $bridge) (global.get $false))) (local.get $bridge-sentinel)))) (if (i32.eqz (ref.eq (local.get $bridge-exn) (local.get $bridge-sentinel))) (then (global.set $result-bytes (call $s-exp->fasl (call $wr-failure (global.get $string:wr-kind:exception) (global.get $false) (call $callback-non-fasl-return-message (if (result (ref eq)) (i32.eqz (local.get $found?)) (then (local.get $sym)) (else (local.get $val))))) (global.get $false))))) (local.set $len (call $copy-bytes-to-callback-buffer (ref.cast (ref $Bytes) (global.get $result-bytes)))) (local.get $len)) (func $wr-call (export "wr-call") (param $fasl i32) (result i32) (local $req (ref $Vector)) (local $arr (ref $Array)) (local $name (ref eq)) (local $args-val (ref eq)) (local $sym (ref $Symbol)) (local $val (ref eq)) (local $found? i32) (local $proc (ref $Procedure)) (local $args (ref $Args)) (local $res (ref eq)) (local $bridge (ref $Vector)) (local $bridge-exn (ref eq)) (local $bridge-sentinel (ref eq)) (local $len i32) (local.set $req (ref.cast (ref $Vector) (call $fasl-memory->s-exp (local.get $fasl)))) (local.set $arr (struct.get $Vector $arr (local.get $req))) (local.set $name (array.get $Array (local.get $arr) (i32.const 0))) (local.set $args-val (array.get $Array (local.get $arr) (i32.const 1))) (local.set $sym (call $wr-name->symbol (local.get $name))) (call $wr-binding-value (local.get $sym)) (local.set $found?) (local.set $val) (local.set $res (global.get $false)) (local.set $bridge (if (result (ref $Vector)) (i32.eqz (local.get $found?)) (then (call $wr-failure (global.get $string:wr-kind:missing-binding) (global.get $false) (call $string-append/2 (global.get $string:missing-binding) (call $symbol->string (local.get $sym))))) (else (if (result (ref $Vector)) (i32.eqz (ref.test (ref $Procedure) (local.get $val))) (then (call $wr-failure (global.get $string:wr-kind:not-procedure) (call $format/display (local.get $val)) (call $string-append/2 (global.get $string:wr:not-procedure) (call $symbol->string (local.get $sym))))) (else (local.set $proc (ref.cast (ref $Procedure) (local.get $val))) (local.set $args (ref.cast (ref $Args) (struct.get $Vector $arr (ref.cast (ref $Vector) (local.get $args-val))))) (local.set $res (block $wr-call-raised (result (ref eq)) (try_table (result (ref eq)) (catch $exn $wr-call-raised) (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (struct.get $Procedure $invoke (local.get $proc)))))) (if (result (ref $Vector)) (ref.eq (call $exn? (local.get $res)) (global.get $false)) (then (call $wr-success (local.get $res))) (else (call $wr-failure (global.get $string:wr-kind:exception) (global.get $false) (call $wr-bridge-error-message (local.get $res)))))))))) (local.set $bridge-sentinel (call $cons (global.get $false) (global.get $false))) (local.set $bridge-exn (block $wr-call-encode-failed (result (ref eq)) (try_table (result (ref eq)) (catch $exn $wr-call-encode-failed) (global.set $result-bytes (call $s-exp->fasl (local.get $bridge) (global.get $false))) (local.get $bridge-sentinel)))) (if (i32.eqz (ref.eq (local.get $bridge-exn) (local.get $bridge-sentinel))) (then (global.set $result-bytes (call $s-exp->fasl (call $wr-failure (global.get $string:wr-kind:exception) (global.get $false) (call $callback-non-fasl-return-message (local.get $res))) (global.get $false))))) (local.set $len (call $copy-bytes-to-callback-buffer (ref.cast (ref $Bytes) (global.get $result-bytes)))) (local.get $len)) (func $wr-names (export "wr-names") (param $ignored i32) (result i32) (call $wr-copy-result (call $wr-top-level-names))) (func $wr-names-detailed (export "wr-names-detailed") (param $ignored i32) (result i32) (call $wr-copy-result (call $wr-top-level-names-detailed))) (func $wr-format (export "wr-format") (param $fasl i32) (result i32) (call $wr-copy-result (call $format/display (call $fasl-memory->s-exp (local.get $fasl))))) (func $callback-register (export "callback-register") (param $p (ref $Procedure)) (result i32) (local $g (ref $GrowableArray)) (local $i i32) (local.set $g (global.get $callback-registry)) (local.set $i (call $growable-array-count (local.get $g))) (call $growable-array-add! (local.get $g) (local.get $p)) (local.get $i)) (func $callback (export "callback") (param $id i32) (param $fasl i32) ; index into linear memory (result i32) (local $proc (ref $Procedure)) (local $vec (ref $Vector)) (local $args (ref $Args)) (local $res (ref eq)) (local $payload (ref eq)) (local $bridge (ref $Vector)) (local $bridge-exn (ref eq)) (local $bridge-sentinel (ref eq)) (local $success? i32) (local $len i32) ;; Look up procedure by id (local.set $proc (ref.cast (ref $Procedure) (call $growable-array-ref (global.get $callback-registry) (local.get $id)))) ;; Decode FASL-encoded arguments from linear memory (local.set $vec (ref.cast (ref $Vector) (call $linear-memory->value (local.get $fasl)))) (local.set $args (ref.cast (ref $Args) (struct.get $Vector $arr (local.get $vec)))) ;; Invoke procedure and tag the callback outcome before ;; serializing anything across the Wasm<->JS callback bridge. (local.set $success? (i32.const 0)) (local.set $res (block $callback-raised (result (ref eq)) (try_table (result (ref eq)) (catch $exn $callback-raised) (local.set $res (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (struct.get $Procedure $invoke (local.get $proc)))) (local.set $success? (i32.const 1)) (local.get $res)))) (local.set $payload (call $callback-result->bridge-payload (local.get $res) (local.get $success?))) (local.set $bridge (call $callback-result->bridge-vector (local.get $success?) (local.get $payload))) ;; Encode tagged result and copy to memory for host. If a ;; successful callback returned a non-FASL value, convert that ;; serialization failure into an ordinary callback failure. (local.set $bridge-sentinel (call $cons (global.get $false) (global.get $false))) (local.set $bridge-exn (block $callback-encode-failed (result (ref eq)) (try_table (result (ref eq)) (catch $exn $callback-encode-failed) (global.set $result-bytes (call $s-exp->fasl (local.get $bridge) (global.get $false))) (local.get $bridge-sentinel)))) (if (i32.eqz (ref.eq (local.get $bridge-exn) (local.get $bridge-sentinel))) (then (local.set $payload (call $callback-non-fasl-return-message (local.get $res))) (local.set $bridge (call $callback-result->bridge-vector (i32.const 0) (local.get $payload))) (global.set $result-bytes (call $s-exp->fasl (local.get $bridge) (global.get $false))))) (local.set $len (call $copy-bytes-to-callback-buffer (ref.cast (ref $Bytes) (global.get $result-bytes)))) (local.get $len)) ;; callback-accepts-argc : callback-id argc -> boolean ;; Returns 1 when the callback procedure accepts argc arguments, else 0. (func $callback-accepts-argc (export "callback-accepts-argc") (param $id i32) (param $argc i32) (result i32) (local $proc (ref $Procedure)) (local.set $proc (ref.cast (ref $Procedure) (call $growable-array-ref (global.get $callback-registry) (local.get $id)))) (call $procedure-arity-includes?/checked/i32 (local.get $proc) (local.get $argc))) ;; callback-expected-arity : callback-id -> fasl-string-byte-length ;; Encodes the callback's expected-arity string in the callback buffer. (func $callback-expected-arity (export "callback-expected-arity") (param $id i32) (result i32) (local $proc (ref $Procedure)) (local $len i32) (local.set $proc (ref.cast (ref $Procedure) (call $growable-array-ref (global.get $callback-registry) (local.get $id)))) (global.set $result-bytes (call $s-exp->fasl (call $procedure-arity->expected-string (local.get $proc)) (global.get $false))) (local.set $len (call $copy-bytes-to-callback-buffer (ref.cast (ref $Bytes) (global.get $result-bytes)))) (local.get $len)) ;; callback-name : callback-id -> fasl-name-byte-length ;; Encodes the callback's object-name result in the callback buffer. (func $callback-name (export "callback-name") (param $id i32) (result i32) (local $proc (ref $Procedure)) (local $len i32) (local.set $proc (ref.cast (ref $Procedure) (call $growable-array-ref (global.get $callback-registry) (local.get $id)))) (global.set $result-bytes (call $s-exp->fasl (call $object-name (local.get $proc)) (global.get $false))) (local.set $len (call $copy-bytes-to-callback-buffer (ref.cast (ref $Bytes) (global.get $result-bytes)))) (local.get $len)) ;; callback-debug-id : callback-id -> fasl-debug-id-byte-length ;; Encodes the callback's closure debug-id in the callback buffer, or #f. (func $callback-debug-id (export "callback-debug-id") (param $id i32) (result i32) (local $proc (ref $Procedure)) (local $len i32) (local $debug-id (ref eq)) (local.set $proc (ref.cast (ref $Procedure) (call $growable-array-ref (global.get $callback-registry) (local.get $id)))) (local.set $debug-id (if (result (ref eq)) (ref.test (ref $Closure) (local.get $proc)) (then (struct.get $Closure $debug-id (ref.cast (ref $Closure) (local.get $proc)))) (else (global.get $false)))) (global.set $result-bytes (call $s-exp->fasl (local.get $debug-id) (global.get $false))) (local.set $len (call $copy-bytes-to-callback-buffer (ref.cast (ref $Bytes) (global.get $result-bytes)))) (local.get $len)) (func $procedure->external (export "procedure->external") (param $proc (ref eq)) (result (ref eq)) (local $p (ref $Procedure)) (local $id i32) (local $cb (ref extern)) ;; Fail-early type check (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $p (ref.cast (ref $Procedure) (local.get $proc))) (local.set $id (call $callback-register (local.get $p))) (local.set $cb (call $js-make-callback (local.get $id))) ;; Wrap extern callback as a Racket external value (struct.new $External (i32.const 0) (local.get $cb))) ;;; ;;; 5. STRUCTURES ;;; ;; TODO ;; [x] structure types and super structs ;; [x] auto fields ;; [ ] prefab structures ;; [ ] structure guards ;; [ ] applicable structures ;; 5.1 Defining Structure Types: struct ;; [/] struct [syntax] ;; [x] struct-field-index [syntax] ;; [x] define-struct [syntax] ;; [x] struct/derived [syntax] ;; [x] define-struct/derived [syntax] ;; 5.2 Creating Structure Types ;; [x] make-struct-type ;; [x] make-struct-field-accessor ;; [x] make-struct-field-mutator ;; [ ] prop:sealed [value] ;; - a structure type property ;; - a sealed struct can not be a supertype of another structure type ;; 5.3 Structure Type Properties ;; [x] make-struct-type-property ;; [x] struct-type-property? ;; [x] struct-type-property-accessor-procedure? ;; [x] struct-type-property-predicate-procedure? ;; 5.4 Generic Interfaces (racket/generic) ;; ... ;; 5.5 Copying and Updating Structures ;; [ ] struct-copy [syntax] ;; 5.6 Structure Utilities ;; [x] struct->vector ;; [x] struct? ;; [x] struct-type? ;; [x] struct-constructor-procedure? ;; [x] struct-predicate-procedure? ;; [x] struct-accessor-procedure? ;; [x] struct-mutator-procedure? ;; [ ] prefab-struct-key ;; [ ] make-prefab-struct ;; [ ] prefab-struct-type-key+ field-count ;; [ ] prefab-key->struct-type ;; [ ] prefab-key? ;; 5.6.1 Additional Structure Utilities ;; [ ] make-constructor-style-printer ;; [x] struct->list (func $struct?/i32 (param $v (ref eq)) (result i32) (ref.test (ref $Struct) (local.get $v))) (func $struct? (type $Prim1) ,@(make-predicate-body '$Struct)) (func $struct-type?/i32 (param $v (ref eq)) (result i32) (ref.test (ref $StructType) (local.get $v))) (func $struct-type? (type $Prim1) ,@(make-predicate-body '$StructType)) (func $raise-check-struct-type (unreachable)) (func $check-struct-type (param $name (ref $String)) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v) (global.get $false)) (then (local.get $v)) ;; allow #f (else (if (result (ref eq)) (ref.test (ref $StructType) (local.get $v)) (then (local.get $v)) (else (call $raise-check-struct-type) #;(call $raise-argument-error (local.get $name) (call $str-struct-type-or-false) (local.get $v)) (unreachable)))))) (func $struct-type-is-a?/i32 ; is $a a subtype of $b ? (param $a (ref eq)) (param $b (ref eq)) (result i32) (local $cur (ref eq)) (local.set $cur (local.get $a)) (loop $walk ;; Success: found a matching struct type in the hierarchy. (if (ref.eq (local.get $cur) (local.get $b)) (then (return (i32.const 1)))) ;; Failure: reached the end of the chain or encountered a ;; non-struct descriptor before finding $b. (if (ref.eq (local.get $cur) (global.get $false)) (then (return (i32.const 0)))) (if (i32.eqz (ref.test (ref $StructType) (local.get $cur))) (then (return (i32.const 0)))) ;; Walk up the supertype chain and continue searching. (local.set $cur (struct.get $StructType $super (ref.cast (ref $StructType) (local.get $cur)))) (br $walk)) (unreachable)) (func $struct-type-authentic? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $std (ref $StructType)) (local $props (ref $HashEqMutable)) (local $has i32) (if (i32.eqz (ref.test (ref $StructType) (local.get $v))) (then (call $raise-argument-error1 (global.get $symbol:struct-type-authentic?) (global.get $string:struct-type?) (local.get $v)) (unreachable))) (local.set $std (ref.cast (ref $StructType) (local.get $v))) (local.set $props (ref.cast (ref $HashEqMutable) (struct.get $StructType $properties (local.get $std)))) (local.set $has (call $struct-type-property-table-has-name?/i32 (local.get $props) (ref.cast (ref $Symbol) (global.get $symbol:prop:authentic)))) (if (result (ref eq)) (i32.eq (local.get $has) (i32.const 1)) (then (global.get $true)) (else (global.get $false)))) ;; Note: The #:on-opaque keyword is accepted as a positional optional argument. ;; Keyword arguments are not yet supported, so callers must pass the ;; mode as the second argument directly. (func $struct->list (type $Prim02) (param $v (ref eq)) (param $mode-raw (ref eq)) ;; optional, defaults to 'error (result (ref eq)) (local $mode (ref eq)) (local $s (ref $Struct)) (local $fields (ref $Array)) (local $i i32) (local $count i32) (local $acc (ref eq)) (local $field (ref eq)) ;; Decode optional #:on-opaque mode. (local.set $mode (global.get $symbol:error)) (if (ref.eq (local.get $mode-raw) (global.get $missing)) (then) (else (if (ref.eq (local.get $mode-raw) (global.get $symbol:error)) (then) (else (if (ref.eq (local.get $mode-raw) (global.get $symbol:return-false)) (then (local.set $mode (global.get $symbol:return-false))) (else (if (ref.eq (local.get $mode-raw) (global.get $symbol:skip)) (then (local.set $mode (global.get $symbol:skip))) (else (call $raise-argument-error1 (global.get $symbol:struct->list) (global.get $string:struct->list:on-opaque) (local.get $mode-raw)) (unreachable))))))))) ;; Handle non-struct inputs according to on-opaque mode. (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (if (ref.eq (local.get $mode) (global.get $symbol:error)) (then (call $raise-argument-error1 (global.get $symbol:struct->list) (global.get $string:struct?) (local.get $v)) (unreachable)) (else (if (ref.eq (local.get $mode) (global.get $symbol:return-false)) (then (return (global.get $false))) (else (return (global.get $null)))))))) ;; Convert accessible struct fields to a list. (local.set $s (ref.cast (ref $Struct) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $s))) (local.set $count (array.len (local.get $fields))) (local.set $acc (global.get $null)) (local.set $i (i32.sub (local.get $count) (i32.const 1))) (block $done (loop $loop (br_if $done (i32.lt_s (local.get $i) (i32.const 0))) (local.set $field (array.get $Array (local.get $fields) (local.get $i))) (local.set $acc (struct.new $Pair (i32.const 0) (local.get $field) (local.get $acc))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $loop))) (local.get $acc)) (func $struct->vector (type $Prim02) (param $v (ref eq)) (param $opaque-raw (ref eq)) ;; optional, defaults to '... (result (ref eq)) (local $opaque (ref eq)) (local $s (ref $Struct)) (local $type (ref $StructType)) (local $name (ref $Symbol)) (local $name-str (ref $String)) (local $tag-str (ref $String)) (local $tag (ref $Symbol)) (local $fields (ref $Array)) (local $arr (ref $Array)) (local $count i32) (local $i i32) (local $field (ref eq)) ;; Decode optional opaque value argument. (local.set $opaque (global.get $symbol:...)) (if (ref.eq (local.get $opaque-raw) (global.get $missing)) (then) (else (local.set $opaque (local.get $opaque-raw)))) ;; Validate struct argument. (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error1 (global.get $symbol:struct->vector) (global.get $string:struct?) (local.get $v)) (unreachable))) ;; Extract struct information. (local.set $s (ref.cast (ref $Struct) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $s))) (local.set $count (array.len (local.get $fields))) (local.set $type (struct.get $Struct $type (local.get $s))) (local.set $name (struct.get $StructType $name (local.get $type))) (local.set $name-str (ref.cast (ref $String) (call $symbol->immutable-string (local.get $name)))) (local.set $tag-str (call $string-append/2 (global.get $string:struct:prefix) (local.get $name-str))) (local.set $tag (call $string->symbol/checked (local.get $tag-str))) ;; Allocate result vector backing array. (local.set $arr (array.new $Array (global.get $false) (i32.add (local.get $count) (i32.const 1)))) (array.set $Array (local.get $arr) (i32.const 0) (local.get $tag)) ;; Copy fields after the tag entry. (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $count))) (local.set $field (array.get $Array (local.get $fields) (local.get $i))) (array.set $Array (local.get $arr) (i32.add (local.get $i) (i32.const 1)) (local.get $field)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Build and return the vector. (struct.new $Vector (i32.const 0) (i32.const 0) (local.get $arr))) (func $raise-format/display:struct:expected-struct (unreachable)) (func $format/display:struct (param $v (ref eq)) (result (ref $String)) (local $s (ref $Struct)) (local $type (ref $StructType)) (local $name (ref eq)) (local $fields (ref $Array)) (local $n i32) (local $i i32) (local $out (ref $GrowableArray)) ;; Check struct type (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-format/display:struct:expected-struct))) ;; Cast and extract (local.set $s (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $s))) (local.set $name (struct.get $StructType $name (local.get $type))) (local.set $fields (struct.get $Struct $fields (local.get $s))) (local.set $n (array.len (local.get $fields))) ;; Start output (local.set $out (call $make-growable-array (i32.const 8))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:struct-open))) ;; "#(struct " ;; Add name (call $growable-array-add! (local.get $out) (call $format/display (local.get $name))) ;; Add each field (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $n))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:space))) (call $growable-array-add! (local.get $out) (call $format/display (array.get $Array (local.get $fields) (local.get $i)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Close output (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:close-paren))) (call $growable-array-of-strings->string (local.get $out))) (func $make-struct-type-descriptor (param $name (ref eq)) ;; (ref $Symbol) (param $super-type (ref eq)) ;; (ref $StructType) or #f (param $init-field-count (ref eq)) ;; fixnum (param $auto-field-count (ref eq)) ;; fixnum ; optional: (param $auto-field-value (ref eq)) ;; value to repeat (param $opt-props (ref eq)) ;; or #f (param $inspector (ref eq)) ;; or #f (param $proc-spec (ref eq)) ;; or #f (param $immutables (ref eq)) ;; or #f (param $opt-guard (ref eq)) ;; or #f (param $constructor-name (ref eq)) ;; or #f (result (ref $StructType)) (local $ifc i32) (local $afc i32) (local $props (ref eq)) (local $super (ref $StructType)) ;; --- Argument checks --- (if (i32.eqz (ref.test (ref $Symbol) (local.get $name))) (then (call $raise-argument-error (local.get $name)))) (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $init-field-count))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $init-field-count))) (i32.const 1)) (i32.const 0))) (then (call $raise-argument-error (local.get $init-field-count)))) (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $auto-field-count))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $auto-field-count))) (i32.const 1)) (i32.const 0))) (then (call $raise-argument-error (local.get $auto-field-count)))) ;; --- Decode fixnums --- (local.set $ifc (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $init-field-count))) (i32.const 1))) (local.set $afc (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $auto-field-count))) (i32.const 1))) ;; --- Handle optional props --- (local.set $props (local.get $opt-props)) (if (ref.eq (local.get $props) (global.get $missing)) (then (local.set $props (global.get $null))) (else (if (ref.eq (local.get $props) (global.get $false)) (then (local.set $props (global.get $null)))))) ;; --- Cast super if not #f --- #;(local.set $super (if (result (ref $StructType)) (ref.eq (local.get $super-type) (global.get $false)) (then (ref.null $StructType)) (else (ref.cast (ref $StructType) (local.get $super-type))))) ;; --- Delegate to /checked --- (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (local.get $name)) (local.get $super-type) (local.get $ifc) (local.get $afc) (local.get $auto-field-value) (local.get $props) (local.get $inspector) (local.get $proc-spec) (local.get $immutables) (local.get $opt-guard) (local.get $constructor-name))) (func $make-struct-type-descriptor/checked (param $name (ref $Symbol)) ;; Struct name (param $super (ref eq)) ;; (ref $StructType) or #f (param $ifc i32) ;; Init field count (decoded) (param $afc i32) ;; Auto field count (decoded) (param $auto-value (ref eq)) ;; Value to fill auto fields (param $props (ref eq)) ;; Property hash or #f (param $inspector (ref eq)) ;; Inspector or #f (param $proc-spec (ref eq)) ;; Unused (param $immutables (ref eq)) ;; Immutables or #f (param $guard (ref eq)) ;; Guard or #f (param $constructor-name (ref eq)) ;; Symbol or #f (result (ref $StructType)) (local $has-super i32) (local $super-typed (ref null $StructType)) (local $stfc i32) (local $init-indices (ref eq)) (local $auto-indices (ref eq)) (local $auto-values (ref eq)) (local $total-fields i32) (local $props-table (ref $HashEqMutable)) ;; Default all list fields (local.set $init-indices (global.get $false)) (local.set $auto-indices (global.get $false)) (local.set $auto-values (global.get $false)) ;; Determine presence of supertype (local.set $has-super (i32.eqz (ref.eq (local.get $super) (global.get $false)))) (if (local.get $has-super) (then (local.set $super-typed (ref.cast (ref $StructType) (local.get $super))) (local.set $stfc (struct.get $StructType $field-count (local.get $super-typed))) (local.set $init-indices (call $append/2 (struct.get $StructType $init-indices (local.get $super-typed)) (call $list-from-range/checked (local.get $stfc) (i32.add (local.get $stfc) (local.get $ifc))))) (local.set $auto-indices (call $append/2 (struct.get $StructType $auto-indices (local.get $super-typed)) (call $list-from-range/checked (i32.add (local.get $stfc) (local.get $ifc)) (i32.add (local.get $stfc) (i32.add (local.get $ifc) (local.get $afc)))))) (local.set $auto-values (call $append/2 (struct.get $StructType $auto-values (local.get $super-typed)) (call $make-list/checked (local.get $afc) (local.get $auto-value))))) (else (local.set $stfc (i32.const 0)) (local.set $init-indices (call $list-from-range/checked (i32.const 0) (local.get $ifc))) (local.set $auto-indices (call $list-from-range/checked (local.get $ifc) (i32.add (local.get $ifc) (local.get $afc)))) (local.set $auto-values (call $make-list/checked (local.get $afc) (local.get $auto-value))))) ;; Structure type properties (local.set $props-table (if (result (ref $HashEqMutable)) (ref.test (ref $HashEqMutable) (local.get $props)) (then (ref.cast (ref $HashEqMutable) (local.get $props))) (else (ref.cast (ref $HashEqMutable) (call $struct-type-properties-normalize (local.get $has-super) (local.get $super-typed) (local.get $props)))))) ;; Compute total field count (local.set $total-fields (i32.add (local.get $stfc) (i32.add (local.get $ifc) (local.get $afc)))) ;; Create struct type descriptor (struct.new $StructType (i32.const 0) ;; hash (lazily computed) (local.get $name) (local.get $super) (local.get $total-fields) (local.get $init-indices) (local.get $auto-indices) (local.get $auto-values) (ref.cast (ref eq) (local.get $props-table)) (local.get $inspector) (local.get $immutables) (local.get $guard) (local.get $constructor-name))) #;(func $make-struct-type-descriptor/checked ;; Required (param $name (ref $Symbol)) ;; Symbol naming the struct type (param $super (ref eq)) ;; (ref $StructType) or #f (param $ifc i32) ;; Number of init fields (unwrapped fixnum) (param $afc i32) ;; Number of auto fields (unwrapped fixnum) (param $auto-value (ref eq)) ;; Value to fill for each auto field ;; Optional (already defaulted appropriately) (param $props (ref eq)) ;; Property table (hash table) or #f (param $inspector (ref eq)) ;; Inspector object or #f (param $proc-spec (ref eq)) ;; Currently unused (param $immutables (ref eq)) ;; Immutables descriptor or #f (param $guard (ref eq)) ;; Guard procedure or #f (param $constructor-name (ref eq)) ;; Name symbol or #f (result (ref $StructType)) (local $has-super i32) (local $super-typed (ref null $StructType)) (local $stfc i32) (local $init-indices (ref eq)) (local $auto-indices (ref eq)) (local $auto-values (ref eq)) (local $total-fields i32) ;; Initialize locals (local.set $init-indices (global.get $false)) (local.set $auto-indices (global.get $false)) (local.set $auto-values (global.get $false)) ;; Determine if super is present (local.set $has-super (i32.eqz (ref.eq (local.get $super) (global.get $false)))) (if (local.get $has-super) (then (local.set $super-typed (ref.cast (ref $StructType) (local.get $super))) (local.set $stfc (struct.get $StructType $field-count (local.get $super-typed))) (local.set $init-indices (call $append/2 (struct.get $StructType $init-indices (local.get $super-typed)) (call $list-from-range/checked (local.get $stfc) (local.get $ifc)))) (local.set $auto-indices (call $append/2 (struct.get $StructType $auto-indices (local.get $super-typed)) (call $list-from-range/checked (i32.add (local.get $stfc) (local.get $ifc)) (local.get $afc)))) (local.set $auto-values (call $append/2 (struct.get $StructType $auto-values (local.get $super-typed)) (call $make-list/checked (local.get $afc) (local.get $auto-value))))) (else (local.set $stfc (i32.const 0)) (local.set $init-indices (call $list-from-range/checked (i32.const 0) (local.get $ifc))) (local.set $auto-indices (call $list-from-range/checked (local.get $ifc) (local.get $afc))) (local.set $auto-values (call $make-list/checked (local.get $afc) (local.get $auto-value))))) (local.set $total-fields (i32.add (local.get $stfc) (i32.add (local.get $ifc) (local.get $afc)))) (struct.new $StructType (i32.const 0) ;; $hash (local.get $name) (local.get $super) (local.get $total-fields) (local.get $init-indices) (local.get $auto-indices) (local.get $auto-values) (local.get $props) (local.get $inspector) (local.get $immutables) (local.get $guard) (local.get $constructor-name))) ;; > (topexpand #`(let () (struct foo (bar)) (foo 11))) ;; # ;; (make-struct-type 'foo ; name ;; '#f ; no super ;; '1 ; init-field-count ;; '0 ; auto-field-cnt ;; optional: ;; '#f ; auto-v ;; null ; props ;; (current-inspector) ; inspector ;; '#f ; proc-spec ;; '(0) ; immutables ;; '#f ; guard ;; 'foo))) ; constructor-name ;; $make-struct-type ;; returns 5 values: ;; struct-type? ;; struct-constructor-procedure? ;; struct-predicate-procedure? ;; struct-accessor-procedure? ;; struct-mutator-procedure? (func $make-struct-type ;; Parameters (param $name (ref eq)) ;; (ref $Symbol) (param $super (ref eq)) ;; (ref $StructType) or #f (param $init-count (ref eq)) ;; fixnum (param $auto-count (ref eq)) ;; fixnum ; optional: (param $auto-val (ref eq)) ;; default value for auto fields (param $props (ref eq)) ;; hash table or #f (param $inspector (ref eq)) ;; inspector object or #f (param $proc-spec (ref eq)) ;; unused (param $immutables (ref eq)) ;; immutable mask or #f (param $guard (ref eq)) ;; closure or #f (param $constructor-name (ref eq)) ;; symbol or #f (result (ref eq)) ;; returns 5-values packed as a pair ; ;; values: (struct-type, constructor, predicate, accessor, mutator) ;; Locals (local $init i32) (local $auto i32) (local $super-count i32) (local $field-count i32) (local $std (ref $StructType)) (local $ctor (ref eq)) (local $pred (ref eq)) (local $acc (ref eq)) (local $mut (ref eq)) (local $super-count-fx (ref eq)) (local $has-super i32) (local $super-typed (ref null $StructType)) (local $props-final (ref $HashEqMutable)) (local $props-new (ref $HashEqMutable)) (local $props-spec (ref eq)) (local $props-list (ref eq)) (local $props-cursor (ref eq)) (local $props-cell (ref $Pair)) (local $entry (ref $Pair)) (local $entry-raw (ref eq)) (local $prop-desc (ref $StructTypeProperty)) (local $prop-raw (ref eq)) (local $value (ref eq)) (local $sti (ref eq)) (local $sentinel (ref eq)) (local $new-list (ref eq)) (local $struct-name (ref eq)) ;; --- Type checks --- (if (i32.eqz (ref.test (ref $Symbol) (local.get $name))) (then (call $raise-argument-error (local.get $name)))) (if (i32.and (i32.eqz (ref.eq (local.get $super) (global.get $false))) (i32.eqz (ref.test (ref $StructType) (local.get $super)))) (then (call $raise-argument-error (local.get $super)))) (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $init-count))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $init-count))) (i32.const 1)) (i32.const 0))) (then (call $raise-argument-error (local.get $init-count)))) (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $auto-count))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $auto-count))) (i32.const 1)) (i32.const 0))) (then (call $raise-argument-error (local.get $auto-count)))) ;; --- Decode fixnums --- (local.set $init (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $init-count))) (i32.const 1))) (local.set $auto (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $auto-count))) (i32.const 1))) ;; --- Super field count --- (local.set $has-super (i32.eqz (ref.eq (local.get $super) (global.get $false)))) (if (local.get $has-super) (then (local.set $super-typed (ref.cast (ref $StructType) (local.get $super)))) (else (local.set $super-typed (ref.null $StructType)))) (local.set $super-count (if (result i32) (local.get $has-super) (then (struct.get $StructType $field-count (ref.as_non_null (local.get $super-typed)))) (else (i32.const 0)))) (local.set $super-count-fx (ref.i31 (i32.shl (local.get $super-count) (i32.const 1)))) (local.set $field-count (i32.add (local.get $super-count) (i32.add (local.get $init) (local.get $auto)))) ;; --- Initialize property tables --- (local.set $props-new (call $struct-type-property-table-empty)) (local.set $props-final (call $struct-type-property-table-empty)) (if (local.get $has-super) (then (local.set $props-final (call $struct-type-property-table-copy (ref.cast (ref $HashEqMutable) (struct.get $StructType $properties (ref.as_non_null (local.get $super-typed)))))) (if (i32.eq (call $struct-type-property-table-has-name?/i32 (local.get $props-final) (ref.cast (ref $Symbol) (global.get $symbol:prop:sealed))) (i32.const 1)) (then (call $raise-argument-error (local.get $super)) (unreachable))))) ;; --- Canonicalize properties specification --- (local.set $props-spec (local.get $props)) (if (ref.eq (local.get $props-spec) (global.get $missing)) (then (local.set $props-spec (global.get $null)))) (if (ref.eq (local.get $props-spec) (global.get $false)) (then (local.set $props-spec (global.get $null)))) (local.set $props-list (local.get $props-spec)) (if (ref.test (ref $HashEqMutable) (local.get $props-list)) (then (local.set $props-list (call $hasheq->list/plain/checked (ref.cast (ref $HashEqMutable) (local.get $props-list)))))) ;; --- Create struct type descriptor --- (local.set $std (call $make-struct-type-descriptor/checked (ref.cast (ref $Symbol) (local.get $name)) (local.get $super) (local.get $init) (local.get $auto) (local.get $auto-val) (ref.cast (ref eq) (local.get $props-final)) (local.get $inspector) (local.get $proc-spec) (local.get $immutables) (local.get $guard) (local.get $constructor-name))) (local.set $struct-name (struct.get $StructType $name (local.get $std))) ;; --- Prepare struct-type-info for property guards --- (local.set $sti (call $struct-type-info-for-guard (local.get $std) (ref.cast (ref $Symbol) (local.get $name)) (local.get $init) (local.get $auto) (local.get $super) (local.get $super-count-fx) (local.get $immutables))) ;; --- Process property associations --- (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $props-cursor (local.get $props-list)) (block $done (loop $walk (br_if $done (ref.eq (local.get $props-cursor) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $props-cursor))) (then (call $raise-argument-error (local.get $props-cursor)) (unreachable))) (local.set $props-cell (ref.cast (ref $Pair) (local.get $props-cursor))) (local.set $entry-raw (struct.get $Pair $a (local.get $props-cell))) (if (i32.eqz (ref.test (ref $Pair) (local.get $entry-raw))) (then (call $raise-argument-error (local.get $entry-raw)) (unreachable))) (local.set $entry (ref.cast (ref $Pair) (local.get $entry-raw))) (local.set $prop-raw (struct.get $Pair $a (local.get $entry))) (if (i32.eqz (ref.test (ref $StructTypeProperty) (local.get $prop-raw))) (then (call $raise-argument-error (local.get $prop-raw)) (unreachable))) (local.set $prop-desc (ref.cast (ref $StructTypeProperty) (local.get $prop-raw))) (local.set $value (struct.get $Pair $d (local.get $entry))) (drop (call $struct-type-property-attach! (local.get $prop-desc) (local.get $value) (local.get $props-new) (local.get $sti) (local.get $sentinel))) (local.set $props-cursor (struct.get $Pair $d (local.get $props-cell))) (br $walk))) ;; --- Merge processed properties into final table --- (local.set $new-list (call $hasheq->list/plain/checked (local.get $props-new))) (drop (call $struct-type-property-merge-list! (local.get $props-final) (local.get $new-list))) ;; --- Create constructor --- (local.set $ctor (call $make-struct-constructor/checked (local.get $std))) (local.set $pred (call $make-struct-predicate/checked (local.get $std))) (local.set $acc (call $make-struct-accessor/checked (local.get $std) (local.get $super-count-fx) (local.get $struct-name))) (local.set $mut (call $make-struct-mutator/checked (local.get $std) (local.get $super-count-fx))) ;; --- Return values as a compound value --- (array.new_fixed $Values 5 (local.get $std) ; struct type descriptor (local.get $ctor) ; constructor procedure (local.get $pred) ; predicate procedure (local.get $acc) ; accessor procedure (local.get $mut))) ; mutator procedure (func $make-struct-accessor (param $std (ref eq)) (param $field-index (ref eq)) ;; fixnum (param $super-count (ref eq)) ;; fixnum (result (ref eq)) ;; $StructAccessorProcedure (local $i i32) (local $std-typed (ref $StructType)) (local $struct-name (ref eq)) ;; Type checks (if (i32.eqz (ref.test (ref $StructType) (local.get $std))) (then (call $raise-argument-error (local.get $std)))) (if (i32.eqz (ref.test (ref i31) (local.get $field-index))) (then (call $raise-argument-error (local.get $field-index)))) (if (i32.eqz (ref.test (ref i31) (local.get $super-count))) (then (call $raise-argument-error (local.get $super-count)))) ;; Decode (local.set $i (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $field-index))) (i32.const 1))) ;; Prepare typed std and accessor name (local.set $std-typed (ref.cast (ref $StructType) (local.get $std))) (local.set $struct-name (struct.get $StructType $name (local.get $std-typed))) ;; Delegate (call $make-struct-accessor/checked (local.get $std-typed) (local.get $super-count) (local.get $struct-name))) (func $make-struct-accessor/checked (param $std (ref $StructType)) (param $super-count (ref eq)) (param $struct-name (ref eq)) (result (ref eq)) ;; StructAccessorProcedure (local $free (ref $Free)) ;; Pack just std and super-count; index comes at runtime (local.set $free (array.new_fixed $Free 2 (local.get $std) (local.get $super-count))) (struct.new $StructAccessorProcedure (i32.const 0) ; hash (local.get $struct-name) ; name: #f or $String (ref.i31 (i32.const 4)) ; arity: 2 (global.get $false) ; realm: #f or $Symbol (ref.func $invoke-closure) ; invoke (used by apply, map, etc.) (global.get $false) ; debug-id (ref.func $struct-accessor) (local.get $free))) (func $struct-accessor (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $free (ref $Free)) (local $std (ref $StructType)) ; free[0] (local $super-count-fx (ref eq)) ; free[1] (local $super-count i32) (local $target (ref eq)) ; args[0] (local $struct (ref $Struct)) (local $index-fx (ref eq)) ; args[1] (local $index i32) (local $skip i32) (local $fields (ref $Array)) ;; Unpack free vars (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $std (ref.cast (ref $StructType) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $super-count-fx (array.get $Free (local.get $free) (i32.const 1))) (local.set $super-count (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $super-count-fx))) (i32.const 1))) (local.set $skip (local.get $super-count)) ;; Get struct (local.set $target (array.get $Args (local.get $args) (i32.const 0))) (if (i32.eqz (ref.test (ref $Struct) (local.get $target))) (then (call $raise-argument-error (local.get $target)))) (local.set $struct (ref.cast (ref $Struct) (local.get $target))) ;; Get index and decode (local.set $index-fx (array.get $Args (local.get $args) (i32.const 1))) (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $index-fx))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $index-fx))) (i32.const 1)) (i32.const 0))) (then (call $raise-argument-error (local.get $index-fx)))) (local.set $index (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $index-fx))) (i32.const 1))) ;; Type match - is $struct a subtype of $std (if (i32.eqz (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (then (call $raise-argument-error (local.get $target)))) ;; Get and return field (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.get $Array (local.get $fields) (i32.add (local.get $skip) (local.get $index)))) (func $make-struct-constructor ;; Parameters (param $v (ref eq)) ;; Expected: (ref $StructType) (result (ref eq)) ;; Returns: closure (local $std (ref $StructType)) ;; Type check (if (i32.eqz (ref.test (ref $StructType) (local.get $v))) (then (call $raise-argument-error (local.get $v)))) ;; Decode (local.set $std (ref.cast (ref $StructType) (local.get $v))) ;; Delegate (call $make-struct-constructor/checked (local.get $std))) (elem declare funcref (ref.func $struct-constructor/no-guard) ; closure body (ref.func $struct-accessor) ; closure body (ref.func $struct-mutator) ; closure body (ref.func $struct-predicate) ; closure body (ref.func $struct-field-accessor/specialized) ; closure body (ref.func $struct-mutator/specialized) (ref.func $struct-type-property-predicate) ; closure body (ref.func $struct-type-property-accessor) ; closure body (ref.func $equal+hash-recur/equal) ; closure body (ref.func $equal+hash-recur/equal-always) ; closure body (ref.func $equal+hash-recur/hash) ; closure body (ref.func $invoke-struct) (ref.func $primitive-invoke) (ref.func $invoke-reduced-procedure) (ref.func $invoke-composed-procedure) (ref.func $code:case-lambda-dispatch) (ref.func $invoke-case-closure) #;(ref.func $struct-constructor/with-guard) ;; Primitive invokers for the different shapes ,@(for/list ([shape (in-list primitive-shapes)]) `(ref.func ,($ (shape->invoker shape)))) ;; Declare all primitives ,@(for/list ([pr (in-list (active-described-primitives-sorted))]) `(ref.func ,($ pr))) ;; Declare FFI-backed primitive wrappers. ,@(for/list ([pr (in-list (active-ffi-primitive-names))]) `(ref.func ,($ pr)))) (func $struct-constructor/no-guard (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $free (ref $Free)) (local $std (ref $StructType)) (local $init-indices (ref eq)) (local $auto-indices (ref eq)) (local $auto-values (ref eq)) (local $name (ref eq)) (local $field-count i32) (local $arr (ref $Array)) (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $std (ref.cast (ref $StructType) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $init-indices (array.get $Free (local.get $free) (i32.const 1))) (local.set $auto-indices (array.get $Free (local.get $free) (i32.const 2))) (local.set $auto-values (array.get $Free (local.get $free) (i32.const 3))) (local.set $name (array.get $Free (local.get $free) (i32.const 4))) (local.set $field-count (struct.get $StructType $field-count (local.get $std))) (local.set $arr (array.new $Array (global.get $false) (local.get $field-count))) (call $fill-fields-from-args (local.get $arr) (local.get $init-indices) (local.get $args)) (call $fill-fields-from-values (local.get $arr) (local.get $auto-indices) (local.get $auto-values)) (struct.new $Struct ; $Heap (i32.const 0) ;; hash ; $Procedure (local.get $name) ;; $false or a $Symbol ,(Imm 0) ;; fixnum (i31 with lsb=0) or (arity-at-least n) ,(Imm #f) ;; $false or $Symbol (ref.func $invoke-struct) ; $Struct (local.get $std) (local.get $arr))) (func $make-struct-constructor/checked (param $std (ref $StructType)) (result (ref eq)) ;; closure (local $field-count i32) (local $guard (ref eq)) (local $struct-name (ref eq)) (local $constructor-name (ref eq)) (local $closure-name (ref eq)) (local $init-indices (ref eq)) (local $auto-indices (ref eq)) (local $auto-values (ref eq)) (local $free (ref $Free)) (local $code (ref $ClosureCode)) (local $arity i32) ;; Extract descriptor data (local.set $field-count (struct.get $StructType $field-count (local.get $std))) (local.set $guard (struct.get $StructType $guard (local.get $std))) (local.set $struct-name (struct.get $StructType $name (local.get $std))) (local.set $constructor-name (struct.get $StructType $constructor-name (local.get $std))) (local.set $init-indices (struct.get $StructType $init-indices (local.get $std))) (local.set $auto-indices (struct.get $StructType $auto-indices (local.get $std))) (local.set $auto-values (struct.get $StructType $auto-values (local.get $std))) (local.set $arity (call $length/i32 (local.get $init-indices))) (local.set $closure-name (if (result (ref eq)) (ref.eq (local.get $constructor-name) (global.get $false)) (then (local.get $struct-name)) (else (local.get $constructor-name)))) ;; Choose code based on guard ;; TODO We are ignoring guards for now. #;(local.set $code (if (result (ref $ClosureCode)) (ref.eq (local.get $guard) (global.get $false)) (then (ref.func $struct-constructor/no-guard)) (else (ref.func $struct-constructor/with-guard)))) (local.set $code (ref.func $struct-constructor/no-guard)) ;; Build free array (local.set $free (array.new_fixed $Free 5 (local.get $std) (local.get $init-indices) (local.get $auto-indices) (local.get $auto-values) local.get $struct-name)) ;; Construct closure (struct.new $Closure (i32.const 0) ; hash (local.get $closure-name) ; name: #f or $Symbol (ref.i31 (i32.shl (local.get $arity) (i32.const 1))) ; arity (global.get $false) ; realm: #f or $Symbol (ref.func $invoke-closure) ; invoke (used by apply, map, etc.) (global.get $false) ; debug-id (local.get $code) (local.get $free))) (func $make-struct-mutator/checked ; Makes a generic mutator that can mutate any field. (param $std (ref $StructType)) (param $super-count-fx (ref eq)) ;; fixnum: number of supertype fields to skip (result (ref eq)) ;; returns a closure (local $free (ref $Free)) ;; Store the struct type and super-field count in the closure's free array (local.set $free (array.new_fixed $Free 2 (local.get $std) (local.get $super-count-fx))) (struct.new $StructMutatorProcedure (i32.const 0) ; hash (global.get $false) ; name: #f or $String (ref.i31 (i32.const 6)) ; arity: 3 (global.get $false) ; realm: #f or $Symbol (ref.func $invoke-closure) ; invoke (used by apply, map, etc.) (global.get $false) ; debug-id (ref.func $struct-mutator) (local.get $free))) (func $struct-mutator ; closure body (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) ;; returns # (local $free (ref $Free)) (local $std (ref $StructType)) ; free[0] (local $super-count-fx (ref eq)) ; free[1] (local $super-count i32) (local $target (ref eq)) ; args[0] (local $struct (ref $Struct)) ; = (cast $Struct $target) (local $index-fx (ref eq)) ; args[1] (local $index i32) ; = decoded $index-fx (local $val (ref eq)) ; args[2] (local $fields (ref $Array)) ; fields of the received struct (local $skip i32) ;; --- Unpack closure free vars --- (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $std (ref.cast (ref $StructType) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $super-count-fx (array.get $Free (local.get $free) (i32.const 1))) (local.set $super-count (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $super-count-fx))) (i32.const 1))) (local.set $skip (local.get $super-count)) ;; --- Get arguments --- ;; struct (local.set $target (array.get $Args (local.get $args) (i32.const 0))) (if (i32.eqz (ref.test (ref $Struct) (local.get $target))) (then (call $raise-argument-error (local.get $target)))) (local.set $struct (ref.cast (ref $Struct) (local.get $target))) ;; index (local.set $index-fx (array.get $Args (local.get $args) (i32.const 1))) (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $index-fx))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $index-fx))) (i32.const 1)) (i32.const 0))) (then (call $raise-argument-error (local.get $index-fx)))) (local.set $index (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $index-fx))) (i32.const 1))) ;; value to store (local.set $val (array.get $Args (local.get $args) (i32.const 2))) ;; --- Type match --- ; Check that the structure received has the same type as the mutator. ;; Type match - is $struct a subtype of $std (if (i32.eqz (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (then (call $raise-argument-error (local.get $target)))) ;; --- Set field --- (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.set $Array (local.get $fields) (i32.add (local.get $skip) (local.get $index)) (local.get $val)) ;; --- Return # --- (global.get $void)) (func $make-struct-mutator/specialized/checked ; helper ; The specialized version saves a fixed index to mutate ; in the free variables. (param $std (ref $StructType)) (param $super-count-fx (ref eq)) ;; fixnum (param $index-fx (ref eq)) ;; fixnum (param $name (ref eq)) ;; symbol used for object-name (param $realm (ref eq)) ;; procedure realm symbol (result (ref eq)) ;; returns closure (local $free (ref $Free)) (local.set $free (array.new_fixed $Free 3 (local.get $std) (local.get $super-count-fx) (local.get $index-fx))) (struct.new $StructMutatorProcedure (i32.const 0) ; hash (local.get $name) ; name: #f or $Symbol (ref.i31 (i32.const 4)) ; arity: 2 (local.get $realm) ; realm: #f or $Symbol (ref.func $invoke-closure) ; invoke (used by apply, map, etc.) (global.get $false) ; debug-id (ref.func $struct-mutator/specialized) (local.get $free))) (func $struct-mutator/specialized ; closure code (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $free (ref $Free)) (local $std (ref $StructType)) ; free[0] (local $super-count-fx (ref eq)) ; free[1] (local $index-fx (ref eq)) ; free[2] (local $super-count i32) (local $index i32) (local $target (ref eq)) ; args[0] (local $struct (ref $Struct)) (local $val (ref eq)) ; args[1] (local $fields (ref $Array)) (local $skip i32) ;; --- Unpack free vars --- (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $std (ref.cast (ref $StructType) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $super-count-fx (array.get $Free (local.get $free) (i32.const 1))) (local.set $index-fx (array.get $Free (local.get $free) (i32.const 2))) (local.set $super-count (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $super-count-fx))) (i32.const 1))) (local.set $index (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $index-fx))) (i32.const 1))) (local.set $skip (local.get $super-count)) ;; --- Get arguments --- (local.set $target (array.get $Args (local.get $args) (i32.const 0))) (if (i32.eqz (ref.test (ref $Struct) (local.get $target))) (then (call $raise-argument-error (local.get $target)))) (local.set $struct (ref.cast (ref $Struct) (local.get $target))) (local.set $val (array.get $Args (local.get $args) (i32.const 1))) ;; --- Type check --- ;; Is $struct a subtype of $std (if (i32.eqz (call $struct-type-is-a?/i32 (struct.get $Struct $type (local.get $struct)) (local.get $std))) (then (call $raise-argument-error (local.get $target)))) ;; --- Set field --- (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.set $Array (local.get $fields) (i32.add (local.get $skip) (local.get $index)) (local.get $val)) ;; --- Return # --- (global.get $void)) (func $build-structure-field-mutator-name ; Builds set--! (param $struct-name (ref $String)) (param $field-name (ref $String)) (result (ref $Symbol)) (local $parts (ref eq)) (local.set $parts (global.get $null)) (local.set $parts (call $cons (global.get $string:bang) (local.get $parts))) (local.set $parts (call $cons (local.get $field-name) (local.get $parts))) (local.set $parts (call $cons (global.get $string:dash) (local.get $parts))) (local.set $parts (call $cons (local.get $struct-name) (local.get $parts))) (local.set $parts (call $cons (global.get $string:dash) (local.get $parts))) (local.set $parts (call $cons (global.get $string:set) (local.get $parts))) (call $string->symbol/checked (ref.cast (ref $String) (call $string-append (local.get $parts))))) (func $make-struct-field-mutator ; Racket primitive ; Note: This functions uses the internal representation of ; a mutator procedure closure. (param $mutator-proc (ref eq)) ;; closure (param $field-pos-fx (ref eq)) ;; fixnum (param $field/proc-name (ref eq)) ;; symbol, string, or #f (param $arg-contract-str (ref eq)) ;; string/symbol/#f (param $realm (ref eq)) ;; symbol or #f (result (ref eq)) ;; returns a specialized mutator (local $clos (ref $Closure)) (local $code (ref $ClosureCode)) (local $free (ref $Free)) (local $std (ref $StructType)) (local $super-count-fx (ref eq)) (local $struct-name (ref eq)) (local $struct-name-sym (ref $Symbol)) (local $struct-name-str (ref $String)) (local $field-name (ref eq)) (local $field-name-sym (ref $Symbol)) (local $field-name-str (ref $String)) (local $name (ref eq)) (local $realm-checked (ref eq)) (local $arg-contract (ref eq)) (local $combine? i32) ;; --- Check mutator-proc is a closure --- (if (i32.eqz (ref.test (ref $Closure) (local.get $mutator-proc))) (then (call $raise-argument-error (local.get $mutator-proc)))) (local.set $clos (ref.cast (ref $Closure) (local.get $mutator-proc))) ;; --- Extract fields --- (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $std (ref.cast (ref $StructType) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $super-count-fx (array.get $Free (local.get $free) (i32.const 1))) (local.set $struct-name (struct.get $StructType $name (local.get $std))) (local.set $field-name (local.get $field/proc-name)) (local.set $arg-contract (local.get $arg-contract-str)) (local.set $realm-checked (local.get $realm)) ;; --- Validate realm --- (if (i32.and (i32.eqz (ref.eq (local.get $realm-checked) (global.get $false))) (i32.eqz (ref.test (ref $Symbol) (local.get $realm-checked)))) (then (call $raise-argument-error (local.get $realm-checked)))) ;; --- Determine procedure name --- (local.set $name (global.get $symbol:mutator)) (if (ref.eq (local.get $field-name) (global.get $false)) (then) (else (if (ref.test (ref $Symbol) (local.get $field-name)) (then (local.set $field-name-sym (ref.cast (ref $Symbol) (local.get $field-name))) (local.set $combine? (if (result i32) (ref.eq (local.get $arg-contract) (global.get $false)) (then (i32.const 1)) (else (if (result i32) (ref.test (ref $Symbol) (local.get $arg-contract)) (then (if (result i32) (i32.eqz (ref.eq (ref.cast (ref $Symbol) (local.get $arg-contract)) (local.get $field-name-sym))) (then (i32.const 0)) (else (i32.const 1)))) (else (if (result i32) (ref.test (ref $String) (local.get $arg-contract)) (then (local.set $field-name-str (ref.cast (ref $String) (call $symbol->immutable-string (local.get $field-name-sym)))) (if (result i32) (call $string=?/i32 (local.get $field-name-str) (ref.cast (ref $String) (local.get $arg-contract))) (then (i32.const 1)) (else (i32.const 0)))) (else (i32.const 0)))))))) (if (local.get $combine?) (then (if (i32.eqz (ref.test (ref $Symbol) (local.get $struct-name))) (then (local.set $name (local.get $field-name))) (else (local.set $struct-name-sym (ref.cast (ref $Symbol) (local.get $struct-name))) (local.set $struct-name-str (ref.cast (ref $String) (call $symbol->immutable-string (local.get $struct-name-sym)))) (local.set $field-name-str (ref.cast (ref $String) (call $symbol->immutable-string (local.get $field-name-sym)))) (local.set $name (call $build-structure-field-mutator-name (local.get $struct-name-str) (local.get $field-name-str)))))) (else (local.set $name (local.get $field-name))))) (else (if (i32.eqz (ref.test (ref $String) (local.get $field-name))) (then (call $raise-argument-error (local.get $field-name))) (else (local.set $name (call $string->symbol/checked (ref.cast (ref $String) (local.get $field-name)))))))))) ;; --- Call specialized mutator constructor --- (call $make-struct-mutator/specialized/checked (local.get $std) (local.get $super-count-fx) (local.get $field-pos-fx) (local.get $name) (local.get $realm-checked))) (func $make-struct-predicate (param $v (ref eq)) ;; expected: (ref $StructType) (result (ref eq)) ;; returns: closure (if (i32.eqz (ref.test (ref $StructType) (local.get $v))) (then (call $raise-argument-error (local.get $v)))) (call $make-struct-predicate/checked (ref.cast (ref $StructType) (local.get $v)))) (func $make-struct-predicate/checked (param $std (ref $StructType)) (result (ref eq)) ;; returns closure (local $free (ref $Free)) (local.set $free (array.new_fixed $Free 1 (local.get $std))) (struct.new $Closure (i32.const 0) ; hash (global.get $false) ; name: #f or $String (ref.i31 (i32.const 2)) ; arity: 1 (global.get $false) ; realm: #f or $Symbol (ref.func $invoke-closure) ; invoke (used by apply, map, etc.) (global.get $false) ; debug-id (ref.func $struct-predicate) (local.get $free))) (func $struct-predicate ; closure code (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $free (ref $Free)) (local $std (ref $StructType)) (local $v (ref eq)) (local $ok i32) (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $std (ref.cast (ref $StructType) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $v (array.get $Args (local.get $args) (i32.const 0))) (local.set $ok (if (result i32) (ref.test (ref $Struct) (local.get $v)) (then ;; Is $struct a subtype of $std (call $struct-type-is-a?/i32 (struct.get $Struct $type (ref.cast (ref $Struct) (local.get $v))) (local.get $std))) (else (i32.const 0)))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (func $make-struct-field-accessor ; primitive, section 5.2, returns specialized accessor ;; Parameters (param $accessor-proc (ref eq)) ;; closure produced by make-struct-accessor (param $field-index-fx (ref eq)) ;; fixnum (param $name (ref eq)) ;; symbol or #f (param $contract-str (ref eq)) ;; string/symbol/#f (ignored) (param $realm (ref eq)) ;; symbol (result (ref eq)) ;; closure: (λ (struct) field-value) ;; Locals (local $accessor (ref $Closure)) (local $free (ref $Free)) (local $std (ref $StructType)) (local $struct-name (ref eq)) (local $field-accessor-name (ref eq)) ;; --- Type checks --- ;; Check accessor-proc is a struct accessor procedure (if (i32.eqz (ref.test (ref $StructAccessorProcedure) (local.get $accessor-proc))) (then (call $raise-argument-error (local.get $accessor-proc)))) (local.set $accessor (ref.cast (ref $Closure) (local.get $accessor-proc))) ;; Check field-index is a fixnum (i31 with lsb = 0) (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $field-index-fx))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $field-index-fx))) (i32.const 1)) (i32.const 0))) (then (call $raise-argument-error (local.get $field-index-fx)))) ;; field name must be a symbol or #f (if (i32.eqz (ref.eq (local.get $name) (global.get $false))) (then (if (i32.eqz (ref.test (ref $Symbol) (local.get $name))) (then (call $raise-argument-error (local.get $name)))))) ;; Build the name of the struct accessor (local.set $free (struct.get $Closure $free (local.get $accessor))) (local.set $std (ref.cast (ref $StructType) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $struct-name (struct.get $StructType $name (local.get $std))) (local.set $field-accessor-name (if (result (ref eq)) (i32.or (ref.eq (local.get $struct-name) (global.get $false)) (ref.eq (local.get $name) (global.get $false))) (then (global.get $false)) (else (call $string->symbol (call $string-append/2 (call $string-append/2 (call $symbol->string (ref.cast (ref $Symbol) (local.get $struct-name))) (call $codepoint->string (i32.const 45))) (call $symbol->string (ref.cast (ref $Symbol) (local.get $name)))))))) ;; --- Build Free vector --- (local.set $free (array.new_fixed $Free 2 (local.get $accessor-proc) (local.get $field-index-fx))) ;; --- Return closure --- (struct.new $Closure (i32.const 0) ; hash (local.get $field-accessor-name) ; name: #f or $Symbol (ref.i31 (i32.const 2)) ; arity: 1 (local.get $realm) ; realm: #f or $Symbol (ref.func $invoke-closure) ; invoke (used by apply, map, etc.) (global.get $false) ; debug-id (ref.func $struct-field-accessor/specialized) (local.get $free))) (func $struct-field-accessor/specialized ;; TODO: This just calls the generic accessor. ;; Making a specialized accessor ought to be more efficient. (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $free (ref $Free)) (local $accessor (ref $Closure)) ;; free[0] generic accessor (local $field-idx (ref eq)) ;; free[1] field-pos as fixnum (local $struct (ref eq)) ;; args[0] (local $args2 (ref $Args)) ;; new arguments used to call the generic accessor ;; Unpack free vars (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $accessor (ref.cast (ref $Closure) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $field-idx (array.get $Free (local.get $free) (i32.const 1))) ;; Get struct argument (local.set $struct (array.get $Args (local.get $args) (i32.const 0))) ;; Construct args array: [struct, field-idx] (local.set $args2 (array.new_fixed $Args 2 (local.get $struct) (local.get $field-idx))) ;; Call the generic accessor directly ; (inlined $call-closure (local.get $accessor) (local.get $args2)) (call_ref $ClosureCode (local.get $accessor) (local.get $args2) (struct.get $Closure $code (local.get $accessor)))) (func $struct-constructor-procedure? (type $Prim1) ,@(make-predicate-body '$StructConstructorProcedure)) (func $struct-predicate-procedure? (type $Prim1) ,@(make-predicate-body '$StructPredicateProcedure)) (func $struct-accessor-procedure? (type $Prim1) ,@(make-predicate-body '$StructAccessorProcedure)) (func $struct-mutator-procedure? (type $Prim1) ,@(make-predicate-body '$StructMutatorProcedure)) ;; TODO #;(func $struct-constructor/with-guard (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $free (ref $Free)) (local $std (ref $StructType)) (local $init-indices (ref eq)) (local $auto-indices (ref eq)) (local $auto-values (ref eq)) (local $name (ref eq)) (local $field-count i32) (local $arr (ref $Array)) (local $g-args (ref $Args)) (local $guard (ref $Closure)) (local $result (ref eq)) (local.set $free (struct.get $Closure 1 (local.get $clos))) (local.set $std (ref.cast (ref $StructType) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $init-indices (array.get $Free (local.get $free) (i32.const 1))) (local.set $auto-indices (array.get $Free (local.get $free) (i32.const 2))) (local.set $auto-values (array.get $Free (local.get $free) (i32.const 3))) (local.set $name (array.get $Free (local.get $free) (i32.const 4))) (local.set $field-count (struct.get $StructType $field-count (local.get $std))) (local.set $guard (ref.cast (ref $Closure) (struct.get $StructType $guard (local.get $std)))) ;; Make $arr and fill it (local.set $arr (array.new_default $Array (global.get $false) (local.get $field-count))) (call $fill-fields-from-args (local.get $arr) (local.get $init-indices) (local.get $args)) (call $fill-fields-from-values (local.get $arr) (local.get $auto-indices) (local.get $auto-values)) ;; Build closure call: [clos, #f, ...fields, name] (local.set $g-args (array.new_default $Args (global.get $false) (i32.add (local.get $field-count) (i32.const 2)))) (array.set $Args (local.get $g-args) (i32.const 0) (local.get $guard)) ;; closure (array.set $Args (local.get $g-args) (i32.const 1) (global.get $false)) ;; tail-call = #f ;; Copy fields into args (local $i i32) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $field-count))) (array.set $Args (local.get $g-args) (i32.add (local.get $i) (i32.const 2)) (array.get $Array (local.get $arr) (local.get $i))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Add struct name at the end (array.set $Args (local.get $g-args) (i32.add (local.get $field-count) (i32.const 2)) (local.get $name)) ;; Call guard (local.set $result (call_indirect (type $clos-call-type) (local.get $guard) (local.get $g-args))) ;; Use returned struct (local.get $result)) ; (call $fill-fields-from-args (local.get $arr) (local.get $init-indices) (local.get $args)) (func $fill-fields-from-args (param $fields (ref $Array)) ;; struct field array (param $indices (ref eq)) ;; list of i31 fixnums (init field indices) (param $args (ref $Args)) ;; argument array (local $i i32) (local $arg (ref eq)) (local $index i32) (local.set $i (i32.const 0)) (block $done (loop $loop ;; If $indices is null, we’re done (br_if $done (ref.eq (local.get $indices) (global.get $null))) ;; Type check: $indices must be $Pair (if (i32.eqz (ref.test (ref $Pair) (local.get $indices))) (then (call $raise-argument-error (local.get $indices)))) ;; Extract current index (fixnum) and decode (local.set $index (i32.shr_u (i31.get_u (ref.cast (ref i31) (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $indices))))) (i32.const 1))) ;; Get arg[i] (local.set $arg (array.get $Args (local.get $args) (local.get $i))) ;; fields[index] := arg[i] (array.set $Array (local.get $fields) (local.get $index) (local.get $arg)) ;; Move to next (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $indices (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $indices)))) (br $loop)))) (func $fill-fields-from-values (param $fields (ref $Array)) ;; struct field array (param $indices (ref eq)) ;; list of i31 fixnums (auto field indices) (param $values (ref eq)) ;; list of values for auto fields (local $val (ref eq)) (local $index i32) (block $done (loop $loop ;; Done if either list is null (br_if $done (i32.or (ref.eq (local.get $indices) (global.get $null)) (ref.eq (local.get $values) (global.get $null)))) ;; Type checks (if (i32.or (i32.eqz (ref.test (ref $Pair) (local.get $indices))) (i32.eqz (ref.test (ref $Pair) (local.get $values)))) (then (call $raise-argument-error (local.get $indices)))) ;; Decode fixnum index (local.set $index (i32.shr_u (i31.get_u (ref.cast (ref i31) (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $indices))))) (i32.const 1))) ;; Get value (local.set $val (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $values)))) ;; fields[index] := val (array.set $Array (local.get $fields) (local.get $index) (local.get $val)) ;; Move to next (local.set $indices (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $indices)))) (local.set $values (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $values)))) (br $loop)))) ;; ------------------------------------------------------------------- ;; Structure Type Properties ;; ------------------------------------------------------------------- (func $struct-type-property-lookup (param $std (ref $StructType)) (param $prop (ref $StructTypeProperty)) (param $sentinel (ref eq)) (result (ref eq)) (local $table (ref eq)) (local.set $table (struct.get $StructType $properties (local.get $std))) (call $hasheq-ref/plain (local.get $table) (ref.cast (ref eq) (local.get $prop)) (local.get $sentinel))) (func $struct-type-property-lookup-by-name (param $std (ref $StructType)) (param $name (ref $Symbol)) (param $sentinel (ref eq)) (result (ref eq)) (local $table (ref $HashEqMutable)) (local $alist (ref eq)) (local $cursor (ref eq)) (local $cell (ref $Pair)) (local $entry-raw (ref eq)) (local $entry (ref $Pair)) (local $prop (ref $StructTypeProperty)) (local.set $table (ref.cast (ref $HashEqMutable) (struct.get $StructType $properties (local.get $std)))) (local.set $alist (call $hasheq->list/plain/checked (local.get $table))) (local.set $cursor (local.get $alist)) (block $done (loop $walk (br_if $done (ref.eq (local.get $cursor) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cursor))) (then (call $raise-argument-error (local.get $cursor)) (unreachable))) (local.set $cell (ref.cast (ref $Pair) (local.get $cursor))) (local.set $entry-raw (struct.get $Pair $a (local.get $cell))) (if (i32.eqz (ref.test (ref $Pair) (local.get $entry-raw))) (then (call $raise-argument-error (local.get $entry-raw)) (unreachable))) (local.set $entry (ref.cast (ref $Pair) (local.get $entry-raw))) (local.set $prop (ref.cast (ref $StructTypeProperty) (struct.get $Pair $a (local.get $entry)))) (if (i32.eq (call $symbol=?/i32 (struct.get $StructTypeProperty $name (local.get $prop)) (local.get $name)) (i32.const 1)) (then (return (struct.get $Pair $d (local.get $entry))))) (local.set $cursor (struct.get $Pair $d (local.get $cell))) (br $walk))) (local.get $sentinel)) (func $struct-type-property-predicate (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $free (ref $Free)) (local $prop (ref $StructTypeProperty)) (local $target (ref eq)) (local $std (ref null $StructType)) (local $struct (ref $Struct)) (local $sentinel (ref eq)) (local $val (ref eq)) (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $prop (ref.cast (ref $StructTypeProperty) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $target (array.get $Args (local.get $args) (i32.const 0))) (local.set $std (ref.null $StructType)) (block $validated (if (ref.test (ref $StructType) (local.get $target)) (then (local.set $std (ref.cast (ref $StructType) (local.get $target))) (br $validated))) (if (ref.test (ref $Struct) (local.get $target)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $target))) (local.set $std (struct.get $Struct $type (local.get $struct))) (br $validated))) (return (global.get $false))) (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $val (call $struct-type-property-lookup (ref.as_non_null (local.get $std)) (local.get $prop) (local.get $sentinel))) (if (result (ref eq)) (ref.eq (local.get $val) (local.get $sentinel)) (then (global.get $false)) (else (global.get $true)))) (func $struct-type-property-accessor (type $ClosureCode) (param $clos (ref $Closure)) (param $args (ref $Args)) (result (ref eq)) (local $free (ref $Free)) (local $prop (ref $StructTypeProperty)) (local $argc i32) (local $target (ref eq)) (local $fallback (ref eq)) (local $std (ref null $StructType)) (local $struct (ref $Struct)) (local $sentinel (ref eq)) (local $val (ref eq)) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $noargs (ref $Args)) (local $fallback-proc? i32) (local $rest (ref eq)) (local $rest-pair (ref $Pair)) (local $rest-tail (ref eq)) (local $given-count i32) (local.set $free (struct.get $Closure $free (local.get $clos))) (local.set $prop (ref.cast (ref $StructTypeProperty) (array.get $Free (local.get $free) (i32.const 0)))) (local.set $argc (array.len (local.get $args))) (if (i32.eqz (i32.ge_u (local.get $argc) (i32.const 1))) (then (call $raise-arity-mismatch/proc (local.get $clos) (local.get $argc)) (unreachable))) (local.set $target (array.get $Args (local.get $args) (i32.const 0))) (local.set $rest (if (result (ref eq)) (i32.gt_u (local.get $argc) (i32.const 1)) (then (array.get $Args (local.get $args) (i32.const 1))) (else (global.get $null)))) (local.set $fallback (global.get $missing)) (if (ref.eq (local.get $rest) (global.get $null)) (then) (else (if (i32.eqz (ref.test (ref $Pair) (local.get $rest))) (then (call $raise-argument-error (local.get $rest)) (unreachable))) (local.set $rest-pair (ref.cast (ref $Pair) (local.get $rest))) (local.set $fallback (struct.get $Pair $a (local.get $rest-pair))) (local.set $rest-tail (struct.get $Pair $d (local.get $rest-pair))) (if (i32.eqz (ref.eq (local.get $rest-tail) (global.get $null))) (then (local.set $given-count (i32.add (i32.const 1) (call $length/i32 (local.get $rest)))) (call $raise-arity-mismatch/proc (local.get $clos) (local.get $given-count)) (unreachable))))) (local.set $std (ref.null $StructType)) (if (ref.test (ref $StructType) (local.get $target)) (then (local.set $std (ref.cast (ref $StructType) (local.get $target)))) (else (if (ref.test (ref $Struct) (local.get $target)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $target))) (local.set $std (struct.get $Struct $type (local.get $struct)))) (else (call $raise-struct-type-property-accessor-contract (local.get $prop) (local.get $target)) (unreachable))))) (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $val (call $struct-type-property-lookup (ref.as_non_null (local.get $std)) (local.get $prop) (local.get $sentinel))) (if (ref.eq (local.get $val) (local.get $sentinel)) (then (if (ref.eq (local.get $fallback) (global.get $missing)) (then (call $raise-struct-type-property-accessor-contract (local.get $prop) (local.get $target)) (unreachable)) (else (local.set $fallback-proc? (ref.eq (call $procedure? (local.get $fallback)) (global.get $true))) (if (local.get $fallback-proc?) (then (local.set $proc (ref.cast (ref $Procedure) (local.get $fallback))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $noargs (array.new $Args (global.get $null) (i32.const 0))) (return_call_ref $ProcedureInvoker (local.get $proc) (local.get $noargs) (local.get $inv))) (else (return (local.get $fallback))))))) (else (return (local.get $val)))) (unreachable)) (func $struct-type-property-predicate->descriptor (param $proc (ref $Closure)) ;; predicate closure (result (ref eq)) ;; descriptor or #f (local $free (ref $Free)) (local $len i32) (local $prop (ref eq)) (local $tag (ref eq)) (local.set $free (struct.get $Closure $free (local.get $proc))) (local.set $len (array.len (local.get $free))) (if (i32.lt_u (local.get $len) (i32.const 2)) (then (return (global.get $false)))) (local.set $prop (array.get $Free (local.get $free) (i32.const 0))) (local.set $tag (array.get $Free (local.get $free) (i32.const 1))) (if (i32.eqz (ref.test (ref $StructTypeProperty) (local.get $prop))) (then (return (global.get $false)))) (if (i32.eqz (ref.eq (local.get $prop) (local.get $tag))) (then (return (global.get $false)))) (local.get $prop)) (func $struct-type-property-accessor->descriptor (param $proc (ref $Closure)) ;; accessor closure (result (ref eq)) ;; descriptor or #f (local $free (ref $Free)) (local $len i32) (local $prop (ref eq)) (local $tag (ref eq)) (local $desc (ref $StructTypeProperty)) (local $contract (ref eq)) (local $name-string (ref eq)) (local $realm-record (ref eq)) (local $accessor-name (ref eq)) (local $clos-name (ref eq)) (local $clos-realm (ref eq)) (local $expected-name (ref eq)) (local.set $free (struct.get $Closure $free (local.get $proc))) (local.set $len (array.len (local.get $free))) (if (i32.lt_u (local.get $len) (i32.const 6)) (then (return (global.get $false)))) (local.set $prop (array.get $Free (local.get $free) (i32.const 0))) (local.set $tag (array.get $Free (local.get $free) (i32.const 1))) (if (i32.eqz (ref.test (ref $StructTypeProperty) (local.get $prop))) (then (return (global.get $false)))) (if (i32.eqz (ref.eq (local.get $prop) (local.get $tag))) (then (return (global.get $false)))) (local.set $desc (ref.cast (ref $StructTypeProperty) (local.get $prop))) (local.set $contract (array.get $Free (local.get $free) (i32.const 2))) (local.set $name-string (array.get $Free (local.get $free) (i32.const 3))) (local.set $realm-record (array.get $Free (local.get $free) (i32.const 4))) (local.set $accessor-name (array.get $Free (local.get $free) (i32.const 5))) (local.set $clos-name (struct.get $Closure $name (local.get $proc))) (local.set $clos-realm (struct.get $Closure $realm (local.get $proc))) ;; Contract metadata must be #f or a string. (if (i32.eqz (ref.eq (local.get $contract) (global.get $false))) (then (if (i32.eqz (ref.test (ref $String) (local.get $contract))) (then (return (global.get $false)))))) ;; Cached property name string must remain a string when present. (if (i32.eqz (ref.eq (local.get $name-string) (global.get $false))) (then (if (i32.eqz (ref.test (ref $String) (local.get $name-string))) (then (return (global.get $false)))))) ;; Realm metadata must be #f or a symbol, matching the closure field. (if (i32.eqz (ref.eq (local.get $realm-record) (global.get $false))) (then (if (i32.eqz (ref.test (ref $Symbol) (local.get $realm-record))) (then (return (global.get $false)))))) (if (i32.eqz (ref.eq (local.get $clos-realm) (local.get $realm-record))) (then (return (global.get $false)))) ;; Accessor names must agree with closure metadata and descriptor hints. (if (i32.eqz (ref.eq (local.get $accessor-name) (global.get $false))) (then (if (i32.eqz (ref.test (ref $String) (local.get $accessor-name))) (then (return (global.get $false)))))) (if (i32.eqz (ref.eq (struct.get $StructTypeProperty $accessor-name-info (local.get $desc)) (local.get $accessor-name))) (then (return (global.get $false)))) (local.set $expected-name (if (result (ref eq)) (ref.eq (local.get $accessor-name) (global.get $false)) (then (ref.cast (ref eq) (call $string->symbol/checked (call $string-append/2 (ref.cast (ref $String) (local.get $name-string)) (global.get $string:accessor-suffix))))) (else (ref.cast (ref eq) (call $string->symbol/checked (ref.cast (ref $String) (local.get $accessor-name))))))) (if (i32.eqz (ref.eq (local.get $clos-name) (local.get $expected-name))) (then (return (global.get $false)))) (local.get $prop)) (func $struct-type-property? (type $Prim1) (param $v (ref eq)) ;; any/c (result (ref eq)) (if (result (ref eq)) (ref.test (ref $StructTypeProperty) (local.get $v)) (then (global.get $true)) (else (global.get $false)))) (func $struct-type-property-accessor-procedure? (type $Prim1) (param $v (ref eq)) ;; procedure? (result (ref eq)) (local $clos (ref $Closure)) (local $prop (ref eq)) (if (i32.eqz (ref.test (ref $Closure) (local.get $v))) (then (return (global.get $false)))) (local.set $clos (ref.cast (ref $Closure) (local.get $v))) (local.set $prop (call $struct-type-property-accessor->descriptor (local.get $clos))) (if (result (ref eq)) (ref.eq (local.get $prop) (global.get $false)) (then (global.get $false)) (else (global.get $true)))) (func $struct-type-property-predicate-procedure? (type $Prim02) (param $v (ref eq)) ;; procedure? (param $prop-info (ref eq)) ;; optional struct-type-property? (#f default) (result (ref eq)) (local $clos (ref $Closure)) (local $prop (ref eq)) (local $expected (ref eq)) (local.set $expected (global.get $false)) (if (ref.eq (local.get $prop-info) (global.get $false)) (then) (else (if (i32.eqz (ref.test (ref $StructTypeProperty) (local.get $prop-info))) (then (call $raise-argument-error (local.get $prop-info)) (unreachable))) (local.set $expected (local.get $prop-info)))) (if (i32.eqz (ref.test (ref $Closure) (local.get $v))) (then (return (global.get $false)))) (local.set $clos (ref.cast (ref $Closure) (local.get $v))) (local.set $prop (call $struct-type-property-predicate->descriptor (local.get $clos))) (if (ref.eq (local.get $prop) (global.get $false)) (then (return (global.get $false)))) (if (result (ref eq)) (ref.eq (local.get $expected) (global.get $false)) (then (global.get $true)) (else (if (result (ref eq)) (ref.eq (local.get $prop) (local.get $expected)) (then (global.get $true)) (else (global.get $false)))))) (func $make-struct-type-property-descriptor (param $name (ref eq)) (param $guard-info (ref eq)) (param $supers (ref eq)) (param $can-impersonate (ref eq)) (param $accessor-name-info (ref eq)) (result (ref $StructTypeProperty)) (if (i32.eqz (ref.test (ref $Symbol) (local.get $name))) (then (call $raise-argument-error (local.get $name)) (unreachable))) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (local.get $name)) (local.get $guard-info) (local.get $supers) (local.get $can-impersonate) (local.get $accessor-name-info))) (func $make-struct-type-property-descriptor/checked (param $name (ref $Symbol)) (param $guard-info (ref eq)) (param $supers (ref eq)) (param $can-impersonate (ref eq)) (param $accessor-name-info (ref eq)) (result (ref $StructTypeProperty)) (local $guard (ref eq)) (local $supers-list (ref eq)) (local $impersonate (ref eq)) (local $accessor-tag (ref eq)) (local.set $guard (local.get $guard-info)) (if (ref.eq (local.get $guard) (global.get $missing)) (then (local.set $guard (global.get $false)))) (local.set $supers-list (local.get $supers)) (if (ref.eq (local.get $supers-list) (global.get $missing)) (then (local.set $supers-list (global.get $null))) (else (if (ref.eq (local.get $supers-list) (global.get $false)) (then (local.set $supers-list (global.get $null)))))) (local.set $impersonate (local.get $can-impersonate)) (if (ref.eq (local.get $impersonate) (global.get $missing)) (then (local.set $impersonate (global.get $false)))) (local.set $accessor-tag (local.get $accessor-name-info)) (if (ref.eq (local.get $accessor-tag) (global.get $missing)) (then (local.set $accessor-tag (global.get $false)))) (struct.new $StructTypeProperty (i32.const 0) (local.get $name) (local.get $guard) (local.get $supers-list) (local.get $impersonate) (local.get $accessor-tag) (global.get $false) (global.get $false))) (func $struct-type-property-table-empty (result (ref $HashEqMutable)) (ref.cast (ref $HashEqMutable) (call $make-empty-hasheq))) (func $struct-type-property-table-copy (param $table (ref $HashEqMutable)) (result (ref $HashEqMutable)) (local $alist (ref eq)) (local $copy (ref eq)) (local.set $alist (call $hasheq->list/plain/checked (local.get $table))) (local.set $copy (call $make-hasheq (local.get $alist))) (ref.cast (ref $HashEqMutable) (local.get $copy))) (func $struct-type-property-merge-list! (param $table (ref $HashEqMutable)) (param $list (ref eq)) (result (ref $HashEqMutable)) (local $cursor (ref eq)) (local $cell (ref $Pair)) (local $entry (ref $Pair)) (local $prop (ref $StructTypeProperty)) (local $val (ref eq)) (local.set $cursor (local.get $list)) (block $done (loop $walk (br_if $done (ref.eq (local.get $cursor) (global.get $null))) (local.set $cell (ref.cast (ref $Pair) (local.get $cursor))) (local.set $entry (ref.cast (ref $Pair) (struct.get $Pair $a (local.get $cell)))) (local.set $prop (ref.cast (ref $StructTypeProperty) (struct.get $Pair $a (local.get $entry)))) (local.set $val (struct.get $Pair $d (local.get $entry))) (call $hasheq-set!/mutable/checked (local.get $table) (ref.cast (ref eq) (local.get $prop)) (local.get $val)) (local.set $cursor (struct.get $Pair $d (local.get $cell))) (br $walk))) (local.get $table)) (func $struct-type-property-guard-apply (param $prop (ref $StructTypeProperty)) (param $value (ref eq)) (param $sti (ref eq)) (result (ref eq)) (local $guard (ref eq)) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local.set $guard (struct.get $StructTypeProperty $guard-info (local.get $prop))) (if (ref.eq (local.get $guard) (global.get $false)) (then (return (local.get $value)))) (local.set $proc (ref.cast (ref $Procedure) (local.get $guard))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.const 2))) (array.set $Args (local.get $args) (i32.const 0) (local.get $value)) (array.set $Args (local.get $args) (i32.const 1) (local.get $sti)) (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv))) (func $struct-type-property-attach! (param $prop (ref $StructTypeProperty)) (param $value (ref eq)) (param $table (ref $HashEqMutable)) (param $sti (ref eq)) (param $sentinel (ref eq)) (result i32) (local $processed (ref eq)) (local $existing (ref eq)) (local $sealed i32) (local $supers (ref eq)) (local $cursor (ref eq)) (local $cell (ref $Pair)) (local $entry (ref $Pair)) (local $entry-raw (ref eq)) (local $super-prop (ref $StructTypeProperty)) (local $converter (ref $Procedure)) (local $converter-raw (ref eq)) (local $conv-inv (ref $ProcedureInvoker)) (local $conv-args (ref $Args)) (local $converted (ref eq)) (local $equal+hash-array (ref $Array)) (local $equal+hash-info (ref $Array)) (local $equal+hash-count i32) (local $equal+hash-index i32) (local $equal+hash-proc (ref eq)) (local.set $sealed (i32.const 0)) (local.set $processed (call $struct-type-property-guard-apply (local.get $prop) (local.get $value) (local.get $sti))) ;; --- prop:equal+hash (if (call $symbol=?/i32 (struct.get $StructTypeProperty $name (local.get $prop)) (global.get $symbol:prop:equal+hash)) (then (if (i32.eqz (ref.test (ref $Array) (local.get $processed))) (then (if (i32.eqz (ref.test (ref $Pair) (local.get $processed))) (then (call $raise-argument-error (local.get $value)) (unreachable))) (local.set $equal+hash-array (ref.cast (ref $Array) (call $list->array (local.get $processed)))) (local.set $equal+hash-count (array.len (local.get $equal+hash-array))) (if (i32.or (i32.lt_s (local.get $equal+hash-count) (i32.const 2)) (i32.gt_s (local.get $equal+hash-count) (i32.const 3))) (then (call $raise-argument-error (local.get $value)) (unreachable))) (local.set $equal+hash-index (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $equal+hash-index) (local.get $equal+hash-count))) (local.set $equal+hash-proc (array.get $Array (local.get $equal+hash-array) (local.get $equal+hash-index))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $equal+hash-proc))) (then (call $raise-argument-error (local.get $value)) (unreachable))) (local.set $equal+hash-index (i32.add (local.get $equal+hash-index) (i32.const 1))) (br $loop))) (local.set $equal+hash-info (array.new_fixed $Array 4 (ref.i31 (i32.shl (local.get $equal+hash-count) (i32.const 1))) (array.get $Array (local.get $equal+hash-array) (i32.const 0)) (array.get $Array (local.get $equal+hash-array) (i32.const 1)) (if (result (ref eq)) (i32.eq (local.get $equal+hash-count) (i32.const 3)) (then (array.get $Array (local.get $equal+hash-array) (i32.const 2))) (else (global.get $false))))) (local.set $processed (ref.cast (ref eq) (local.get $equal+hash-info)))) (else (local.set $equal+hash-info (ref.cast (ref $Array) (local.get $processed))) (local.set $equal+hash-count (i32.shr_u (i31.get_u (ref.cast (ref i31) (array.get $Array (local.get $equal+hash-info) (i32.const 0)))) (i32.const 1))) (if (i32.or (i32.lt_s (local.get $equal+hash-count) (i32.const 2)) (i32.gt_s (local.get $equal+hash-count) (i32.const 3))) (then (call $raise-argument-error (local.get $value)) (unreachable))))))) ;; --- prop:authentic (if (i32.eq (call $symbol=?/i32 (struct.get $StructTypeProperty $name (local.get $prop)) (global.get $symbol:prop:authentic)) (i32.const 1)) (then (local.set $processed (global.get $true)))) (local.set $existing (call $hasheq-ref/plain (ref.cast (ref eq) (local.get $table)) (ref.cast (ref eq) (local.get $prop)) (local.get $sentinel))) (if (ref.eq (local.get $existing) (local.get $sentinel)) (then (call $hasheq-set!/mutable/checked (local.get $table) (ref.cast (ref eq) (local.get $prop)) (local.get $processed)) (if (i32.eq (call $symbol=?/i32 (struct.get $StructTypeProperty $name (local.get $prop)) (global.get $symbol:prop:sealed)) (i32.const 1)) (then (local.set $sealed (i32.const 1)))) (local.set $supers (struct.get $StructTypeProperty $supers (local.get $prop))) (block $done (loop $walk (br_if $done (ref.eq (local.get $supers) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $supers))) (then (call $raise-argument-error (local.get $supers)) (unreachable))) (local.set $cell (ref.cast (ref $Pair) (local.get $supers))) (local.set $entry-raw (struct.get $Pair $a (local.get $cell))) (if (i32.eqz (ref.test (ref $Pair) (local.get $entry-raw))) (then (call $raise-argument-error (local.get $entry-raw)) (unreachable))) (local.set $entry (ref.cast (ref $Pair) (local.get $entry-raw))) (local.set $super-prop (ref.cast (ref $StructTypeProperty) (struct.get $Pair $a (local.get $entry)))) (local.set $converter-raw (struct.get $Pair $d (local.get $entry))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $converter-raw))) (then (call $raise-argument-error (local.get $converter-raw)) (unreachable))) (local.set $converter (ref.cast (ref $Procedure) (local.get $converter-raw))) (local.set $conv-inv (struct.get $Procedure $invoke (local.get $converter))) (local.set $conv-args (array.new $Args (global.get $null) (i32.const 1))) (array.set $Args (local.get $conv-args) (i32.const 0) (local.get $processed)) (local.set $converted (call_ref $ProcedureInvoker (local.get $converter) (local.get $conv-args) (local.get $conv-inv))) (local.set $sealed (i32.or (local.get $sealed) (call $struct-type-property-attach! (local.get $super-prop) (local.get $converted) (local.get $table) (local.get $sti) (local.get $sentinel)))) (local.set $supers (struct.get $Pair $d (local.get $cell))) (br $walk)))) (else (if (i32.eqz (ref.eq (local.get $existing) (local.get $processed))) (then (call $raise-argument-error (local.get $value)) (unreachable)) (else (if (i32.eq (call $symbol=?/i32 (struct.get $StructTypeProperty $name (local.get $prop)) (global.get $symbol:prop:sealed)) (i32.const 1)) (then (local.set $sealed (i32.const 1)))))))) (local.get $sealed)) (func $struct-type-property-table-has-name?/i32 (param $table (ref $HashEqMutable)) (param $name (ref $Symbol)) (result i32) (local $alist (ref eq)) (local $cursor (ref eq)) (local $cell (ref $Pair)) (local $entry (ref $Pair)) (local $entry-raw (ref eq)) (local $prop (ref $StructTypeProperty)) (local.set $alist (call $hasheq->list/plain/checked (local.get $table))) (local.set $cursor (local.get $alist)) (block $done (loop $walk (br_if $done (ref.eq (local.get $cursor) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cursor))) (then (call $raise-argument-error (local.get $cursor)) (unreachable))) (local.set $cell (ref.cast (ref $Pair) (local.get $cursor))) (local.set $entry-raw (struct.get $Pair $a (local.get $cell))) (if (i32.eqz (ref.test (ref $Pair) (local.get $entry-raw))) (then (call $raise-argument-error (local.get $entry-raw)) (unreachable))) (local.set $entry (ref.cast (ref $Pair) (local.get $entry-raw))) (local.set $prop (ref.cast (ref $StructTypeProperty) (struct.get $Pair $a (local.get $entry)))) (if (i32.eq (call $symbol=?/i32 (struct.get $StructTypeProperty $name (local.get $prop)) (local.get $name)) (i32.const 1)) (then (return (i32.const 1)))) (local.set $cursor (struct.get $Pair $d (local.get $cell))) (br $walk))) (i32.const 0)) (func $struct-type-info-for-guard (param $std (ref $StructType)) (param $name (ref $Symbol)) (param $init-count i32) (param $auto-count i32) (param $super (ref eq)) (param $super-count-fx (ref eq)) (param $immutables (ref eq)) (result (ref eq)) (local $init-fx (ref eq)) (local $auto-fx (ref eq)) (local $immutables-list (ref eq)) (local $accessor (ref eq)) (local $mutator (ref eq)) (local $list (ref eq)) (local.set $init-fx (ref.i31 (i32.shl (local.get $init-count) (i32.const 1)))) (local.set $auto-fx (ref.i31 (i32.shl (local.get $auto-count) (i32.const 1)))) (local.set $immutables-list (local.get $immutables)) (if (ref.eq (local.get $immutables-list) (global.get $false)) (then (local.set $immutables-list (global.get $null)))) (local.set $accessor (call $make-struct-accessor/checked (local.get $std) (local.get $super-count-fx) (local.get $name))) (local.set $mutator (call $make-struct-mutator/checked (local.get $std) (local.get $super-count-fx))) (local.set $list (global.get $null)) (local.set $list (call $cons (global.get $false) (local.get $list))) (local.set $list (call $cons (local.get $super) (local.get $list))) (local.set $list (call $cons (local.get $immutables-list) (local.get $list))) (local.set $list (call $cons (local.get $mutator) (local.get $list))) (local.set $list (call $cons (local.get $accessor) (local.get $list))) (local.set $list (call $cons (local.get $auto-fx) (local.get $list))) (local.set $list (call $cons (local.get $init-fx) (local.get $list))) (local.set $list (call $cons (ref.cast (ref eq) (local.get $name)) (local.get $list))) (local.get $list)) (func $struct-type-properties-normalize (param $has-super i32) (param $super (ref null $StructType)) (param $raw (ref eq)) (result (ref eq)) (local $table (ref $HashEqMutable)) (local $spec (ref eq)) (local $alist (ref eq)) ;; Initialize non-defaultable locals (local.set $table (ref.cast (ref $HashEqMutable) (call $make-empty-hasheq))) (if (local.get $has-super) (then (local.set $table (call $struct-type-property-table-copy (ref.cast (ref $HashEqMutable) (struct.get $StructType $properties (ref.as_non_null (local.get $super))))))) (else (local.set $table (call $struct-type-property-table-empty)))) (local.set $spec (local.get $raw)) (if (ref.eq (local.get $spec) (global.get $missing)) (then (local.set $spec (global.get $null)))) (if (ref.eq (local.get $spec) (global.get $false)) (then (local.set $spec (global.get $null)))) (if (ref.eq (local.get $spec) (global.get $null)) (then (return (ref.cast (ref eq) (local.get $table))))) (if (ref.test (ref $HashEqMutable) (local.get $spec)) (then (local.set $alist (call $hasheq->list/plain/checked (ref.cast (ref $HashEqMutable) (local.get $spec)))) (local.set $spec (local.get $alist)))) (drop (call $struct-type-property-merge-list! (local.get $table) (local.get $spec))) (ref.cast (ref eq) (local.get $table))) (func $make-struct-type-property (param $name (ref eq)) ;; symbol ;; optional parameters (defaults in parentheses): (param $guard-info (ref eq)) ;; guard/#f ('#f) (param $supers-spec (ref eq)) ;; list of (cons prop proc)/#f ('()) (param $can-impersonate? (ref eq)) ;; any/c (#f) (param $accessor-name-info (ref eq)) ;; symbol/string/#f (#f) (param $contract-info (ref eq)) ;; string/symbol/#f (#f) (param $realm-info (ref eq)) ;; symbol/#f ('racket) (result (ref eq)) (local $name-sym (ref $Symbol)) (local $name-string (ref $String)) (local $guard (ref eq)) (local $supers (ref eq)) (local $impersonate (ref eq)) (local $accessor-name (ref eq)) (local $contract-str (ref eq)) (local $realm (ref eq)) (local $prop (ref $StructTypeProperty)) (local $pred (ref eq)) (local $acc (ref eq)) (local $pred-free (ref $Free)) (local $acc-free (ref $Free)) (local $supers-cursor (ref eq)) (local $supers-cell (ref $Pair)) (local $supers-entry (ref $Pair)) (local $supers-entry-raw (ref eq)) (local $super-prop (ref $StructTypeProperty)) (local $super-prop-raw (ref eq)) (local $super-proc (ref $Procedure)) (local $super-proc-raw (ref eq)) (local $closure-name (ref eq)) ;; Validate property name and capture its string form. (if (i32.eqz (ref.test (ref $Symbol) (local.get $name))) (then (call $raise-argument-error (local.get $name)) (unreachable))) (local.set $name-sym (ref.cast (ref $Symbol) (local.get $name))) (local.set $name-string (ref.cast (ref $String) (call $symbol->immutable-string (local.get $name-sym)))) ;; Default optional arguments. (local.set $guard (global.get $false)) (local.set $supers (global.get $null)) (local.set $impersonate (global.get $false)) (local.set $accessor-name (global.get $false)) (local.set $contract-str (global.get $false)) (local.set $realm (global.get $symbol:racket)) (local.set $closure-name (call $string->symbol/checked (call $string-append/2 (local.get $name-string) (global.get $string:accessor-suffix)))) ;; Guard: allow #f, a procedure, or the symbol 'can-impersonate. (if (ref.eq (local.get $guard-info) (global.get $missing)) (then) (else (if (ref.eq (local.get $guard-info) (global.get $false)) (then) (else (if (ref.test (ref $Symbol) (local.get $guard-info)) (then (if (i32.eq (call $symbol=?/i32 (local.get $guard-info) (global.get $symbol:can-impersonate)) (i32.const 1)) (then (local.set $impersonate (global.get $true))) (else (call $raise-argument-error (local.get $guard-info)) (unreachable)))) (else (if (i32.eqz (ref.test (ref $Procedure) (local.get $guard-info))) (then (call $raise-argument-error (local.get $guard-info)) (unreachable))) (local.set $guard (local.get $guard-info)))))))) ;; Supers: expect a list of (cons prop proc) pairs. (if (ref.eq (local.get $supers-spec) (global.get $missing)) (then) (else (if (ref.eq (local.get $supers-spec) (global.get $false)) (then) (else (local.set $supers (local.get $supers-spec)) (local.set $supers-cursor (local.get $supers)) (block $done (loop $walk (br_if $done (ref.eq (local.get $supers-cursor) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $supers-cursor))) (then (call $raise-argument-error (local.get $supers-cursor)) (unreachable))) (local.set $supers-cell (ref.cast (ref $Pair) (local.get $supers-cursor))) (local.set $supers-entry-raw (struct.get $Pair $a (local.get $supers-cell))) (if (i32.eqz (ref.test (ref $Pair) (local.get $supers-entry-raw))) (then (call $raise-argument-error (local.get $supers-entry-raw)) (unreachable))) (local.set $supers-entry (ref.cast (ref $Pair) (local.get $supers-entry-raw))) (local.set $super-prop-raw (struct.get $Pair $a (local.get $supers-entry))) (if (i32.eqz (ref.test (ref $StructTypeProperty) (local.get $super-prop-raw))) (then (call $raise-argument-error (local.get $super-prop-raw)) (unreachable))) (local.set $super-prop (ref.cast (ref $StructTypeProperty) (local.get $super-prop-raw))) (local.set $super-proc-raw (struct.get $Pair $d (local.get $supers-entry))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $super-proc-raw))) (then (call $raise-argument-error (local.get $super-proc-raw)) (unreachable))) (local.set $super-proc (ref.cast (ref $Procedure) (local.get $super-proc-raw))) (local.set $supers-cursor (struct.get $Pair $d (local.get $supers-cell))) (br $walk))))))) ;; can-impersonate? treats any non-#f as true. (if (ref.eq (local.get $can-impersonate?) (global.get $missing)) (then) (else (if (ref.eq (local.get $can-impersonate?) (global.get $false)) (then (local.set $impersonate (global.get $false))) (else (local.set $impersonate (global.get $true)))))) ;; Accessor name: allow #f, string, or symbol (converted to string). (if (ref.eq (local.get $accessor-name-info) (global.get $missing)) (then) (else (if (ref.eq (local.get $accessor-name-info) (global.get $false)) (then) (else (if (ref.test (ref $Symbol) (local.get $accessor-name-info)) (then (local.set $accessor-name (ref.cast (ref eq) (ref.cast (ref $String) (call $symbol->immutable-string (ref.cast (ref $Symbol) (local.get $accessor-name-info)))))) (local.set $closure-name (local.get $accessor-name-info))) (else (if (ref.test (ref $String) (local.get $accessor-name-info)) (then (local.set $accessor-name (ref.cast (ref eq) (ref.cast (ref $String) (local.get $accessor-name-info)))) (local.set $closure-name (ref.cast (ref eq) (call $string->symbol/checked (ref.cast (ref $String) (local.get $accessor-name-info)))))) (else (call $raise-argument-error (local.get $accessor-name-info)) (unreachable))))))))) ;; Contract string: allow #f, string, or symbol (converted to string). (if (ref.eq (local.get $contract-info) (global.get $missing)) (then) (else (if (ref.eq (local.get $contract-info) (global.get $false)) (then) (else (if (ref.test (ref $Symbol) (local.get $contract-info)) (then (local.set $contract-str (ref.cast (ref eq) (ref.cast (ref $String) (call $symbol->immutable-string (ref.cast (ref $Symbol) (local.get $contract-info))))))) (else (if (ref.test (ref $String) (local.get $contract-info)) (then (local.set $contract-str (ref.cast (ref eq) (ref.cast (ref $String) (local.get $contract-info))))) (else (call $raise-argument-error (local.get $contract-info)) (unreachable))))))))) ;; Realm defaults to 'racket unless a symbol is supplied. (if (ref.eq (local.get $realm-info) (global.get $missing)) (then) (else (if (ref.eq (local.get $realm-info) (global.get $false)) (then) (else (if (i32.eqz (ref.test (ref $Symbol) (local.get $realm-info))) (then (call $raise-argument-error (local.get $realm-info)) (unreachable))) (local.set $realm (local.get $realm-info)))))) ;; Construct descriptor and cached procedures. (local.set $prop (call $make-struct-type-property-descriptor/checked (local.get $name-sym) (local.get $guard) (local.get $supers) (local.get $impersonate) (local.get $accessor-name))) (local.set $pred-free (array.new_fixed $Free 2 (ref.cast (ref eq) (local.get $prop)) (ref.cast (ref eq) (local.get $prop)))) (local.set $pred (struct.new $Closure (i32.const 0) (global.get $false) (ref.i31 (i32.const 2)) ; arity = 1 (global.get $false) (ref.func $invoke-closure) (global.get $false) (ref.func $struct-type-property-predicate) (local.get $pred-free))) (local.set $acc-free (array.new_fixed $Free 6 (ref.cast (ref eq) (local.get $prop)) (ref.cast (ref eq) (local.get $prop)) (local.get $contract-str) (ref.cast (ref eq) (local.get $name-string)) (local.get $realm) (local.get $accessor-name))) (local.set $acc (struct.new $Closure (i32.const 0) (local.get $closure-name) (ref.i31 (i32.const -4)) ; arity-at-least 1 (optional fallback) (local.get $realm) (ref.func $invoke-closure) (global.get $false) (ref.func $struct-type-property-accessor) (local.get $acc-free))) (struct.set $StructTypeProperty $predicate-cache (local.get $prop) (local.get $pred)) (struct.set $StructTypeProperty $accessor-cache (local.get $prop) (local.get $acc)) (array.new_fixed $Values 3 (ref.cast (ref eq) (local.get $prop)) (ref.cast (ref eq) (local.get $pred)) (ref.cast (ref eq) (local.get $acc)))) ;;; ;;; PROCEDURES ;;; (func $procedure? (type $Prim1) ,@(make-predicate-body '$Procedure)) ; Notes: repacking of arguments are done in $invoke-closure, ; so no repacking is needed in $apply. (func $apply (type $Prim>=1) (param $proc (ref eq)) ;; procedure to apply (param $args-list (ref eq)) ;; list of arguments (direct args + final list) (result (ref eq)) (local $p (ref $Procedure)) (local $args-array (ref $Array)) (local $final-array (ref $Array)) (local $result-array (ref $Array)) (local $final-list (ref eq)) (local $total-count i32) (local $direct-count i32) (local $final-count i32) (local $i i32) (local $val (ref eq)) ;; Initialize non-defaultable locals. (local.set $args-array (call $make-array (i32.const 0) (global.get $null))) (local.set $final-array (call $make-array (i32.const 0) (global.get $null))) (local.set $result-array (call $make-array (i32.const 0) (global.get $null))) (local.set $final-list (global.get $null)) (local.set $total-count (i32.const 0)) (local.set $direct-count (i32.const 0)) (local.set $final-count (i32.const 0)) (local.set $i (i32.const 0)) (local.set $val (global.get $null)) ;; Step 1: ensure $proc is a procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $p (ref.cast (ref $Procedure) (local.get $proc))) ;; Step 2: convert arguments list into arrays for indexed access (if (ref.eq (call $list? (local.get $args-list)) (global.get $false)) (then (call $raise-apply-final-arg-not-list (local.get $args-list)) (unreachable))) (local.set $args-array (call $list->array (local.get $args-list))) (local.set $total-count (array.len (local.get $args-array))) ;; The final element of $args-array is the list of trailing arguments. (local.set $final-list (array.get $Array (local.get $args-array) (i32.sub (local.get $total-count) (i32.const 1)))) (if (ref.eq (call $list? (local.get $final-list)) (global.get $false)) (then (call $raise-apply-final-arg-not-list (local.get $final-list)) (unreachable))) (local.set $final-array (call $list->array (local.get $final-list))) (local.set $final-count (array.len (local.get $final-array))) (local.set $direct-count (i32.sub (local.get $total-count) (i32.const 1))) ;; Step 3: allocate the combined argument array (local.set $result-array (call $make-array (i32.add (local.get $direct-count) (local.get $final-count)) (global.get $null))) ;; Step 4: copy direct arguments (local.set $i (i32.const 0)) (block $direct-done (loop $direct (if (i32.ge_u (local.get $i) (local.get $direct-count)) (then (br $direct-done))) (local.set $val (array.get $Array (local.get $args-array) (local.get $i))) (call $array-set! (local.get $result-array) (local.get $i) (local.get $val)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $direct))) ;; Step 5: append values from the final list (local.set $i (i32.const 0)) (block $append-done (loop $append (if (i32.ge_u (local.get $i) (local.get $final-count)) (then (br $append-done))) (local.set $val (array.get $Array (local.get $final-array) (local.get $i))) (call $array-set! (local.get $result-array) (i32.add (local.get $i) (local.get $direct-count)) (local.get $val)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $append))) ;; Step 6: apply via the procedure's invoke field (return_call_ref $ProcedureInvoker (local.get $p) (local.get $result-array) (struct.get $Procedure $invoke (local.get $p)))) (func $procedure-rename (param $proc (ref eq)) ;; any procedure (param $name (ref eq)) ;; symbol (param $realm (ref eq)) ;; symbol or $missing (result (ref eq)) (local $arity (ref eq)) (local $invoke (ref $ProcedureInvoker)) (local $realm* (ref eq)) (local $case-proc (ref $CaseClosure)) ;; Step 1: If realm is #f, replace with 'racket (local.set $realm* (if (result (ref eq)) (ref.eq (local.get $realm) (global.get $missing)) (then (global.get $symbol:racket)) (else (local.get $realm)))) ;; Step 2: If $proc is a CaseClosure, preserve its arms and arity table (if (ref.test (ref $CaseClosure) (local.get $proc)) (then (local.set $case-proc (ref.cast (ref $CaseClosure) (local.get $proc))) (return (struct.new $CaseClosure (i32.const 0) ;; hash (local.get $name) (struct.get $CaseClosure $arity (local.get $case-proc)) (local.get $realm*) (struct.get $CaseClosure $invoke (local.get $case-proc)) (struct.get $CaseClosure $debug-id (local.get $case-proc)) (struct.get $CaseClosure $code (local.get $case-proc)) (struct.get $CaseClosure $free (local.get $case-proc)) (struct.get $CaseClosure $arities (local.get $case-proc)) (struct.get $CaseClosure $arms (local.get $case-proc)))))) ;; Step 3: If $proc is a Closure (if (ref.test (ref $Closure) (local.get $proc)) (then (return (struct.new $Closure (i32.const 0) ;; hash (local.get $name) (struct.get $Closure $arity (ref.cast (ref $Closure) (local.get $proc))) (local.get $realm*) (struct.get $Closure $invoke (ref.cast (ref $Closure) (local.get $proc))) (struct.get $Closure $debug-id (ref.cast (ref $Closure) (local.get $proc))) (struct.get $Closure $code (ref.cast (ref $Closure) (local.get $proc))) (struct.get $Closure $free (ref.cast (ref $Closure) (local.get $proc))))))) ;; Step 4: If $proc is a PrimitiveClosure (if (ref.test (ref $PrimitiveClosure) (local.get $proc)) (then (return (struct.new $PrimitiveClosure (i32.const 0) (local.get $name) (struct.get $PrimitiveClosure $arity (ref.cast (ref $PrimitiveClosure) (local.get $proc))) (local.get $realm*) (struct.get $PrimitiveClosure $invoke (ref.cast (ref $PrimitiveClosure) (local.get $proc))) (struct.get $PrimitiveClosure $code (ref.cast (ref $PrimitiveClosure) (local.get $proc))) (struct.get $PrimitiveClosure $result-arity (ref.cast (ref $PrimitiveClosure) (local.get $proc))))))) ;; Step 5: If $proc is a PrimitiveProcedure (if (ref.test (ref $PrimitiveProcedure) (local.get $proc)) (then (return (struct.new $PrimitiveProcedure (i32.const 0) (local.get $name) (struct.get $PrimitiveProcedure $arity (ref.cast (ref $PrimitiveProcedure) (local.get $proc))) (local.get $realm*) (struct.get $PrimitiveProcedure $invoke (ref.cast (ref $PrimitiveProcedure) (local.get $proc))) (struct.get $PrimitiveProcedure $code (ref.cast (ref $PrimitiveProcedure) (local.get $proc))) (struct.get $PrimitiveProcedure $result-arity (ref.cast (ref $PrimitiveProcedure) (local.get $proc))))))) ;; Step 6: Not a supported procedure type (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable)) (func $raise-argument-error:procedure-expected (param $got (ref eq)) (call $raise-argument-error1 (global.get $symbol:procedure) (global.get $string:procedure?) (local.get $got))) ;; Support for the arity-at-least structure used by procedure-arity. (func $ensure-arity-at-least-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $indices (ref eq)) (local.set $existing (global.get $arity-at-least-type)) (if (ref.is_null (local.get $existing)) (then (local.set $indices (call $list-from-range/checked (i32.const 0) (i32.const 1))) (local.set $std (struct.new $StructType (i32.const 0) (ref.cast (ref $Symbol) (global.get $symbol:arity-at-least)) (global.get $false) (i32.const 1) (local.get $indices) (global.get $null) (global.get $null) (ref.cast (ref eq) (call $struct-type-property-table-empty)) (global.get $false) (local.get $indices) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:arity-at-least)))) (global.set $arity-at-least-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) (func $arity-at-least/make (param $value i32) ;; exact non-negative integer (result (ref eq)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-arity-at-least-type)) (local.set $fields (array.new_fixed $Array 1 (ref.i31 (i32.shl (local.get $value) (i32.const 1))))) (ref.cast (ref eq) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields)))) (func $make-arity-at-least (type $Prim1) (param $value (ref eq)) ; exact-nonnegative-integer? (result (ref eq)) (if (ref.eq (call $exact-nonnegative-integer? (local.get $value)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:make-arity-at-least) (global.get $string:exact-nonnegative-integer?) (local.get $value)) (unreachable))) (call $arity-at-least/make (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $value))) (i32.const 1)))) (func $arity-at-least (type $Prim1) (param $value (ref eq)) ; exact-nonnegative-integer? (result (ref eq)) (return_call $make-arity-at-least (local.get $value))) (func $raise-argument-error:arity-at-least-expected (param $who (ref eq)) (param $got (ref eq)) (call $raise-argument-error1 (local.get $who) (global.get $string:arity-at-least?) (local.get $got))) (func $arity-at-least-unwrap (param $who (ref eq)) (param $v (ref eq)) (result (ref $Struct)) (local $struct (ref $Struct)) (local $type (ref eq)) (local $std (ref $StructType)) (local $ok i32) (local.set $std (call $ensure-arity-at-least-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error:arity-at-least-expected (local.get $who) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $ok (call $struct-type-is-a?/i32 (local.get $type) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error:arity-at-least-expected (local.get $who) (local.get $v)) (unreachable))) (local.get $struct)) (func $arity-at-least? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $struct (ref $Struct)) (local $type (ref eq)) (local $std (ref $StructType)) (local $ok i32) (local.set $std (call $ensure-arity-at-least-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $ok (call $struct-type-is-a?/i32 (local.get $type) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $arity-at-least-value (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $struct (ref $Struct)) (local $fields (ref $Array)) (local.set $struct (call $arity-at-least-unwrap (global.get $symbol:arity-at-least-value) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (array.get $Array (local.get $fields) (i32.const 0))) (func $procedure-arity-entry->marker/i32 (param $who (ref eq)) (param $v (ref eq)) (result i32) (local $struct (ref $Struct)) (local $fields (ref $Array)) (local $value (ref eq)) (if (ref.eq (call $exact-nonnegative-integer? (local.get $v)) (global.get $true)) (then (return (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $v))) (i32.const 1))))) (if (ref.eq (call $arity-at-least? (local.get $v)) (global.get $true)) (then (local.set $struct (call $arity-at-least-unwrap (local.get $who) (local.get $v))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (local.set $value (array.get $Array (local.get $fields) (i32.const 0))) (return (i32.sub (i32.const -1) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $value))) (i32.const 1)))))) (call $raise-argument-error1 (local.get $who) (global.get $string:procedure-arity?) (local.get $v)) (unreachable)) (func $procedure-arity-value->field (param $who (ref eq)) (param $arity (ref eq)) (result (ref eq)) (local $len i32) (local $arr (ref $I32Array)) (local $i i32) (local $node (ref eq)) (local $entry (ref eq)) (local $marker i32) (if (ref.eq (call $exact-nonnegative-integer? (local.get $arity)) (global.get $true)) (then (return (local.get $arity)))) (if (ref.eq (call $arity-at-least? (local.get $arity)) (global.get $true)) (then (local.set $marker (call $procedure-arity-entry->marker/i32 (local.get $who) (local.get $arity))) (return (ref.i31 (i32.shl (local.get $marker) (i32.const 1)))))) (if (ref.eq (call $list? (local.get $arity)) (global.get $false)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:procedure-arity?) (local.get $arity)) (unreachable))) (local.set $len (call $length/i32 (local.get $arity))) (if (i32.eqz (local.get $len)) (then (return (ref.cast (ref eq) (array.new_fixed $I32Array 0))))) (if (i32.eq (local.get $len) (i32.const 1)) (then (return (ref.i31 (i32.shl (call $procedure-arity-entry->marker/i32 (local.get $who) (call $car (local.get $arity))) (i32.const 1)))))) (local.set $arr (call $i32array-make (local.get $len) (i32.const 0))) (local.set $node (local.get $arity)) (local.set $i (i32.const 0)) (block $done (loop $fill (br_if $done (ref.eq (local.get $node) (global.get $null))) (local.set $entry (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $node)))) (local.set $marker (call $procedure-arity-entry->marker/i32 (local.get $who) (local.get $entry))) (call $i32array-set! (local.get $arr) (local.get $i) (local.get $marker)) (local.set $node (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $node)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $fill))) (ref.cast (ref eq) (local.get $arr))) (func $procedure-arity-field->mask/i32 (param $a (ref eq)) (result i32) (local $arr (ref $I32Array)) (local $i i32) (local $n i32) (local $m i32) (local $arity i32) (local $mask i32) (local $start i32) (if (ref.test (ref i31) (local.get $a)) (then (local.set $arity (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $a))) (i32.const 1))) (return (if (result i32) (i32.ge_s (local.get $arity) (i32.const 0)) (then (i32.shl (i32.const 1) (local.get $arity))) (else (i32.shl (i32.const -1) (i32.sub (i32.const -1) (local.get $arity)))))))) (local.set $arr (ref.cast (ref $I32Array) (local.get $a))) (local.set $n (array.len (local.get $arr))) (local.set $i (i32.const 0)) (local.set $mask (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $n))) (local.set $m (array.get $I32Array (local.get $arr) (local.get $i))) (if (i32.ge_s (local.get $m) (i32.const 0)) (then (local.set $mask (i32.or (local.get $mask) (i32.shl (i32.const 1) (local.get $m))))) (else (local.set $start (i32.sub (i32.const -1) (local.get $m))) (local.set $mask (i32.or (local.get $mask) (i32.shl (i32.const -1) (local.get $start)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $mask)) ;; make-composed-procedure : list? boolean? -> procedure? ;; Build the procedure returned by compose or compose1. (func $make-composed-procedure (param $args (ref eq)) (param $single? (ref eq)) (result (ref eq)) (local $procs (ref $Array)) (local $n i32) (local $i i32) (local $p (ref $Procedure)) (local $free (ref $Free)) (local.set $procs (call $list->array (local.get $args))) (local.set $n (array.len (local.get $procs))) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $n))) (if (i32.eqz (ref.test (ref $Procedure) (array.get $Array (local.get $procs) (local.get $i)))) (then (call $raise-argument-error:procedure-expected (array.get $Array (local.get $procs) (local.get $i))) (unreachable))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (i32.eq (local.get $n) (i32.const 1)) (then (return (array.get $Array (local.get $procs) (i32.const 0))))) (if (i32.eqz (local.get $n)) (then (local.set $free (array.new_fixed $Free 2 (ref.cast (ref eq) (local.get $procs)) (local.get $single?))) (return (struct.new $Closure (i32.const 0) (global.get $symbol:compose) (ref.i31 (i32.const -2)) (global.get $the-racket-realm) (ref.func $invoke-composed-procedure) (global.get $symbol:compose) (ref.func $dummy-code) (local.get $free))))) (local.set $p (ref.cast (ref $Procedure) (array.get $Array (local.get $procs) (i32.sub (local.get $n) (i32.const 1))))) (local.set $free (array.new_fixed $Free 2 (ref.cast (ref eq) (local.get $procs)) (local.get $single?))) (struct.new $Closure (i32.const 0) (global.get $symbol:compose) (struct.get $Procedure $arity (local.get $p)) (global.get $the-racket-realm) (ref.func $invoke-composed-procedure) (global.get $symbol:compose) (ref.func $dummy-code) (local.get $free))) (func $compose (type $Prim>=0) (param $args (ref eq)) (result (ref eq)) (call $make-composed-procedure (local.get $args) (global.get $false))) (func $compose1 (type $Prim>=0) (param $args (ref eq)) (result (ref eq)) (call $make-composed-procedure (local.get $args) (global.get $true))) (func $procedure-reduce-arity (param $proc (ref eq)) ; procedure? (param $arity (ref eq)) ; procedure-arity? (param $name (ref eq)) ; (or/c symbol? #f), default #f (param $realm (ref eq)) ; symbol?, default 'racket (result (ref eq)) (local $p (ref $Procedure)) (local $new-arity (ref eq)) (local $new-mask i32) (local $old-mask i32) (local $new-name (ref eq)) (local $new-realm (ref eq)) (local $free (ref $Free)) (local.set $new-arity (global.get $false)) (local.set $new-name (global.get $false)) (local.set $new-realm (global.get $false)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $p (ref.cast (ref $Procedure) (local.get $proc))) (local.set $new-arity (call $procedure-arity-value->field (global.get $symbol:procedure-reduce-arity) (local.get $arity))) (local.set $new-mask (call $procedure-arity-field->mask/i32 (local.get $new-arity))) (local.set $old-mask (call $procedure-arity-mask/checked/i32 (local.get $p))) (if (i32.ne (i32.and (local.get $new-mask) (i32.xor (local.get $old-mask) (i32.const -1))) (i32.const 0)) (then (call $raise-argument-error1 (global.get $symbol:procedure-reduce-arity) (global.get $string:procedure-arity?) (local.get $arity)) (unreachable))) (if (ref.eq (local.get $name) (global.get $missing)) (then (local.set $name (global.get $false)))) (if (ref.eq (local.get $name) (global.get $false)) (then (local.set $new-name (struct.get $Procedure $name (local.get $p))) (local.set $new-realm (struct.get $Procedure $realm (local.get $p)))) (else (if (i32.eqz (ref.test (ref $Symbol) (local.get $name))) (then (call $raise-argument-error1 (global.get $symbol:procedure-reduce-arity) (global.get $string:symbol?) (local.get $name)) (unreachable))) (local.set $new-name (local.get $name)) (if (ref.eq (local.get $realm) (global.get $missing)) (then (local.set $new-realm (global.get $symbol:racket))) (else (if (i32.eqz (ref.test (ref $Symbol) (local.get $realm))) (then (call $raise-argument-error1 (global.get $symbol:procedure-reduce-arity) (global.get $string:symbol?) (local.get $realm)) (unreachable))) (local.set $new-realm (local.get $realm)))))) ;; Keep the original procedure as payload. The reduced wrapper ;; checks its new arity, then forwards the original user args. (local.set $free (array.new_fixed $Free 1 (local.get $p))) (struct.new $Closure (i32.const 0) (local.get $new-name) (local.get $new-arity) (local.get $new-realm) (ref.func $invoke-reduced-procedure) (local.get $new-name) (ref.func $dummy-code) (local.get $free))) (func $procedure-arity (type $Prim1) ; Wrapper: accepts any value, checks that it’s a procedure, then delegates ; to the checked version that expects (ref $Procedure). (param $proc (ref eq)) (result (ref eq)) ;; 1. Check that $proc is a procedure (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; 2. Delegate to the checked implementation (return_call $procedure-arity/checked (ref.cast (ref $Procedure) (local.get $proc)))) (func $procedure-arity/checked ; Produces normalized arity information: exact integers or arity-at-least structs. (param $p (ref $Procedure)) (result (ref eq)) (local $a (ref eq)) (local $arr (ref $I32Array)) (local $n i32) (local $i i32) (local $m i32) (local $list (ref eq)) (local $fx (ref i31)) (local $elem (ref eq)) (local $arity-i32 i32) (local $value i32) ;; Initialize non-defaultable locals (local.set $elem (global.get $false)) ;; 1. Extract arity field (either a fixnum (ref i31) or an $I32Array of markers) (local.set $a (struct.get $Procedure $arity (local.get $p))) ;; 2. If it’s a single-arity fixnum, normalize negatives to arity-at-least (if (ref.test (ref i31) (local.get $a)) (then (local.set $fx (ref.cast (ref i31) (local.get $a))) (local.set $arity-i32 (i32.shr_s (i31.get_s (local.get $fx)) (i32.const 1))) (if (i32.ge_s (local.get $arity-i32) (i32.const 0)) (then (return (local.get $a))) (else (local.set $value (i32.sub (i32.const -1) (local.get $arity-i32))) (return (call $arity-at-least/make (local.get $value))))))) ;; 3. Otherwise, cast to $I32Array and build a list of ALL markers (incl. negatives) (local.set $arr (ref.cast (ref $I32Array) (local.get $a))) (local.set $n (array.len (local.get $arr))) (local.set $list (global.get $null)) ;; Iterate right-to-left so that consing preserves original order (local.set $i (i32.sub (local.get $n) (i32.const 1))) (loop $rev ;; 4. If done, return the accumulated list (if (i32.lt_s (local.get $i) (i32.const 0)) (then (return (local.get $list)))) ;; 5. Read marker m at index i and normalize it (local.set $m (array.get $I32Array (local.get $arr) (local.get $i))) (if (i32.ge_s (local.get $m) (i32.const 0)) (then (local.set $elem (ref.i31 (i32.shl (local.get $m) (i32.const 1))))) (else (local.set $value (i32.sub (i32.const -1) (local.get $m))) (local.set $elem (call $arity-at-least/make (local.get $value))))) (local.set $list (call $cons (local.get $elem) (local.get $list))) ;; 6. Decrement i and continue (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $rev)) (unreachable)) (func $procedure-arity-mask (type $Prim1) ; TODO: Only tested with closures. ; Also test with primitives and case-lambda (param $proc (ref eq)) (result (ref eq)) (local $p (ref null $Procedure)) (local $mask i32) ;; Step 1: type check and cast (if (ref.test (ref $Procedure) (local.get $proc)) (then (local.set $p (ref.cast (ref $Procedure) (local.get $proc)))) (else (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) ;; Step 2: compute mask in checked helper (local.set $mask (call $procedure-arity-mask/checked/i32 (ref.cast (ref $Procedure) (local.get $p)))) ;; Step 4: return as fixnum (ref.i31 (i32.shl (local.get $mask) (i32.const 1)))) (func $procedure-arity-mask/checked/i32 ; TODO: Only tested with closures. ; Also test with primitives and case-lambda (param $proc (ref $Procedure)) (result i32) (local $a (ref eq)) (local $arr (ref $I32Array)) (local $i i32) (local $n i32) (local $m i32) (local $arity i32) (local $mask i32) (local $start i32) ;; Step 1: inspect the procedure arity representation (local.set $a (struct.get $Procedure $arity (local.get $proc))) ;; Step 2: single arity marker stored as fixnum (if (ref.test (ref i31) (local.get $a)) (then (local.set $arity (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $a))) (i32.const 1))) (local.set $mask (if (result i32) (i32.ge_s (local.get $arity) (i32.const 0)) ;; If arity ≥ 0, mask = 1 << arity (then (i32.shl (i32.const 1) (local.get $arity))) ;; If arity < 0, mask = -1 << (-1 - arity) (else (i32.shl (i32.const -1) (i32.sub (i32.const -1) (local.get $arity)))))) (return (local.get $mask)))) ;; Step 3: arity set stored as $I32Array of markers (local.set $arr (ref.cast (ref $I32Array) (local.get $a))) (local.set $n (array.len (local.get $arr))) (local.set $i (i32.const 0)) (local.set $mask (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $n))) (local.set $m (array.get $I32Array (local.get $arr) (local.get $i))) (if (i32.ge_s (local.get $m) (i32.const 0)) (then (local.set $mask (i32.or (local.get $mask) (i32.shl (i32.const 1) (local.get $m))))) (else (local.set $start (i32.sub (i32.const -1) (local.get $m))) (local.set $mask (i32.or (local.get $mask) (i32.shl (i32.const -1) (local.get $start)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.get $mask)) (func $raise-argument-error:fixnum-expected (unreachable)) (func $procedure-arity-includes? (param $proc (ref eq)) (param $k (ref eq)) ;; fixnum (param $kws-ok? (ref eq)) ;; fixnum or #f (result (ref eq)) ;; returns #true or #false (local $k-fx (ref i31)) (local $k-i32 i32) (local $mask-fx (ref i31)) (local $mask i32) ;; Step 1: type check and decode k (if (ref.test (ref i31) (local.get $k)) (then (local.set $k-fx (ref.cast (ref i31) (local.get $k))) (local.set $k-i32 (i32.shr_u (i31.get_u (local.get $k-fx)) (i32.const 1)))) (else (call $raise-argument-error:fixnum-expected) (unreachable))) ;; Step 2: call procedure-arity-mask (local.set $mask-fx (ref.cast (ref i31) (call $procedure-arity-mask (local.get $proc)))) (local.set $mask (i32.shr_u (i31.get_u (local.get $mask-fx)) (i32.const 1))) ;; Step 3: check if bit $k-i32 is set in $mask (if (i32.ne (i32.and (local.get $mask) (i32.shl (i32.const 1) (local.get $k-i32))) (i32.const 0)) (then (return (global.get $true))) (else (return (global.get $false)))) (unreachable)) (func $procedure-arity-includes?/checked/i32 (param $proc (ref $Procedure)) (param $k-i32 i32) ; (param $kws-ok? (ref eq)) ;; we don't use this yet (result i32) (local $mask i32) ;; Step 1: get the arity mask (local.set $mask (call $procedure-arity-mask/checked/i32 (local.get $proc))) ;; Step 2: check if bit $k-i32 is set in $mask (i32.ne (i32.and (local.get $mask) (i32.shl (i32.const 1) (local.get $k-i32))) (i32.const 0))) ;;; ;;; PRIMITIVES ;;; ; https://docs.racket-lang.org/reference/procedures.html ; 4.20.2 Reflecting on Primitives (func $raise-argument-error:primitive-procedure-expected (unreachable)) (func $primitive? (type $Prim1) ,@(make-predicate-body '$PrimitiveProcedure)) (func $primitive-result-arity (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $p (ref $PrimitiveProcedure)) ;; Step 1: Validate type (if (i32.eqz (ref.test (ref $PrimitiveProcedure) (local.get $v))) (then (call $raise-argument-error:primitive-procedure-expected))) ;; Step 2: Cast after validation (local.set $p (ref.cast (ref $PrimitiveProcedure) (local.get $v))) ;; Step 3: Return result-arity field (struct.get $PrimitiveProcedure $result-arity (local.get $p))) (func $primitive-closure? (type $Prim1) ,@(make-predicate-body '$PrimitiveClosure)) ;;; ;;; 4.21 Void ;;; ;; https://docs.racket-lang.org/reference/void.html (func $void? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v) (global.get $void)) (then (global.get $true)) (else (global.get $false)))) (func $void (type $Prim>=0) (param $xs (ref eq)) (result (ref eq)) (global.get $void)) (func $make-void (type $Prim0) (result (ref eq)) ; no arguments (return (global.get $void))) ;;; ;;; 10. CONTROL FLOW ;;; ;; 10.1 Multiple Values (func $values (type $Prim>=0) (param $args (ref eq)) (result (ref eq)) (local $use-args? i32) (local $as (ref $Args)) (local $arr (ref $Array)) (local $len i32) (local $vals (ref $Values)) ;; Initialize non-defaultable locals. (local.set $as (array.new $Args (global.get $null) (i32.const 0))) (local.set $arr (call $make-array (i32.const 0) (global.get $null))) (local.set $vals (array.new $Values (global.get $null) (i32.const 0))) (local.set $len (i32.const 0)) ;; Determine whether the rest arguments are in an $Args array or a list. (local.set $use-args? (ref.test (ref $Args) (local.get $args))) (if (local.get $use-args?) ;; -- $Args case -- (then (local.set $as (ref.cast (ref $Args) (local.get $args))) (local.set $len (array.len (local.get $as))) ;; Single argument -> return it directly. (if (i32.eq (local.get $len) (i32.const 1)) (then (return (array.get $Args (local.get $as) (i32.const 0))))) ;; No arguments -> return an empty $Values array (zero values). (if (i32.eqz (local.get $len)) (then (return (local.get $vals)))) ;; Multiple arguments -> pack them into a new $Values array. (local.set $vals (array.new $Values (global.get $null) (local.get $len))) (array.copy $Values $Args (local.get $vals) (i32.const 0) (local.get $as) (i32.const 0) (local.get $len))) ;; -- list case -- (else (local.set $arr (call $list->array (local.get $args))) (local.set $len (array.len (local.get $arr))) ;; Single argument -> return it directly. (if (i32.eq (local.get $len) (i32.const 1)) (then (return (array.get $Array (local.get $arr) (i32.const 0))))) ;; No arguments -> return an empty $Values array (zero values). (if (i32.eqz (local.get $len)) (then (return (local.get $vals)))) ;; Multiple arguments -> pack them into a new $Values array. (local.set $vals (array.new $Values (global.get $null) (local.get $len))) (array.copy $Values $Array (local.get $vals) (i32.const 0) (local.get $arr) (i32.const 0) (local.get $len)))) (local.get $vals)) (func $call-with-values (type $Prim2) (param $gen (ref eq)) (param $rec (ref eq)) (result (ref eq)) (local $g (ref $Procedure)) (local $r (ref $Procedure)) (local $ginv (ref $ProcedureInvoker)) (local $rinv (ref $ProcedureInvoker)) (local $vals (ref eq)) (local $vals* (ref $Values)) (local $args (ref $Args)) (local $n i32) ;; initialize $args to satisfy the validator; overwritten later (local.set $args (array.new $Args (global.get $null) (i32.const 0))) ;; Step 1: type check generator (if (i32.eqz (ref.test (ref $Procedure) (local.get $gen))) (then (call $raise-argument-error:procedure-expected (local.get $gen)) (unreachable))) (local.set $g (ref.cast (ref $Procedure) (local.get $gen))) ;; Step 2: type check receiver (if (i32.eqz (ref.test (ref $Procedure) (local.get $rec))) (then (call $raise-argument-error:procedure-expected (local.get $rec)) (unreachable))) (local.set $r (ref.cast (ref $Procedure) (local.get $rec))) ;; Step 3: call generator with zero arguments (local.set $ginv (struct.get $Procedure $invoke (local.get $g))) (local.set $vals (call_ref $ProcedureInvoker (local.get $g) (array.new $Args (global.get $null) (i32.const 0)) (local.get $ginv))) ;; Step 4: unpack returned values into argument array (if (ref.test (ref $Values) (local.get $vals)) (then (local.set $vals* (ref.cast (ref $Values) (local.get $vals))) (local.set $n (array.len (local.get $vals*))) (local.set $args (array.new $Args (global.get $null) (local.get $n))) (array.copy $Args $Values (local.get $args) (i32.const 0) (local.get $vals*) (i32.const 0) (local.get $n))) (else (local.set $args (array.new_fixed $Args 1 (local.get $vals))))) ;; Step 5: call receiver in tail position (local.set $rinv (struct.get $Procedure $invoke (local.get $r))) (return_call_ref $ProcedureInvoker (local.get $r) (local.get $args) (local.get $rinv))) ;; 10.2 Exceptions ;; 10.3 Delayed Evaluation ;; 10.4 Continuations ;; 10.5 Continuation Marks ;; current-continuation-marks : [continuation-prompt-tag?] -> #f (func $current-continuation-marks (type $Prim01) ;; prompt-tag : continuation-prompt-tag? (optional, default = (default-continuation-prompt-tag)) ;; NOTE: WebRacket currently lacks continuation marks, so this stub always returns #f. (param $prompt-tag (ref eq)) (result (ref eq)) (global.get $false)) ;; 10.6 Breaks ;; 10.7 Exiting ;; 10.8 Unreachable Expressions ;;; ;;; 13. INPUT AND OUTPUT ;;; ;; 13.8 Printer Extension (func $custom-write? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $type (ref null $StructType)) (local $struct (ref $Struct)) (local $sentinel (ref eq)) (local $prop-name (ref $Symbol)) (local $prop-val (ref eq)) (if (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct)))) (else (if (ref.test (ref $StructType) (local.get $v)) (then (local.set $type (ref.cast (ref $StructType) (local.get $v)))) (else (return (global.get $false)))))) (local.set $prop-name (ref.cast (ref $Symbol) (global.get $symbol:prop:custom-write))) (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $prop-val (call $struct-type-property-lookup-by-name (ref.as_non_null (local.get $type)) (local.get $prop-name) (local.get $sentinel))) (if (result (ref eq)) (ref.eq (local.get $prop-val) (local.get $sentinel)) (then (global.get $false)) (else (global.get $true)))) (func $custom-write-accessor (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $type (ref null $StructType)) (local $struct (ref $Struct)) (local $sentinel (ref eq)) (local $prop-name (ref $Symbol)) (local $prop-val (ref eq)) (if (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct)))) (else (if (ref.test (ref $StructType) (local.get $v)) (then (local.set $type (ref.cast (ref $StructType) (local.get $v)))) (else (call $raise-argument-error1 (global.get $symbol:custom-write-accessor) (global.get $string:custom-write?) (local.get $v)) (unreachable))))) (local.set $prop-name (ref.cast (ref $Symbol) (global.get $symbol:prop:custom-write))) (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $prop-val (call $struct-type-property-lookup-by-name (ref.as_non_null (local.get $type)) (local.get $prop-name) (local.get $sentinel))) (if (ref.eq (local.get $prop-val) (local.get $sentinel)) (then (call $raise-argument-error1 (global.get $symbol:custom-write-accessor) (global.get $string:custom-write?) (local.get $v)) (unreachable))) (local.get $prop-val)) (func $format/display:struct/custom-write (param $s (ref $Struct)) (param $proc (ref $Procedure)) (result (ref $String)) (local $port (ref $OutputStringPort)) (local $args (ref $Args)) (local $inv (ref $ProcedureInvoker)) (local.set $port (ref.cast (ref $OutputStringPort) (call $open-output-string (global.get $missing)))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.const 3))) (array.set $Args (local.get $args) (i32.const 0) (local.get $s)) (array.set $Args (local.get $args) (i32.const 1) (local.get $port)) (array.set $Args (local.get $args) (i32.const 2) (global.get $false)) (drop (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv))) (ref.cast (ref $String) (call $get-output-string (local.get $port)))) ;; 13.10 Fast-Load Serialization (global $fasl-fixnum (ref i31) ,(Imm 0)) (global $fasl-character (ref i31) ,(Imm 1)) (global $fasl-symbol (ref i31) ,(Imm 2)) (global $fasl-string (ref i31) ,(Imm 3)) (global $fasl-bytes (ref i31) ,(Imm 4)) (global $fasl-boolean (ref i31) ,(Imm 5)) (global $fasl-null (ref i31) ,(Imm 6)) (global $fasl-pair (ref i31) ,(Imm 7)) (global $fasl-vector (ref i31) ,(Imm 8)) (global $fasl-flonum (ref i31) ,(Imm 9)) (global $fasl-void (ref i31) ,(Imm 10)) (global $fasl-eof (ref i31) ,(Imm 11)) (global $fasl-external (ref i31) ,(Imm 12)) (func $s-exp->fasl (type $Prim2) (param $v (ref eq)) ; optionals: (param $out (ref eq)) ;; a StringPort or #f (or $missing) (result (ref eq)) (local $port (ref eq)) (local $res (ref eq)) ; Handle optional arguments (if (ref.eq (local.get $out) (global.get $missing)) (then (local.set $out (global.get $false)))) (if (result (ref eq)) (ref.eq (local.get $out) (global.get $false)) (then (local.set $port (call $open-output-bytes (global.get $missing))) (call $fasl:s-exp->fasl (local.get $v) (local.get $port)) (local.set $res (call $get-output-bytes (local.get $port))) (local.get $res)) (else (if (result (ref eq)) (ref.test (ref $OutputStringPort) (local.get $out)) (then (call $fasl:s-exp->fasl (local.get $v) (local.get $out)) (global.get $void)) (else (call $raise-check-port-or-false (local.get $out)) (unreachable)))))) ;; raise-s-exp->fasl:unsupported-value : any/c -> none ;; Raise a regular exception for values that cannot be FASL encoded. (func $raise-s-exp->fasl:unsupported-value (param $v (ref eq)) (drop (call $raise (call $make-exn:fail (call $format/display (local.get $v)) (call $current-continuation-marks (global.get $missing))) (global.get $true))) (unreachable)) (func $fasl:s-exp->fasl (param $v (ref eq)) (param $out (ref eq)) ;; a StringPort (local $i i32) (local $n i32) (local $vec (ref $Vector)) (local $arr (ref $Array)) ;; Dispatch by type tag (if (ref.test (ref i31) (local.get $v)) ;; Fixnum or immediate (then (local.set $i (i31.get_u (ref.cast (ref i31) (local.get $v)))) (if (i32.eqz (i32.and (local.get $i) (i32.const 1))) ;; Fixnum (then (drop (call $write-byte (global.get $fasl-fixnum) (local.get $out))) (call $fasl:write-u32 (i32.shr_u (local.get $i) (i32.const 1)) (local.get $out))) ;; Immediate — test tag (else (call $s-exp->fasl/immediate (local.get $i) (local.get $v) (local.get $out))))) ;; Otherwise check boxed types (else (if (ref.test (ref $String) (local.get $v)) (then (drop (call $write-byte (global.get $fasl-string) (local.get $out))) (call $fasl:write-string (ref.cast (ref $String) (local.get $v)) (local.get $out))) (else (if (ref.test (ref $Bytes) (local.get $v)) (then (drop (call $write-byte (global.get $fasl-bytes) (local.get $out))) (call $fasl:write-bytes (ref.cast (ref $Bytes) (local.get $v)) (local.get $out))) (else (if (ref.test (ref $Symbol) (local.get $v)) (then (drop (call $write-byte (global.get $fasl-symbol) (local.get $out))) (call $fasl:write-symbol (ref.cast (ref $Symbol) (local.get $v)) (local.get $out))) (else (if (ref.test (ref $Flonum) (local.get $v)) (then (drop (call $write-byte (global.get $fasl-flonum) (local.get $out))) (call $fasl:write-f64 (struct.get $Flonum $v (ref.cast (ref $Flonum) (local.get $v))) (local.get $out))) (else (if (ref.test (ref $Pair) (local.get $v)) (then (drop (call $write-byte (global.get $fasl-pair) (local.get $out))) (call $fasl:s-exp->fasl (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $v))) (local.get $out)) (call $fasl:s-exp->fasl (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $v))) (local.get $out))) (else (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (drop (call $write-byte (global.get $fasl-vector) (local.get $out))) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $n (array.len (local.get $arr))) (call $fasl:write-u32 (local.get $n) (local.get $out)) (local.set $i (i32.const 0)) (block $break (loop $loop (br_if $break (i32.ge_u (local.get $i) (local.get $n))) (call $fasl:s-exp->fasl (array.get $Array (local.get $arr) (local.get $i)) (local.get $out)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)))) (else (if (ref.test (ref $External) (local.get $v)) (then (drop (call $write-byte (global.get $fasl-external) (local.get $out))) (call $fasl:write-u32 (call $js-register-external (ref.as_non_null (struct.get $External $v (ref.cast (ref $External) (local.get $v))))) (local.get $out))) (else (call $raise-s-exp->fasl:unsupported-value (local.get $v)) (unreachable)))))))))))))))))) ;; unsupported type (func $s-exp->fasl/immediate (param $i i32) (param $v (ref eq)) (param $out (ref eq)) (local $b i32) ;; Character immediate (if (i32.eq (i32.and (local.get $i) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (drop (call $write-byte (global.get $fasl-character) (local.get $out))) (call $fasl:write-u32 (i32.shr_u (local.get $i) (i32.const ,char-shift)) (local.get $out))) (else ;; Boolean immediate (if (i32.eq (i32.and (local.get $i) (i32.const ,boolean-mask)) (i32.const ,boolean-tag)) (then (local.set $b (i32.shr_u (local.get $i) (i32.const ,boolean-shift))) (drop (call $write-byte (global.get $fasl-boolean) (local.get $out))) (drop (call $write-byte (ref.i31 (i32.shl (local.get $b) (i32.const 1))) (local.get $out)))) (else ;; Null immediate (if (i32.eq (local.get $i) (i32.const ,empty-value)) (then (drop (call $write-byte (global.get $fasl-null) (local.get $out)))) (else ;; Void immediate (if (i32.eq (local.get $i) (i32.const ,void-value)) (then (drop (call $write-byte (global.get $fasl-void) (local.get $out)))) (else ;; EOF immediate (if (i32.eq (local.get $i) (i32.const ,eof-value)) (then (drop (call $write-byte (global.get $fasl-eof) (local.get $out)))) (else (unreachable)))))))))))) (func $fasl:write-u32 (param $v i32) (param $out (ref eq)) ;; write the four bytes of $v in big-endian order (drop (call $write-byte (ref.i31 (i32.shl (i32.and (i32.shr_u (local.get $v) (i32.const 24)) (i32.const 255)) (i32.const 1))) (local.get $out))) (drop (call $write-byte (ref.i31 (i32.shl (i32.and (i32.shr_u (local.get $v) (i32.const 16)) (i32.const 255)) (i32.const 1))) (local.get $out))) (drop (call $write-byte (ref.i31 (i32.shl (i32.and (i32.shr_u (local.get $v) (i32.const 8)) (i32.const 255)) (i32.const 1))) (local.get $out))) (drop (call $write-byte (ref.i31 (i32.shl (i32.and (local.get $v) (i32.const 255)) (i32.const 1))) (local.get $out)))) (func $fasl:write-bytes (param $b (ref $Bytes)) (param $out (ref eq)) (local $arr (ref $I8Array)) (local $len i32) (local $i i32) (local $val i32) ;; write length first (local.set $arr (struct.get $Bytes $bs (local.get $b))) (local.set $len (array.len (local.get $arr))) (call $fasl:write-u32 (local.get $len) (local.get $out)) ;; output each byte (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $val (call $i8array-ref (local.get $arr) (local.get $i))) (drop (call $write-byte (ref.i31 (i32.shl (local.get $val) (i32.const 1))) (local.get $out))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop)))) (func $fasl:write-string (param $s (ref $String)) (param $out (ref eq)) (local $bs (ref eq)) (local.set $bs (call $string->bytes/utf-8 (local.get $s) (global.get $false) (global.get $false) (global.get $false))) (call $fasl:write-bytes (ref.cast (ref $Bytes) (local.get $bs)) (local.get $out))) (func $fasl:write-symbol (param $sym (ref $Symbol)) (param $out (ref eq)) (call $fasl:write-string (struct.get $Symbol $name (local.get $sym)) (local.get $out))) (func $fasl:write-f64 (param $v f64) (param $out (ref eq)) (local $bits i64) (local $hi i32) (local $lo i32) (local.set $bits (i64.reinterpret_f64 (local.get $v))) (local.set $hi (i32.wrap_i64 (i64.shr_u (local.get $bits) (i64.const 32)))) (local.set $lo (i32.wrap_i64 (local.get $bits))) (call $fasl:write-u32 (local.get $hi) (local.get $out)) (call $fasl:write-u32 (local.get $lo) (local.get $out))) ;;; Fasl decoding (from byte string) (func $fasl->s-exp (type $Prim1) ;; entry point: decode byte string (param $bs (ref eq)) (result (ref eq)) (local $b (ref $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $val (ref eq)) (local $i i32) (if (i32.eqz (ref.test (ref $Bytes) (local.get $bs))) (then (call $raise-argument-error (local.get $bs)) (unreachable))) (local.set $b (ref.cast (ref $Bytes) (local.get $bs))) (local.set $arr (struct.get $Bytes $bs (local.get $b))) (local.set $len (array.len (local.get $arr))) (local.set $i (i32.const 0)) ;; ; the call returns two values: the decoded value and the index (call $fasl:read-s-exp (local.get $arr) (local.get $len) (local.get $i)) (local.set $i) (local.set $val) ; note: reversed compared to $fasl:read-s-exp (local.get $val)) (func $fasl:read-u32 ;; read 4 bytes as big-endian u32 ;; returns value and new index (param $arr (ref $I8Array)) (param $i i32) (result i32 i32) (local $b0 i32) (local $b1 i32) (local $b2 i32) (local $b3 i32) (local.set $b0 (array.get_u $I8Array (local.get $arr) (local.get $i))) (local.set $b1 (array.get_u $I8Array (local.get $arr) (i32.add (local.get $i) (i32.const 1)))) (local.set $b2 (array.get_u $I8Array (local.get $arr) (i32.add (local.get $i) (i32.const 2)))) (local.set $b3 (array.get_u $I8Array (local.get $arr) (i32.add (local.get $i) (i32.const 3)))) (return (i32.or (i32.shl (local.get $b0) (i32.const 24)) (i32.or (i32.shl (local.get $b1) (i32.const 16)) (i32.or (i32.shl (local.get $b2) (i32.const 8)) (local.get $b3)))) (i32.add (local.get $i) (i32.const 4)))) (func $fasl:read-bytes ;; read length-prefixed byte array (param $arr (ref $I8Array)) (param $i i32) (result (ref $Bytes) i32) (local $len i32) (local $next i32) (local $data (ref $I8Array)) (call $fasl:read-u32 (local.get $arr) (local.get $i)) (local.set $next) (local.set $len) ; note: reversed (local.set $data (call $i8array-copy (local.get $arr) (local.get $next) (i32.add (local.get $next) (local.get $len)))) (return (struct.new $Bytes (i32.const 0) ; hash (i32.const 1) ; immutable (local.get $data)) ; I8Array (i32.add (local.get $next) (local.get $len)))) (func $fasl:read-string ;; read bytes and convert to string (param $arr (ref $I8Array)) (param $i i32) (result (ref $String) i32) (local $bs (ref $Bytes)) (local $next i32) (call $fasl:read-bytes (local.get $arr) (local.get $i)) (local.set $next) (local.set $bs) ; note: reversed (return (call $bytes->string/utf-8/checked (local.get $bs)) (local.get $next))) (func $fasl:read-symbol ;; read string and intern symbol (param $arr (ref $I8Array)) (param $i i32) (result (ref $Symbol) i32) (local $str (ref $String)) (local $next i32) (call $fasl:read-string (local.get $arr) (local.get $i)) (local.set $next) (local.set $str) ; note: reversed (return (ref.cast (ref $Symbol) (call $string->symbol (local.get $str))) (local.get $next))) (func $fasl:read-f64 ;; read IEEE double (param $arr (ref $I8Array)) (param $i i32) (result (ref $Flonum) i32) (local $hi i32) (local $idx i32) (local $lo i32) (local $next i32) (local $bits i64) (call $fasl:read-u32 (local.get $arr) (local.get $i)) (local.set $idx) (local.set $hi) ; reversed (call $fasl:read-u32 (local.get $arr) (local.get $idx)) (local.set $next) (local.set $lo) ; reversed (local.set $bits (i64.or (i64.shl (i64.extend_i32_u (local.get $hi)) (i64.const 32)) (i64.extend_i32_u (local.get $lo)))) (return (struct.new $Flonum (i32.const 0) (f64.reinterpret_i64 (local.get $bits))) (local.get $next))) (func $fasl:read-s-exp ;; decode one FASL value by tag (param $arr (ref $I8Array)) (param $len i32) (param $i i32) (result (ref eq) i32) (local $tag i32) (local $val i32) (local $cp i32) (local $sym (ref $Symbol)) (local $str (ref $String)) (local $bs (ref $Bytes)) (local $b i32) (local $car (ref eq)) (local $cdr (ref eq)) (local $n i32) (local $vec (ref $Vector)) (local $j i32) (local $elem (ref eq)) (local $fl (ref $Flonum)) (local $idx i32) (local.set $tag (array.get_u $I8Array (local.get $arr) (local.get $i))) (local.set $tag (i32.shl (local.get $tag) (i32.const 1))) ; as fixnum (local.set $i (i32.add (local.get $i) (i32.const 1))) ;; fixnum (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-fixnum))) (then (call $fasl:read-u32 (local.get $arr) (local.get $i)) (local.set $i) (local.set $val) (return (ref.i31 (i32.shl (local.get $val) (i32.const 1))) (local.get $i))) (else ;; character (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-character))) (then (call $fasl:read-u32 (local.get $arr) (local.get $i)) (local.set $i) (local.set $cp) (return (ref.i31 (i32.or (i32.shl (local.get $cp) (i32.const ,char-shift)) (i32.const ,char-tag))) (local.get $i))) (else ;; symbol (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-symbol))) (then (call $fasl:read-symbol (local.get $arr) (local.get $i)) (local.set $i) (local.set $sym) (return (local.get $sym) (local.get $i))) (else ;; string (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-string))) (then (call $fasl:read-string (local.get $arr) (local.get $i)) (local.set $i) (local.set $str) (return (local.get $str) (local.get $i))) (else ;; bytes (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-bytes))) (then (call $fasl:read-bytes (local.get $arr) (local.get $i)) (local.set $i) (local.set $bs) (return (local.get $bs) (local.get $i))) (else ;; boolean (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-boolean))) (then (local.set $b (array.get_u $I8Array (local.get $arr) (local.get $i))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (return (if (result (ref eq)) (i32.ne (local.get $b) (i32.const 0)) (then (global.get $true)) (else (global.get $false))) (local.get $i))) (else ;; null (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-null))) (then (return (global.get $null) (local.get $i))) (else ;; pair (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-pair))) (then (call $fasl:read-s-exp (local.get $arr) (local.get $len) (local.get $i)) (local.set $i) (local.set $car) (call $fasl:read-s-exp (local.get $arr) (local.get $len) (local.get $i)) (local.set $i) (local.set $cdr) (return (call $cons (local.get $car) (local.get $cdr)) (local.get $i))) (else ;; vector (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-vector))) (then (call $fasl:read-u32 (local.get $arr) (local.get $i)) (local.set $i) (local.set $n) (local.set $vec (call $make-vector/checked (local.get $n) (global.get $void))) (local.set $j (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $j) (local.get $n))) (call $fasl:read-s-exp (local.get $arr) (local.get $len) (local.get $i)) (local.set $i) (local.set $elem) (call $vector-set!/checked (local.get $vec) (local.get $j) (local.get $elem)) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $loop))) (return (local.get $vec) (local.get $i))) (else ;; flonum (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-flonum))) (then (call $fasl:read-f64 (local.get $arr) (local.get $i)) (local.set $i) (local.set $fl) (return (local.get $fl) (local.get $i))) (else ;; void (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-void))) (then (return (global.get $void) (local.get $i))) (else ;; eof (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-eof))) (then (return (ref.i31 (i32.const ,eof-value)) (local.get $i))) (else ;; external (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-external))) (then (call $fasl:read-u32 (local.get $arr) (local.get $i)) (local.set $i) (local.set $idx) (return (struct.new $External (i32.const 0) (call $js-lookup-external (local.get $idx))) (local.get $i))) (else (unreachable)))))))))))))))))))))))))))) ;; Decode FASL data directly from linear memory. (func $fasl-memory:read-u32 (param $i i32) (result i32 i32) (local $b0 i32) (local $b1 i32) (local $b2 i32) (local $b3 i32) (local.set $b0 (i32.load8_u (local.get $i))) (local.set $b1 (i32.load8_u (i32.add (local.get $i) (i32.const 1)))) (local.set $b2 (i32.load8_u (i32.add (local.get $i) (i32.const 2)))) (local.set $b3 (i32.load8_u (i32.add (local.get $i) (i32.const 3)))) (return (i32.or (i32.shl (local.get $b0) (i32.const 24)) (i32.or (i32.shl (local.get $b1) (i32.const 16)) (i32.or (i32.shl (local.get $b2) (i32.const 8)) (local.get $b3)))) (i32.add (local.get $i) (i32.const 4)))) (func $fasl-memory:read-bytes (param $i i32) (result (ref $Bytes) i32) (local $len i32) (local $next i32) (local $data (ref $I8Array)) (local $j i32) (call $fasl-memory:read-u32 (local.get $i)) (local.set $next) (local.set $len) (local.set $data (array.new_default $I8Array (local.get $len))) (local.set $j (i32.const 0)) (block $done (loop $copy (br_if $done (i32.ge_u (local.get $j) (local.get $len))) (array.set $I8Array (local.get $data) (local.get $j) (i32.load8_u (i32.add (local.get $next) (local.get $j)))) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $copy))) (return (struct.new $Bytes (i32.const 0) (i32.const 1) (local.get $data)) (i32.add (local.get $next) (local.get $len)))) (func $fasl-memory:read-string (param $i i32) (result (ref $String) i32) (local $bs (ref $Bytes)) (local $next i32) (call $fasl-memory:read-bytes (local.get $i)) (local.set $next) (local.set $bs) (return (call $bytes->string/utf-8/checked (local.get $bs)) (local.get $next))) (func $fasl-memory:read-symbol (param $i i32) (result (ref $Symbol) i32) (local $str (ref $String)) (local $next i32) (call $fasl-memory:read-string (local.get $i)) (local.set $next) (local.set $str) (return (ref.cast (ref $Symbol) (call $string->symbol (local.get $str))) (local.get $next))) (func $fasl-memory:read-f64 (param $i i32) (result (ref $Flonum) i32) (local $hi i32) (local $idx i32) (local $lo i32) (local $next i32) (local $bits i64) (call $fasl-memory:read-u32 (local.get $i)) (local.set $idx) (local.set $hi) (call $fasl-memory:read-u32 (local.get $idx)) (local.set $next) (local.set $lo) (local.set $bits (i64.or (i64.shl (i64.extend_i32_u (local.get $hi)) (i64.const 32)) (i64.extend_i32_u (local.get $lo)))) (return (struct.new $Flonum (i32.const 0) (f64.reinterpret_i64 (local.get $bits))) (local.get $next))) (func $fasl-memory:read-s-exp (param $i i32) (result (ref eq) i32) (local $tag i32) (local $val i32) (local $cp i32) (local $sym (ref $Symbol)) (local $str (ref $String)) (local $bs (ref $Bytes)) (local $b i32) (local $car (ref eq)) (local $cdr (ref eq)) (local $n i32) (local $vec (ref $Vector)) (local $j i32) (local $elem (ref eq)) (local $fl (ref $Flonum)) (local $idx i32) (local.set $tag (i32.load8_u (local.get $i))) (local.set $tag (i32.shl (local.get $tag) (i32.const 1))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-fixnum))) (then (call $fasl-memory:read-u32 (local.get $i)) (local.set $i) (local.set $val) (return (ref.i31 (i32.shl (local.get $val) (i32.const 1))) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-character))) (then (call $fasl-memory:read-u32 (local.get $i)) (local.set $i) (local.set $cp) (return (ref.i31 (i32.or (i32.shl (local.get $cp) (i32.const ,char-shift)) (i32.const ,char-tag))) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-symbol))) (then (call $fasl-memory:read-symbol (local.get $i)) (local.set $i) (local.set $sym) (return (local.get $sym) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-string))) (then (call $fasl-memory:read-string (local.get $i)) (local.set $i) (local.set $str) (return (local.get $str) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-bytes))) (then (call $fasl-memory:read-bytes (local.get $i)) (local.set $i) (local.set $bs) (return (local.get $bs) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-boolean))) (then (local.set $b (i32.load8_u (local.get $i))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (return (if (result (ref eq)) (i32.ne (local.get $b) (i32.const 0)) (then (global.get $true)) (else (global.get $false))) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-null))) (then (return (global.get $null) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-pair))) (then (call $fasl-memory:read-s-exp (local.get $i)) (local.set $i) (local.set $car) (call $fasl-memory:read-s-exp (local.get $i)) (local.set $i) (local.set $cdr) (return (call $cons (local.get $car) (local.get $cdr)) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-vector))) (then (call $fasl-memory:read-u32 (local.get $i)) (local.set $i) (local.set $n) (local.set $vec (call $make-vector/checked (local.get $n) (global.get $void))) (local.set $j (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $j) (local.get $n))) (call $fasl-memory:read-s-exp (local.get $i)) (local.set $i) (local.set $elem) (call $vector-set!/checked (local.get $vec) (local.get $j) (local.get $elem)) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $loop))) (return (local.get $vec) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-flonum))) (then (call $fasl-memory:read-f64 (local.get $i)) (local.set $i) (local.set $fl) (return (local.get $fl) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-void))) (then (return (global.get $void) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-eof))) (then (return (ref.i31 (i32.const ,eof-value)) (local.get $i))) (else (if (result (ref eq) i32) (i32.eq (local.get $tag) (i31.get_u (global.get $fasl-external))) (then (call $fasl-memory:read-u32 (local.get $i)) (local.set $i) (local.set $idx) (return (struct.new $External (i32.const 0) (call $js-lookup-external (local.get $idx))) (local.get $i))) (else (unreachable)))))))))))))))))))))))))))) (func $fasl-memory->s-exp (param $start i32) (result (ref eq)) (local $val (ref eq)) (local $end i32) (call $fasl-memory:read-s-exp (local.get $start)) (local.set $end) (local.set $val) (local.get $val)) (func $copy-memory-to-i8array (export "copy-memory-to-i8array") (param $start i32) (result (ref $I8Array) i32) (local $mem-bytes i32) (local $len i32) (local $arr (ref $I8Array)) (local $i i32) (local $end i32) (local $res (ref $I8Array)) ; The Performance tab in Chrome shows that we are spending way too much time here. ; It is called from linear-memory->value. ; Why are we copying linear memory? ; memory.size returns number of pages. The page size is 64 KiB = 65536. (local.set $mem-bytes (i32.mul (memory.size) (i32.const 65536))) (local.set $len (i32.sub (local.get $mem-bytes) (local.get $start))) (local.set $arr (array.new_default $I8Array (local.get $len))) (local.set $i (i32.const 0)) ; Note: Currently (sep 2025) there are no bulk copy operations from ; memory to array. So the loop can't be replaced with bulk copy. (block $done (loop $copy (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (array.set $I8Array (local.get $arr) (local.get $i) (i32.load8_u (i32.add (local.get $start) (local.get $i)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $copy))) (call $fasl:read-s-exp (local.get $arr) (local.get $len) (i32.const 0)) (local.set $end) (drop) (local.set $res (call $i8array-copy (local.get $arr) (i32.const 0) (local.get $end))) (return (local.get $res) (i32.add (local.get $start) (local.get $end)))) (func $linear-memory->value (export "linear-memory->value") (param $start i32) (result (ref eq)) (local $val (ref eq)) (local.set $val (call $fasl-memory->s-exp (local.get $start))) (local.get $val)) (func $linear-memory->string (export "linear-memory->string") (param $start i32) (result (ref eq)) (local $v (ref eq)) (local.set $v (call $linear-memory->value (local.get $start))) (if (i32.eqz (ref.test (ref $String) (local.get $v))) (then (call $raise-expected-string) (unreachable))) (return (ref.cast (ref $String) (local.get $v)))) ;;; ;;; 14. REFLECTION AND SECURITY ;;; ;; 14.1 Namespaces (func $namespace? (type $Prim1) ,@(make-predicate-body '$Namespace)) ; The form `#%variable-reference` can occur in fully expanded syntax, ; so the compiler builds $VariableReference instances directly. ; This function determines if the variable stems from a module compiled in unsafe mode or not. (func $variable-reference-from-unsafe? (type $Prim1) (param $varref (ref eq)) (result (ref eq)) (struct.get $VariableReference $from-unsafe? (ref.cast (ref $VariableReference) (local.get $varref)))) (func $variable-reference-constant? (type $Prim1) (param $varref (ref eq)) (result (ref eq)) (struct.get $VariableReference $constant? (ref.cast (ref $VariableReference) (local.get $varref)))) (func $raise-unbound-variable-reference (type $Prim1) (param $name (ref eq)) (result (ref eq)) (drop (call $js-log (local.get $name))) (unreachable)) ;; 14.2 Evaluation and compilation ;; 14.3 The racket/load language ;; 14.4 Module names and loading ;; 14.5 Impersonators and chaperones ;; 14.6 Security Guards ;; 14.7 Custodians ;; 14.8 Thread Groups ;; 14.9 Structure Inspectors (func $current-inspector (type $Prim0) ; TODO: dummy (result (ref eq)) (global.get $false)) ;; Racket's object-name recognizes additional values such as regexp objects, ;; loggers, prompt tags, and structures with the prop:object-name property. ;; WebRacket currently supports procedures, structure instances, structure ;; type descriptors, and string ports. (func $object-name (type $Prim1) (param $v (ref eq)) ;; any/c (result (ref eq)) (local $proc (ref $Procedure)) (local $struct (ref $Struct)) (local $type (ref $StructType)) (local $name (ref eq)) (local $prop-val (ref eq)) (local $sentinel (ref eq)) (local $fields (ref $Array)) (local $idx i32) (local $super (ref eq)) (local $super-type (ref null $StructType)) (local $super-count i32) (local $abs-index i32) (local $prop-proc (ref $Procedure)) (local $prop-inv (ref $ProcedureInvoker)) (local $prop-args (ref $Args)) (local $prop-name (ref $Symbol)) ;; Structure instances report the associated struct type name. (if (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $sentinel (call $cons (global.get $false) (global.get $false))) (local.set $prop-name (ref.cast (ref $Symbol) (global.get $symbol:object-name))) (local.set $prop-val (call $struct-type-property-lookup-by-name (local.get $type) (local.get $prop-name) (local.get $sentinel))) (if (i32.eqz (ref.eq (local.get $prop-val) (local.get $sentinel))) (then (if (ref.test (ref i31) (local.get $prop-val)) (then (local.set $idx (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $prop-val))) (i32.const 1))) (local.set $super (struct.get $StructType $super (local.get $type))) (local.set $super-type (ref.null $StructType)) (local.set $super-count (i32.const 0)) (if (i32.eqz (ref.eq (local.get $super) (global.get $false))) (then (local.set $super-type (ref.cast (ref $StructType) (local.get $super))) (local.set $super-count (struct.get $StructType $field-count (local.get $super-type))))) (local.set $fields (struct.get $Struct $fields (local.get $struct))) (local.set $abs-index (i32.add (local.get $super-count) (local.get $idx))) (return (array.get $Array (local.get $fields) (local.get $abs-index)))) (else (if (ref.test (ref $Procedure) (local.get $prop-val)) (then (local.set $prop-proc (ref.cast (ref $Procedure) (local.get $prop-val))) (local.set $prop-inv (struct.get $Procedure $invoke (local.get $prop-proc))) (local.set $prop-args (array.new $Args (global.get $null) (i32.const 1))) (array.set $Args (local.get $prop-args) (i32.const 0) (local.get $struct)) (return_call_ref $ProcedureInvoker (local.get $prop-proc) (local.get $prop-args) (local.get $prop-inv))) (else (return (local.get $prop-val))))))) (else (return (struct.get $StructType $name (local.get $type))))))) ;; Structure type descriptors return their recorded name symbol. (if (ref.test (ref $StructType) (local.get $v)) (then (return (struct.get $StructType $name (ref.cast (ref $StructType) (local.get $v)))))) ;; Ports expose the stored port name. (if (ref.test (ref $Port) (local.get $v)) (then (return (struct.get $Port $name (ref.cast (ref $Port) (local.get $v)))))) ;; Procedures use the cached name field when available. (if (ref.test (ref $Procedure) (local.get $v)) (then (local.set $proc (ref.cast (ref $Procedure) (local.get $v))) (local.set $name (struct.get $Procedure $name (local.get $proc))) (return (if (result (ref eq)) (ref.eq (local.get $name) (global.get $false)) (then (global.get $false)) (else (local.get $name)))))) ;; Unnamed objects fall back to #f. (global.get $false)) ;; 14.10 Code Inspectors ;; 14.11 Plumbers ;; 14.12 Sandboxed Evaluation ;; 14.13 The racket/repl library ;;; ;;; 14.14 Linklets and the compiler ;;; ;; Instances (func $raise-argument-error:instance-expected (param $got (ref eq)) ;; value that was not an instance (call $raise-argument-error1 (global.get $symbol:instance) (global.get $string:instance?) (local.get $got)) (unreachable)) (func $raise-make-instance-missing-value (param $sym (ref eq)) ;; offending variable name (call $raise-argument-error1 (global.get $symbol:make-instance) (global.get $string:missing-variable-value) (local.get $sym)) (unreachable)) (func $raise-link-missing-binding (param $sym (ref eq)) ;; missing symbol (call $js-log (global.get $symbol:link)) (call $js-log (global.get $string:missing-binding)) (call $js-log (local.get $sym)) (unreachable)) (func $raise-instance-variable-box-missing-binding (param $sym (ref eq)) ;; missing symbol (call $js-log (global.get $symbol:instance-variable-box)) (call $js-log (global.get $string:missing-binding)) (call $js-log (local.get $sym)) (unreachable)) (func $raise-instance-variable-not-found (param $sym (ref eq)) ;; missing symbol (call $js-log (global.get $symbol:instance-variable-value)) (call $js-log (global.get $string:instance-variable-not-found)) (call $js-log (local.get $sym)) (unreachable)) (func $raise-instance-variable-constant (param $sym (ref eq)) ;; constant symbol (call $js-log (local.get $sym)) (call $js-log (global.get $string:cannot-modify-constant)) (unreachable)) (func $raise-instance-optional-argument (param $who (ref eq)) ;; symbol naming the primitive (param $rest (ref eq)) ;; unexpected arguments (call $raise-argument-error1 (local.get $who) (global.get $string:at-most-one-optional-argument) (local.get $rest)) (unreachable)) (func $instance? (type $Prim1) (param $v (ref eq)) ;; value to check (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Instance) (local.get $v)) (then (global.get $true)) (else (global.get $false)))) (func $instance-name (type $Prim1) (param $inst (ref eq)) ;; instance (result (ref eq)) (local $instance (ref $Instance)) (if (i32.eqz (ref.test (ref $Instance) (local.get $inst))) (then (call $raise-argument-error:instance-expected (local.get $inst)) (unreachable))) (local.set $instance (ref.cast (ref $Instance) (local.get $inst))) (struct.get $Instance $name (local.get $instance))) (func $instance-data (type $Prim1) (param $inst (ref eq)) ;; instance (result (ref eq)) (local $instance (ref $Instance)) (if (i32.eqz (ref.test (ref $Instance) (local.get $inst))) (then (call $raise-argument-error:instance-expected (local.get $inst)) (unreachable))) (local.set $instance (ref.cast (ref $Instance) (local.get $inst))) (struct.get $Instance $data (local.get $instance))) (func $make-instance (type $Prim>=1) (param $name (ref eq)) ;; instance name (param $rest (ref eq)) ;; [data #f] [mode #f] variable bindings (result (ref eq)) (local $data (ref eq)) (local $mode (ref eq)) (local $content (ref eq)) (local $node (ref eq)) (local $pair (ref $Pair)) (local $sym (ref eq)) (local $sym-val (ref $Symbol)) (local $value (ref eq)) (local $inst (ref $Instance)) (local $vars (ref $HashEqMutable)) (local $constants (ref $HashEqMutable)) (local $box (ref $Box)) (local.set $data (global.get $false)) ;; optional data defaults to #f (local.set $mode (global.get $false)) ;; optional mode defaults to #f (local.set $content (local.get $rest)) ;; Extract optional data argument when present. (if (ref.eq (local.get $content) (global.get $null)) (then (nop)) (else (local.set $pair (block $ok (result (ref $Pair)) (br_on_cast $ok (ref eq) (ref $Pair) (local.get $content)) (call $raise-pair-expected (local.get $content)) (unreachable))) (local.set $data (struct.get $Pair $a (local.get $pair))) (local.set $content (struct.get $Pair $d (local.get $pair))) ;; Extract optional mode argument when present. (if (ref.eq (local.get $content) (global.get $null)) (then (nop)) (else (local.set $pair (block $ok (result (ref $Pair)) (br_on_cast $ok (ref eq) (ref $Pair) (local.get $content)) (call $raise-pair-expected (local.get $content)) (unreachable))) (local.set $mode (struct.get $Pair $a (local.get $pair))) (local.set $content (struct.get $Pair $d (local.get $pair))))))) ;; Racket accepts only the three linklet instance modes here. (if (i32.eqz (i32.or (ref.eq (local.get $mode) (global.get $false)) (i32.or (ref.eq (local.get $mode) (global.get $symbol:constant)) (ref.eq (local.get $mode) (global.get $symbol:consistent))))) (then (call $raise-argument-error1 (global.get $symbol:make-instance) (global.get $string:instance-mode?) (local.get $mode)) (unreachable))) ;; Allocate the instance and its variable table. (local.set $vars (ref.cast (ref $HashEqMutable) (call $make-empty-hasheq))) (local.set $constants (ref.cast (ref $HashEqMutable) (call $make-empty-hasheq))) (local.set $inst (struct.new $Instance (i32.const 0) (local.get $name) (local.get $data) (local.get $vars) (local.get $constants))) ;; Populate variables from the remaining content list. (local.set $node (local.get $content)) (block $done (loop $loop (br_if $done (ref.eq (local.get $node) (global.get $null))) (local.set $pair (block $ok (result (ref $Pair)) (br_on_cast $ok (ref eq) (ref $Pair) (local.get $node)) (call $raise-pair-expected (local.get $node)) (unreachable))) (local.set $sym (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-argument-error1 (global.get $symbol:make-instance) (global.get $string:symbol?) (local.get $sym)) (unreachable))) (local.set $sym-val (ref.cast (ref $Symbol) (local.get $sym))) (local.set $node (struct.get $Pair $d (local.get $pair))) (if (ref.eq (local.get $node) (global.get $null)) (then (call $raise-make-instance-missing-value (local.get $sym)) (unreachable))) (local.set $pair (block $ok (result (ref $Pair)) (br_on_cast $ok (ref eq) (ref $Pair) (local.get $node)) (call $raise-pair-expected (local.get $node)) (unreachable))) (local.set $value (struct.get $Pair $a (local.get $pair))) (local.set $node (struct.get $Pair $d (local.get $pair))) (local.set $box (ref.cast (ref $Box) (call $instance-variable-box (ref.cast (ref eq) (local.get $inst)) (local.get $sym-val) (global.get $true)))) (call $set-box! (ref.cast (ref eq) (local.get $box)) (local.get $value)) (if (i32.eqz (ref.eq (local.get $mode) (global.get $false))) (then (call $hasheq-set!/mutable/checked (local.get $constants) (local.get $sym-val) (global.get $true)))) (br $loop))) (ref.cast (ref eq) (local.get $inst))) (func $instance-variable-names (type $Prim1) (param $inst (ref eq)) ;; instance (result (ref eq)) (local $instance (ref $Instance)) (local $vars (ref $HashEqMutable)) (local $keys (ref eq)) (local $acc (ref eq)) (local $pair (ref $Pair)) (local $key (ref eq)) (local $got (ref eq)) (local $box (ref $Box)) (local $val (ref eq)) ;; Type check arguments (if (i32.eqz (ref.test (ref $Instance) (local.get $inst))) (then (call $raise-argument-error:instance-expected (local.get $inst)) (unreachable))) ;; Get all variables in the hash table (local.set $instance (ref.cast (ref $Instance) (local.get $inst))) (local.set $vars (struct.get $Instance $variables (local.get $instance))) (local.set $keys (call $hasheq-keys (ref.cast (ref eq) (local.get $vars)) (global.get $false))) ;; Discard unset variables (local.set $acc (global.get $null)) (block $done (loop $loop ; done? (br_if $done (ref.eq (local.get $keys) (global.get $null))) ; get next variable (local.set $pair (ref.cast (ref $Pair) (local.get $keys))) (local.set $key (struct.get $Pair $a (local.get $pair))) (local.set $keys (struct.get $Pair $d (local.get $pair))) (local.set $got (call $hasheq-ref (ref.cast (ref eq) (local.get $vars)) (local.get $key) (global.get $false))) ; is it unset or not? (if (ref.test (ref $Box) (local.get $got)) (then (local.set $box (ref.cast (ref $Box) (local.get $got))) (local.set $val (struct.get $Box $v (local.get $box))) (if (i32.eqz (ref.eq (local.get $val) (global.get $undefined))) (then (local.set $acc (call $cons (local.get $key) (local.get $acc))))))) (br $loop))) (local.get $acc)) (func $instance-variable-box (type $Prim3) (param $inst (ref eq)) ;; instance (param $sym (ref eq)) ;; symbol (param $can-create? (ref eq)) ;; boolean, #f => do not create (result (ref eq)) (local $instance (ref $Instance)) (local $symbol (ref $Symbol)) (local $vars (ref $HashEqMutable)) (local $got (ref eq)) (local $box (ref $Box)) (local $create? i32) (if (i32.eqz (ref.test (ref $Instance) (local.get $inst))) (then (call $raise-argument-error:instance-expected (local.get $inst)) (unreachable))) (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-argument-error1 (global.get $symbol:instance-variable-box) (global.get $string:symbol?) (local.get $sym)) (unreachable))) (local.set $instance (ref.cast (ref $Instance) (local.get $inst))) (local.set $symbol (ref.cast (ref $Symbol) (local.get $sym))) (local.set $vars (struct.get $Instance $variables (local.get $instance))) (local.set $got (call $hasheq-ref (ref.cast (ref eq) (local.get $vars)) (local.get $symbol) (global.get $false))) (if (ref.test (ref $Box) (local.get $got)) (then (return (local.get $got)))) (local.set $create? (i32.eqz (ref.eq (local.get $can-create?) (global.get $false)))) (if (i32.eqz (local.get $create?)) (then (call $raise-instance-variable-box-missing-binding (local.get $sym)) (unreachable))) (local.set $box (ref.cast (ref $Box) (call $box (global.get $undefined)))) (call $hasheq-set!/mutable/checked (local.get $vars) (local.get $symbol) (ref.cast (ref eq) (local.get $box))) (ref.cast (ref eq) (local.get $box))) (func $instance-set-variable-value! (type $Prim>=3) (param $inst (ref eq)) ;; instance (param $sym (ref eq)) ;; symbol (param $val (ref eq)) ;; value (param $rest (ref eq)) ;; optional mode (#f by default) (result (ref eq)) (local $mode (ref eq)) (local $pair (ref $Pair)) (local $extra (ref eq)) (local $instance (ref $Instance)) (local $constants (ref $HashEqMutable)) (local $constant? (ref eq)) (local $box (ref $Box)) (if (i32.eqz (ref.test (ref $Instance) (local.get $inst))) (then (call $raise-argument-error:instance-expected (local.get $inst)) (unreachable))) (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-argument-error1 (global.get $symbol:instance-set-variable-value!) (global.get $string:symbol?) (local.get $sym)) (unreachable))) (local.set $mode (global.get $false)) ;; optional mode defaults to #f (local.set $extra (local.get $rest)) (if (ref.eq (local.get $extra) (global.get $null)) (then (nop)) (else (local.set $pair (block $ok (result (ref $Pair)) (br_on_cast $ok (ref eq) (ref $Pair) (local.get $extra)) (call $raise-pair-expected (local.get $extra)) (unreachable))) (local.set $mode (struct.get $Pair $a (local.get $pair))) (local.set $extra (struct.get $Pair $d (local.get $pair))) (if (ref.eq (local.get $extra) (global.get $null)) (then (nop)) (else (call $raise-instance-optional-argument (global.get $symbol:instance-set-variable-value!) (local.get $extra)) (unreachable))))) ;; Racket accepts only the three linklet instance modes here. (if (i32.eqz (i32.or (ref.eq (local.get $mode) (global.get $false)) (i32.or (ref.eq (local.get $mode) (global.get $symbol:constant)) (ref.eq (local.get $mode) (global.get $symbol:consistent))))) (then (call $raise-argument-error1 (global.get $symbol:instance-set-variable-value!) (global.get $string:instance-mode?) (local.get $mode)) (unreachable))) (local.set $instance (ref.cast (ref $Instance) (local.get $inst))) (local.set $constants (struct.get $Instance $constants (local.get $instance))) (local.set $constant? (call $hasheq-ref (ref.cast (ref eq) (local.get $constants)) (local.get $sym) (global.get $false))) (if (i32.eqz (ref.eq (local.get $constant?) (global.get $false))) (then (call $raise-instance-variable-constant (local.get $sym)) (unreachable))) (local.set $box (ref.cast (ref $Box) (call $instance-variable-box (local.get $inst) (local.get $sym) (global.get $true)))) (drop (call $set-box! (ref.cast (ref eq) (local.get $box)) (local.get $val))) (if (i32.eqz (ref.eq (local.get $mode) (global.get $false))) (then (call $hasheq-set!/mutable/checked (local.get $constants) (local.get $sym) (global.get $true)))) (global.get $void)) (func $instance-unset-variable! (type $Prim2) (param $inst (ref eq)) ;; instance (param $sym (ref eq)) ;; symbol (result (ref eq)) (local $box (ref $Box)) (if (i32.eqz (ref.test (ref $Instance) (local.get $inst))) (then (call $raise-argument-error:instance-expected (local.get $inst)) (unreachable))) (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-argument-error1 (global.get $symbol:instance-unset-variable!) (global.get $string:symbol?) (local.get $sym)) (unreachable))) (local.set $box (ref.cast (ref $Box) (call $instance-variable-box (local.get $inst) (local.get $sym) (global.get $true)))) (drop (call $set-box! (ref.cast (ref eq) (local.get $box)) (global.get $undefined))) (global.get $void)) (func $instance-variable-value (type $Prim>=2) (param $inst (ref eq)) ;; instance (param $sym (ref eq)) ;; symbol (param $rest (ref eq)) ;; optional failure result/procedure (result (ref eq)) (local $instance (ref $Instance)) (local $symbol (ref $Symbol)) (local $vars (ref $HashEqMutable)) (local $got (ref eq)) (local $box (ref $Box)) (local $val (ref eq)) (local $fail (ref eq)) (local $pair (ref $Pair)) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $noargs (ref $Args)) (if (i32.eqz (ref.test (ref $Instance) (local.get $inst))) (then (call $raise-argument-error:instance-expected (local.get $inst)) (unreachable))) (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-argument-error1 (global.get $symbol:instance-variable-value) (global.get $string:symbol?) (local.get $sym)) (unreachable))) (local.set $instance (ref.cast (ref $Instance) (local.get $inst))) (local.set $symbol (ref.cast (ref $Symbol) (local.get $sym))) (local.set $vars (struct.get $Instance $variables (local.get $instance))) (local.set $fail (global.get $missing)) ;; sentinel meaning raise error (if (ref.eq (local.get $rest) (global.get $null)) (then (nop)) (else (local.set $pair (block $ok (result (ref $Pair)) (br_on_cast $ok (ref eq) (ref $Pair) (local.get $rest)) (call $raise-pair-expected (local.get $rest)) (unreachable))) (local.set $fail (struct.get $Pair $a (local.get $pair))) (local.set $rest (struct.get $Pair $d (local.get $pair))) (if (ref.eq (local.get $rest) (global.get $null)) (then (nop)) (else (call $raise-instance-optional-argument (global.get $symbol:instance-variable-value) (local.get $rest)) (unreachable))))) (local.set $got (call $hasheq-ref (ref.cast (ref eq) (local.get $vars)) (local.get $symbol) (global.get $false))) (if (ref.test (ref $Box) (local.get $got)) (then (local.set $box (ref.cast (ref $Box) (local.get $got))) (local.set $val (struct.get $Box $v (local.get $box))) (if (ref.eq (local.get $val) (global.get $unsafe-undefined)) (then (call $raise-instance-variable-not-found (local.get $sym)) (unreachable))) (if (i32.eqz (ref.eq (local.get $val) (global.get $undefined))) (then (return (local.get $val)))))) (if (result (ref eq)) (ref.eq (local.get $fail) (global.get $missing)) (then (call $raise-instance-variable-not-found (local.get $sym)) (unreachable)) (else (if (result (ref eq)) (ref.test (ref $Procedure) (local.get $fail)) (then (local.set $proc (ref.cast (ref $Procedure) (local.get $fail))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $noargs (array.new $Args (global.get $null) (i32.const 0))) (return_call_ref $ProcedureInvoker (local.get $proc) (local.get $noargs) (local.get $inv))) (else (local.get $fail)))))) ;; Linklets (func $linklet? (type $Prim1) (param $v (ref eq)) ;; value to check (result (ref eq)) (if (result (ref eq)) (ref.test (ref $Linklet) (local.get $v)) (then (global.get $true)) (else (global.get $false)))) (func $linklet-body-reserved-symbol? (type $Prim1) (param $sym (ref eq)) (result (ref eq)) (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-argument-error1 (global.get $symbol:linklet-body-reserved-symbol?) (global.get $string:symbol?) (local.get $sym)) (unreachable))) ,@(for/list ([sym (in-list linklet-body-reserved-symbols)]) `(if (ref.eq (local.get $sym) (global.get ,($ (string->symbol (~a "symbol:" sym))))) (then (return (global.get $true))))) (global.get $false)) (func $linklet-bundle? (type $Prim1) ,@(make-predicate-body '$LinkletBundle)) (func $hash->linklet-bundle (type $Prim1) (param $content (ref eq)) (result (ref eq)) (local $ht (ref $HashEqMutable)) (local $pairs (ref eq)) (local $node (ref eq)) (local $pair (ref $Pair)) (local $entry (ref $Pair)) (local $key (ref eq)) (if (i32.eqz (ref.test (ref $HashEqMutable) (local.get $content))) (then (call $raise-argument-error1 (global.get $symbol:hash->linklet-bundle) (global.get $string:mutable-hasheq?) (local.get $content)) (unreachable))) (local.set $ht (ref.cast (ref $HashEqMutable) (local.get $content))) (local.set $pairs (call $hash->list (local.get $content) (global.get $false))) (local.set $node (local.get $pairs)) (block $done (loop $loop (br_if $done (ref.eq (local.get $node) (global.get $null))) (local.set $pair (ref.cast (ref $Pair) (local.get $node))) (local.set $entry (ref.cast (ref $Pair) (struct.get $Pair $a (local.get $pair)))) (local.set $key (struct.get $Pair $a (local.get $entry))) (if (i32.eqz (i32.or (ref.test (ref $Symbol) (local.get $key)) (ref.eq (call $fixnum? (local.get $key)) (global.get $true)))) (then (call $raise-argument-error1 (global.get $symbol:hash->linklet-bundle) (global.get $string:linklet-bundle-key?) (local.get $key)) (unreachable))) (local.set $node (struct.get $Pair $d (local.get $pair))) (br $loop))) (ref.cast (ref eq) (struct.new $LinkletBundle (i32.const 0) (local.get $ht)))) (func $linklet-bundle->hash (type $Prim1) (param $bundle (ref eq)) (result (ref eq)) (if (i32.eqz (ref.test (ref $LinkletBundle) (local.get $bundle))) (then (call $raise-argument-error1 (global.get $symbol:linklet-bundle->hash) (global.get $string:linklet-bundle?) (local.get $bundle)) (unreachable))) (ref.cast (ref eq) (struct.get $LinkletBundle $content (ref.cast (ref $LinkletBundle) (local.get $bundle))))) (func $linklet-directory? (type $Prim1) ,@(make-predicate-body '$LinkletDirectory)) (func $hash->linklet-directory (type $Prim1) (param $content (ref eq)) (result (ref eq)) (local $ht (ref $HashEqMutable)) (local $pairs (ref eq)) (local $node (ref eq)) (local $pair (ref $Pair)) (local $entry (ref $Pair)) (local $key (ref eq)) (local $val (ref eq)) (if (i32.eqz (ref.test (ref $HashEqMutable) (local.get $content))) (then (call $raise-argument-error1 (global.get $symbol:hash->linklet-directory) (global.get $string:mutable-hasheq?) (local.get $content)) (unreachable))) (local.set $ht (ref.cast (ref $HashEqMutable) (local.get $content))) (local.set $pairs (call $hash->list (local.get $content) (global.get $false))) (local.set $node (local.get $pairs)) (block $done (loop $loop (br_if $done (ref.eq (local.get $node) (global.get $null))) (local.set $pair (ref.cast (ref $Pair) (local.get $node))) (local.set $entry (ref.cast (ref $Pair) (struct.get $Pair $a (local.get $pair)))) (local.set $key (struct.get $Pair $a (local.get $entry))) (local.set $val (struct.get $Pair $d (local.get $entry))) (if (i32.eqz (i32.or (ref.test (ref $Symbol) (local.get $key)) (ref.eq (local.get $key) (global.get $false)))) (then (call $raise-argument-error1 (global.get $symbol:hash->linklet-directory) (global.get $string:linklet-directory-key?) (local.get $key)) (unreachable))) (if (ref.eq (local.get $key) (global.get $false)) (then (if (i32.eqz (ref.test (ref $LinkletBundle) (local.get $val))) (then (call $raise-argument-error1 (global.get $symbol:hash->linklet-directory) (global.get $string:linklet-bundle?) (local.get $val)) (unreachable)))) (else (if (i32.eqz (ref.test (ref $LinkletDirectory) (local.get $val))) (then (call $raise-argument-error1 (global.get $symbol:hash->linklet-directory) (global.get $string:linklet-directory?) (local.get $val)) (unreachable))))) (local.set $node (struct.get $Pair $d (local.get $pair))) (br $loop))) (ref.cast (ref eq) (struct.new $LinkletDirectory (i32.const 0) (local.get $ht)))) (func $linklet-directory->hash (type $Prim1) (param $directory (ref eq)) (result (ref eq)) (if (i32.eqz (ref.test (ref $LinkletDirectory) (local.get $directory))) (then (call $raise-argument-error1 (global.get $symbol:linklet-directory->hash) (global.get $string:linklet-directory?) (local.get $directory)) (unreachable))) (ref.cast (ref eq) (struct.get $LinkletDirectory $content (ref.cast (ref $LinkletDirectory) (local.get $directory))))) (func $make-compiled-linklet (type $Prim4) (param $name (ref eq)) ;; #f or symbol (param $importss (ref eq)) ;; (listof (listof symbol?)) (param $exports (ref eq)) ;; (listof symbol?) (param $proc (ref eq)) ;; procedure (result (ref eq)) (local $imports-node (ref eq)) (local $imports-pair (ref $Pair)) (local $single-imports (ref eq)) (local $inner-node (ref eq)) (local $inner-pair (ref $Pair)) (local $sym (ref eq)) (local $exports-node (ref eq)) (local $exports-pair (ref $Pair)) (local $seen-exports (ref $HashEqMutable)) (local $seen? (ref eq)) (local $compiled-linklet (ref $CompiledLinklet)) ;; Validate name (allow #f for anonymous linklets). (if (i32.eqz (ref.eq (local.get $name) (global.get $false))) (then (if (i32.eqz (ref.test (ref $Symbol) (local.get $name))) (then (call $raise-argument-error1 (global.get $symbol:make-compiled-linklet) (global.get $string:symbol-or-false) (local.get $name)) (unreachable))))) ;; Validate imports: listof listof symbol? (local.set $imports-node (local.get $importss)) (block $imports-done (loop $imports-loop (br_if $imports-done (ref.eq (local.get $imports-node) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $imports-node))) (then (call $raise-argument-error1 (global.get $symbol:make-compiled-linklet) (global.get $string:listof-listof-symbol?) (local.get $importss)) (unreachable))) (local.set $imports-pair (ref.cast (ref $Pair) (local.get $imports-node))) (local.set $single-imports (struct.get $Pair $a (local.get $imports-pair))) (local.set $inner-node (local.get $single-imports)) (block $inner-done (loop $inner-loop (br_if $inner-done (ref.eq (local.get $inner-node) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $inner-node))) (then (call $raise-argument-error1 (global.get $symbol:make-compiled-linklet) (global.get $string:listof-symbol?) (local.get $single-imports)) (unreachable))) (local.set $inner-pair (ref.cast (ref $Pair) (local.get $inner-node))) (local.set $sym (struct.get $Pair $a (local.get $inner-pair))) (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-argument-error1 (global.get $symbol:make-compiled-linklet) (global.get $string:symbol?) (local.get $sym)) (unreachable))) (local.set $inner-node (struct.get $Pair $d (local.get $inner-pair))) (br $inner-loop))) (local.set $imports-node (struct.get $Pair $d (local.get $imports-pair))) (br $imports-loop))) ;; Validate exports: listof symbol? with no duplicate names. (local.set $seen-exports (ref.cast (ref $HashEqMutable) (call $make-empty-hasheq))) (local.set $exports-node (local.get $exports)) (block $exports-done (loop $exports-loop (br_if $exports-done (ref.eq (local.get $exports-node) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $exports-node))) (then (call $raise-argument-error1 (global.get $symbol:make-compiled-linklet) (global.get $string:listof-symbol?) (local.get $exports)) (unreachable))) (local.set $exports-pair (ref.cast (ref $Pair) (local.get $exports-node))) (local.set $sym (struct.get $Pair $a (local.get $exports-pair))) (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-argument-error1 (global.get $symbol:make-compiled-linklet) (global.get $string:symbol?) (local.get $sym)) (unreachable))) (local.set $seen? (call $hasheq-ref (ref.cast (ref eq) (local.get $seen-exports)) (local.get $sym) (global.get $false))) (if (i32.eqz (ref.eq (local.get $seen?) (global.get $false))) (then (call $raise-argument-error1 (global.get $symbol:make-compiled-linklet) (global.get $string:distinct-listof-symbol?) (local.get $exports)) (unreachable))) (call $hasheq-set!/mutable/checked (local.get $seen-exports) (local.get $sym) (global.get $true)) (local.set $exports-node (struct.get $Pair $d (local.get $exports-pair))) (br $exports-loop))) ;; Validate procedure argument. (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $compiled-linklet (struct.new $CompiledLinklet (i32.const 0) (local.get $name) (local.get $importss) (local.get $exports) (local.get $proc))) (ref.cast (ref eq) (local.get $compiled-linklet))) (func $linklet-name (type $Prim1) (param $linklet (ref eq)) (result (ref eq)) (local $plain (ref $Linklet)) (if (i32.eqz (ref.test (ref $Linklet) (local.get $linklet))) (then (call $raise-argument-error1 (global.get $symbol:linklet-name) (global.get $string:linklet?) (local.get $linklet)) (unreachable))) (local.set $plain (ref.cast (ref $Linklet) (local.get $linklet))) (struct.get $Linklet $name (local.get $plain))) (func $linklet-import-variables (type $Prim1) (param $linklet (ref eq)) (result (ref eq)) (local $plain (ref $Linklet)) (local $compiled (ref $CompiledLinklet)) (if (i32.eqz (ref.test (ref $Linklet) (local.get $linklet))) (then (call $raise-argument-error1 (global.get $symbol:linklet-import-variables) (global.get $string:linklet?) (local.get $linklet)) (unreachable))) (local.set $plain (ref.cast (ref $Linklet) (local.get $linklet))) (struct.get $Linklet $importss (local.get $plain))) (func $linklet-export-variables (type $Prim1) (param $linklet (ref eq)) (result (ref eq)) (local $plain (ref $Linklet)) (local $compiled (ref $CompiledLinklet)) (if (i32.eqz (ref.test (ref $Linklet) (local.get $linklet))) (then (call $raise-argument-error1 (global.get $symbol:linklet-export-variables) (global.get $string:linklet?) (local.get $linklet)) (unreachable))) (local.set $plain (ref.cast (ref $Linklet) (local.get $linklet))) (struct.get $Linklet $exports (local.get $plain))) ; version 1 #;(func $instantiate-linklet (type $Prim24) ;; WebRacket currently ignores use-prompt?. (param $linklet (ref eq)) ;; compiled linklet (param $import-instances (ref eq)) ;; (listof instance?) (param $target-instance (ref eq)) ;; optional instance, defaults to #f (param $use-prompt? (ref eq)) ;; optional any/c, defaults to #f (ignored) (result (ref eq)) (local $compiled (ref $CompiledLinklet)) (local $imports-node (ref eq)) (local $imports-node2 (ref eq)) (local $pair (ref $Pair)) (local $value (ref eq)) (local $imports-expected (ref eq)) (local $target (ref $Instance)) (local $proc (ref $Procedure)) (local $invoke (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $result (ref eq)) (local $name (ref eq)) (local $import-count i32) (local $expected-count i32) (local $i i32) (local $new? i32) ;; Optional parameters default to #f. (if (ref.eq (local.get $target-instance) (global.get $missing)) (then (local.set $target-instance (global.get $false)))) (if (ref.eq (local.get $use-prompt?) (global.get $missing)) (then (local.set $use-prompt? (global.get $false)))) ;; Validate linklet argument. (if (i32.eqz (ref.test (ref $Linklet) (local.get $linklet))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:linklet?) (local.get $linklet)) (unreachable))) (if (i32.eqz (ref.test (ref $CompiledLinklet) (local.get $linklet))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:compiled-linklet?) (local.get $linklet)) (unreachable))) (local.set $compiled (ref.cast (ref $CompiledLinklet) (local.get $linklet))) ;; Validate import-instances list and count elements. (local.set $import-count (i32.const 0)) (local.set $imports-node (local.get $import-instances)) (block $imports-done (loop $imports-loop (br_if $imports-done (ref.eq (local.get $imports-node) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $imports-node))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:listof-instance?) (local.get $import-instances)) (unreachable))) (local.set $pair (ref.cast (ref $Pair) (local.get $imports-node))) (local.set $value (struct.get $Pair $a (local.get $pair))) (if (i32.eqz (ref.test (ref $Instance) (local.get $value))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:listof-instance?) (local.get $import-instances)) (unreachable))) (local.set $imports-node (struct.get $Pair $d (local.get $pair))) (local.set $import-count (i32.add (local.get $import-count) (i32.const 1))) (br $imports-loop))) ;; Determine expected number of import instances from the compiled linklet. (local.set $expected-count (i32.const 0)) (local.set $imports-expected (struct.get $CompiledLinklet $importss (local.get $compiled))) (local.set $imports-node2 (local.get $imports-expected)) (block $expected-done (loop $expected-loop (br_if $expected-done (ref.eq (local.get $imports-node2) (global.get $null))) (local.set $pair (ref.cast (ref $Pair) (local.get $imports-node2))) (local.set $imports-node2 (struct.get $Pair $d (local.get $pair))) (local.set $expected-count (i32.add (local.get $expected-count) (i32.const 1))) (br $expected-loop))) (if (i32.ne (local.get $expected-count) (local.get $import-count)) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:instantiate-linklet:import-count) (local.get $import-instances)) (unreachable))) ;; Determine or create the target instance. #;(local.set $target (ref.null $Instance)) (local.set $new? (i32.const 0)) (if (i32.eqz (ref.eq (local.get $target-instance) (global.get $false))) (then (if (i32.eqz (ref.test (ref $Instance) (local.get $target-instance))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:instance-or-false) (local.get $target-instance)) (unreachable))) (local.set $target (ref.cast (ref $Instance) (local.get $target-instance)))) (else (local.set $name (struct.get $CompiledLinklet $name (local.get $compiled))) (local.set $target (ref.cast (ref $Instance) (call $make-instance (local.get $name) (global.get $null)))) (local.set $new? (i32.const 1)))) ;; Prepare arguments for the compiled linklet procedure. (local.set $proc (ref.cast (ref $Procedure) (struct.get $CompiledLinklet $proc (local.get $compiled)))) (local.set $invoke (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.add (local.get $import-count) (i32.const 1)))) (array.set $Args (local.get $args) (i32.const 0) (ref.cast (ref eq) (local.get $target))) (local.set $imports-node (local.get $import-instances)) (local.set $i (i32.const 0)) (block $fill-done (loop $fill (br_if $fill-done (ref.eq (local.get $imports-node) (global.get $null))) (local.set $pair (ref.cast (ref $Pair) (local.get $imports-node))) (local.set $value (struct.get $Pair $a (local.get $pair))) (array.set $Args (local.get $args) (i32.add (local.get $i) (i32.const 1)) (local.get $value)) (local.set $imports-node (struct.get $Pair $d (local.get $pair))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $fill))) (local.set $result (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $invoke))) (if (i32.eqz (local.get $new?)) (then (return (local.get $result))) (else (drop (local.get $result)) (return (ref.cast (ref eq) (local.get $target)))))) ; version 2 (func $instantiate-linklet (type $Prim24) (param $linklet (ref eq)) ;; linklet (param $import-instances (ref eq)) ;; (listof instance) (param $target (ref eq)) ;; optional instance, defaults to #f (param $use-prompt (ref eq)) ;; optional any/c, defaults to #f (ignored) (result (ref eq)) (local $compiled (ref $CompiledLinklet)) (local $imports-node (ref eq)) (local $imports-pair (ref $Pair)) (local $import-value (ref eq)) (local $expected-node (ref eq)) (local $expected-pair (ref $Pair)) (local $expected-imports (ref eq)) (local $expected-import (ref $Pair)) (local $import-symbol (ref eq)) (local $exports-node (ref eq)) (local $exports-pair (ref $Pair)) (local $export-symbol (ref eq)) (local $export-box (ref $Box)) (local $export-val (ref eq)) (local $target-plain (ref $Instance)) (local $target-constants (ref $HashEqMutable)) (local $proc (ref $Procedure)) (local $inv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $result (ref eq)) (local $target-instance (ref eq)) (local $name (ref eq)) (local $actual-count i32) (local $expected-count i32) (local $index i32) (local $return-instance i32) (local.set $actual-count (i32.const 0)) (local.set $expected-count (i32.const 0)) (local.set $target-instance (global.get $false)) (local.set $return-instance (i32.const 0)) (if (i32.eqz (ref.test (ref $Linklet) (local.get $linklet))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:linklet?) (local.get $linklet)) (unreachable))) (if (i32.eqz (ref.test (ref $CompiledLinklet) (local.get $linklet))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:compiled-linklet?) (local.get $linklet)) (unreachable))) (local.set $compiled (ref.cast (ref $CompiledLinklet) (local.get $linklet))) (local.set $imports-node (local.get $import-instances)) (block $imports-done (loop $imports-loop (br_if $imports-done (ref.eq (local.get $imports-node) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $imports-node))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:listof-instance?) (local.get $import-instances)) (unreachable))) (local.set $imports-pair (ref.cast (ref $Pair) (local.get $imports-node))) (local.set $import-value (struct.get $Pair $a (local.get $imports-pair))) (if (i32.eqz (ref.test (ref $Instance) (local.get $import-value))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:listof-instance?) (local.get $import-instances)) (unreachable))) (local.set $actual-count (i32.add (local.get $actual-count) (i32.const 1))) (local.set $imports-node (struct.get $Pair $d (local.get $imports-pair))) (br $imports-loop))) (if (ref.eq (local.get $target) (global.get $missing)) (then (nop)) (else (local.set $target-instance (local.get $target)))) (if (i32.eqz (ref.eq (local.get $target-instance) (global.get $false))) (then (if (i32.eqz (ref.test (ref $Instance) (local.get $target-instance))) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:instance-or-false) (local.get $target-instance)) (unreachable)))) (else (local.set $return-instance (i32.const 1)))) (local.set $expected-node (struct.get $Linklet $importss (local.get $compiled))) (block $expected-done (loop $expected-loop (br_if $expected-done (ref.eq (local.get $expected-node) (global.get $null))) (local.set $imports-pair (ref.cast (ref $Pair) (local.get $expected-node))) (local.set $expected-count (i32.add (local.get $expected-count) (i32.const 1))) (local.set $expected-node (struct.get $Pair $d (local.get $imports-pair))) (br $expected-loop))) (if (i32.ne (local.get $expected-count) (local.get $actual-count)) (then (call $raise-argument-error1 (global.get $symbol:instantiate-linklet) (global.get $string:instantiate-linklet:import-count) (local.get $import-instances)) (unreachable))) ;; Validate declared imports before running the linklet body. (local.set $expected-node (struct.get $Linklet $importss (local.get $compiled))) (local.set $imports-node (local.get $import-instances)) (block $validate-done (loop $validate-loop (br_if $validate-done (ref.eq (local.get $expected-node) (global.get $null))) (local.set $expected-pair (ref.cast (ref $Pair) (local.get $expected-node))) (local.set $imports-pair (ref.cast (ref $Pair) (local.get $imports-node))) (local.set $expected-imports (struct.get $Pair $a (local.get $expected-pair))) (local.set $import-value (struct.get $Pair $a (local.get $imports-pair))) (block $symbols-done (loop $symbols-loop (br_if $symbols-done (ref.eq (local.get $expected-imports) (global.get $null))) (local.set $expected-import (ref.cast (ref $Pair) (local.get $expected-imports))) (local.set $import-symbol (struct.get $Pair $a (local.get $expected-import))) (drop (call $instance-variable-box (local.get $import-value) (local.get $import-symbol) (global.get $false))) (local.set $expected-imports (struct.get $Pair $d (local.get $expected-import))) (br $symbols-loop))) (local.set $expected-node (struct.get $Pair $d (local.get $expected-pair))) (local.set $imports-node (struct.get $Pair $d (local.get $imports-pair))) (br $validate-loop))) (if (i32.eq (local.get $return-instance) (i32.const 1)) (then (local.set $name (struct.get $Linklet $name (local.get $compiled))) (local.set $target-instance (call $make-instance (local.get $name) (global.get $null))))) (local.set $proc (ref.cast (ref $Procedure) (struct.get $CompiledLinklet $proc (local.get $compiled)))) (local.set $inv (struct.get $Procedure $invoke (local.get $proc))) (local.set $args (array.new $Args (global.get $null) (i32.add (local.get $actual-count) (i32.const 1)))) (array.set $Args (local.get $args) (i32.const 0) (local.get $target-instance)) (local.set $index (i32.const 1)) (local.set $imports-node (local.get $import-instances)) (block $fill-done (loop $fill-loop (br_if $fill-done (ref.eq (local.get $imports-node) (global.get $null))) (local.set $imports-pair (ref.cast (ref $Pair) (local.get $imports-node))) (array.set $Args (local.get $args) (local.get $index) (struct.get $Pair $a (local.get $imports-pair))) (local.set $index (i32.add (local.get $index) (i32.const 1))) (local.set $imports-node (struct.get $Pair $d (local.get $imports-pair))) (br $fill-loop))) (local.set $result (call_ref $ProcedureInvoker (local.get $proc) (local.get $args) (local.get $inv))) ;; Exported bindings are constant from the instance API after ;; instantiation, even though the linklet body initializes them. (local.set $target-plain (ref.cast (ref $Instance) (local.get $target-instance))) (local.set $target-constants (struct.get $Instance $constants (local.get $target-plain))) (local.set $exports-node (struct.get $Linklet $exports (local.get $compiled))) (block $exports-done (loop $exports-loop (br_if $exports-done (ref.eq (local.get $exports-node) (global.get $null))) (local.set $exports-pair (ref.cast (ref $Pair) (local.get $exports-node))) (local.set $export-symbol (struct.get $Pair $a (local.get $exports-pair))) (local.set $export-box (ref.cast (ref $Box) (call $instance-variable-box (local.get $target-instance) (local.get $export-symbol) (global.get $true)))) (local.set $export-val (struct.get $Box $v (local.get $export-box))) (if (ref.eq (local.get $export-val) (global.get $undefined)) (then (drop (call $set-box! (ref.cast (ref eq) (local.get $export-box)) (global.get $unsafe-undefined))))) (call $hasheq-set!/mutable/checked (local.get $target-constants) (local.get $export-symbol) (global.get $true)) (local.set $exports-node (struct.get $Pair $d (local.get $exports-pair))) (br $exports-loop))) (if (result (ref eq)) (i32.eq (local.get $return-instance) (i32.const 1)) (then (drop (local.get $result)) (local.get $target-instance)) (else (local.get $result)))) ;; Correlated Syntax (func $ensure-correlated-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $indices (ref eq)) (local.set $existing (global.get $correlated-type)) (if (ref.is_null (local.get $existing)) (then (local.set $indices (call $list-from-range/checked (i32.const 0) (i32.const 7))) (local.set $std (struct.new $StructType (i32.const 0) (ref.cast (ref $Symbol) (global.get $symbol:correlated)) (global.get $false) (i32.const 7) (local.get $indices) (global.get $null) (global.get $null) (ref.cast (ref eq) (call $struct-type-property-table-empty)) (global.get $false) (local.get $indices) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:correlated)))) (global.set $correlated-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) (func $ensure-correlated-empty-props (result (ref eq)) (local $props (ref eq)) (local.set $props (global.get $correlated-empty-props)) (if (ref.eq (local.get $props) (global.get $undefined)) (then (local.set $props (call $make-hash (global.get $missing))) (global.set $correlated-empty-props (local.get $props)))) (local.get $props)) (func $correlated/make (param $source (ref eq)) (param $line (ref eq)) (param $column (ref eq)) (param $position (ref eq)) (param $span (ref eq)) (param $e (ref eq)) (param $props (ref eq)) (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-correlated-type)) (local.set $fields (array.new_fixed $Array 7 (local.get $source) (local.get $line) (local.get $column) (local.get $position) (local.get $span) (local.get $e) (local.get $props))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) (func $raise-argument-error:correlated-expected (param $who (ref eq)) (param $got (ref eq)) (call $raise-argument-error1 (local.get $who) (global.get $string:correlated?) (local.get $got))) (func $correlated-build (param $who (ref eq)) (param $source (ref eq)) (param $line (ref eq)) (param $column (ref eq)) (param $position (ref eq)) (param $span (ref eq)) (param $e (ref eq)) (param $props (ref eq)) (result (ref eq)) (local $line-checked (ref eq)) (local $column-checked (ref eq)) (local $position-checked (ref eq)) (local $span-checked (ref eq)) (local $props-checked (ref eq)) ;; Initialize non-defaultable locals (local.set $props-checked (global.get $false)) ;; Check arguments (local.set $line-checked (call $srcloc-check-positive (local.get $who) (local.get $line))) (local.set $column-checked (call $srcloc-check-nonnegative (local.get $who) (local.get $column))) (local.set $position-checked (call $srcloc-check-positive (local.get $who) (local.get $position))) (local.set $span-checked (call $srcloc-check-nonnegative (local.get $who) (local.get $span))) (if (ref.eq (local.get $props) (global.get $missing)) (then (local.set $props-checked (call $ensure-syntax-empty-props))) (else (if (ref.eq (call $hash? (local.get $props)) (global.get $true)) (then (local.set $props-checked (local.get $props))) (else (call $raise-argument-error1 (local.get $who) (global.get $string:hash?) (local.get $props)) (unreachable))))) ;; Construct correlated syntax (call $correlated/make (local.get $source) (local.get $line-checked) (local.get $column-checked) (local.get $position-checked) (local.get $span-checked) (local.get $e) (local.get $props-checked))) (func $correlated (param $source (ref eq)) (param $line (ref eq)) (param $column (ref eq)) (param $position (ref eq)) (param $span (ref eq)) (param $e (ref eq)) (param $props (ref eq)) (result (ref eq)) (call $correlated-build (global.get $symbol:correlated) (local.get $source) (local.get $line) (local.get $column) (local.get $position) (local.get $span) (local.get $e) (local.get $props))) (func $make-correlated (param $source (ref eq)) (param $line (ref eq)) (param $column (ref eq)) (param $position (ref eq)) (param $span (ref eq)) (param $e (ref eq)) (param $props (ref eq)) (result (ref eq)) (call $correlated-build (global.get $symbol:make-correlated) (local.get $source) (local.get $line) (local.get $column) (local.get $position) (local.get $span) (local.get $e) (local.get $props))) (func $correlated? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $struct (ref $Struct)) (local $type (ref eq)) (local $std (ref $StructType)) (local $ok i32) (local.set $std (call $ensure-correlated-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $ok (call $struct-type-is-a?/i32 (local.get $type) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $correlated-unwrap (param $who (ref eq)) (param $v (ref eq)) (result (ref $Array)) (local $struct (ref $Struct)) (local $type (ref eq)) (local $std (ref $StructType)) (local $ok i32) (local.set $std (call $ensure-correlated-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error:correlated-expected (local.get $who) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $ok (call $struct-type-is-a?/i32 (local.get $type) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error:correlated-expected (local.get $who) (local.get $v)) (unreachable))) (struct.get $Struct $fields (local.get $struct))) (func $correlated-source (type $Prim1) (param $crlt (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-source) (local.get $crlt))) (array.get $Array (local.get $fields) (i32.const 0))) (func $correlated-line (type $Prim1) (param $crlt (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-line) (local.get $crlt))) (array.get $Array (local.get $fields) (i32.const 1))) (func $correlated-column (type $Prim1) (param $crlt (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-column) (local.get $crlt))) (array.get $Array (local.get $fields) (i32.const 2))) (func $correlated-position (type $Prim1) (param $crlt (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-position) (local.get $crlt))) (array.get $Array (local.get $fields) (i32.const 3))) (func $correlated-span (type $Prim1) (param $crlt (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-span) (local.get $crlt))) (array.get $Array (local.get $fields) (i32.const 4))) (func $correlated-e (type $Prim1) (param $crlt (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-e) (local.get $crlt))) (array.get $Array (local.get $fields) (i32.const 5))) (func $correlated-props (param $crlt (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-props) (local.get $crlt))) (array.get $Array (local.get $fields) (i32.const 6))) (func $correlated->datum/convert (param $who (ref eq)) (param $v (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $e (ref eq)) (local $pair (ref $Pair)) (local $car-raw (ref eq)) (local $cdr-raw (ref eq)) (local $car-datum (ref eq)) (local $cdr-datum (ref eq)) (if (ref.eq (call $correlated? (local.get $v)) (global.get $true)) (then (local.set $fields (call $correlated-unwrap (local.get $who) (local.get $v))) (local.set $e (array.get $Array (local.get $fields) (i32.const 5))) (return (call $correlated->datum/convert (local.get $who) (local.get $e))))) (if (ref.test (ref $Pair) (local.get $v)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $v))) (local.set $car-raw (struct.get $Pair $a (local.get $pair))) (local.set $cdr-raw (struct.get $Pair $d (local.get $pair))) (local.set $car-datum (call $correlated->datum/convert (local.get $who) (local.get $car-raw))) (local.set $cdr-datum (call $correlated->datum/convert (local.get $who) (local.get $cdr-raw))) (return (call $cons (local.get $car-datum) (local.get $cdr-datum))))) (if (ref.test (ref $Vector) (local.get $v)) (then ;; Match Racket's behavior: recurse through pairs, but preserve ;; vector elements as-is, including correlated values. (return (local.get $v)))) (if (ref.test (ref $Box) (local.get $v)) (then ;; Match Racket's behavior: boxes are preserved like vectors, ;; including any correlated value inside the box. (return (local.get $v)))) (local.get $v)) (func $correlated->datum (type $Prim1) (param $v (ref eq)) (result (ref eq)) (call $correlated->datum/convert (global.get $symbol:correlated->datum) (local.get $v))) (func $datum->correlated (type $Prim13) (param $v (ref eq)) (param $srcloc (ref eq)) ; optional, default = #f (param $prop (ref eq)) ; optional, default = #f (result (ref eq)) (local $who (ref eq)) (local $source (ref eq)) (local $line (ref eq)) (local $column (ref eq)) (local $position (ref eq)) (local $span (ref eq)) (local $srcloc-val (ref eq)) (local $tmp (ref eq)) (local $vec (ref $Vector)) (local $arr (ref $Array)) (local $len i32) (local $props (ref eq)) (local $prop-val (ref eq)) (local.set $who (global.get $symbol:datum->correlated)) (local.set $source (global.get $false)) (local.set $line (global.get $false)) (local.set $column (global.get $false)) (local.set $position (global.get $false)) (local.set $span (global.get $false)) (local.set $props (global.get $missing)) ; $srcloc-val is optional with default value #f (local.set $srcloc-val (local.get $srcloc)) (if (ref.eq (local.get $srcloc-val) (global.get $missing)) (then (local.set $srcloc-val (global.get $false)))) (block $srcloc-done (if (ref.eq (local.get $srcloc-val) (global.get $false)) (then (br $srcloc-done))) (if (ref.eq (call $correlated? (local.get $srcloc-val)) (global.get $true)) (then (local.set $source (call $correlated-source (local.get $srcloc-val))) (local.set $tmp (call $correlated-line (local.get $srcloc-val))) (local.set $line (call $srcloc-check-positive (local.get $who) (local.get $tmp))) (local.set $tmp (call $correlated-column (local.get $srcloc-val))) (local.set $column (call $srcloc-check-nonnegative (local.get $who) (local.get $tmp))) (local.set $tmp (call $correlated-position (local.get $srcloc-val))) (local.set $position (call $srcloc-check-positive (local.get $who) (local.get $tmp))) (local.set $tmp (call $correlated-span (local.get $srcloc-val))) (local.set $span (call $srcloc-check-nonnegative (local.get $who) (local.get $tmp))) (br $srcloc-done))) (if (ref.eq (call $srcloc? (local.get $srcloc-val)) (global.get $true)) (then (local.set $source (call $srcloc-source (local.get $srcloc-val))) (local.set $tmp (call $srcloc-line (local.get $srcloc-val))) (local.set $line (call $srcloc-check-positive (local.get $who) (local.get $tmp))) (local.set $tmp (call $srcloc-column (local.get $srcloc-val))) (local.set $column (call $srcloc-check-nonnegative (local.get $who) (local.get $tmp))) (local.set $tmp (call $srcloc-position (local.get $srcloc-val))) (local.set $position (call $srcloc-check-positive (local.get $who) (local.get $tmp))) (local.set $tmp (call $srcloc-span (local.get $srcloc-val))) (local.set $span (call $srcloc-check-nonnegative (local.get $who) (local.get $tmp))) (br $srcloc-done))) (if (ref.test (ref $Vector) (local.get $srcloc-val)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $srcloc-val))) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $len (array.len (local.get $arr))) (if (i32.ne (local.get $len) (i32.const 5)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:datum->correlated-srcloc) (local.get $srcloc-val)) (unreachable))) (local.set $source (array.get $Array (local.get $arr) (i32.const 0))) (local.set $tmp (array.get $Array (local.get $arr) (i32.const 1))) (local.set $line (call $srcloc-check-positive (local.get $who) (local.get $tmp))) (local.set $tmp (array.get $Array (local.get $arr) (i32.const 2))) (local.set $column (call $srcloc-check-nonnegative (local.get $who) (local.get $tmp))) (local.set $tmp (array.get $Array (local.get $arr) (i32.const 3))) (local.set $position (call $srcloc-check-positive (local.get $who) (local.get $tmp))) (local.set $tmp (array.get $Array (local.get $arr) (i32.const 4))) (local.set $span (call $srcloc-check-nonnegative (local.get $who) (local.get $tmp))) (br $srcloc-done))) (if (ref.eq (call $list? (local.get $srcloc-val)) (global.get $true)) (then (local.set $srcloc-val (call $list->vector (local.get $srcloc-val))) (if (i32.eqz (ref.test (ref $Vector) (local.get $srcloc-val))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:datum->correlated-srcloc) (local.get $srcloc-val)) (unreachable))) (local.set $vec (ref.cast (ref $Vector) (local.get $srcloc-val))) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $len (array.len (local.get $arr))) (if (i32.ne (local.get $len) (i32.const 5)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:datum->correlated-srcloc) (local.get $srcloc-val)) (unreachable))) (local.set $source (array.get $Array (local.get $arr) (i32.const 0))) (local.set $tmp (array.get $Array (local.get $arr) (i32.const 1))) (local.set $line (call $srcloc-check-positive (local.get $who) (local.get $tmp))) (local.set $tmp (array.get $Array (local.get $arr) (i32.const 2))) (local.set $column (call $srcloc-check-nonnegative (local.get $who) (local.get $tmp))) (local.set $tmp (array.get $Array (local.get $arr) (i32.const 3))) (local.set $position (call $srcloc-check-positive (local.get $who) (local.get $tmp))) (local.set $tmp (array.get $Array (local.get $arr) (i32.const 4))) (local.set $span (call $srcloc-check-nonnegative (local.get $who) (local.get $tmp))) (br $srcloc-done))) (call $raise-argument-error1 (local.get $who) (global.get $string:datum->correlated-srcloc) (local.get $srcloc-val)) (unreachable)) ; $prop-val is optional with default value #f (local.set $prop-val (local.get $prop)) (if (ref.eq (local.get $prop-val) (global.get $missing)) (then (local.set $prop-val (global.get $false)))) (if (ref.eq (local.get $prop-val) (global.get $false)) (then (local.set $props (global.get $missing))) (else (if (ref.eq (call $correlated? (local.get $prop-val)) (global.get $true)) (then (local.set $props (call $correlated-props (local.get $prop-val)))) (else (call $raise-argument-error1 (local.get $who) (global.get $string:correlated-or-false) (local.get $prop-val)) (unreachable))))) ;; `datum->correlated` mirrors `datum->syntax`: optional ;; arguments are validated, but correlated input is unchanged. (if (ref.eq (call $correlated? (local.get $v)) (global.get $true)) (then (return (local.get $v)))) (call $correlated-build (local.get $who) (local.get $source) (local.get $line) (local.get $column) (local.get $position) (local.get $span) (local.get $v) (local.get $props))) (func $correlated-property (type $Prim23) (param $crlt (ref eq)) (param $key (ref eq)) (param $val (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $props (ref eq)) (local $source (ref eq)) (local $line (ref eq)) (local $column (ref eq)) (local $position (ref eq)) (local $span (ref eq)) (local $e (ref eq)) (local $val-arg (ref eq)) (local $new-props (ref eq)) (local $pairs (ref eq)) (local $list (ref eq)) (local $pair (ref $Pair)) (local $entry (ref $Pair)) (local $entry-key (ref eq)) (local $entry-val (ref eq)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-property) (local.get $crlt))) (local.set $source (array.get $Array (local.get $fields) (i32.const 0))) (local.set $line (array.get $Array (local.get $fields) (i32.const 1))) (local.set $column (array.get $Array (local.get $fields) (i32.const 2))) (local.set $position (array.get $Array (local.get $fields) (i32.const 3))) (local.set $span (array.get $Array (local.get $fields) (i32.const 4))) (local.set $e (array.get $Array (local.get $fields) (i32.const 5))) (local.set $props (array.get $Array (local.get $fields) (i32.const 6))) (if (ref.eq (local.get $val) (global.get $missing)) (then (return (call $hash-ref (local.get $props) (local.get $key) (global.get $false))))) (local.set $val-arg (local.get $val)) (local.set $new-props (call $make-hash (global.get $missing))) (local.set $pairs (call $hash->list (local.get $props) (global.get $false))) (local.set $list (local.get $pairs)) (block $done (loop $loop (br_if $done (ref.eq (local.get $list) (global.get $null))) (local.set $pair (ref.cast (ref $Pair) (local.get $list))) (local.set $entry (ref.cast (ref $Pair) (struct.get $Pair $a (local.get $pair)))) (local.set $list (struct.get $Pair $d (local.get $pair))) (local.set $entry-key (struct.get $Pair $a (local.get $entry))) (local.set $entry-val (struct.get $Pair $d (local.get $entry))) ; Correlated-property keys are identity-sensitive. (if (ref.eq (local.get $entry-key) (local.get $key)) (then (br $loop))) (drop (call $hash-set! (local.get $new-props) (local.get $entry-key) (local.get $entry-val))) (br $loop))) (drop (call $hash-set! (local.get $new-props) (local.get $key) (local.get $val-arg))) (call $correlated/make (local.get $source) (local.get $line) (local.get $column) (local.get $position) (local.get $span) (local.get $e) (local.get $new-props))) (func $correlated-property-symbol-keys (type $Prim1) (param $crlt (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $props (ref eq)) (local $pairs (ref eq)) (local $list (ref eq)) (local $pair (ref $Pair)) (local $entry (ref $Pair)) (local $key (ref eq)) (local $acc (ref eq)) (local.set $fields (call $correlated-unwrap (global.get $symbol:correlated-property-symbol-keys) (local.get $crlt))) (local.set $props (array.get $Array (local.get $fields) (i32.const 6))) (local.set $pairs (call $hash->list (local.get $props) (global.get $false))) (local.set $list (local.get $pairs)) (local.set $acc (global.get $null)) (block $done (loop $loop (br_if $done (ref.eq (local.get $list) (global.get $null))) (local.set $pair (ref.cast (ref $Pair) (local.get $list))) (local.set $entry (ref.cast (ref $Pair) (struct.get $Pair $a (local.get $pair)))) (local.set $list (struct.get $Pair $d (local.get $pair))) (local.set $key (struct.get $Pair $a (local.get $entry))) (if (ref.test (ref $Symbol) (local.get $key)) (then (local.set $acc (struct.new $Pair (i32.const 0) (local.get $key) (local.get $acc))))) (br $loop))) (call $reverse (local.get $acc))) ;;; ;;; 17. UNSAFE OPERATIONS ;;; ;; 17.1 Unsafe Numeric Operations (func $unsafe-fx+/2 (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $y (ref eq)) ;; y must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) ; the tag was chosen, so we could do this: (ref.i31 (i32.add (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))))) (func $unsafe-fx+ (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $sum i32) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $sum (i32.const 0)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $sum (i32.add (local.get $sum) (i31.get_s (ref.cast (ref i31) (local.get $v))))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (ref.i31 (local.get $sum))) (func $unsafe-fx- (type $Prim>=1) (param $a1 (ref eq)) (param $rest (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $acc i32) (local.set $xs (local.get $rest)) (local.set $acc (i31.get_s (ref.cast (ref i31) (local.get $a1)))) (if (result (ref eq)) (ref.eq (local.get $xs) (global.get $null)) (then (ref.i31 (i32.sub (i32.const 0) (local.get $acc)))) (else (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $acc (i32.sub (local.get $acc) (i31.get_s (ref.cast (ref i31) (local.get $v))))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (ref.i31 (local.get $acc))))) (func $unsafe-fx* (type $Prim>=0) (param $xs0 (ref eq)) (result (ref eq)) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $prod i32) (local.set $xs (if (result (ref eq)) (ref.test (ref $Args) (local.get $xs0)) (then (call $rest-arguments->list (ref.cast (ref $Args) (local.get $xs0)) (i32.const 0))) (else (local.get $xs0)))) (local.set $prod (i32.const 2)) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $prod (i32.mul (local.get $prod) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $v))) (i32.const 1)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (ref.i31 (local.get $prod))) (func $unsafe-fx= (type $Prim2) (param $x (ref eq)) (param $y (ref eq)) (result (ref eq)) (if (result (ref eq)) (i32.eq (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))) (then (global.get $true)) (else (global.get $false)))) (func $unsafe-fx< (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $y (ref eq)) ;; y must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a boolean (if (result (ref eq)) (i32.lt_s (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))) (then (global.get $true)) (else (global.get $false)))) (func $unsafe-fx> (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $y (ref eq)) ;; y must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a boolean (if (result (ref eq)) (i32.gt_s (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))) (then (global.get $true)) (else (global.get $false)))) (func $unsafe-fx<= (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $y (ref eq)) ;; y must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a boolean (if (result (ref eq)) (i32.le_s (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))) (then (global.get $true)) (else (global.get $false)))) (func $unsafe-fx>= (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $y (ref eq)) ;; y must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a boolean (if (result (ref eq)) (i32.ge_s (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))) (then (global.get $true)) (else (global.get $false)))) (func $unsafe-fxmin (type $Prim>=1) (param $x0 (ref eq)) ;; first fixnum argument (i31 with lsb = 0) (param $xs (ref eq)) ;; xs is a list of remaining fixnum arguments (result (ref eq)) ;; result is a fixnum (i31) (local $node (ref $Pair)) (local $fx (ref eq)) (local $best (ref eq)) (local $best-i32 i32) (local $fx-i32 i32) (local.set $best (local.get $x0)) (local.set $best-i32 (i31.get_s (ref.cast (ref i31) (local.get $best)))) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $fx (struct.get $Pair $a (local.get $node))) (local.set $fx-i32 (i31.get_s (ref.cast (ref i31) (local.get $fx)))) (if (i32.lt_s (local.get $fx-i32) (local.get $best-i32)) (then (local.set $best (local.get $fx)) (local.set $best-i32 (local.get $fx-i32)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $best)) (func $unsafe-fxmax (type $Prim>=1) (param $x0 (ref eq)) ;; first fixnum argument (i31 with lsb = 0) (param $xs (ref eq)) ;; xs is a list of remaining fixnum arguments (result (ref eq)) ;; result is a fixnum (i31) (local $node (ref $Pair)) (local $fx (ref eq)) (local $best (ref eq)) (local $best-i32 i32) (local $fx-i32 i32) (local.set $best (local.get $x0)) (local.set $best-i32 (i31.get_s (ref.cast (ref i31) (local.get $best)))) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $fx (struct.get $Pair $a (local.get $node))) (local.set $fx-i32 (i31.get_s (ref.cast (ref i31) (local.get $fx)))) (if (i32.gt_s (local.get $fx-i32) (local.get $best-i32)) (then (local.set $best (local.get $fx)) (local.set $best-i32 (local.get $fx-i32)))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $best)) (func $unsafe-fxand (type $Prim>=1) (param $a1 (ref eq)) ;; first fixnum argument (i31 with lsb = 0) (param $rest (ref eq)) ;; list of remaining fixnum arguments (result (ref eq)) ;; result is a fixnum (i31) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $acc i32) (local.set $xs (local.get $rest)) (local.set $acc (i31.get_s (ref.cast (ref i31) (local.get $a1)))) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $acc (i32.and (local.get $acc) (i31.get_s (ref.cast (ref i31) (local.get $v))))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (ref.i31 (local.get $acc))) (func $unsafe-fxior (type $Prim>=1) (param $a1 (ref eq)) ;; first fixnum argument (i31 with lsb = 0) (param $rest (ref eq)) ;; list of remaining fixnum arguments (result (ref eq)) ;; result is a fixnum (i31) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $acc i32) (local.set $xs (local.get $rest)) (local.set $acc (i31.get_s (ref.cast (ref i31) (local.get $a1)))) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $acc (i32.or (local.get $acc) (i31.get_s (ref.cast (ref i31) (local.get $v))))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (ref.i31 (local.get $acc))) (func $unsafe-fxxor (type $Prim>=1) (param $a1 (ref eq)) ;; first fixnum argument (i31 with lsb = 0) (param $rest (ref eq)) ;; list of remaining fixnum arguments (result (ref eq)) ;; result is a fixnum (i31) (local $xs (ref eq)) (local $node (ref $Pair)) (local $v (ref eq)) (local $acc i32) (local.set $xs (local.get $rest)) (local.set $acc (i31.get_s (ref.cast (ref i31) (local.get $a1)))) (block $done (loop $loop (br_if $done (ref.eq (local.get $xs) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $xs))) (local.set $v (struct.get $Pair $a (local.get $node))) (local.set $acc (i32.xor (local.get $acc) (i31.get_s (ref.cast (ref i31) (local.get $v))))) (local.set $xs (struct.get $Pair $d (local.get $node))) (br $loop))) (ref.i31 (local.get $acc))) (func $unsafe-fxnot (type $Prim1) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.shl (i32.xor (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $x))) (i32.const 1)) (i32.const -1)) (i32.const 1)))) (func $unsafe-fxpopcount (type $Prim1) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.shl (i32.popcnt (i31.get_u (ref.cast (ref i31) (local.get $x)))) (i32.const 1)))) (func $unsafe-fxpopcount32 (type $Prim1) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.shl (i32.popcnt (i31.get_u (ref.cast (ref i31) (local.get $x)))) (i32.const 1)))) (func $unsafe-fxpopcount16 (type $Prim1) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.shl (i32.popcnt (i32.and (i31.get_u (ref.cast (ref i31) (local.get $x))) (i32.const 65535))) (i32.const 1)))) (func $unsafe-fxlshift (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $k (ref eq)) ;; k must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.shl (i31.get_s (ref.cast (ref i31) (local.get $x))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $k))) (i32.const 1))))) (func $unsafe-fx+/wraparound (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $y (ref eq)) ;; y must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.add (i31.get_s (ref.cast (ref i31) (local.get $x))) (i31.get_s (ref.cast (ref i31) (local.get $y)))))) (func $unsafe-fx-/wraparound (type $Prim>=1) (param $a1 (ref eq)) ;; first argument (fixnum) (param $rest (ref eq)) ;; optional second argument; defaults to 0 when absent (result (ref eq)) ;; result is a fixnum (i31) (local $a (ref eq)) (local $b (ref eq)) (local $node (ref $Pair)) ;; Eager init so locals are definitely assigned before any possible get. (local.set $a (ref.i31 (i32.const 0))) (local.set $b (local.get $a1)) (if (ref.eq (local.get $rest) (global.get $null)) (then (local.set $a (ref.i31 (i32.const 0))) (local.set $b (local.get $a1))) (else (local.set $node (ref.cast (ref $Pair) (local.get $rest))) (local.set $a (local.get $a1)) (local.set $b (struct.get $Pair $a (local.get $node))))) (ref.i31 (i32.sub (i31.get_s (ref.cast (ref i31) (local.get $a))) (i31.get_s (ref.cast (ref i31) (local.get $b)))))) (func $unsafe-fx*/wraparound (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $y (ref eq)) ;; y must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.mul (i31.get_s (ref.cast (ref i31) (local.get $x))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $y))) (i32.const 1))))) (func $unsafe-fxlshift/wraparound (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $k (ref eq)) ;; k must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.shl (i31.get_s (ref.cast (ref i31) (local.get $x))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $k))) (i32.const 1))))) (func $unsafe-fxrshift (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $k (ref eq)) ;; k must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $x))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $k))) (i32.const 1))))) (func $unsafe-fxrshift/logical (type $Prim2) (param $x (ref eq)) ;; x must be a fixnum (i31 with lsb = 0) (param $k (ref eq)) ;; k must be a fixnum (i31 with lsb = 0) (result (ref eq)) ;; result is a fixnum (i31) (ref.i31 (i32.shl (i32.shr_u (i32.shr_s (i31.get_u (ref.cast (ref i31) (local.get $x))) (i32.const 1)) (i32.shr_s (i31.get_u (ref.cast (ref i31) (local.get $k))) (i32.const 1))) (i32.const 1)))) ;; 17.2 Unsafe Character Operations ;; 17.3 Unsafe Compound-Data Operations (func $unsafe-car (type $Prim1) (param $v (ref eq)) (result (ref eq)) (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $v)))) (func $unsafe-cdr (type $Prim1) (param $v (ref eq)) (result (ref eq)) (struct.get $Pair $d (ref.cast (ref $Pair) (local.get $v)))) (func $unsafe-struct-ref (type $Prim2) (param $v (ref eq)) (param $k (ref eq)) (result (ref eq)) (array.get $Array (struct.get $Struct $fields (ref.cast (ref $Struct) (local.get $v))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $k))) (i32.const 1)))) (func $unsafe-vector-ref (type $Prim2) (param $v (ref eq)) (param $k (ref eq)) (result (ref eq)) (array.get $Array (struct.get $Vector $arr (ref.cast (ref $Vector) (local.get $v))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $k))) (i32.const 1)))) (func $unsafe-vector*-ref (type $Prim2) (param $v (ref eq)) (param $k (ref eq)) (result (ref eq)) (array.get $Array (struct.get $Vector $arr (ref.cast (ref $Vector) (local.get $v))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $k))) (i32.const 1)))) (func $unsafe-vector-length (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (ref.i31 (i32.shl (array.len (struct.get $Vector $arr (local.get $vec))) (i32.const 1)))) ;; Note the `unsafe-vector*-...` variants do not work on impersonators. ;; (the `unsafe-vector-...` variants do) (func $unsafe-vector*-length (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $vec (ref $Vector)) (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (ref.i31 (i32.shl (array.len (struct.get $Vector $arr (local.get $vec))) (i32.const 1)))) (func $unsafe-vector-set! (type $Prim3) (param $vec (ref eq)) (param $idx (ref eq)) (param $val (ref eq)) (result (ref eq)) (array.set $Array (struct.get $Vector $arr (ref.cast (ref $Vector) (local.get $vec))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $idx))) (i32.const 1)) (local.get $val)) (global.get $void)) (func $unsafe-vector*-set! (type $Prim3) (param $vec (ref eq)) (param $idx (ref eq)) (param $val (ref eq)) (result (ref eq)) (array.set $Array (struct.get $Vector $arr (ref.cast (ref $Vector) (local.get $vec))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $idx))) (i32.const 1)) (local.get $val)) (global.get $void)) (func $unsafe-struct-set! (param $struct (ref eq)) (param $idx (ref eq)) (param $val (ref eq)) (result (ref eq)) (array.set $Array (struct.get $Struct $fields (ref.cast (ref $Struct) (local.get $struct))) (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $idx))) (i32.const 1)) (local.get $val)) (global.get $void)) (func $unsafe-string-length (type $Prim1) (param $s (ref eq)) (result (ref eq)) (local $str (ref $String)) (local.set $str (ref.cast (ref $String) (local.get $s))) (ref.i31 (i32.shl (array.len (struct.get $String $codepoints (local.get $str))) (i32.const 1)))) (func $unsafe-bytes-length (type $Prim1) (param $b (ref eq)) (result (ref eq)) (local $bs (ref $Bytes)) (local.set $bs (ref.cast (ref $Bytes) (local.get $b))) (ref.i31 (i32.shl (array.len (struct.get $Bytes $bs (local.get $bs))) (i32.const 1)))) (func $unsafe-bytes-ref (type $Prim2) (param $bstr (ref eq)) ;; bytes (param $k (ref eq)) ;; fixnum index (result (ref eq)) (local $bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $idx i32) (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $idx (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $k))) (i32.const 1))) (ref.i31 (i32.shl (call $i8array-ref (local.get $arr) (local.get $idx)) (i32.const 1)))) (func $unsafe-bytes-set! (type $Prim3) (param $bstr (ref eq)) ;; bytes (param $k (ref eq)) ;; fixnum index (param $b (ref eq)) ;; byte (result (ref eq)) (local $bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $idx i32) (local $bv i32) (local.set $bs (ref.cast (ref $Bytes) (local.get $bstr))) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $idx (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $k))) (i32.const 1))) (local.set $bv (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $b))) (i32.const 1))) (call $i8array-set! (local.get $arr) (local.get $idx) (local.get $bv)) (global.get $void)) ;; 17.4 Unsafe Extflonum Operations ;; 17.5 Unsafe Impersonators and Chaperones ;; 17.6 Unsafe Assertions ;; 17.7 Unsafe Undefined ;;; ;;; FORMATTING - DISPLAY MODE ;;; (func $i8array->string (param $arr (ref $I8Array)) (result (ref $String)) (call $bytes->string/utf-8/checked (call $i8array->immutable-bytes (local.get $arr)))) (func $raise-format/display:unknown-datatype (unreachable)) (func $raise-format/display:got-boxed (unreachable)) (func $raise-format/display:got-box (unreachable)) (func $raise-format/display:got-values (unreachable)) (func $format/display (param $v (ref eq)) (result (ref $String)) (local $s (ref $String)) (local $i31 (ref i31)) (local $n i32) (local $struct (ref $Struct)) (local $struct-type (ref $StructType)) (local $cw-sentinel (ref eq)) (local $cw-name (ref $Symbol)) (local $cw-val (ref eq)) (local $cw-proc (ref $Procedure)) (local $hash (ref $Hash)) ;; --- Case: fixnum --- (if (ref.test (ref i31) (local.get $v)) (then (local.set $i31 (ref.cast (ref i31) (local.get $v))) (local.set $n (i31.get_u (local.get $i31))) (if (i32.eqz (i32.and (local.get $n) (i32.const 1))) ;; check lsb = 0 (then (return (ref.cast (ref $String) (call $number->string (local.get $v) ,(Imm 10)))))))) ;; --- Case: null --- (if (ref.eq (local.get $v) (global.get $null)) (then (return (ref.cast (ref $String) (global.get $string:null))))) ;; --- Case: true --- (if (ref.eq (local.get $v) (global.get $true)) (then (return (ref.cast (ref $String) (global.get $string:hash-t))))) ;; --- Case: false --- (if (ref.eq (local.get $v) (global.get $false)) (then (return (ref.cast (ref $String) (global.get $string:hash-f))))) ;; --- Case: void --- (if (ref.eq (local.get $v) (global.get $void)) (then (return (ref.cast (ref $String) (global.get $string:void))))) ;; --- Case: eof --- (if (ref.eq (local.get $v) (global.get $eof)) (then (return (ref.cast (ref $String) (global.get $string:eof))))) ;; --- Case: missing --- (if (ref.eq (local.get $v) (global.get $missing)) (then (return (ref.cast (ref $String) (global.get $string:missing))))) ;; --- Case: undefined --- (if (ref.eq (local.get $v) (global.get $undefined)) (then (return (ref.cast (ref $String) (global.get $string:undefined))))) ;; --- Case: unsafe-undefined --- (if (ref.eq (local.get $v) (global.get $unsafe-undefined)) (then (return (ref.cast (ref $String) (global.get $string:unsafe-undefined))))) ;; --- Case: syntax --- (if (ref.eq (call $syntax? (local.get $v)) (global.get $true)) (then (return (call $format/display:syntax (local.get $v))))) ;; --- Case: struct (must appear before procedure) --- (if (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $struct-type (struct.get $Struct $type (local.get $struct))) (local.set $cw-name (ref.cast (ref $Symbol) (global.get $symbol:prop:custom-write))) (local.set $cw-sentinel (call $cons (global.get $false) (global.get $false))) (local.set $cw-val (call $struct-type-property-lookup-by-name (local.get $struct-type) (local.get $cw-name) (local.get $cw-sentinel))) (if (i32.eqz (ref.eq (local.get $cw-val) (local.get $cw-sentinel))) (then (if (ref.test (ref $Procedure) (local.get $cw-val)) (then (local.set $cw-proc (ref.cast (ref $Procedure) (local.get $cw-val))) (return (call $format/display:struct/custom-write (local.get $struct) (local.get $cw-proc)))))) (else (return (call $format/display:struct (local.get $struct))))))) ;; --- Case: closure --- (if (ref.test (ref $Closure) (local.get $v)) (then (return (call $format/display:procedure (ref.cast (ref $Procedure) (local.get $v)))))) ;; --- Case: case-lambda --- (if (ref.test (ref $CaseClosure) (local.get $v)) (then (return (call $format/display:procedure (ref.cast (ref $Procedure) (local.get $v)))))) ;; --- Case: primitive --- (if (ref.test (ref $PrimitiveProcedure) (local.get $v)) (then (return (call $format/display:primitive-procedure (ref.cast (ref $PrimitiveProcedure) (local.get $v)))))) ;; --- Case: procedure --- (if (ref.test (ref $Procedure) (local.get $v)) (then (return (call $format/display:procedure (ref.cast (ref $Procedure) (local.get $v)))))) ;; --- Case: string --- (if (ref.test (ref $String) (local.get $v)) (then (return (ref.cast (ref $String) (local.get $v))))) ;; --- Case: bytes --- (if (ref.test (ref $Bytes) (local.get $v)) (then (return (call $format/display:bytes (ref.cast (ref $Bytes) (local.get $v)))))) ;; --- Case: pair --- (if (ref.test (ref $Pair) (local.get $v)) (then (return (call $format/display:pair (ref.cast (ref $Pair) (local.get $v)))))) ;; --- Case: vector --- (if (ref.test (ref $Vector) (local.get $v)) (then (return (call $format/display:vector (ref.cast (ref $Vector) (local.get $v)))))) ;; --- Case: box --- (if (ref.test (ref $Box) (local.get $v)) (then (return (call $format/display:box (ref.cast (ref $Box) (local.get $v)))))) ;; --- Case: symbol --- (if (ref.test (ref $Symbol) (local.get $v)) (then (return (call $format/display:symbol (ref.cast (ref $Symbol) (local.get $v)))))) ;; --- Case: keyword --- (if (ref.test (ref $Keyword) (local.get $v)) (then (return (call $format/display:keyword (ref.cast (ref $Keyword) (local.get $v)))))) ;; --- Case: flonum --- (if (ref.test (ref $Flonum) (local.get $v)) (then (return (call $format/display:flonum (ref.cast (ref $Flonum) (local.get $v)))))) ;; --- Case: values --- (if (ref.test (ref $Values) (local.get $v)) (then (return (call $format/display:values (ref.cast (ref $Values) (local.get $v)))))) ;; --- Case: external --- (if (ref.test (ref $External) (local.get $v)) (then (return (call $format/display:external (ref.cast (ref $External) (local.get $v)))))) ;; --- Case: char --- (if (ref.test (ref i31) (local.get $v)) (then (local.set $i31 (ref.cast (ref i31) (local.get $v))) (local.set $n (i31.get_u (local.get $i31))) (if (i32.eq (i32.and (local.get $n) (i32.const ,char-mask)) (i32.const ,char-tag)) (then (return (call $format/display:char (local.get $v))))))) ;; --- Case: hash table --- (if (ref.test (ref $Hash) (local.get $v)) (then (local.set $hash (ref.cast (ref $Hash) (local.get $v))) (return (call $format/display:hash (local.get $hash))))) ;; --- Case: namespace --- (if (ref.test (ref $Namespace) (local.get $v)) (then (return (call $format/display:namespace (ref.cast (ref $Namespace) (local.get $v)))))) ;; --- Case: variable-reference --- (if (ref.test (ref $VariableReference) (local.get $v)) (then (return (call $format/display:variable-reference (ref.cast (ref $VariableReference) (local.get $v)))))) ;; --- Case: struct-type-property --- (if (ref.test (ref $StructTypeProperty) (local.get $v)) (then (return (call $format/display:struct-type-property (ref.cast (ref $StructTypeProperty) (local.get $v)))))) ;; --- Case: linklet --- (if (ref.test (ref $Linklet) (local.get $v)) (then (return (ref.cast (ref $String) (global.get $string:linklet))))) ;; --- Case: instance --- (if (ref.test (ref $Instance) (local.get $v)) (then (return (ref.cast (ref $String) (global.get $string:instance))))) ;; --- Case: unquoted-printing-string --- (if (ref.test (ref $UnquotedPrintingString) (local.get $v)) (then (return (call $format/display:unquoted-printing-string (ref.cast (ref $UnquotedPrintingString) (local.get $v)))))) ;; --- Internal data types --- ;; These shouldn't leak to the outside, but nice to know if it happens. ;; --- Case: boxed --- (shouldn't happen) (if (ref.test (ref $Boxed) (local.get $v)) #;(then (call $raise-format/display:got-boxed)) (then (return (call $format/display:boxed (ref.cast (ref $Boxed) (local.get $v)))))) ;; --- Case: path --- (if (ref.test (ref $Path) (local.get $v)) (then (return (call $format/display:path (ref.cast (ref $Path) (local.get $v)))))) ;; --- Fallback --- (call $raise-format/display:unknown-datatype) (unreachable)) (func $format/display:hash (param $ht (ref $Hash)) (result (ref $String)) (local $alist (ref eq)) (local $alist-str (ref $String)) (local $prefix (ref $String)) (local $out (ref $GrowableArray)) ;; Convert hash entries to an association list. (local.set $alist (call $hash->list (ref.cast (ref eq) (local.get $ht)) (global.get $false))) (local.set $alist-str (call $format/display:assoc (local.get $alist))) ;; Determine hash prefix based on table equivalence. (local.set $prefix (ref.cast (ref $String) (global.get $string:hash-hash))) (if (ref.test (ref $HashEq) (local.get $ht)) (then (local.set $prefix (ref.cast (ref $String) (global.get $string:hash-hasheq))))) (if (ref.test (ref $HashEqv) (local.get $ht)) (then (local.set $prefix (ref.cast (ref $String) (global.get $string:hash-hasheqv))))) (if (ref.test (ref $HashEqualAlways) (local.get $ht)) (then (local.set $prefix (ref.cast (ref $String) (global.get $string:hash-hashalw))))) ;; Prefix + association list content -> final string. (local.set $out (call $make-growable-array (i32.const 2))) (call $growable-array-add! (local.get $out) (local.get $prefix)) (call $growable-array-add! (local.get $out) (local.get $alist-str)) (call $growable-array-of-strings->string (local.get $out))) (func $format/display:variable-reference (param $v (ref eq)) (result (ref $String)) (ref.cast (ref $String) (global.get $string:hash-variable-reference))) (func $format/display:namespace (param $ns (ref $Namespace)) (result (ref $String)) (local $name (ref eq)) (local $out (ref $GrowableArray)) ;; Extract namespace name (local.set $name (struct.get $Namespace $name (local.get $ns))) ;; If no name, return constant (if (result (ref $String)) (ref.eq (local.get $name) (global.get $false)) (then (ref.cast (ref $String) (global.get $string:namespace))) (else ;; Build "#" (local.set $out (call $make-growable-array (i32.const 3))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:hash-less-namespace-colon))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (local.get $name))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:->))) (call $growable-array-of-strings->string (local.get $out))))) (func $format/display:struct-type-property (param $prop (ref $StructTypeProperty)) (result (ref $String)) (local $ga (ref $GrowableArray)) (local $name (ref $Symbol)) (local.set $name (struct.get $StructTypeProperty $name (local.get $prop))) (local.set $ga (call $make-growable-array (i32.const 3))) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:hash-less-struct-type-property-colon))) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (call $symbol->string (local.get $name)))) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:->))) (call $array-of-strings->string (call $growable-array->array (local.get $ga)))) (func $format/display:external (param $v (ref $External)) (result (ref $String)) (if (result (ref $String)) (ref.is_null (struct.get $External $v (local.get $v))) (then (ref.cast (ref $String) (global.get $string:external-null))) (else (ref.cast (ref $String) (global.get $string:external))))) (func $format/display:procedure ; # (param $v (ref eq)) (result (ref $String)) (local $p (ref $Procedure)) (local $name (ref eq)) ;; $false or $Symbol #;(local $arity-fx (ref eq)) ;; fixnum (i31) #;(local $arity-str (ref $String)) (local $mask i32) (local $mask-str (ref $String)) (local $ga (ref $GrowableArray)) ;; Step 1: type check and cast (if (i32.eqz (ref.test (ref $Procedure) (local.get $v))) (then (call $raise-argument-error:procedure-expected (local.get $v)))) (local.set $p (ref.cast (ref $Procedure) (local.get $v))) ;; Step 2: extract fields (local.set $name (struct.get $Procedure $name (local.get $p))) #;(local.set $arity-fx (struct.get $Procedure $arity (local.get $p))) ;; Step 3: convert arity to string #;(local.set $arity-str (call $number->string (local.get $arity-fx) (global.get $false))) ;; Step 4: get mask and convert to string ; (local.set $mask (call $procedure-arity-mask/checked/i32 (local.get $p))) ; (local.set $mask-str (call $i32->string (local.get $mask))) ;; Step 5: build output (local.set $ga (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:hash-less-procedure-colon))) (call $growable-array-add! (local.get $ga) (if (result (ref eq)) (ref.eq (local.get $name) (global.get $false)) (then (ref.cast (ref $String) (global.get $string:unknown))) (else (ref.cast (ref $String) (call $symbol->string (local.get $name)))))) #;(call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:colon))) #;(call $growable-array-add! (local.get $ga) (local.get $arity-str)) #;(call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:colon))) #;(call $growable-array-add! (local.get $ga) (local.get $mask-str)) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:->))) ;; Step 5: convert to string (call $array-of-strings->string (call $growable-array->array (local.get $ga)))) (func $format/display:primitive-procedure (param $v (ref eq)) (result (ref $String)) (local $p (ref $PrimitiveProcedure)) (local $name (ref eq)) ;; $false or $Symbol (local $arity-str (ref $String)) (local $mask i32) (local $mask-str (ref $String)) (local $ga (ref $GrowableArray)) ;; Step 1: type check and cast (if (i32.eqz (ref.test (ref $PrimitiveProcedure) (local.get $v))) (then (call $raise-argument-error:primitive-procedure-expected))) (local.set $p (ref.cast (ref $PrimitiveProcedure) (local.get $v))) ;; Step 2: extract fields (local.set $name (struct.get $PrimitiveProcedure $name (local.get $p))) ;; Step 3: convert arity to string (local.set $arity-str (call $procedure-arity->expected-string (local.get $p))) ;; Step 4: get mask and convert to string (local.set $mask (call $procedure-arity-mask/checked/i32 (local.get $p))) (local.set $mask-str (call $i32->string (local.get $mask))) ;; Step 5: build output (local.set $ga (call $make-growable-array (i32.const 5))) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:hash-less-primitive-colon))) (call $growable-array-add! (local.get $ga) (if (result (ref eq)) (ref.eq (local.get $name) (global.get $false)) (then (ref.cast (ref $String) (global.get $string:unknown))) (else (call $symbol->string (local.get $name))))) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:colon))) (call $growable-array-add! (local.get $ga) (local.get $arity-str)) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:colon))) (call $growable-array-add! (local.get $ga) (local.get $mask-str)) (call $growable-array-add! (local.get $ga) (ref.cast (ref $String) (global.get $string:->))) ;; Step 6: convert to string (call $array-of-strings->string (call $growable-array->array (local.get $ga)))) (func $format/display:symbol (param $v (ref eq)) (result (ref $String)) (call $format/display (call $symbol->string (local.get $v)))) (func $format/display:keyword (param $v (ref eq)) (result (ref $String)) ;; Fail early if not a keyword (and make the path unreachable). (if (i32.eqz (ref.test (ref $Keyword) (local.get $v))) (then (call $raise-keyword-expected (local.get $v)) (unreachable))) ;; "#:" ++ underlying name string (call $string-append/2 (global.get $string:hash-colon) (struct.get $Keyword $str (ref.cast (ref $Keyword) (local.get $v))))) (func $format/display:bytes (param $bs (ref $Bytes)) (result (ref $String)) (local $arr (ref $I8Array)) (local $len i32) (local $i i32) (local $byte i32) (local $esc i32) (local $s (ref $String)) (local $out (ref $GrowableArray)) ;; Extract raw byte array and its length (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (array.len (local.get $arr))) ;; Allocate result buffer. ;; Format is #"", so we need 2 chars for the quotes, plus worst-case ;; room to escape *every* byte (e.g., "\ooo" or "\n"), hence len * 4 slack. (local.set $out (call $make-growable-array (i32.add (i32.const 2) (i32.mul (local.get $len) (i32.const 4))))) ;; Emit the bytes display prefix: #" (call $growable-array-add! (local.get $out) (global.get $string:bytes-prefix)) ;; Iterate bytes and append escaped fragments (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) ;; Read next byte as unsigned 0..255 (kept in i32) (local.set $byte (call $i8array-ref (local.get $arr) (local.get $i))) ;; If ASCII (<= 127) and graphic or blank, emit directly (with escapings for \" and \\). ;; Otherwise, try a named special escape (e.g., \n, \t); if none, fall back to octal. (if (i32.and (i32.le_u (local.get $byte) (i32.const 127)) (i32.or (call $is-graphic (local.get $byte)) (call $is-blank (local.get $byte)))) (then ;; Handle the two ASCII characters that must be escaped inside quotes: ;; 92 = '\' backslash, 34 = '"' double quote (if (i32.eq (local.get $byte) (i32.const 92)) ;; '\' (then ;; Emit "\\" to represent a single backslash (call $growable-array-add! (local.get $out) (global.get $string:backslash)) (call $growable-array-add! (local.get $out) (global.get $string:backslash))) (else (if (i32.eq (local.get $byte) (i32.const 34)) ;; '"' (then ;; Emit \" to include a literal quote inside the string (call $growable-array-add! (local.get $out) (global.get $string:backslash)) (call $growable-array-add! (local.get $out) (global.get $string:double-quote))) (else ;; Safe printable ASCII → emit 1-char string directly (local.set $s (call $make-string/checked (i32.const 1) (local.get $byte))) (call $growable-array-add! (local.get $out) (local.get $s))))))) (else ;; Non-printable or non-ASCII: ;; Try special escapes first (returns 0 if none, else codepoint of escaped letter). (local.set $esc (call $get-special-escape (local.get $byte))) (if (i32.ne (local.get $esc) (i32.const 0)) (then ;; Emit backslash + special escape letter (e.g., "\n") (call $growable-array-add! (local.get $out) (global.get $string:backslash)) (local.set $s (call $make-string/checked (i32.const 1) (local.get $esc))) (call $growable-array-add! (local.get $out) (local.get $s))) (else ;; Fallback: octal escape like "\ooo" (call $growable-array-add! (local.get $out) (global.get $string:backslash)) (local.set $s (call $make-oct-string (local.get $byte))) (call $growable-array-add! (local.get $out) (local.get $s)))))) ;; Advance to next byte (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) ;; Close the display with a trailing quote and materialize the final string (call $growable-array-add! (local.get $out) (global.get $string:double-quote)) (call $growable-array-of-strings->string (local.get $out))) (func $raise-format/display:pair:expected-pair (unreachable)) (func $format/display:pair (param $v (ref eq)) (result (ref $String)) (local $out (ref $GrowableArray)) (local $stack (ref $GrowableArray)) (local $car (ref eq)) (local $cdr (ref $Pair)) (local $tail (ref eq)) (local $str (ref $String)) ;; Initialize buffers (local.set $out (call $make-growable-array (i32.const 8))) (local.set $stack (call $make-growable-array (i32.const 8))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:open-paren))) (call $growable-array-add! (local.get $stack) (local.get $v)) (block $done (loop $walk (br_if $done (i32.eqz (call $growable-array-count (local.get $stack)))) (local.set $tail (call $growable-array-remove-last! (local.get $stack))) (if (ref.test (ref $Pair) (local.get $tail)) (then (local.set $cdr (ref.cast (ref $Pair) (local.get $tail))) (local.set $car (struct.get $Pair $a (local.get $cdr))) (local.set $tail (struct.get $Pair $d (local.get $cdr))) ;; Format car (local.set $str (call $format/display (local.get $car))) (call $growable-array-add! (local.get $out) (local.get $str)) ;; Handle cdr (if (ref.test (ref $Pair) (local.get $tail)) (then (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:space))) (call $growable-array-add! (local.get $stack) (local.get $tail))) (else (if (ref.eq (local.get $tail) (global.get $null)) (then) ;; proper list: done (else ;; improper list: emit ". cdr" (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:space-dot-space))) (local.set $str (call $format/display (local.get $tail))) (call $growable-array-add! (local.get $out) (local.get $str))))))) (else (call $raise-format/display:pair:expected-pair) (unreachable))) (br $walk))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:close-paren))) (call $growable-array-of-strings->string (local.get $out))) (func $raise-format/display:vector:expected-vector (unreachable)) (func $format/display:vector (param $v (ref $Vector)) (result (ref $String)) (local $out (ref $GrowableArray)) (local $arr (ref $Array)) (local $len i32) (local $i i32) (local $elem (ref eq)) (local $str (ref $String)) ;; Extract internal array and its length (local.set $arr (struct.get $Vector $arr (local.get $v))) (local.set $len (array.len (local.get $arr))) ;; Allocate result buffer (local.set $out (call $make-growable-array (i32.const 8))) ;; Emit "#(" (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:vector-prefix))) ;; Add formatted elements with spaces (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $elem (array.get $Array (local.get $arr) (local.get $i))) (local.set $str (call $format/display (local.get $elem))) (call $growable-array-add! (local.get $out) (local.get $str)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (if (i32.lt_u (local.get $i) (local.get $len)) (then (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:space))))) (br $loop))) ;; Emit ")" (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:close-paren))) ;; Combine and return (call $growable-array-of-strings->string (local.get $out))) ; Note: the `boxed` is internal to the compiler, so the following ; is strictly for debug purposes (func $format/display:boxed (param $b (ref $Boxed)) (result (ref $String)) (local $val (ref eq)) (local $val-str (ref $String)) (local $out (ref $GrowableArray)) ;; Extract value (local.set $val (struct.get $Boxed $v (local.get $b))) ;; Format the value (local.set $val-str (call $format/display (local.get $val))) ;; Allocate a growable array (local.set $out (call $make-growable-array (i32.const 3))) ;; Append "#" (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:->))) ;; Combine and return (call $growable-array-of-strings->string (local.get $out))) (func $format/display:box (param $b (ref $Box)) (result (ref $String)) (local $val (ref eq)) (local $val-str (ref $String)) (local $out (ref $GrowableArray)) ;; Get the boxed value (local.set $val (struct.get $Box $v (local.get $b))) ;; Format the value (local.set $val-str (call $format/display (local.get $val))) ;; Allocate a growable array (local.set $out (call $make-growable-array (i32.const 3))) ;; Append "#&" (call $growable-array-add! (local.get $out) (global.get $string:box-prefix)) ;; "#&" ;; Append formatted value (call $growable-array-add! (local.get $out) (local.get $val-str)) ;; Combine and return (call $growable-array-of-strings->string (local.get $out))) (func $raise-format/display:vector:expected-values (unreachable)) (func $format/display:values (param $arr (ref $Values)) (result (ref $String)) (local $out (ref $GrowableArray)) (local $len i32) (local $i i32) (local $elem (ref eq)) (local $str (ref $String)) ;; Extract length (local.set $len (array.len (local.get $arr))) ;; Allocate result buffer (local.set $out (call $make-growable-array (i32.const 8))) ;; Emit "(values" (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:values-prefix))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:space))) ;; Add formatted elements with spaces (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $elem (array.get $Array (local.get $arr) (local.get $i))) (local.set $str (call $format/display (local.get $elem))) (call $growable-array-add! (local.get $out) (local.get $str)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (if (i32.lt_u (local.get $i) (local.get $len)) (then (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:space))))) (br $loop))) ;; Emit ")" (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:close-paren))) ;; Combine and return (call $growable-array-of-strings->string (local.get $out))) (func $format/display:path (param $p (ref $Path)) (result (ref $String)) (local $conv (ref eq)) (local $bytes (ref $Bytes)) (local $prefix (ref $String)) (local $out (ref $GrowableArray)) ;; Extract path convention and byte representation. (local.set $conv (struct.get $Path $convention (local.get $p))) (local.set $bytes (struct.get $Path $bytes (local.get $p))) ;; Choose an appropriate prefix based on the path convention. (local.set $prefix (ref.cast (ref $String) (global.get $string:hash-less-path-colon))) (if (ref.eq (local.get $conv) (global.get $system-path-convention)) (then (nop)) (else (if (ref.eq (local.get $conv) (global.get $symbol:unix)) (then (local.set $prefix (ref.cast (ref $String) (global.get $string:hash-less-unix-path-colon)))) (else (if (ref.eq (local.get $conv) (global.get $symbol:windows)) (then (local.set $prefix (ref.cast (ref $String) (global.get $string:hash-less-windows-path-colon))))))))) ;; Render the unreadable path descriptor. (local.set $out (call $make-growable-array (i32.const 3))) (call $growable-array-add! (local.get $out) (local.get $prefix)) (call $growable-array-add! (local.get $out) (call $bytes->string/utf-8/checked (local.get $bytes))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:->))) (call $growable-array-of-strings->string (local.get $out))) (func $format/display:assoc (param $alist (ref eq)) ;; association list (list of pairs) (result (ref $String)) (local $out (ref $GrowableArray)) (local $cursor (ref eq)) (local $cell (ref $Pair)) (local $entry (ref eq)) (local $entry-str (ref $String)) (local $first? i32) ;; Empty association list prints as "()". (if (ref.eq (local.get $alist) (global.get $null)) (then (return (ref.cast (ref $String) (global.get $string:null))))) ;; Prepare builders and iteration state. (local.set $out (call $make-growable-array (i32.const 8))) (local.set $cursor (local.get $alist)) (local.set $first? (i32.const 1)) ;; Emit opening parenthesis. (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:open-paren))) ;; Iterate through the list, verifying every element is a pair ;; and printing it using the generic formatter. (block $done (loop $walk (br_if $done (ref.eq (local.get $cursor) (global.get $null))) (if (i32.eqz (ref.test (ref $Pair) (local.get $cursor))) (then (call $raise-format/display:pair:expected-pair) (unreachable))) (local.set $cell (ref.cast (ref $Pair) (local.get $cursor))) (local.set $entry (struct.get $Pair $a (local.get $cell))) (if (i32.eqz (ref.test (ref $Pair) (local.get $entry))) (then (call $raise-format/display:pair:expected-pair) (unreachable))) (if (i32.eqz (local.get $first?)) (then (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:space)))) (else (local.set $first? (i32.const 0)))) (local.set $entry-str (call $format/display (local.get $entry))) (call $growable-array-add! (local.get $out) (local.get $entry-str)) (local.set $cursor (struct.get $Pair $d (local.get $cell))) (br $walk))) ;; Close and materialize the final string. (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:close-paren))) (call $growable-array-of-strings->string (local.get $out))) (func $format/display:unquoted-printing-string (param $ups (ref $UnquotedPrintingString)) (result (ref $String)) (ref.cast (ref $String) (struct.get $UnquotedPrintingString $value (local.get $ups)))) (func $format/display:syntax (param $stx (ref eq)) (result (ref $String)) (local $out (ref $GrowableArray)) (local $datum (ref eq)) (local.set $out (call $make-growable-array (i32.const 3))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:syntax-open))) (local.set $datum (call $syntax-e (local.get $stx))) (call $growable-array-add! (local.get $out) (call $format/display (local.get $datum))) (call $growable-array-add! (local.get $out) (ref.cast (ref $String) (global.get $string:syntax-close))) (call $growable-array-of-strings->string (local.get $out))) ; Note: This uses the write conventions instead of display. (func $format/display:char (param $v (ref eq)) (result (ref $String)) (local $cp i32) ;; codepoint (local $s (ref $String)) ;; temporary string ;; Check if input is a character immediate (if (i32.or (i32.eqz (ref.test (ref i31) (local.get $v))) (i32.ne (i32.and (i31.get_u (ref.cast (ref i31) (local.get $v))) (i32.const ,char-mask)) (i32.const ,char-tag))) (then (call $raise-check-char (local.get $v)))) ;; Decode character (local.set $cp (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $v))) (i32.const ,char-shift))) ;; Special character names (if (i32.eq (local.get $cp) (i32.const 10)) ;; newline (then (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash)) (ref.cast (ref $String) (global.get $string:word-newline)))))) (if (i32.eq (local.get $cp) (i32.const 13)) ;; return (then (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash)) (ref.cast (ref $String) (global.get $string:word-return)))))) (if (i32.eq (local.get $cp) (i32.const 9)) ;; tab (then (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash)) (ref.cast (ref $String) (global.get $string:word-tab)))))) (if (i32.eq (local.get $cp) (i32.const 8)) ;; backspace (then (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash)) (ref.cast (ref $String) (global.get $string:word-backspace)))))) (if (i32.eq (local.get $cp) (i32.const 127)) ;; rubout (then (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash)) (ref.cast (ref $String) (global.get $string:word-rubout)))))) (if (i32.eq (local.get $cp) (i32.const 32)) ;; space (then (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash)) (ref.cast (ref $String) (global.get $string:word-space)))))) (if (i32.eq (local.get $cp) (i32.const 0)) ;; nul (then (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash)) (ref.cast (ref $String) (global.get $string:word-nul)))))) ;; Printable graphic character (if (call $is-graphic (local.get $cp)) (then (local.set $s (call $make-string/checked (i32.const 1) (local.get $cp))) (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash)) (local.get $s))))) ;; Fallback for non-printable or out-of-range characters ;; Fallback: #\uXXXX and #\UXXXXXX (if (i32.le_u (local.get $cp) (i32.const 65535)) ;; ≤ 0xFFFF (then (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash-u)) (call $make-hex-string (local.get $cp) (i32.const 4))))) ;; Else use #\UXXXXXX (else (return (call $string-append/2 (ref.cast (ref $String) (global.get $string:hash-backslash-U)) (call $make-hex-string (local.get $cp) (i32.const 6)))))) (unreachable)) (func $make-oct-string (param $n i32) (result (ref $String)) (call $number->string:convert (local.get $n) (i32.const 8) (i32.const 3))) (func $make-hex-string (param $n i32) ;; input number (param $digits i32) ;; minimum number of hex digits (result (ref $String)) (local $raw (ref $String)) (local $len i32) (local $pad i32) ;; Step 1: Convert number to hex string (unpadded) (local.set $raw (call $number->string:convert (local.get $n) (i32.const 16) ;; radix = 16 (i32.const 8))) ;; allow up to 8 chars for safety ;; Step 2: Compute padding = digits - string-length(raw) (local.set $len (array.len (struct.get $String $codepoints (local.get $raw)))) (local.set $pad (select (i32.sub (local.get $digits) (local.get $len)) (i32.const 0) (i32.gt_s (local.get $digits) (local.get $len)))) ;; Step 3: If padding is zero, return raw (if (i32.eqz (local.get $pad)) (then (return (local.get $raw)))) ;; Step 4: Make padding string of '0's and append (return (call $string-append/2 (call $make-string/checked (local.get $pad) (i32.const 48)) ;; 48 = '0' (local.get $raw)))) ;;; ;;; Formatting ;;; (func $str-number (param $n (ref eq)) (result (ref $Bytes)) ;; Converts a fixnum (with LSB = 0) to a UTF-8 encoded byte string. (local $i31 (ref i31)) (local $v i32) (local $abs i32) (local $neg i32) (local $tmp i32) (local $i i32) (local $j i32) (local $len i32) (local $buf (ref $I8Array)) (local $out (ref $I8Array)) ;; 1. Cast and unbox fixnum (assume it has LSB=0) (local.set $i31 (ref.cast (ref i31) (local.get $n))) (local.set $v (i32.shr_s (i31.get_s (local.get $i31)) (i32.const 1))) ;; shift to remove tag bit ;; 2. Compute abs and sign (local.set $neg (i32.lt_s (local.get $v) (i32.const 0))) (local.set $abs (select (i32.sub (i32.const 0) (local.get $v)) (local.get $v) (local.get $neg))) ;; 3. Special case: 0 (if (i32.eqz (local.get $abs)) (then (return (struct.new $Bytes (i32.const 0) (i32.const 1) (array.new $I8Array (i32.const 48) (i32.const 1)))))) ;; ASCII '0' ;; 4. Allocate temporary buffer (max 11 digits) (local.set $buf (array.new_default $I8Array (i32.const 11))) (local.set $i (i32.const 11)) ;; 5. Extract digits (in reverse) (block $done (loop $loop (br_if $done (i32.eqz (local.get $abs))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (local.set $tmp (i32.rem_u (local.get $abs) (i32.const 10))) (array.set $I8Array (local.get $buf) (local.get $i) (i32.add (local.get $tmp) (i32.const 48))) ;; ASCII '0' + digit (local.set $abs (i32.div_u (local.get $abs) (i32.const 10))) (br $loop))) ;; 6. Add minus sign if needed (if (local.get $neg) (then (local.set $i (i32.sub (local.get $i) (i32.const 1))) (array.set $I8Array (local.get $buf) (local.get $i) (i32.const 45)))) ;; ASCII '-' ;; 7. Allocate output and copy from temp (local.set $len (i32.sub (i32.const 11) (local.get $i))) (local.set $out (array.new_default $I8Array (local.get $len))) (local.set $j (i32.const 0)) (block $done (loop $copy (br_if $done (i32.ge_u (local.get $j) (local.get $len))) (array.set $I8Array (local.get $out) (local.get $j) (array.get_u $I8Array (local.get $buf) (i32.add (local.get $i) (local.get $j)))) (local.set $j (i32.add (local.get $j) (i32.const 1))) (br $copy))) ;; 8. Wrap in $Bytes struct (struct.new $Bytes (i32.const 0) ;; hash (i32.const 1) ;; immutable (local.get $out))) ;; ---------------------------------------------------------------------------- ;; Racket-style (str-bytes v opt-mode) in Wasm GC ;; ;; This module implements a function `$str-bytes` that converts a string `v` ;; into a byte string representation based on a given mode, following Racket's ;; conventions for `write`, `print`, and `display`. ;; ;; Assumed existing definitions: ;; (type $Bytes (array (mut i8))) ;; (type $String (array (mut i32))) ;; An array of Unicode code points ;; (type (sub (ref i31)) (ref eq)) ;; For fixnums ;; ---------------------------------------------------------------------------- ;;;; ------------------------------------------------------------------------ ;;;; Helper Functions ;;;; ------------------------------------------------------------------------ ;;; (func $is-blank char) -> bool ;;; A simplified check for blank characters: space (32) and tab (9). (func $is-blank (param $char i32) (result i32) (i32.or (i32.eq (local.get $char) (i32.const 32)) ;; #\space (i32.eq (local.get $char) (i32.const 9)))) ;; #\tab ;;; (func $is-graphic char) -> bool ;;; A simplified check for graphic characters. This is an approximation ;;; of Racket's `char-graphic?`. It treats characters as graphic if they ;;; are not C0 control characters (U+00-U+1F), not DEL (U+7F). (func $is-graphic (param $char i32) (result i32) (i32.and (i32.ge_u (local.get $char) (i32.const 32)) (i32.ne (local.get $char) (i32.const 127)))) ;;; (func $get-special-escape char) -> escape_char_or_0 ;;; Checks if a character has a simple one-letter escape sequence. ;;; Returns the escape character's ASCII value (e.g., 'a' for BEL) or 0. (func $get-special-escape (param $char i32) (result i32) (block $result (result i32) (if (i32.eq (local.get $char) (i32.const 7)) (then (return (i32.const 97)))) ;; \a BEL (if (i32.eq (local.get $char) (i32.const 8)) (then (return (i32.const 98)))) ;; \b BS (if (i32.eq (local.get $char) (i32.const 9)) (then (return (i32.const 116)))) ;; \t TAB (if (i32.eq (local.get $char) (i32.const 10)) (then (return (i32.const 110)))) ;; \n LF (if (i32.eq (local.get $char) (i32.const 11)) (then (return (i32.const 118)))) ;; \v VT (if (i32.eq (local.get $char) (i32.const 12)) (then (return (i32.const 102)))) ;; \f FF (if (i32.eq (local.get $char) (i32.const 13)) (then (return (i32.const 114)))) ;; \r CR (if (i32.eq (local.get $char) (i32.const 27)) (then (return (i32.const 101)))) ;; \e ESC (i32.const 0))) ;; No special escape ;;; (func $utf8-size char) -> byte_count ;;; Calculates the number of bytes required to represent a Unicode ;;; code point in UTF-8. (func $utf8-size (param $char i32) (result i32) (if (result i32) (i32.le_u (local.get $char) (i32.const 0x7f)) (then (i32.const 1)) (else (if (result i32) (i32.le_u (local.get $char) (i32.const 0x7ff)) (then (i32.const 2)) (else (if (result i32) (i32.le_u (local.get $char) (i32.const 0xffff)) (then (i32.const 3)) (else (i32.const 4)))))))) ;;; (func $write-utf8 buf idx char) -> new_idx ;;; Encodes a Unicode code point as UTF-8 and writes it into the buffer. ;;; Returns the updated buffer index. (func $write-utf8 (param $buf (ref $Bytes)) (param $idx i32) (param $char i32) (result i32) (local $arr (ref $I8Array)) ;; Extract raw array from $Bytes struct (local.set $arr (struct.get $Bytes 2 (local.get $buf))) ;; field 2 = $bs ;; Encode UTF-8 based on code point range (if (i32.le_u (local.get $char) (i32.const 0x7f)) (then (array.set $I8Array (local.get $arr) (local.get $idx) (local.get $char)) (return (i32.add (local.get $idx) (i32.const 1))))) (if (i32.le_u (local.get $char) (i32.const 0x7ff)) (then (array.set $I8Array (local.get $arr) (local.get $idx) (i32.or (i32.shr_u (local.get $char) (i32.const 6)) (i32.const 0xc0))) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 1)) (i32.or (i32.and (local.get $char) (i32.const 0x3f)) (i32.const 0x80))) (return (i32.add (local.get $idx) (i32.const 2))))) (if (i32.le_u (local.get $char) (i32.const 0xffff)) (then (array.set $I8Array (local.get $arr) (local.get $idx) (i32.or (i32.shr_u (local.get $char) (i32.const 12)) (i32.const 0xe0))) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 1)) (i32.or (i32.and (i32.shr_u (local.get $char) (i32.const 6)) (i32.const 0x3f)) (i32.const 0x80))) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 2)) (i32.or (i32.and (local.get $char) (i32.const 0x3f)) (i32.const 0x80))) (return (i32.add (local.get $idx) (i32.const 3))))) (if (i32.le_u (local.get $char) (i32.const 0x10ffff)) (then (array.set $I8Array (local.get $arr) (local.get $idx) (i32.or (i32.shr_u (local.get $char) (i32.const 18)) (i32.const 0xf0))) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 1)) (i32.or (i32.and (i32.shr_u (local.get $char) (i32.const 12)) (i32.const 0x3f)) (i32.const 0x80))) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 2)) (i32.or (i32.and (i32.shr_u (local.get $char) (i32.const 6)) (i32.const 0x3f)) (i32.const 0x80))) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 3)) (i32.or (i32.and (local.get $char) (i32.const 0x3f)) (i32.const 0x80))) (return (i32.add (local.get $idx) (i32.const 4))))) ;; Invalid input — return unchanged index (local.get $idx)) ;; Should not happen for valid Unicode ;;; (func $write-hex buf idx val digits) ;;; Writes the lower `digits` of `val` as hexadecimal characters into the buffer. (func $write-hex (param $buf (ref $Bytes)) (param $idx i32) (param $val i32) (param $digits i32) (local $i i32) (local $shift i32) (local $nibble i32) (local $char i32) (local $write-pos i32) (local $arr (ref $I8Array)) ;; Extract the raw array from the $Bytes struct (local.set $arr (struct.get $Bytes 2 (local.get $buf))) (local.set $i (i32.sub (local.get $digits) (i32.const 1))) (loop $hex-loop (if (i32.ge_s (local.get $i) (i32.const 0)) (then ;; Compute nibble from val (local.set $shift (i32.shl (local.get $i) (i32.const 2))) ;; same as *4 (local.set $nibble (i32.and (i32.shr_u (local.get $val) (local.get $shift)) (i32.const 0xF))) ;; Convert nibble to ASCII (local.set $char (if (result i32) (i32.lt_s (local.get $nibble) (i32.const 10)) (then (i32.add (local.get $nibble) (i32.const 48))) ;; '0'..'9' (else (i32.add (local.get $nibble) (i32.const 87))))) ;; 'a'..'f' ;; Compute write index and store (local.set $write-pos (i32.add (local.get $idx) (i32.sub (i32.sub (local.get $digits) (local.get $i)) (i32.const 1)))) (array.set $I8Array (local.get $arr) (local.get $write-pos) (local.get $char)) ;; Continue loop (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $hex-loop))))) ;;;; ------------------------------------------------------------------------ ;;;; Mode-Specific Implementations ;;;; ------------------------------------------------------------------------ ;;; Converts a string to its 'display' representation (raw UTF-8 bytes). (func $string->bytes:display (param $str (ref $String)) (result (ref $Bytes)) (local $out-buf (ref $Bytes)) (local $arr (ref $I8Array)) (local $str-len i32) (local $i i32) (local $char i32) (local $total-size i32) (local $idx i32) ;; Pass 1: calculate required UTF-8 buffer size (local.set $str-len (struct.get $String 1 (local.get $str))) ;; string length (local.set $i (i32.const 0)) (local.set $total-size (i32.const 0)) (loop $size-loop (if (i32.lt_u (local.get $i) (local.get $str-len)) (then (local.set $char (array.get $I32Array (struct.get $String 2 (local.get $str)) (local.get $i))) (local.set $total-size (i32.add (local.get $total-size) (call $utf8-size (local.get $char)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $size-loop)))) ;; Allocate backing byte array and wrap in Bytes (local.set $arr (array.new_default $I8Array (local.get $total-size))) (local.set $out-buf (struct.new $Bytes (i32.const 0) ;; hash = 0 (i32.const 1) ;; immutable = true (local.get $arr))) ;; backing array ;; Pass 2: encode UTF-8 into buffer (local.set $i (i32.const 0)) (local.set $idx (i32.const 0)) (loop $fill-loop (if (i32.lt_u (local.get $i) (local.get $str-len)) (then (local.set $char (array.get $I32Array (struct.get $String 2 (local.get $str)) (local.get $i))) (local.set $idx (call $write-utf8 (local.get $out-buf) (local.get $idx) (local.get $char))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $fill-loop)))) (local.get $out-buf)) ;;; Converts a string to its 'write'/'print' representation (quoted, escaped). #;(func $string->bytes:write (param $str (ref $String)) (result (ref $Bytes)) (local $out-buf (ref $Bytes)) (local $arr (ref $I8Array)) (local $str-len i32) (local $i i32) (local $char i32) (local $escape i32) (local $total-size i32) (local $idx i32) ;; Pass 1: calculate output size (local.set $str-len (struct.get $String 1 (local.get $str))) ; todo: wrong (local.set $i (i32.const 0)) (local.set $total-size (i32.const 2)) ;; opening and closing quote (loop $size-loop (if (i32.lt_u (local.get $i) (local.get $str-len)) (then (local.set $char (array.get $I32Array (struct.get $String 2 (local.get $str)) (local.get $i))) (block $char-size-done (if (i32.eq (local.get $char) (i32.const 34)) (then (local.set $total-size (i32.add (local.get $total-size) (i32.const 2))) (br $char-size-done))) (if (i32.eq (local.get $char) (i32.const 92)) (then (local.set $total-size (i32.add (local.get $total-size) (i32.const 2))) (br $char-size-done))) (if (call $get-special-escape (local.get $char)) (then (local.set $total-size (i32.add (local.get $total-size) (i32.const 2))) (br $char-size-done))) (if (i32.or (call $is-graphic (local.get $char)) (call $is-blank (local.get $char))) (then (local.set $total-size (i32.add (local.get $total-size) (call $utf8-size (local.get $char)))) (br $char-size-done))) ;; Otherwise use hex escape (if (i32.le_u (local.get $char) (i32.const 0xffff)) (then (local.set $total-size (i32.add (local.get $total-size) (i32.const 6)))) (else (local.set $total-size (i32.add (local.get $total-size) (i32.const 10)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $size-loop)))) ;; Allocate output array and wrap in Bytes (local.set $arr (array.new_default $I8Array (local.get $total-size))) (local.set $out-buf (struct.new $Bytes (i32.const 0) ;; hash (i32.const 1) ;; immutable (local.get $arr))) ;; backing array ;; Write opening quote (array.set $I8Array (local.get $arr) (i32.const 0) (i32.const 34)) (local.set $idx (i32.const 1)) (local.set $i (i32.const 0)) ;; Fill output array (loop $fill-loop (if (i32.lt_u (local.get $i) (local.get $str-len)) (then (local.set $char (array.get $I32Array (struct.get $String 2 (local.get $str)) (local.get $i))) (block $char-done (if (i32.eq (local.get $char) (i32.const 34)) (then (array.set $I8Array (local.get $arr) (local.get $idx) (i32.const 92)) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 1)) (i32.const 34)) (local.set $idx (i32.add (local.get $idx) (i32.const 2))) (br $char-done))) (if (i32.eq (local.get $char) (i32.const 92)) (then (array.set $I8Array (local.get $arr) (local.get $idx) (i32.const 92)) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 1)) (i32.const 92)) (local.set $idx (i32.add (local.get $idx) (i32.const 2))) (br $char-done))) (local.set $escape (call $get-special-escape (local.get $char))) (if (local.get $escape) (then (array.set $I8Array (local.get $arr) (local.get $idx) (i32.const 92)) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 1)) (local.get $escape)) (local.set $idx (i32.add (local.get $idx) (i32.const 2))) (br $char-done))) (if (i32.or (call $is-graphic (local.get $char)) (call $is-blank (local.get $char))) (then (local.set $idx (call $write-utf8 (local.get $out-buf) (local.get $idx) (local.get $char))) (br $char-done))) (if (i32.le_u (local.get $char) (i32.const 0xffff)) (then (array.set $I8Array (local.get $arr) (local.get $idx) (i32.const 92)) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 1)) (i32.const 117)) ;; 'u' (call $write-hex (local.get $out-buf) (i32.add (local.get $idx) (i32.const 2)) (local.get $char) (i32.const 4)) (local.set $idx (i32.add (local.get $idx) (i32.const 6)))) (else (array.set $I8Array (local.get $arr) (local.get $idx) (i32.const 92)) (array.set $I8Array (local.get $arr) (i32.add (local.get $idx) (i32.const 1)) (i32.const 85)) ;; 'U' (call $write-hex (local.get $out-buf) (i32.add (local.get $idx) (i32.const 2)) (local.get $char) (i32.const 8)) (local.set $idx (i32.add (local.get $idx) (i32.const 10)))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $fill-loop))) ;; Final quote (array.set $I8Array (local.get $arr) (local.get $idx) (i32.const 34)) (local.get $out-buf))) (func $raise-not-a-string (param $val (ref eq)) (unreachable)) #;(func $string->bytes (export "string->bytes") ; todo: check mode (param $v (ref eq)) (param $mode-raw (ref eq)) (result (ref eq)) (local $mode (ref i31)) (local $int-mode i32) (local $str (ref null $String)) ;; 1. Extract integer mode from fixnum (local.set $mode (ref.cast (ref i31) (local.get $mode-raw))) (local.set $int-mode (i31.get_u (local.get $mode))) ;; 2. Check if $v is a string (if (ref.test (ref $String) (local.get $v)) (then (local.set $str (ref.cast (ref $String) (local.get $v)))) (else ;; Raise an exception: not a string (return (call $raise-not-a-string (local.get $v))))) ;; 3. Dispatch by mode: 1 = display, else write (if (result (ref eq)) (i32.eq (local.get $int-mode) (i32.const 1)) (then (call $string->bytes:display (local.get $str))) (else (call $string->bytes:write (local.get $str))))) #;(func $str-list (param $v (ref eq)) (param $opt-mode (ref eq)) (result (ref $Bytes)) (local $result (ref $SegmentVec)) (local $mode i32) (local $bs (ref $Bytes)) ;; Determine mode (local.set $mode (select ,(Half `(i31.get_s (ref.cast (ref i31) (local.get $opt-mode)))) (i32.const 0) (ref.eq (local.get $opt-mode) (global.get $false)))) ;; Initialize accumulator with "(" (local.set $result (array.new_fixed $SegmentVec (array.new_data $Bytes "(" (i32.const 1)))) ;; Traverse list and accumulate (block $done (loop $loop (br_if $done (ref.eq (local.get $v) (global.get $null))) ;; car (local.set $bs (call $str (call $car (local.get $v)) (ref.i31 (local.get $mode)))) (local.set $result (call $str-segment-vec-append (local.get $result) (local.get $bs))) ;; cdr (local.set $v (call $cdr (local.get $v))) (br_if $loop (ref.eq (local.get $v) (global.get $null))) ;; space between elements (local.set $result (call $str-segment-vec-append (local.get $result) (array.new_data $Bytes " " (i32.const 1)))) (br $loop))) ;; Add closing ")" (local.set $result (call $str-segment-vec-append (local.get $result) (array.new_data $Bytes ")" (i32.const 1)))) ;; Join all segments with no delimiter (call $str-join (local.get $result))) ;;; ;;; Support for the for-family: for, for/list, for/vector ;;; #;(func $grow-vector (param $vec (ref $Vector)) (result (ref $Vector)) (local $arr (ref $Array)) (local $len i32) (local $new-len i32) (local $new-vec (ref $Vector)) (local $new-arr (ref $Array)) ;; Extract underlying array and its length (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $len (array.len (local.get $arr))) (local.set $new-len (i32.mul (local.get $len) (i32.const 2))) ;; Allocate new array and vector wrapper (local.set $new-arr (array.new_default $Array (local.get $new-len) (global.get $null))) (local.set $new-vec (struct.new $Vector (i32.const 0) (i32.const 0) (local.get $new-arr))) ;; Copy elements (call $vector-copy! (local.get $new-vec) (ref.i31 (i32.const 0)) (local.get $vec) (ref.i31 (i32.const 0)) (ref.i31 (local.get $len))) (return (local.get $new-vec))) #;(func $shrink-vector (param $vec (ref $Vector)) (param $i (ref eq)) ;; fixnum (result (ref $Vector)) (local $i/u i32) (local $new-vec (ref $Vector)) ;; Validate fixnum index (if (i32.eqz (ref.test (ref i31) (local.get $i))) (then (call $raise-check-fixnum (local.get $i)) (unreachable))) (local.set $i/u (i32.shr_u (i31.get_u (ref.cast (ref i31) (local.get $i))) (i32.const 1))) ;; Allocate new vector and copy (local.set $new-vec (struct.new $Vector (i32.const 0) (i32.const 0) (array.new_default $Array (local.get $i/u) (global.get $null)))) (call $vector-copy! (local.get $new-vec) (ref.i31 (i32.const 0)) (local.get $vec) (ref.i31 (i32.const 0)) (ref.i31 (i32.shl (local.get $i/u) (i32.const 1)))) ;; convert i32 to fixnum (return (local.get $new-vec))) ;;; ;;; 14. REFLECTION AND SECURITY ;;; ;; 14.1 Namespaces (func $make-empty-namespace (type $Prim0) (result (ref eq)) ;; and (ref $Namespace) (struct.new $Namespace (i32.const 0) ;; $hash (global.get $false) ;; $name (i32.const 0) ;; $base-phase (ref.cast (ref $HashEqMutable) ;; $table (call $make-empty-hasheq)) (global.get $empty-module-registry) ;; $modules (i32.const 0))) ;; $protect (func $raise-undefined-top (unreachable)) (func $raise-argument-error:namespace-expected (unreachable)) (func $namespace-variable-value-simple (type $Prim2) (param $ns (ref eq)) (param $sym (ref eq)) (result (ref eq)) ;; 1) Check that $ns is a namespace (if (i32.eqz (ref.test (ref $Namespace) (local.get $ns))) (then (call $raise-argument-error:namespace-expected (local.get $ns)) (unreachable))) ;; 2) Check that $sym is a symbol (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-check-symbol (local.get $sym)) (unreachable))) ;; 3) Delegate to the checked implementation (call $namespace-variable-value/checked (ref.cast (ref $Namespace) (local.get $ns)) (ref.cast (ref $Symbol) (local.get $sym)))) (func $namespace-variable-value/checked (param $ns (ref $Namespace)) (param $sym (ref $Symbol)) (result (ref eq)) (local $tab (ref $HashEqMutable)) (local $got (ref eq)) (local $box (ref $Boxed)) (local.set $tab (struct.get $Namespace $table (local.get $ns))) (local.set $got (call $hasheq-ref (ref.cast (ref eq) (local.get $tab)) (local.get $sym) (global.get $false))) ;; Fail early if the binding is missing or not a $Boxed. (if (i32.eqz (ref.test (ref $Boxed) (local.get $got))) (then (call $raise-undefined-top (local.get $sym)) (unreachable))) ;; Cast and return the boxed value. (local.set $box (ref.cast (ref $Boxed) (local.get $got))) (struct.get $Boxed $v (local.get $box))) (func $namespace-set-variable-value! (param $ns (ref eq)) (param $sym (ref eq)) (param $val (ref eq)) (result (ref eq)) ;; 1) Check that $ns is a namespace (if (i32.eqz (ref.test (ref $Namespace) (local.get $ns))) (then (call $raise-argument-error:namespace-expected (local.get $ns)) (unreachable))) ;; 2) Check that $sym is a symbol (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-check-symbol (local.get $sym)) (unreachable))) ;; 3) Delegate to the checked implementation (call $namespace-set-variable-value!/checked (ref.cast (ref $Namespace) (local.get $ns)) (ref.cast (ref $Symbol) (local.get $sym)) (local.get $val))) (func $namespace-set-variable-value!/checked ;; Racket semantics: set OR define ;; If `sym` is bound => mutate the existing $Boxed. ;; If `sym` is unbound => install a fresh $Boxed with `val`. (param $ns (ref $Namespace)) (param $sym (ref $Symbol)) (param $val (ref eq)) (result (ref eq)) (local $tab (ref $HashEqMutable)) (local $got (ref eq)) (local $box (ref $Boxed)) (local.set $tab (struct.get $Namespace $table (local.get $ns))) (local.set $got (call $hasheq-ref (ref.cast (ref eq) (local.get $tab)) (local.get $sym) (global.get $false))) (if (ref.test (ref $Boxed) (local.get $got)) (then ;; Binding exists => mutate in place (local.set $box (ref.cast (ref $Boxed) (local.get $got))) (struct.set $Boxed $v (local.get $box) (local.get $val)) (return (global.get $void))) (else ;; Binding missing => create and insert new box (local.set $box (struct.new $Boxed (local.get $val))) (call $hasheq-set!/mutable/checked (local.get $tab) (local.get $sym) (ref.cast (ref eq) (local.get $box))) (return (global.get $void)))) (unreachable)) (func $namespace-undefine-variable! (type $Prim2) (param $ns (ref eq)) (param $sym (ref eq)) (result (ref eq)) ;; 1) Check that $ns is a namespace (if (i32.eqz (ref.test (ref $Namespace) (local.get $ns))) (then (call $raise-argument-error:namespace-expected (local.get $ns)) (unreachable))) ;; 2) Check that $sym is a symbol (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-check-symbol (local.get $sym)) (unreachable))) ;; 3) Delegate to the checked implementation (call $namespace-undefine-variable!/checked (ref.cast (ref $Namespace) (local.get $ns)) (ref.cast (ref $Symbol) (local.get $sym)))) (func $namespace-undefine-variable!/checked (param $ns (ref $Namespace)) (param $sym (ref $Symbol)) (result (ref eq)) (local $tab (ref $HashEqMutable)) (local.set $tab (struct.get $Namespace $table (local.get $ns))) ; returns void (call $hasheq-remove!/mutable/checked (local.get $tab) (local.get $sym)) (global.get $void)) (func $namespace-has-key? (param $ns (ref eq)) (param $sym (ref eq)) (result (ref eq)) ;; 1) Check that $ns is a namespace (if (i32.eqz (ref.test (ref $Namespace) (local.get $ns))) (then (call $raise-argument-error:namespace-expected (local.get $ns)) (unreachable))) ;; 2) Check that $sym is a symbol (if (i32.eqz (ref.test (ref $Symbol) (local.get $sym))) (then (call $raise-check-symbol (local.get $sym)) (unreachable))) ;; 3) Delegate to the checked implementation (call $namespace-has-key?/checked (ref.cast (ref $Namespace) (local.get $ns)) (ref.cast (ref $Symbol) (local.get $sym)))) (func $namespace-has-key?/checked (param $ns (ref $Namespace)) (param $sym (ref $Symbol)) (result (ref eq)) (local $tab (ref $HashEqMutable)) (local.set $tab (struct.get $Namespace $table (local.get $ns))) (call $hash-has-key? (ref.cast (ref eq) (local.get $tab)) (local.get $sym))) ;;; ;;; 14.2 Source Locations ;;; (func $ensure-srcloc-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $indices (ref eq)) (local.set $existing (global.get $srcloc-type)) (if (ref.is_null (local.get $existing)) (then (local.set $indices (call $list-from-range/checked (i32.const 0) (i32.const 5))) (local.set $std (struct.new $StructType (i32.const 0) (ref.cast (ref $Symbol) (global.get $symbol:srcloc)) (global.get $false) (i32.const 5) (local.get $indices) (global.get $null) (global.get $null) (ref.cast (ref eq) (call $struct-type-property-table-empty)) (global.get $false) (local.get $indices) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:srcloc)))) (global.set $srcloc-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) (func $srcloc/make (param $source (ref eq)) (param $line (ref eq)) (param $column (ref eq)) (param $position (ref eq)) (param $span (ref eq)) (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-srcloc-type)) (local.set $fields (array.new_fixed $Array 5 (local.get $source) (local.get $line) (local.get $column) (local.get $position) (local.get $span))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) (func $srcloc-check-positive (param $who (ref eq)) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v) (global.get $false)) (then (local.get $v)) (else (if (result (ref eq)) (ref.eq (call $exact-positive-integer? (local.get $v)) (global.get $true)) (then (local.get $v)) (else (call $raise-argument-error1 (local.get $who) (global.get $string:srcloc-positive-or-false) (local.get $v)) (unreachable)))))) (func $srcloc-check-nonnegative (param $who (ref eq)) (param $v (ref eq)) (result (ref eq)) (if (result (ref eq)) (ref.eq (local.get $v) (global.get $false)) (then (local.get $v)) (else (if (result (ref eq)) (ref.eq (call $exact-nonnegative-integer? (local.get $v)) (global.get $true)) (then (local.get $v)) (else (call $raise-argument-error1 (local.get $who) (global.get $string:srcloc-nonnegative-or-false) (local.get $v)) (unreachable)))))) (func $srcloc-build (param $who (ref eq)) (param $source (ref eq)) (param $line (ref eq)) (param $column (ref eq)) (param $position (ref eq)) (param $span (ref eq)) (result (ref eq)) (local $line-checked (ref eq)) (local $column-checked (ref eq)) (local $position-checked (ref eq)) (local $span-checked (ref eq)) (local.set $line-checked (call $srcloc-check-positive (local.get $who) (local.get $line))) (local.set $column-checked (call $srcloc-check-nonnegative (local.get $who) (local.get $column))) (local.set $position-checked (call $srcloc-check-positive (local.get $who) (local.get $position))) (local.set $span-checked (call $srcloc-check-nonnegative (local.get $who) (local.get $span))) (call $srcloc/make (local.get $source) (local.get $line-checked) (local.get $column-checked) (local.get $position-checked) (local.get $span-checked))) (func $srcloc (type $Prim5) (param $source (ref eq)) (param $line (ref eq)) (param $column (ref eq)) (param $position (ref eq)) (param $span (ref eq)) (result (ref eq)) (call $srcloc-build (global.get $symbol:srcloc) (local.get $source) (local.get $line) (local.get $column) (local.get $position) (local.get $span))) (func $make-srcloc (type $Prim5) (param $source (ref eq)) (param $line (ref eq)) (param $column (ref eq)) (param $position (ref eq)) (param $span (ref eq)) (result (ref eq)) (call $srcloc-build (global.get $symbol:make-srcloc) (local.get $source) (local.get $line) (local.get $column) (local.get $position) (local.get $span))) (func $srcloc? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $struct (ref $Struct)) (local $type (ref eq)) (local $std (ref $StructType)) (local $ok i32) (local.set $std (call $ensure-srcloc-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $ok (call $struct-type-is-a?/i32 (local.get $type) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $raise-argument-error:srcloc-expected (param $who (ref eq)) (param $got (ref eq)) (call $raise-argument-error1 (local.get $who) (global.get $string:srcloc?) (local.get $got))) (func $srcloc-unwrap (param $who (ref eq)) (param $v (ref eq)) (result (ref $Array)) (local $struct (ref $Struct)) (local $type (ref eq)) (local $std (ref $StructType)) (local $ok i32) (local.set $std (call $ensure-srcloc-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error:srcloc-expected (local.get $who) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $ok (call $struct-type-is-a?/i32 (local.get $type) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error:srcloc-expected (local.get $who) (local.get $v)) (unreachable))) (struct.get $Struct $fields (local.get $struct))) (func $srcloc-source (type $Prim1) (param $loc (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $srcloc-unwrap (global.get $symbol:srcloc-source) (local.get $loc))) (array.get $Array (local.get $fields) (i32.const 0))) (func $srcloc-line (type $Prim1) (param $loc (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $srcloc-unwrap (global.get $symbol:srcloc-line) (local.get $loc))) (array.get $Array (local.get $fields) (i32.const 1))) (func $srcloc-column (type $Prim1) (param $loc (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $srcloc-unwrap (global.get $symbol:srcloc-column) (local.get $loc))) (array.get $Array (local.get $fields) (i32.const 2))) (func $srcloc-position (type $Prim1) (param $loc (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $srcloc-unwrap (global.get $symbol:srcloc-position) (local.get $loc))) (array.get $Array (local.get $fields) (i32.const 3))) (func $srcloc-span (type $Prim1) (param $loc (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $srcloc-unwrap (global.get $symbol:srcloc-span) (local.get $loc))) (array.get $Array (local.get $fields) (i32.const 4))) ; Formats an `srcloc` into a string suitable for use in an error message. (func $srcloc->string (type $Prim1) (param $loc (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $source (ref eq)) (local $line (ref eq)) (local $column (ref eq)) (local $position (ref eq)) (local $span (ref eq)) (local $parts (ref eq)) (local $part (ref eq)) (local $path (ref $Path)) (local $relative (ref eq)) (local $relative-parts (ref eq)) (local $relative-node (ref $Pair)) (local $reversed (ref eq)) (local $with-seps (ref eq)) ;; Initialize non-defaultable locals (local.set $part (global.get $string:empty)) (local.set $path (ref.cast (ref $Path) (call $bytes->path (global.get $bytes:non-empty) ; ignored (global.get $missing)))) (local.set $relative (global.get $false)) (local.set $relative-parts (global.get $null)) (local.set $relative-node (ref.cast (ref $Pair) (call $cons (global.get $false) (global.get $null)))) ;; Extract fields (local.set $fields (call $srcloc-unwrap (global.get $symbol:srcloc->string) (local.get $loc))) (local.set $source (array.get $Array (local.get $fields) (i32.const 0))) (local.set $line (array.get $Array (local.get $fields) (i32.const 1))) (local.set $column (array.get $Array (local.get $fields) (i32.const 2))) (local.set $position (array.get $Array (local.get $fields) (i32.const 3))) (local.set $span (array.get $Array (local.get $fields) (i32.const 4))) ;; Accumulate output in parts (local.set $parts (global.get $null)) (if (ref.eq (local.get $source) (global.get $false)) (then (nop)) (else (if (ref.test (ref $String) (local.get $source)) (then (local.set $part (local.get $source))) (else (if (ref.test (ref $Path) (local.get $source)) (then (local.set $path (ref.cast (ref $Path) (local.get $source))) (local.set $relative (call $find-relative-path (global.get $current-directory-for-user-path) (local.get $path) (global.get $false) (global.get $true) (global.get $true))) (local.set $relative-parts (call $explode-path (local.get $relative))) (if (i32.and (ref.test (ref $Pair) (local.get $relative-parts)) (ref.eq (struct.get $Pair $a (ref.cast (ref $Pair) (local.get $relative-parts))) (global.get $symbol:up))) (then (local.set $relative (local.get $path)))) (local.set $part (call $path->string (local.get $relative)))) (else (local.set $part (call $format/display (local.get $source))))))) (local.set $parts (call $cons (local.get $part) (local.get $parts))))) (if (ref.eq (local.get $line) (global.get $false)) (then (nop)) (else (local.set $part (call $number->string (local.get $line) (global.get $missing))) (local.set $parts (call $cons (local.get $part) (local.get $parts))))) (if (ref.eq (local.get $column) (global.get $false)) (then (nop)) (else (local.set $part (call $number->string (local.get $column) (global.get $missing))) (local.set $parts (call $cons (local.get $part) (local.get $parts))))) ;; Note: support for position/span depends on struct-type-property? ;; which is not yet implemented, so skip them for now. (drop (local.get $position)) (drop (local.get $span)) (local.set $reversed (call $reverse (local.get $parts))) (local.set $with-seps (call $add-between (local.get $reversed) (global.get $string:colon))) (call $string-append* (local.get $with-seps) (global.get $null))) ;;; ;;; 12. Macros ;;; ;;; 12.2 Syntax Object COntent (func $ensure-syntax-type (result (ref $StructType)) (local $existing (ref null $StructType)) (local $std (ref $StructType)) (local $indices (ref eq)) (local.set $existing (global.get $syntax-type)) (if (ref.is_null (local.get $existing)) (then (local.set $indices (call $list-from-range/checked (i32.const 0) (i32.const 5))) (local.set $std (struct.new $StructType (i32.const 0) (ref.cast (ref $Symbol) (global.get $symbol:syntax)) (global.get $false) (i32.const 5) (local.get $indices) (global.get $null) (global.get $null) (ref.cast (ref eq) (call $struct-type-property-table-empty)) (global.get $false) (local.get $indices) (global.get $false) (ref.cast (ref $Symbol) (global.get $symbol:syntax)))) (global.set $syntax-type (local.get $std)) (local.set $existing (local.get $std)))) (ref.as_non_null (local.get $existing))) (func $ensure-syntax-empty-props (result (ref eq)) (local $props (ref eq)) (local.set $props (global.get $syntax-empty-props)) (if (ref.eq (local.get $props) (global.get $undefined)) (then (local.set $props (call $make-hash (global.get $missing))) (global.set $syntax-empty-props (local.get $props)))) (local.get $props)) (func $syntax/make (param $e (ref eq)) (param $scopes (ref eq)) (param $shifted (ref eq)) (param $srcloc (ref eq)) (param $props (ref eq)) (result (ref $Struct)) (local $std (ref $StructType)) (local $fields (ref $Array)) (local.set $std (call $ensure-syntax-type)) (local.set $fields (array.new_fixed $Array 5 (local.get $e) (local.get $scopes) (local.get $shifted) (local.get $srcloc) (local.get $props))) (struct.new $Struct (i32.const 0) (global.get $false) (ref.i31 (i32.const 0)) (global.get $false) (ref.func $invoke-struct) (local.get $std) (local.get $fields))) (func $raise-argument-error:syntax-expected (param $who (ref eq)) (param $got (ref eq)) (call $raise-argument-error1 (local.get $who) (global.get $string:syntax?) (local.get $got))) (func $syntax-build (param $who (ref eq)) (param $e (ref eq)) (param $scopes (ref eq)) (param $shifted (ref eq)) (param $srcloc (ref eq)) (param $props (ref eq)) (result (ref eq)) (local $srcloc-checked (ref eq)) (local $props-checked (ref eq)) ;; Initialize non-defaultable locals (local.set $srcloc-checked (global.get $false)) (local.set $props-checked (global.get $false)) (if (ref.eq (local.get $srcloc) (global.get $false)) (then (local.set $srcloc-checked (local.get $srcloc))) (else (if (ref.eq (call $srcloc? (local.get $srcloc)) (global.get $true)) (then (local.set $srcloc-checked (local.get $srcloc))) (else (call $raise-argument-error:srcloc-expected (local.get $who) (local.get $srcloc)) (unreachable))))) (if (ref.eq (local.get $props) (global.get $missing)) (then (local.set $props-checked (call $ensure-syntax-empty-props))) (else (if (ref.eq (call $hash? (local.get $props)) (global.get $true)) (then (local.set $props-checked (local.get $props))) (else (call $raise-argument-error1 (local.get $who) (global.get $string:hash?) (local.get $props)) (unreachable))))) (call $syntax/make (local.get $e) (local.get $scopes) (local.get $shifted) (local.get $srcloc-checked) (local.get $props-checked))) (func $syntax #;(type $Prim5) (param $e (ref eq)) (param $scopes (ref eq)) (param $shifted (ref eq)) (param $srcloc (ref eq)) (param $props (ref eq)) (result (ref eq)) (call $syntax-build (global.get $symbol:syntax) (local.get $e) (local.get $scopes) (local.get $shifted) (local.get $srcloc) (local.get $props))) (func $make-syntax #;(type $Prim5) (param $e (ref eq)) (param $scopes (ref eq)) (param $shifted (ref eq)) (param $srcloc (ref eq)) (param $props (ref eq)) (result (ref eq)) (call $syntax-build (global.get $symbol:make-syntax) (local.get $e) (local.get $scopes) (local.get $shifted) (local.get $srcloc) (local.get $props))) (func $syntax? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $struct (ref $Struct)) (local $type (ref eq)) (local $std (ref $StructType)) (local $ok i32) (local.set $std (call $ensure-syntax-type)) (if (result (ref eq)) (ref.test (ref $Struct) (local.get $v)) (then (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $ok (call $struct-type-is-a?/i32 (local.get $type) (local.get $std))) (if (result (ref eq)) (local.get $ok) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) (func $syntax-unwrap (param $who (ref eq)) (param $v (ref eq)) (result (ref $Array)) (local $struct (ref $Struct)) (local $type (ref eq)) (local $std (ref $StructType)) (local $ok i32) (local.set $std (call $ensure-syntax-type)) (if (i32.eqz (ref.test (ref $Struct) (local.get $v))) (then (call $raise-argument-error:syntax-expected (local.get $who) (local.get $v)) (unreachable))) (local.set $struct (ref.cast (ref $Struct) (local.get $v))) (local.set $type (struct.get $Struct $type (local.get $struct))) (local.set $ok (call $struct-type-is-a?/i32 (local.get $type) (local.get $std))) (if (i32.eqz (local.get $ok)) (then (call $raise-argument-error:syntax-expected (local.get $who) (local.get $v)) (unreachable))) (struct.get $Struct $fields (local.get $struct))) (func $syntax-e (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-e) (local.get $stx))) (array.get $Array (local.get $fields) (i32.const 0))) (func $syntax->list (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $current (ref eq)) (local $pair (ref $Pair)) (local $elem (ref eq)) (local $acc (ref eq)) (local $result (ref eq)) (local.set $acc (global.get $null)) (local.set $result (global.get $false)) (local.set $current (call $syntax-e (local.get $stx))) (block $done (block $fail (loop $loop ; current = '() => done (if (ref.eq (local.get $current) (global.get $null)) (then (local.set $result (call $reverse (local.get $acc))) (br $done))) ; current = (cons elem ...) => cons `elem` to $acc (if (ref.test (ref $Pair) (local.get $current)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $current))) (local.set $elem (struct.get $Pair $a (local.get $pair))) (local.set $acc (struct.new $Pair (i32.const 0) (local.get $elem) (local.get $acc))) (local.set $current (struct.get $Pair $d (local.get $pair))) (br $loop))) ; unwrap syntax in the cdr, if necessary (if (ref.eq (call $syntax? (local.get $current)) (global.get $true)) (then (local.set $current (call $syntax-e (local.get $current))) (br $loop))) (br $fail)))) (local.get $result)) (func $syntax-scopes (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-scopes) (local.get $stx))) (array.get $Array (local.get $fields) (i32.const 1))) (func $syntax-shifted-multi-scopes (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-shifted-multi-scopes) (local.get $stx))) (array.get $Array (local.get $fields) (i32.const 2))) (func $syntax-srcloc (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-srcloc) (local.get $stx))) (array.get $Array (local.get $fields) (i32.const 3))) (func $syntax-srclocs (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $srcloc (ref eq)) (struct.new $Pair (i32.const 0) (call $srcloc (call $syntax-source (local.get $stx)) (call $syntax-line (local.get $stx)) (call $syntax-column (local.get $stx)) (call $syntax-position (local.get $stx)) (call $syntax-span (local.get $stx))) (global.get $null))) (func $syntax-props (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-props) (local.get $stx))) (array.get $Array (local.get $fields) (i32.const 4))) (func $empty-props (result (ref eq)) (call $ensure-syntax-empty-props)) (func $syntax-source (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $srcloc (ref eq)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-source) (local.get $stx))) (local.set $srcloc (array.get $Array (local.get $fields) (i32.const 3))) (if (result (ref eq)) (ref.eq (local.get $srcloc) (global.get $false)) (then (global.get $false)) (else (call $srcloc-source (local.get $srcloc))))) (func $syntax-line (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $srcloc (ref eq)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-line) (local.get $stx))) (local.set $srcloc (array.get $Array (local.get $fields) (i32.const 3))) (if (result (ref eq)) (ref.eq (local.get $srcloc) (global.get $false)) (then (global.get $false)) (else (call $srcloc-line (local.get $srcloc))))) (func $syntax-column (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $srcloc (ref eq)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-column) (local.get $stx))) (local.set $srcloc (array.get $Array (local.get $fields) (i32.const 3))) (if (result (ref eq)) (ref.eq (local.get $srcloc) (global.get $false)) (then (global.get $false)) (else (call $srcloc-column (local.get $srcloc))))) (func $syntax-position (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $srcloc (ref eq)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-position) (local.get $stx))) (local.set $srcloc (array.get $Array (local.get $fields) (i32.const 3))) (if (result (ref eq)) (ref.eq (local.get $srcloc) (global.get $false)) (then (global.get $false)) (else (call $srcloc-position (local.get $srcloc))))) (func $syntax-span (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $srcloc (ref eq)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax-span) (local.get $stx))) (local.set $srcloc (array.get $Array (local.get $fields) (i32.const 3))) (if (result (ref eq)) (ref.eq (local.get $srcloc) (global.get $false)) (then (global.get $false)) (else (call $srcloc-span (local.get $srcloc))))) (func $identifier? (type $Prim1) (param $v (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $datum (ref eq)) (if (result (ref eq)) (ref.eq (call $syntax? (local.get $v)) (global.get $true)) (then (local.set $fields (call $syntax-unwrap (global.get $symbol:identifier?) (local.get $v))) (local.set $datum (array.get $Array (local.get $fields) (i32.const 0))) (if (result (ref eq)) (ref.eq (call $symbol? (local.get $datum)) (global.get $true)) (then (global.get $true)) (else (global.get $false)))) (else (global.get $false)))) ;; NOTE: This initial implementation does not yet recursively convert ;; datum contents (pairs, vectors, boxes, etc.) as Racket's ;; datum->syntax does. Nested data are left unchanged. #;(func $datum->syntax (type $Prim15) (param $ctxt (ref eq)) ;; syntax? or #f (param $datum (ref eq)) ;; any/c (param $srcloc (ref eq)) ;; optional srcloc? (default: #f) (param $prop (ref eq)) ;; optional syntax? (default: #f) (param $ignored (ref eq)) ;; optional syntax? (default: #f) (result (ref eq)) (local $who (ref eq)) (local $fields (ref $Array)) (local $scopes (ref eq)) (local $shifted (ref eq)) (local $srcloc-checked (ref eq)) (local $props-checked (ref eq)) (local $is-syntax (ref eq)) (local.set $who (global.get $symbol:datum->syntax)) (local.set $scopes (global.get $null)) (local.set $shifted (global.get $null)) (local.set $srcloc-checked (global.get $false)) (local.set $props-checked (global.get $missing)) (if (ref.eq (local.get $ctxt) (global.get $false)) (then (nop)) (else (local.set $fields (call $syntax-unwrap (local.get $who) (local.get $ctxt))) (local.set $scopes (array.get $Array (local.get $fields) (i32.const 1))) (local.set $shifted (array.get $Array (local.get $fields) (i32.const 2))))) (if (ref.eq (local.get $srcloc) (global.get $missing)) (then (nop)) (else (if (ref.eq (local.get $srcloc) (global.get $false)) (then (nop)) (else (if (ref.eq (call $syntax? (local.get $srcloc)) (global.get $true)) (then (local.set $srcloc-checked (call $syntax-srcloc (local.get $srcloc)))) (else (if (ref.eq (call $srcloc? (local.get $srcloc)) (global.get $true)) (then (local.set $srcloc-checked (local.get $srcloc))) (else (call $raise-argument-error:srcloc-expected (local.get $who) (local.get $srcloc)) (unreachable))))))))) (if (ref.eq (local.get $prop) (global.get $missing)) (then (nop)) (else (if (ref.eq (local.get $prop) (global.get $false)) (then (nop)) (else (if (ref.eq (call $syntax? (local.get $prop)) (global.get $true)) (then (local.set $props-checked (call $syntax-props (local.get $prop)))) (else (call $raise-argument-error:syntax-expected (local.get $who) (local.get $prop)) (unreachable))))))) (if (ref.eq (local.get $ignored) (global.get $missing)) (then (nop)) (else (if (ref.eq (local.get $ignored) (global.get $false)) (then (nop)) (else (if (ref.eq (call $syntax? (local.get $ignored)) (global.get $true)) (then (nop)) (else (call $raise-argument-error:syntax-expected (local.get $who) (local.get $ignored)) (unreachable))))))) (local.set $is-syntax (call $syntax? (local.get $datum))) (if (ref.eq (local.get $is-syntax) (global.get $true)) (then (return (local.get $datum)))) (call $syntax-build (local.get $who) (local.get $datum) (local.get $scopes) (local.get $shifted) (local.get $srcloc-checked) (local.get $props-checked))) ;; --------------------------------------------------------------- ;; NOTE: This implementation currently supports recursive conversion of ;; pairs, vectors, and boxes. Immutable hash tables and prefab ;; structures are not yet converted and will be wrapped as-is. ;; The `srcloc` argument accepts #f, srcloc? values, or syntax objects. (func $datum->syntax/convert (param $scopes (ref eq)) (param $shifted (ref eq)) (param $srcloc (ref eq)) (param $props (ref eq)) (param $v (ref eq)) (result (ref eq)) (local $pair (ref $Pair)) (local $car (ref eq)) (local $cdr (ref eq)) (local $car-stx (ref eq)) (local $cdr-stx (ref eq)) (local $vec (ref $Vector)) (local $arr (ref $Array)) (local $new-arr (ref $Array)) (local $len i32) (local $i i32) (local $elem (ref eq)) (local $elem-stx (ref eq)) (local $result (ref eq)) (local $box (ref $Box)) (local $box-val (ref eq)) ;; Already syntax? return as-is. (if (ref.eq (call $syntax? (local.get $v)) (global.get $true)) (then (return (local.get $v)))) ;; Convert pairs. (if (ref.test (ref $Pair) (local.get $v)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $v))) (local.set $car (struct.get $Pair $a (local.get $pair))) (local.set $cdr (struct.get $Pair $d (local.get $pair))) (local.set $car-stx (call $datum->syntax/convert (local.get $scopes) (local.get $shifted) (local.get $srcloc) (global.get $missing) (local.get $car))) (local.set $cdr-stx (call $datum->syntax/convert (local.get $scopes) (local.get $shifted) (local.get $srcloc) (global.get $missing) (local.get $cdr))) (local.set $result (call $cons (local.get $car-stx) (local.get $cdr-stx))) (return (call $syntax-build (global.get $symbol:datum->syntax) (local.get $result) (local.get $scopes) (local.get $shifted) (local.get $srcloc) (local.get $props))))) ;; Convert vectors (always producing an immutable vector). (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $len (array.len (local.get $arr))) (local.set $new-arr (call $make-array (local.get $len) (global.get $false))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $elem (array.get $Array (local.get $arr) (local.get $i))) (local.set $elem-stx (call $datum->syntax/convert (local.get $scopes) (local.get $shifted) (local.get $srcloc) (global.get $missing) (local.get $elem))) (array.set $Array (local.get $new-arr) (local.get $i) (local.get $elem-stx)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.set $result (struct.new $Vector (i32.const 0) (i32.const 1) (local.get $new-arr))) (return (call $syntax-build (global.get $symbol:datum->syntax) (local.get $result) (local.get $scopes) (local.get $shifted) (local.get $srcloc) (local.get $props))))) ;; Convert boxes (always immutable in the result). (if (ref.test (ref $Box) (local.get $v)) (then (local.set $box (ref.cast (ref $Box) (local.get $v))) (local.set $box-val (struct.get $Box $v (local.get $box))) (local.set $box-val (call $datum->syntax/convert (local.get $scopes) (local.get $shifted) (local.get $srcloc) (global.get $missing) (local.get $box-val))) (local.set $result (struct.new $Box (i32.const 0) (i32.const 1) (local.get $box-val))) (return (call $syntax-build (global.get $symbol:datum->syntax) (local.get $result) (local.get $scopes) (local.get $shifted) (local.get $srcloc) (local.get $props))))) (call $syntax-build (global.get $symbol:datum->syntax) (local.get $v) (local.get $scopes) (local.get $shifted) (local.get $srcloc) (local.get $props))) (func $datum->syntax (type $Prim25) (param $ctxt (ref eq)) (param $v (ref eq)) (param $srcloc (ref eq)) (param $prop (ref eq)) (param $ignored (ref eq)) (result (ref eq)) (local $scopes (ref eq)) (local $shifted (ref eq)) (local $default-srcloc (ref eq)) (local $default-props (ref eq)) (local $srcloc-checked (ref eq)) (local $props-checked (ref eq)) ;; Initialize defaults. (local.set $scopes (global.get $null)) (local.set $shifted (global.get $null)) (local.set $default-srcloc (global.get $false)) (local.set $default-props (global.get $missing)) ;; Extract lexical context from the context syntax when available. (if (ref.eq (call $syntax? (local.get $ctxt)) (global.get $true)) (then (local.set $scopes (call $syntax-scopes (local.get $ctxt))) (local.set $shifted (call $syntax-shifted-multi-scopes (local.get $ctxt))) (local.set $default-srcloc (call $syntax-srcloc (local.get $ctxt))) (local.set $default-props (call $syntax-props (local.get $ctxt))))) ;; Decode srcloc argument. (local.set $srcloc-checked (if (result (ref eq)) (ref.eq (local.get $srcloc) (global.get $missing)) (then (local.get $default-srcloc)) (else (if (result (ref eq)) (ref.eq (local.get $srcloc) (global.get $false)) (then (global.get $false)) (else (if (result (ref eq)) (ref.eq (call $syntax? (local.get $srcloc)) (global.get $true)) (then (call $syntax-srcloc (local.get $srcloc))) (else (if (result (ref eq)) (ref.eq (call $srcloc? (local.get $srcloc)) (global.get $true)) (then (local.get $srcloc)) (else (call $raise-argument-error1 (global.get $symbol:datum->syntax) (global.get $string:datum->syntax-srcloc) (local.get $srcloc)) (unreachable)))))))))) ;; Decode properties argument. (local.set $props-checked (if (result (ref eq)) (ref.eq (local.get $prop) (global.get $missing)) (then (local.get $default-props)) (else (if (result (ref eq)) (ref.eq (local.get $prop) (global.get $false)) (then (global.get $missing)) (else (if (result (ref eq)) (ref.eq (call $syntax? (local.get $prop)) (global.get $true)) (then (call $syntax-props (local.get $prop))) (else (call $raise-argument-error1 (global.get $symbol:datum->syntax) (global.get $string:syntax-or-false) (local.get $prop)) (unreachable)))))))) (call $datum->syntax/convert (local.get $scopes) (local.get $shifted) (local.get $srcloc-checked) (local.get $props-checked) (local.get $v))) ;; --------------------------------------------------------------- ;; (func $datum->syntax-copy ;; (param $v (ref eq)) ;; (result (ref eq)) ;; (local $pair (ref $Pair)) ;; (local $new-car (ref eq)) ;; (local $new-cdr (ref eq)) ;; (local $vec (ref $Vector)) ;; (local $arr (ref $Array)) ;; (local $new-arr (ref $Array)) ;; (local $len i32) ;; (local $i i32) ;; (local $elem (ref eq)) ;; (local $converted (ref eq)) ;; (local $box (ref $Box)) ;; ;; Leave existing syntax objects untouched. ;; (if (ref.eq (call $syntax? (local.get $v)) (global.get $true)) ;; (then (return (local.get $v)))) ;; ;; Copy pairs recursively. ;; (if (ref.test (ref $Pair) (local.get $v)) ;; (then ;; (local.set $pair (ref.cast (ref $Pair) (local.get $v))) ;; (local.set $new-car (call $datum->syntax-copy ;; (struct.get $Pair $a (local.get $pair)))) ;; (local.set $new-cdr (call $datum->syntax-copy ;; (struct.get $Pair $d (local.get $pair)))) ;; (return (call $cons (local.get $new-car) (local.get $new-cdr))))) ;; ;; Copy vectors and ensure the result is immutable. ;; (if (ref.test (ref $Vector) (local.get $v)) ;; (then ;; (local.set $vec (ref.cast (ref $Vector) (local.get $v))) ;; (local.set $arr (struct.get $Vector $arr (local.get $vec))) ;; (local.set $len (array.len (local.get $arr))) ;; (local.set $new-arr (call $make-array (local.get $len) (global.get $false))) ;; (local.set $i (i32.const 0)) ;; (block $done ;; (loop $loop ;; (br_if $done (i32.ge_u (local.get $i) (local.get $len))) ;; (local.set $elem (array.get $Array (local.get $arr) (local.get $i))) ;; (local.set $converted (call $datum->syntax-copy (local.get $elem))) ;; (array.set $Array (local.get $new-arr) (local.get $i) (local.get $converted)) ;; (local.set $i (i32.add (local.get $i) (i32.const 1))) ;; (br $loop))) ;; (return (struct.new $Vector ;; (i32.const 0) ;; (i32.const 1) ;; (local.get $new-arr))))) ;; ;; Copy boxes and make them immutable. ;; (if (ref.test (ref $Box) (local.get $v)) ;; (then ;; (local.set $box (ref.cast (ref $Box) (local.get $v))) ;; (local.set $converted (call $datum->syntax-copy ;; (struct.get $Box $v (local.get $box)))) ;; (return (call $box-immutable (local.get $converted))))) ;; (local.get $v)) ;; (func $datum->syntax (type $Prim15) ;; (param $ctxt (ref eq)) ;; (param $v (ref eq)) ;; (param $srcloc-raw (ref eq)) ;; (param $prop-raw (ref eq)) ;; (param $ignored (ref eq)) ;; (result (ref eq)) ;; (local $scopes (ref eq)) ;; (local $shifted (ref eq)) ;; (local $srcloc (ref eq)) ;; (local $props (ref eq)) ;; (local $fields (ref $Array)) ;; (local $prop (ref eq)) ;; (local $converted (ref eq)) ;; ;; Default context fields. ;; (local.set $scopes (global.get $null)) ;; (local.set $shifted (global.get $null)) ;; (local.set $srcloc (global.get $false)) ;; (local.set $props (global.get $missing)) ;; ;; If v is already a syntax object, return it unchanged. ;; (if (ref.eq (call $syntax? (local.get $v)) (global.get $true)) ;; (then (return (local.get $v)))) ;; ;; Extract lexical information from the context. ;; (if (ref.eq (local.get $ctxt) (global.get $false)) ;; (then (nop)) ;; (else ;; (if (ref.eq (call $syntax? (local.get $ctxt)) (global.get $true)) ;; (then ;; (local.set $fields (call $syntax-unwrap ;; (global.get $symbol:datum->syntax) ;; (local.get $ctxt))) ;; (local.set $scopes (array.get $Array (local.get $fields) (i32.const 1))) ;; (local.set $shifted (array.get $Array (local.get $fields) (i32.const 2))) ;; (if (ref.eq (local.get $srcloc-raw) (global.get $missing)) ;; (then (local.set $srcloc (array.get $Array (local.get $fields) (i32.const 3)))))) ;; (else ;; (call $raise-argument-error1 ;; (global.get $symbol:datum->syntax) ;; (global.get $string:syntax?) ;; (local.get $ctxt)) ;; (unreachable))))) ;; ;; Interpret the srcloc argument if provided. ;; (if (ref.eq (local.get $srcloc-raw) (global.get $missing)) ;; (then (nop)) ;; (else ;; (if (ref.eq (local.get $srcloc-raw) (global.get $false)) ;; (then (local.set $srcloc (global.get $false))) ;; (else ;; (if (ref.eq (call $syntax? (local.get $srcloc-raw)) (global.get $true)) ;; (then (local.set $srcloc (call $syntax-srcloc (local.get $srcloc-raw)))) ;; (else (local.set $srcloc (local.get $srcloc-raw))))))) ;; ;; Determine properties to attach. ;; (local.set $prop (local.get $prop-raw)) ;; (if (ref.eq (local.get $prop) (global.get $missing)) ;; (then (local.set $props (global.get $missing))) ;; (else ;; (if (ref.eq (local.get $prop) (global.get $false)) ;; (then (local.set $props (global.get $missing))) ;; (else ;; (if (ref.eq (call $syntax? (local.get $prop)) (global.get $true)) ;; (then (local.set $props (call $syntax-props (local.get $prop)))) ;; (else ;; (call $raise-argument-error1 ;; (global.get $symbol:datum->syntax) ;; (global.get $string:syntax?) ;; (local.get $prop)) ;; (unreachable)))))) ;; (local.set $converted (call $datum->syntax-copy (local.get $v))) ;; (call $syntax-build ;; (global.get $symbol:datum->syntax) ;; (local.get $converted) ;; (local.get $scopes) ;; (local.get $shifted) ;; (local.get $srcloc) ;; (local.get $props))) (func $syntax->datum/convert (param $who (ref eq)) (param $v (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $e (ref eq)) (local $pair (ref $Pair)) (local $car-raw (ref eq)) (local $cdr-raw (ref eq)) (local $car-datum (ref eq)) (local $cdr-datum (ref eq)) (local $vec (ref $Vector)) (local $arr (ref $Array)) (local $new-arr (ref $Array)) (local $len i32) (local $i i32) (local $elem (ref eq)) (local $elem-datum (ref eq)) (local $hash i32) (local $immutable i32) (local $box (ref $Box)) (local $box-val (ref eq)) ;; Unwrap nested syntax objects recursively. (if (ref.eq (call $syntax? (local.get $v)) (global.get $true)) (then (local.set $fields (call $syntax-unwrap (local.get $who) (local.get $v))) (local.set $e (array.get $Array (local.get $fields) (i32.const 0))) (return (call $syntax->datum/convert (local.get $who) (local.get $e))))) ;; Convert pairs. (if (ref.test (ref $Pair) (local.get $v)) (then (local.set $pair (ref.cast (ref $Pair) (local.get $v))) (local.set $car-raw (struct.get $Pair $a (local.get $pair))) (local.set $cdr-raw (struct.get $Pair $d (local.get $pair))) (local.set $car-datum (call $syntax->datum/convert (local.get $who) (local.get $car-raw))) (local.set $cdr-datum (call $syntax->datum/convert (local.get $who) (local.get $cdr-raw))) (return (call $cons (local.get $car-datum) (local.get $cdr-datum))))) ;; Convert vectors element-wise. (if (ref.test (ref $Vector) (local.get $v)) (then (local.set $vec (ref.cast (ref $Vector) (local.get $v))) (local.set $arr (struct.get $Vector $arr (local.get $vec))) (local.set $len (array.len (local.get $arr))) (local.set $new-arr (call $make-array (local.get $len) (global.get $false))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $elem (array.get $Array (local.get $arr) (local.get $i))) (local.set $elem-datum (call $syntax->datum/convert (local.get $who) (local.get $elem))) (array.set $Array (local.get $new-arr) (local.get $i) (local.get $elem-datum)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (local.set $hash (struct.get $Vector $hash (local.get $vec))) (local.set $immutable (struct.get $Vector $immutable (local.get $vec))) (return (struct.new $Vector (local.get $hash) (local.get $immutable) (local.get $new-arr))))) ;; Convert boxes. (if (ref.test (ref $Box) (local.get $v)) (then (local.set $box (ref.cast (ref $Box) (local.get $v))) (local.set $box-val (struct.get $Box $v (local.get $box))) (local.set $box-val (call $syntax->datum/convert (local.get $who) (local.get $box-val))) (local.set $hash (struct.get $Box $hash (local.get $box))) (local.set $immutable (struct.get $Box $immutable (local.get $box))) (return (struct.new $Box (local.get $hash) (local.get $immutable) (local.get $box-val))))) (local.get $v)) (func $syntax->datum (type $Prim1) (param $stx (ref eq)) (result (ref eq)) (local $fields (ref $Array)) (local $e (ref eq)) (local.set $fields (call $syntax-unwrap (global.get $symbol:syntax->datum) (local.get $stx))) (local.set $e (array.get $Array (local.get $fields) (i32.const 0))) (call $syntax->datum/convert (global.get $symbol:syntax->datum) (local.get $e))) ;;; ;;; 15. OPERATING SYSTEM ;;; ;;; 15.1 Paths (func $raise-path-expected (param $x (ref eq)) (call $js-log (local.get $x)) (unreachable)) (func $path? (type $Prim1) (param $v (ref eq)) ;; any/c (result (ref eq)) (local $path (ref $Path)) (local $conv (ref eq)) (if (i32.eqz (ref.test (ref $Path) (local.get $v))) (then (return (global.get $false)))) (local.set $path (ref.cast (ref $Path) (local.get $v))) (local.set $conv (struct.get $Path $convention (local.get $path))) (if (result (ref eq)) (ref.eq (local.get $conv) (global.get $system-path-convention)) (then (global.get $true)) (else (global.get $false)))) (func $path-for-some-system? (type $Prim1) (param $v (ref eq)) ;; any/c (result (ref eq)) (if (i32.eqz (ref.test (ref $Path) (local.get $v))) (then (return (global.get $false)))) (global.get $true)) (func $unix-or-windows (param $sym (ref $Symbol)) ;; symbol? (result (ref eq)) ;; boolean? (if (ref.eq (local.get $sym) (global.get $symbol:unix)) (then (return (global.get $true)))) (if (ref.eq (local.get $sym) (global.get $symbol:windows)) (then (return (global.get $true)))) (global.get $false)) ; / is a separator on both unix and windows ; \ is a separator on windows (func $is-path-sep? (param $cp i32) ;; codepoint (char->integer) (param $conv (ref eq)) ;; symbol? (result (ref eq)) ;; boolean? (if (i32.eq (local.get $cp) (i32.const 47)) ; #\/ (then (return (global.get $true)))) (if (ref.eq (local.get $conv) (global.get $symbol:windows)) (then (if (i32.eq (local.get $cp) (i32.const 92)) ; #\\ (then (return (global.get $true)))))) (global.get $false)) (func $non-empty-bytes-without-nuls (param $bs (ref $Bytes)) ;; bytes? (result (ref eq)) ;; boolean? (local $arr (ref $I8Array)) (local $len i32) (local $i i32) (local $b i32) (local.set $arr (struct.get $Bytes $bs (local.get $bs))) (local.set $len (array.len (local.get $arr))) (if (i32.eqz (local.get $len)) (then (return (global.get $false)))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $b (call $i8array-ref (local.get $arr) (local.get $i))) (if (i32.eqz (local.get $b)) (then (return (global.get $false)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (global.get $true)) (func $non-empty-string-without-nuls (param $str (ref $String)) ;; string? (result (ref eq)) ;; boolean? (local $len i32) (local $i i32) (local $cp i32) (local.set $len (call $string-length/checked/i32 (local.get $str))) (if (i32.eqz (local.get $len)) (then (return (global.get $false)))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $cp (call $string-ref/checked/i32 (local.get $str) (local.get $i))) (if (i32.eqz (local.get $cp)) (then (return (global.get $false)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (global.get $true)) (func $path-string? (type $Prim1) (param $v (ref eq)) ;; any/c (result (ref eq)) (local $path? (ref eq)) (local $str (ref $String)) ;; If it's already a path for the current system, succeed. (local.set $path? (call $path? (local.get $v))) (if (ref.eq (local.get $path?) (global.get $true)) (then (return (global.get $true)))) ;; Otherwise accept non-empty strings with no nul characters. (if (i32.eqz (ref.test (ref $String) (local.get $v))) (then (return (global.get $false)))) (local.set $str (ref.cast (ref $String) (local.get $v))) (call $non-empty-string-without-nuls (local.get $str))) (func $path->bytes (type $Prim1) (param $path-raw (ref eq)) ;; path-for-some-system? (result (ref eq)) ;; bytes? (local $path (ref $Path)) (if (i32.eqz (ref.test (ref $Path) (local.get $path-raw))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (struct.get $Path $bytes (local.get $path))) (func $path->string (type $Prim1) (param $path-raw (ref eq)) ;; path? (result (ref eq)) ;; string? (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $conv (ref eq)) (if (i32.eqz (ref.test (ref $Path) (local.get $path-raw))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (local.set $conv (struct.get $Path $convention (local.get $path))) (if (i32.eqz (ref.eq (local.get $conv) (global.get $system-path-convention))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (call $bytes->string/utf-8/checked (local.get $bytes))) (func $path->bytes/current/checked (param $who (ref eq)) ;; symbol? (param $path-raw (ref eq)) ;; path? (result (ref $Bytes)) (local $path (ref $Path)) (if (i32.eqz (ref.test (ref $Path) (local.get $path-raw))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (if (i32.eqz (ref.eq (struct.get $Path $convention (local.get $path)) (global.get $system-path-convention))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:path?) (local.get $path-raw)) (unreachable))) (struct.get $Path $bytes (local.get $path))) ;; path boolean? ;; Compare current-system paths by their byte-string encodings. (func $path=1) (param $first (ref eq)) ;; path? (param $rest (ref eq)) ;; listof path? (result (ref eq)) (local $prev (ref $Bytes)) (local $curr (ref $Bytes)) (local $node (ref $Pair)) (local $next (ref eq)) (local.set $prev (call $path->bytes/current/checked (global.get $symbol:pathbytes/current/checked (global.get $symbol:pathstring : path-for-some-system? -> string? ;; Convert any represented path convention through UTF-8 path bytes. (func $some-system-path->string (type $Prim1) (param $path-raw (ref eq)) ;; path-for-some-system? (result (ref eq)) ;; string? (local $path (ref $Path)) (if (i32.eqz (ref.test (ref $Path) (local.get $path-raw))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (call $bytes->string/utf-8/checked (struct.get $Path $bytes (local.get $path)))) ;; path-convention-type : path-for-some-system? -> (or/c 'unix 'windows) ;; Return the stored convention symbol for a represented path. (func $path-convention-type (type $Prim1) (param $path-raw (ref eq)) ;; path-for-some-system? (result (ref eq)) ;; symbol? (local $path (ref $Path)) (if (i32.eqz (ref.test (ref $Path) (local.get $path-raw))) (then (call $raise-argument-error1 (global.get $symbol:path-convention-type) (global.get $string:path-for-some-system?) (local.get $path-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (struct.get $Path $convention (local.get $path))) ;; system-path-convention-type : -> (or/c 'unix 'windows) ;; Return the active system path convention symbol. (func $system-path-convention-type (type $Prim0) (result (ref eq)) ;; symbol? (global.get $system-path-convention)) (func $raise-bytes->path:nul (param $bstr (ref eq)) (call $js-log (local.get $bstr)) (unreachable)) (func $raise-bytes->path:bad-type (param $type (ref eq)) (call $js-log (local.get $type)) (unreachable)) (func $bytes->path (type $Prim12) (param $bstr-raw (ref eq)) ;; bytes? (param $type-raw (ref eq)) ;; optional, defaults to (system-path-convention-type) (result (ref eq)) ;; path? (local $input-bytes (ref $Bytes)) (local $bytes (ref $Bytes)) (local $conv (ref eq)) (local $type (ref $Symbol)) ;; Initialize non-defaultable locals (local.set $conv (global.get $system-path-convention)) ;; Ensure the source is a byte string. (if (i32.eqz (ref.test (ref $Bytes) (local.get $bstr-raw))) (then (call $raise-check-bytes (local.get $bstr-raw)) (unreachable))) (local.set $input-bytes (ref.cast (ref $Bytes) (local.get $bstr-raw))) ;; Reject byte strings that are empty or contain a NUL byte. (if (i32.eqz (ref.eq (call $non-empty-bytes-without-nuls (local.get $input-bytes)) (global.get $true))) (then (call $raise-bytes->path:nul (local.get $bstr-raw)) (unreachable))) ;; Normalize to an immutable byte string for storage. (local.set $bytes (ref.cast (ref $Bytes) (call $bytes->immutable-bytes (local.get $bstr-raw)))) ;; Determine the path convention symbol. (if (ref.eq (local.get $type-raw) (global.get $missing)) (then (nop)) ; the default is system path convention (else (if (i32.eqz (ref.test (ref $Symbol) (local.get $type-raw))) (then (call $raise-check-symbol (local.get $type-raw)) (unreachable))) (local.set $type (ref.cast (ref $Symbol) (local.get $type-raw))) (if (i32.eqz (ref.eq (call $unix-or-windows (local.get $type)) (global.get $true))) (then (call $raise-bytes->path:bad-type (local.get $type-raw)) (unreachable))) (local.set $conv (local.get $type-raw)))) ;; Construct and return the new path struct. (struct.new $Path (i32.const 0) (local.get $bytes) (local.get $conv))) (func $string->path (type $Prim1) (param $str-raw (ref eq)) ;; string? (result (ref eq)) ;; path? (local $str (ref $String)) (local $bytes (ref eq)) (if (i32.eqz (ref.test (ref $String) (local.get $str-raw))) (then (call $raise-check-string (local.get $str-raw)) (unreachable))) (local.set $str (ref.cast (ref $String) (local.get $str-raw))) (if (i32.eqz (ref.eq (call $non-empty-string-without-nuls (local.get $str)) (global.get $true))) (then (call $raise-path-expected (local.get $str-raw)) (unreachable))) (local.set $bytes (call $string->bytes/utf-8 (local.get $str) (global.get $false) (global.get $false) (global.get $false))) (call $bytes->path (local.get $bytes) (global.get $missing))) ;; string->path-element : string? [any/c] -> (or/c path-element? #f) ;; Convert a string to a path element; optional false-on-non-element? defaults to #f. (func $string->path-element (type $Prim12) (param $str-raw (ref eq)) ;; string? (param $false-on-non-element? (ref eq)) ;; any/c, optional default #f (result (ref eq)) (local $str (ref $String)) (local $path (ref eq)) (if (i32.eqz (ref.test (ref $String) (local.get $str-raw))) (then (call $raise-check-string (local.get $str-raw)) (unreachable))) (if (ref.eq (local.get $false-on-non-element?) (global.get $missing)) (then (local.set $false-on-non-element? (global.get $false)))) (local.set $str (ref.cast (ref $String) (local.get $str-raw))) (if (i32.eqz (ref.eq (call $non-empty-string-without-nuls (local.get $str)) (global.get $true))) (then (if (ref.eq (local.get $false-on-non-element?) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:string->path-element) (global.get $string:path-element?) (local.get $str-raw)) (unreachable)) (else (return (global.get $false)))))) (local.set $path (call $string->path (local.get $str))) (if (ref.eq (call $path-element? (local.get $path)) (global.get $true)) (then (return (local.get $path)))) (if (result (ref eq)) (ref.eq (local.get $false-on-non-element?) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:string->path-element) (global.get $string:path-element?) (local.get $str-raw)) (unreachable)) (else (global.get $false)))) ;; bytes->path-element : bytes? [(or/c 'unix 'windows)] [any/c] -> (or/c path-element? #f) ;; Convert bytes to a path element; optional type defaults to the system convention, and false-on-non-element? defaults to #f. (func $bytes->path-element (type $Prim3) (param $bstr-raw (ref eq)) ;; bytes? (param $type-raw (ref eq)) ;; (or/c 'unix 'windows), optional default system (param $false-on-non-element? (ref eq)) ;; any/c, optional default #f (result (ref eq)) (local $bytes (ref $Bytes)) (local $path (ref eq)) (if (i32.eqz (ref.test (ref $Bytes) (local.get $bstr-raw))) (then (call $raise-check-bytes (local.get $bstr-raw)) (unreachable))) (if (ref.eq (local.get $false-on-non-element?) (global.get $missing)) (then (local.set $false-on-non-element? (global.get $false)))) (local.set $bytes (ref.cast (ref $Bytes) (local.get $bstr-raw))) (if (i32.eqz (ref.eq (call $non-empty-bytes-without-nuls (local.get $bytes)) (global.get $true))) (then (if (ref.eq (local.get $false-on-non-element?) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:bytes->path-element) (global.get $string:path-element?) (local.get $bstr-raw)) (unreachable)) (else (return (global.get $false)))))) (local.set $path (call $bytes->path (local.get $bstr-raw) (local.get $type-raw))) (if (ref.eq (call $path-element? (local.get $path)) (global.get $true)) (then (return (local.get $path)))) (if (result (ref eq)) (ref.eq (local.get $false-on-non-element?) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:bytes->path-element) (global.get $string:path-element?) (local.get $bstr-raw)) (unreachable)) (else (global.get $false)))) ;; string->some-system-path : string? (or/c 'unix 'windows) -> path-for-some-system? ;; Encode a string as UTF-8 path bytes for an explicit convention. (func $string->some-system-path (type $Prim2) (param $str-raw (ref eq)) ;; string? (param $kind-raw (ref eq)) ;; (or/c 'unix 'windows) (result (ref eq)) (local $str (ref $String)) (local $bytes (ref eq)) (local $kind (ref $Symbol)) (if (i32.eqz (ref.test (ref $String) (local.get $str-raw))) (then (call $raise-check-string (local.get $str-raw)) (unreachable))) (if (i32.eqz (ref.test (ref $Symbol) (local.get $kind-raw))) (then (call $raise-check-symbol (local.get $kind-raw)) (unreachable))) (local.set $str (ref.cast (ref $String) (local.get $str-raw))) (local.set $kind (ref.cast (ref $Symbol) (local.get $kind-raw))) (if (i32.eqz (ref.eq (call $non-empty-string-without-nuls (local.get $str)) (global.get $true))) (then (call $raise-path-expected (local.get $str-raw)) (unreachable))) (if (i32.eqz (ref.eq (call $unix-or-windows (local.get $kind)) (global.get $true))) (then (call $raise-bytes->path:bad-type (local.get $kind-raw)) (unreachable))) (local.set $bytes (call $string->bytes/utf-8 (local.get $str) (global.get $false) (global.get $false) (global.get $false))) (call $bytes->path (local.get $bytes) (local.get $kind-raw))) (func $path-string->path/checked (param $who (ref eq)) ;; symbol? (currently for diagnostics) (param $path-raw (ref eq)) ;; path-string? (result (ref $Path)) (local $path (ref $Path)) (local $conv (ref eq)) (local $str (ref $String)) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (local.set $conv (struct.get $Path $convention (local.get $path))) (if (i32.eqz (ref.eq (local.get $conv) (global.get $system-path-convention))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (return (local.get $path)))) (if (i32.eqz (ref.test (ref $String) (local.get $path-raw))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (local.set $str (ref.cast (ref $String) (local.get $path-raw))) (if (i32.eqz (ref.eq (call $non-empty-string-without-nuls (local.get $str)) (global.get $true))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (ref.cast (ref $Path) (call $string->path (local.get $str)))) (func $path-bytes-absolute? (param $bytes (ref $Bytes)) (result i32) (local $arr (ref $I8Array)) (local $len i32) (local.set $arr (struct.get $Bytes $bs (local.get $bytes))) (local.set $len (array.len (local.get $arr))) (if (i32.eqz (local.get $len)) (then (return (i32.const 0)))) (i32.eq (array.get_u $I8Array (local.get $arr) (i32.const 0)) (i32.const 47))) (func $absolute-path? (type $Prim1) (param $path-raw (ref eq)) ;; path? string? or path-for-some-system? (result (ref eq)) (local $path (ref null $Path)) (local $bytes (ref $Bytes)) (local $str (ref $String)) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (return (if (result (ref eq)) (call $path-bytes-absolute? (local.get $bytes)) (then (global.get $true)) (else (global.get $false)))))) (if (i32.eqz (ref.test (ref $String) (local.get $path-raw))) (then (return (global.get $false)))) (local.set $str (ref.cast (ref $String) (local.get $path-raw))) (if (i32.eqz (ref.eq (call $non-empty-string-without-nuls (local.get $str)) (global.get $true))) (then (return (global.get $false)))) (local.set $bytes (ref.cast (ref $Bytes) (call $string->bytes/utf-8 (local.get $str) (global.get $false) (global.get $false) (global.get $false)))) (if (result (ref eq)) (call $path-bytes-absolute? (local.get $bytes)) (then (global.get $true)) (else (global.get $false)))) (func $relative-path? (type $Prim1) (param $path-raw (ref eq)) (result (ref eq)) (local $abs? (ref eq)) (local.set $abs? (call $absolute-path? (local.get $path-raw))) (if (result (ref eq)) (ref.eq (local.get $abs?) (global.get $true)) (then (global.get $false)) (else (if (result (ref eq)) (ref.eq (call $path-string? (local.get $path-raw)) (global.get $true)) (then (global.get $true)) (else (global.get $false)))))) (func $complete-path? (type $Prim1) (param $path-raw (ref eq)) (result (ref eq)) ;; In the initial browser/Unix path model, complete and ;; absolute paths are the same. (call $absolute-path? (local.get $path-raw))) (func $path-join-bytes (param $base-raw (ref eq)) ;; path-string? (param $rel-raw (ref eq)) ;; path-string? (result (ref eq)) ;; path? (local $base (ref $Path)) (local $rel (ref $Path)) (local $base-bs (ref $Bytes)) (local $rel-bs (ref $Bytes)) (local $base-arr (ref $I8Array)) (local $rel-arr (ref $I8Array)) (local $base-len i32) (local $rel-len i32) (local $need-sep i32) (local $joined (ref eq)) (local.set $base (call $path-string->path/checked (global.get $symbol:build-path) (local.get $base-raw))) (local.set $rel (call $path-string->path/checked (global.get $symbol:build-path) (local.get $rel-raw))) (local.set $rel-bs (struct.get $Path $bytes (local.get $rel))) (if (call $path-bytes-absolute? (local.get $rel-bs)) (then (call $raise-path-expected (local.get $rel-raw)) (unreachable))) (local.set $base-bs (struct.get $Path $bytes (local.get $base))) (local.set $base-arr (struct.get $Bytes $bs (local.get $base-bs))) (local.set $rel-arr (struct.get $Bytes $bs (local.get $rel-bs))) (local.set $base-len (array.len (local.get $base-arr))) (local.set $rel-len (array.len (local.get $rel-arr))) (local.set $need-sep (i32.const 1)) (if (i32.eqz (local.get $base-len)) (then (local.set $need-sep (i32.const 0)))) (if (i32.eqz (local.get $rel-len)) (then (local.set $need-sep (i32.const 0)))) (if (i32.and (local.get $base-len) (i32.eq (array.get_u $I8Array (local.get $base-arr) (i32.sub (local.get $base-len) (i32.const 1))) (i32.const 47))) (then (local.set $need-sep (i32.const 0)))) (local.set $joined (if (result (ref eq)) (local.get $need-sep) (then (call $bytes-append/2 (call $bytes-append/2 (local.get $base-bs) (global.get $bytes:slash)) (local.get $rel-bs))) (else (call $bytes-append/2 (local.get $base-bs) (local.get $rel-bs))))) (call $bytes->path (local.get $joined) (global.get $missing))) (func $build-path (type $Prim>=1) (param $first (ref eq)) ;; path part (param $rest (ref eq)) ;; list of additional path parts (result (ref eq)) (local $first-path (ref $Path)) (local $conv (ref eq)) (local $node (ref $Pair)) (local $result (ref eq)) (local $next (ref eq)) (local.set $conv (global.get $system-path-convention)) (if (ref.test (ref $Path) (local.get $first)) (then (local.set $first-path (ref.cast (ref $Path) (local.get $first))) (local.set $conv (struct.get $Path $convention (local.get $first-path))))) (local.set $result (call $path-part->path/convention (global.get $symbol:build-path) (local.get $conv) (local.get $first))) (block $done (loop $loop (br_if $done (ref.eq (local.get $rest) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $rest))) (local.set $next (struct.get $Pair $a (local.get $node))) (local.set $result (call $path-join-bytes/convention (global.get $symbol:build-path) (local.get $conv) (local.get $result) (local.get $next))) (local.set $rest (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $result)) (func $path-part->path/convention (param $who (ref eq)) ;; symbol? (param $conv (ref eq)) ;; (or/c 'unix 'windows) (param $part-raw (ref eq)) ;; path-string?, path-for-some-system?, 'up, or 'same (result (ref $Path)) (local $path (ref $Path)) (local $str (ref $String)) (if (ref.eq (local.get $part-raw) (global.get $symbol:same)) (then (return (ref.cast (ref $Path) (call $bytes->path (global.get $bytes:dot) (local.get $conv)))))) (if (ref.eq (local.get $part-raw) (global.get $symbol:up)) (then (return (ref.cast (ref $Path) (call $bytes->path (global.get $bytes:dot-dot) (local.get $conv)))))) (if (ref.test (ref $Path) (local.get $part-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $part-raw))) (if (i32.eqz (ref.eq (struct.get $Path $convention (local.get $path)) (local.get $conv))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:path-for-some-system?) (local.get $part-raw)) (unreachable))) (return (local.get $path)))) (if (i32.eqz (ref.test (ref $String) (local.get $part-raw))) (then (call $raise-path-expected (local.get $part-raw)) (unreachable))) (local.set $str (ref.cast (ref $String) (local.get $part-raw))) (if (i32.eqz (ref.eq (call $non-empty-string-without-nuls (local.get $str)) (global.get $true))) (then (call $raise-path-expected (local.get $part-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (call $string->path (local.get $str)))) (if (i32.eqz (ref.eq (struct.get $Path $convention (local.get $path)) (local.get $conv))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:path-for-some-system?) (local.get $part-raw)) (unreachable))) (local.get $path)) (func $path-join-bytes/convention (param $who (ref eq)) ;; symbol? (param $conv (ref eq)) ;; (or/c 'unix 'windows) (param $base-raw (ref eq)) ;; path-string?, path-for-some-system?, 'up, or 'same (param $rel-raw (ref eq)) ;; relative path-string?, path-for-some-system?, 'up, or 'same (result (ref eq)) ;; path-for-some-system? (local $base (ref $Path)) (local $rel (ref $Path)) (local $base-bs (ref $Bytes)) (local $rel-bs (ref $Bytes)) (local $sep-bs (ref $Bytes)) (local $base-arr (ref $I8Array)) (local $rel-arr (ref $I8Array)) (local $base-len i32) (local $rel-len i32) (local $need-sep i32) (local $joined (ref eq)) (local.set $base (call $path-part->path/convention (local.get $who) (local.get $conv) (local.get $base-raw))) (local.set $rel (call $path-part->path/convention (local.get $who) (local.get $conv) (local.get $rel-raw))) (local.set $rel-bs (struct.get $Path $bytes (local.get $rel))) (if (call $path-bytes-absolute? (local.get $rel-bs)) (then (call $raise-path-expected (local.get $rel-raw)) (unreachable))) (local.set $base-bs (struct.get $Path $bytes (local.get $base))) (local.set $sep-bs (if (result (ref $Bytes)) (ref.eq (local.get $conv) (global.get $symbol:windows)) (then (ref.cast (ref $Bytes) (global.get $bytes:backslash))) (else (ref.cast (ref $Bytes) (global.get $bytes:slash))))) (local.set $base-arr (struct.get $Bytes $bs (local.get $base-bs))) (local.set $rel-arr (struct.get $Bytes $bs (local.get $rel-bs))) (local.set $base-len (array.len (local.get $base-arr))) (local.set $rel-len (array.len (local.get $rel-arr))) (local.set $need-sep (i32.const 1)) (if (i32.eqz (local.get $base-len)) (then (local.set $need-sep (i32.const 0)))) (if (i32.eqz (local.get $rel-len)) (then (local.set $need-sep (i32.const 0)))) (if (local.get $base-len) (then (if (call $path-byte-separator? (array.get_u $I8Array (local.get $base-arr) (i32.sub (local.get $base-len) (i32.const 1))) (local.get $conv)) (then (local.set $need-sep (i32.const 0)))))) (local.set $joined (if (result (ref eq)) (local.get $need-sep) (then (call $bytes-append/2 (call $bytes-append/2 (local.get $base-bs) (local.get $sep-bs)) (local.get $rel-bs))) (else (call $bytes-append/2 (local.get $base-bs) (local.get $rel-bs))))) (call $bytes->path (local.get $joined) (local.get $conv))) ;; build-path/convention-type : (or/c 'unix 'windows) path-part ... -> path-for-some-system? ;; Build a path under an explicit convention; supports 'same and 'up path elements. (func $build-path/convention-type (type $Prim>=2) (param $type-raw (ref eq)) ;; (or/c 'unix 'windows) (param $first (ref eq)) ;; path part (param $rest (ref eq)) ;; list of additional path parts (result (ref eq)) (local $node (ref $Pair)) (local $type (ref $Symbol)) (local $result (ref eq)) (local $next (ref eq)) (if (i32.eqz (ref.test (ref $Symbol) (local.get $type-raw))) (then (call $raise-check-symbol (local.get $type-raw)) (unreachable))) (local.set $type (ref.cast (ref $Symbol) (local.get $type-raw))) (if (i32.eqz (ref.eq (call $unix-or-windows (local.get $type)) (global.get $true))) (then (call $raise-bytes->path:bad-type (local.get $type-raw)) (unreachable))) (local.set $result (call $path-part->path/convention (global.get $symbol:build-path/convention-type) (local.get $type-raw) (local.get $first))) (block $done (loop $loop (br_if $done (ref.eq (local.get $rest) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $rest))) (local.set $next (struct.get $Pair $a (local.get $node))) (local.set $result (call $path-join-bytes/convention (global.get $symbol:build-path/convention-type) (local.get $type-raw) (local.get $result) (local.get $next))) (local.set $rest (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $result)) ;; find-system-path : symbol? -> path? ;; Return stable complete paths in WebRacket's virtual filesystem. (func $find-system-path (type $Prim1) (param $kind-raw (ref eq)) ;; symbol? (result (ref eq)) (local $kind (ref $Symbol)) (if (i32.eqz (ref.test (ref $Symbol) (local.get $kind-raw))) (then (call $raise-argument-error1 (global.get $symbol:find-system-path) (global.get $string:symbol?) (local.get $kind-raw)) (unreachable))) (local.set $kind (ref.cast (ref $Symbol) (local.get $kind-raw))) (if (ref.eq (local.get $kind) (global.get $symbol:home-dir)) (then (return (call $bytes->path (global.get $bytes:vfs-home-dir) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:pref-dir)) (then (return (call $bytes->path (global.get $bytes:vfs-pref-dir) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:pref-file)) (then (return (call $bytes->path (global.get $bytes:vfs-pref-file) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:temp-dir)) (then (return (call $bytes->path (global.get $bytes:vfs-temp-dir) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:init-dir)) (then (return (call $bytes->path (global.get $bytes:vfs-home-dir) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:init-file)) (then (return (call $bytes->path (global.get $bytes:vfs-init-file) (global.get $missing))))) (if (i32.or (ref.eq (local.get $kind) (global.get $symbol:config-dir)) (ref.eq (local.get $kind) (global.get $symbol:host-config-dir))) (then (return (call $bytes->path (global.get $bytes:vfs-config-dir) (global.get $missing))))) (if (i32.or (ref.eq (local.get $kind) (global.get $symbol:addon-dir)) (ref.eq (local.get $kind) (global.get $symbol:host-addon-dir))) (then (return (call $bytes->path (global.get $bytes:vfs-addon-dir) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:cache-dir)) (then (return (call $bytes->path (global.get $bytes:vfs-cache-dir) (global.get $missing))))) (if (i32.or (ref.eq (local.get $kind) (global.get $symbol:doc-dir)) (ref.eq (local.get $kind) (global.get $symbol:desk-dir))) (then (return (call $bytes->path (global.get $bytes:vfs-home-dir) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:sys-dir)) (then (return (call $bytes->path (global.get $bytes:vfs-root) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:exec-file)) (then (return (call $bytes->path (global.get $bytes:vfs-exec-file) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:run-file)) (then (return (call $bytes->path (global.get $bytes:vfs-run-file) (global.get $missing))))) (if (i32.or (ref.eq (local.get $kind) (global.get $symbol:collects-dir)) (ref.eq (local.get $kind) (global.get $symbol:host-collects-dir))) (then (return (call $bytes->path (global.get $bytes:vfs-collects-dir) (global.get $missing))))) (if (ref.eq (local.get $kind) (global.get $symbol:orig-dir)) (then (return (call $bytes->path (global.get $bytes:app-dir) (global.get $missing))))) (call $raise-argument-error1 (global.get $symbol:find-system-path) (global.get $string:system-path-kind) (local.get $kind-raw)) (unreachable)) ;; current-drive : -> path? ;; Return the current VFS drive/root for WebRacket's Unix-style paths. (func $current-drive (type $Prim0) (result (ref eq)) (call $bytes->path (global.get $bytes:vfs-root) (global.get $missing))) (func $current-directory (type $Prim01) (param $path-raw (ref eq)) ;; optional path-string? (result (ref eq)) (local $path (ref $Path)) (if (ref.eq (local.get $path-raw) (global.get $missing)) (then (return (global.get $current-directory-path)))) (local.set $path (call $path-string->path/checked (global.get $symbol:current-directory) (local.get $path-raw))) (if (i32.eqz (call $path-bytes-absolute? (struct.get $Path $bytes (local.get $path)))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (global.set $current-directory-path (local.get $path)) (global.get $void)) (func $current-directory-for-user (type $Prim01) (param $path-raw (ref eq)) ;; optional path-string? (result (ref eq)) (local $path (ref $Path)) (if (ref.eq (local.get $path-raw) (global.get $missing)) (then (return (global.get $current-directory-for-user-path)))) (local.set $path (call $path-string->path/checked (global.get $symbol:current-directory-for-user) (local.get $path-raw))) (if (i32.eqz (call $path-bytes-absolute? (struct.get $Path $bytes (local.get $path)))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (global.set $current-directory-for-user-path (local.get $path)) (global.get $void)) (func $path->complete-path (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $base-raw (ref eq)) ;; optional path-string?, default current-directory (result (ref eq)) ;; path? (local $path (ref $Path)) (local $base (ref eq)) (local.set $path (call $path-string->path/checked (global.get $symbol:path->complete-path) (local.get $path-raw))) (if (call $path-bytes-absolute? (struct.get $Path $bytes (local.get $path))) (then (return (local.get $path)))) (local.set $base (if (result (ref eq)) (ref.eq (local.get $base-raw) (global.get $missing)) (then (global.get $current-directory-path)) (else (local.get $base-raw)))) (call $path-join-bytes (local.get $base) (local.get $path))) ;; path->directory-path : (or/c path-string? path-for-some-system?) -> path-for-some-system? ;; Ensure a path syntactically ends in a convention-specific directory separator. (func $path->directory-path (type $Prim1) (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (result (ref eq)) (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $arr (ref $I8Array)) (local $conv (ref eq)) (local $sep (ref $Bytes)) (local $len i32) (local.set $path (ref.cast (ref $Path) (global.get $current-directory-path))) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw)))) (else (local.set $path (call $path-string->path/checked (global.get $symbol:path->directory-path) (local.get $path-raw))))) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (local.set $arr (struct.get $Bytes $bs (local.get $bytes))) (local.set $len (array.len (local.get $arr))) (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.sub (local.get $len) (i32.const 1))) (local.get $conv)) (then (return (local.get $path)))) (local.set $sep (if (result (ref $Bytes)) (ref.eq (local.get $conv) (global.get $symbol:windows)) (then (ref.cast (ref $Bytes) (global.get $bytes:backslash))) (else (ref.cast (ref $Bytes) (global.get $bytes:slash))))) (call $bytes->path (call $bytes-append/2 (local.get $bytes) (local.get $sep)) (local.get $conv))) ;; cleanse-path : (or/c path-string? path-for-some-system?) -> path-for-some-system? ;; Remove redundant consecutive separators without consulting the filesystem. (func $cleanse-path (type $Prim1) (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (result (ref eq)) (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $arr (ref $I8Array)) (local $out (ref $GrowableBytes)) (local $conv (ref eq)) (local $len i32) (local $i i32) (local $b i32) (local $prev-sep? i32) (local $sep? i32) (local.set $path (ref.cast (ref $Path) (global.get $current-directory-path))) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw)))) (else (local.set $path (call $path-string->path/checked (global.get $symbol:cleanse-path) (local.get $path-raw))))) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (local.set $arr (struct.get $Bytes $bs (local.get $bytes))) (local.set $len (array.len (local.get $arr))) (local.set $out (call $make-growable-bytes (local.get $len))) (local.set $i (i32.const 0)) (local.set $prev-sep? (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $b (array.get_u $I8Array (local.get $arr) (local.get $i))) (local.set $sep? (call $path-byte-separator? (local.get $b) (local.get $conv))) (if (i32.and (local.get $sep?) (local.get $prev-sep?)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (call $growable-bytes-add! (local.get $out) (local.get $b)) (local.set $prev-sep? (local.get $sep?)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (call $bytes->path (call $growable-bytes->bytes (local.get $out)) (local.get $conv))) ;; resolve-path : path-string? -> path? ;; Return the validated VFS path; soft links and full cleansing are not modeled yet. (func $resolve-path (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (call $path-string->path/checked (global.get $symbol:resolve-path) (local.get $path-raw))) (func $path-root-element? (param $path (ref $Path)) (result i32) (local $bytes (ref $Bytes)) (local $arr (ref $I8Array)) (local $conv (ref eq)) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (local.set $arr (struct.get $Bytes $bs (local.get $bytes))) (if (i32.ne (array.len (local.get $arr)) (i32.const 1)) (then (return (i32.const 0)))) (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.const 0)) (local.get $conv))) ;; simplify-path : (or/c path-string? path-for-some-system?) [boolean?] -> path-for-some-system? ;; Syntactically remove redundant separators, "." elements, and cancellable ".." elements. ;; The optional use-filesystem? argument defaults to #t but is currently ignored: ;; WebRacket does not model filesystem or link resolution here yet. (func $simplify-path (type $Prim12) (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (param $use-filesystem? (ref eq)) ;; boolean?, optional default #t; currently ignored (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $conv (ref eq)) (local $parts (ref eq)) (local $stack (ref eq)) (local $ordered (ref eq)) (local $node (ref $Pair)) (local $top-node (ref $Pair)) (local $part (ref eq)) (local $top (ref eq)) (local $result (ref eq)) (local $dir? i32) (local.set $path (ref.cast (ref $Path) (global.get $current-directory-path))) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw)))) (else (local.set $path (call $path-string->path/checked (global.get $symbol:simplify-path) (local.get $path-raw))))) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $dir? (call $path-syntactic-directory? (local.get $path-bs) (local.get $conv))) (local.set $parts (call $explode-path (local.get $path))) (local.set $stack (global.get $null)) (block $done (loop $loop (br_if $done (ref.eq (local.get $parts) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $parts))) (local.set $part (struct.get $Pair $a (local.get $node))) (if (ref.eq (local.get $part) (global.get $symbol:same)) (then (local.set $parts (struct.get $Pair $d (local.get $node))) (br $loop))) (if (ref.eq (local.get $part) (global.get $symbol:up)) (then (if (ref.eq (local.get $stack) (global.get $null)) (then (local.set $stack (struct.new $Pair (i32.const 0) (local.get $part) (local.get $stack)))) (else (local.set $top-node (ref.cast (ref $Pair) (local.get $stack))) (local.set $top (struct.get $Pair $a (local.get $top-node))) (if (ref.test (ref $Path) (local.get $top)) (then (if (call $path-root-element? (ref.cast (ref $Path) (local.get $top))) (then) (else (local.set $stack (struct.get $Pair $d (local.get $top-node)))))) (else (local.set $stack (struct.new $Pair (i32.const 0) (local.get $part) (local.get $stack)))))))) (else (local.set $stack (struct.new $Pair (i32.const 0) (local.get $part) (local.get $stack))))) (local.set $parts (struct.get $Pair $d (local.get $node))) (br $loop))) (local.set $ordered (call $reverse (local.get $stack))) (local.set $result (call $path-part->path/convention (global.get $symbol:simplify-path) (local.get $conv) (global.get $symbol:same))) (if (ref.eq (local.get $ordered) (global.get $null)) (then) (else (local.set $node (ref.cast (ref $Pair) (local.get $ordered))) (local.set $result (call $path-part->path/convention (global.get $symbol:simplify-path) (local.get $conv) (struct.get $Pair $a (local.get $node)))) (local.set $ordered (struct.get $Pair $d (local.get $node))) (block $build-done (loop $build-loop (br_if $build-done (ref.eq (local.get $ordered) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $ordered))) (local.set $result (call $path-join-bytes/convention (global.get $symbol:simplify-path) (local.get $conv) (local.get $result) (struct.get $Pair $a (local.get $node)))) (local.set $ordered (struct.get $Pair $d (local.get $node))) (br $build-loop))))) (if (local.get $dir?) (then (return (call $path->directory-path (local.get $result))))) (local.get $result)) ;; simple-form-path : path-string? -> path? ;; Complete a path, then simplify it syntactically. (func $simple-form-path (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (call $simplify-path (call $path->complete-path (local.get $path-raw) (global.get $missing)) (global.get $true))) ;; normalize-path : path-string? [complete-path?] -> path? ;; Complete a path relative to wrt, then simplify syntactically. ;; WebRacket does not resolve VFS links here yet. (func $normalize-path (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $wrt-raw (ref eq)) ;; complete path-string?, optional default current-directory (result (ref eq)) (local $wrt (ref eq)) (local $wrt-path (ref $Path)) (local.set $wrt (global.get $current-directory-path)) (if (i32.eqz (ref.eq (local.get $wrt-raw) (global.get $missing))) (then (local.set $wrt-path (call $path-string->path/checked (global.get $symbol:normalize-path) (local.get $wrt-raw))) (if (i32.eqz (call $path-bytes-absolute? (struct.get $Path $bytes (local.get $wrt-path)))) (then (call $raise-path-expected (local.get $wrt-raw)) (unreachable))) (local.set $wrt (local.get $wrt-path)))) (call $simplify-path (call $path->complete-path (local.get $path-raw) (local.get $wrt)) (global.get $true))) (func $path-part-equal? (param $a (ref eq)) (param $b (ref eq)) (result i32) (if (ref.eq (local.get $a) (local.get $b)) (then (return (i32.const 1)))) (if (i32.eqz (ref.test (ref $Path) (local.get $a))) (then (return (i32.const 0)))) (if (i32.eqz (ref.test (ref $Path) (local.get $b))) (then (return (i32.const 0)))) (ref.eq (call $bytes=?/2/checked (struct.get $Path $bytes (ref.cast (ref $Path) (local.get $a))) (struct.get $Path $bytes (ref.cast (ref $Path) (local.get $b)))) (global.get $true))) (func $path-parts-suffix-diff-size (param $a (ref eq)) ;; reversed path parts (param $b (ref eq)) ;; reversed path parts (result i32) (local $a-node (ref $Pair)) (local $b-node (ref $Pair)) (local $size i32) (local.set $size (i32.const 1)) (block $done (loop $loop (br_if $done (ref.eq (local.get $a) (global.get $null))) (br_if $done (ref.eq (local.get $b) (global.get $null))) (local.set $a-node (ref.cast (ref $Pair) (local.get $a))) (local.set $b-node (ref.cast (ref $Pair) (local.get $b))) (br_if $done (i32.eqz (call $path-part-equal? (struct.get $Pair $a (local.get $a-node)) (struct.get $Pair $a (local.get $b-node))))) (local.set $a (struct.get $Pair $d (local.get $a-node))) (local.set $b (struct.get $Pair $d (local.get $b-node))) (local.set $size (i32.add (local.get $size) (i32.const 1))) (br $loop))) (local.get $size)) (func $path-parts-equal? (param $a (ref eq)) ;; reversed path parts (param $b (ref eq)) ;; reversed path parts (result i32) (local $a-node (ref $Pair)) (local $b-node (ref $Pair)) (block $done (loop $loop (if (ref.eq (local.get $a) (global.get $null)) (then (return (if (result i32) (ref.eq (local.get $b) (global.get $null)) (then (i32.const 1)) (else (i32.const 0)))))) (if (ref.eq (local.get $b) (global.get $null)) (then (return (i32.const 0)))) (local.set $a-node (ref.cast (ref $Pair) (local.get $a))) (local.set $b-node (ref.cast (ref $Pair) (local.get $b))) (if (i32.eqz (call $path-part-equal? (struct.get $Pair $a (local.get $a-node)) (struct.get $Pair $a (local.get $b-node)))) (then (return (i32.const 0)))) (local.set $a (struct.get $Pair $d (local.get $a-node))) (local.set $b (struct.get $Pair $d (local.get $b-node))) (br $loop))) (i32.const 1)) (func $build-path-from-reversed-prefix (param $who (ref eq)) ;; symbol? (param $conv (ref eq)) ;; (or/c 'unix 'windows) (param $parts-rev (ref eq)) ;; reversed path parts (param $size i32) (result (ref eq)) (local $i i32) (local $prefix (ref eq)) (local $node (ref $Pair)) (local $result (ref eq)) (local.set $i (i32.const 0)) (local.set $prefix (global.get $null)) (block $take-done (loop $take-loop (br_if $take-done (i32.ge_u (local.get $i) (local.get $size))) (br_if $take-done (ref.eq (local.get $parts-rev) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $parts-rev))) (local.set $prefix (struct.new $Pair (i32.const 0) (struct.get $Pair $a (local.get $node)) (local.get $prefix))) (local.set $parts-rev (struct.get $Pair $d (local.get $node))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $take-loop))) (local.set $node (ref.cast (ref $Pair) (local.get $prefix))) (local.set $result (call $path-part->path/convention (local.get $who) (local.get $conv) (struct.get $Pair $a (local.get $node)))) (local.set $prefix (struct.get $Pair $d (local.get $node))) (block $build-done (loop $build-loop (br_if $build-done (ref.eq (local.get $prefix) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $prefix))) (local.set $result (call $path-join-bytes/convention (local.get $who) (local.get $conv) (local.get $result) (struct.get $Pair $a (local.get $node)))) (local.set $prefix (struct.get $Pair $d (local.get $node))) (br $build-loop))) (local.get $result)) ;; find-relative-path : (or/c path-string? path-for-some-system?) (or/c path-string? path-for-some-system?) [any/c] [any/c] [any/c] -> path-string? ;; Keywordless form of Racket's #:more-than-root? (default #f), ;; #:more-than-same? (default #t), and #:normalize-case? (default #t). (func $find-relative-path (type $Prim25) (param $base-raw (ref eq)) ;; path-string? or path-for-some-system? (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (param $more-than-root-raw (ref eq)) ;; optional any/c, default #f (param $more-than-same-raw (ref eq)) ;; optional any/c, default #t (param $normalize-case-raw (ref eq)) ;; optional any/c, default #t (result (ref eq)) (local $base-path (ref $Path)) (local $path (ref $Path)) (local $conv (ref eq)) (local $base (ref eq)) (local $target (ref eq)) (local $base-node (ref $Pair)) (local $path-node (ref $Pair)) (local $part (ref eq)) (local $result (ref eq)) (local $has? i32) (local $common-count i32) (local $common-root? i32) (local $more-than-root? i32) (local $more-than-same? i32) (local $normalize-case? i32) (local $original-result (ref eq)) (local.set $base-path (call $path-string->path/checked (global.get $symbol:find-relative-path) (local.get $base-raw))) (local.set $path (call $path-string->path/checked (global.get $symbol:find-relative-path) (local.get $path-raw))) (local.set $conv (struct.get $Path $convention (local.get $base-path))) (if (i32.eqz (ref.eq (local.get $conv) (struct.get $Path $convention (local.get $path)))) (then (call $raise-argument-error1 (global.get $symbol:find-relative-path) (global.get $string:path-for-some-system?) (local.get $path-raw)) (unreachable))) (local.set $more-than-root? (i32.and (i32.eqz (ref.eq (local.get $more-than-root-raw) (global.get $missing))) (i32.eqz (ref.eq (local.get $more-than-root-raw) (global.get $false))))) (local.set $more-than-same? (i32.or (ref.eq (local.get $more-than-same-raw) (global.get $missing)) (i32.eqz (ref.eq (local.get $more-than-same-raw) (global.get $false))))) (local.set $normalize-case? (i32.or (ref.eq (local.get $normalize-case-raw) (global.get $missing)) (i32.eqz (ref.eq (local.get $normalize-case-raw) (global.get $false))))) (local.set $original-result (if (result (ref eq)) (ref.test (ref $String) (local.get $path-raw)) (then (local.get $path-raw)) (else (local.get $path)))) (local.set $base (call $explode-path (if (result (ref eq)) (local.get $normalize-case?) (then (call $normal-case-path (local.get $base-path))) (else (local.get $base-path))))) (local.set $target (call $explode-path (if (result (ref eq)) (local.get $normalize-case?) (then (call $normal-case-path (local.get $path))) (else (local.get $path))))) (block $common-done (loop $common-loop (br_if $common-done (ref.eq (local.get $base) (global.get $null))) (br_if $common-done (ref.eq (local.get $target) (global.get $null))) (local.set $base-node (ref.cast (ref $Pair) (local.get $base))) (local.set $path-node (ref.cast (ref $Pair) (local.get $target))) (br_if $common-done (i32.eqz (call $path-part-equal? (struct.get $Pair $a (local.get $base-node)) (struct.get $Pair $a (local.get $path-node))))) (local.set $part (struct.get $Pair $a (local.get $base-node))) (if (i32.and (i32.eqz (local.get $common-count)) (ref.test (ref $Path) (local.get $part))) (then (local.set $common-root? (call $path-bytes-absolute? (struct.get $Path $bytes (ref.cast (ref $Path) (local.get $part))))))) (local.set $common-count (i32.add (local.get $common-count) (i32.const 1))) (local.set $base (struct.get $Pair $d (local.get $base-node))) (local.set $target (struct.get $Pair $d (local.get $path-node))) (br $common-loop))) (if (i32.and (i32.and (local.get $more-than-root?) (i32.and (local.get $common-root?) (i32.eq (local.get $common-count) (i32.const 1)))) (i32.and (i32.eqz (ref.eq (local.get $base) (global.get $null))) (i32.eqz (ref.eq (local.get $target) (global.get $null))))) (then (return (local.get $original-result)))) (if (i32.and (i32.and (local.get $more-than-same?) (ref.eq (local.get $base) (global.get $null))) (ref.eq (local.get $target) (global.get $null))) (then (return (local.get $original-result)))) (local.set $has? (i32.const 0)) (local.set $result (global.get $symbol:same)) (block $base-done (loop $base-loop (br_if $base-done (ref.eq (local.get $base) (global.get $null))) (local.set $base-node (ref.cast (ref $Pair) (local.get $base))) (if (local.get $has?) (then (local.set $result (call $path-join-bytes/convention (global.get $symbol:find-relative-path) (local.get $conv) (local.get $result) (global.get $symbol:up)))) (else (local.set $result (call $path-part->path/convention (global.get $symbol:find-relative-path) (local.get $conv) (global.get $symbol:up))) (local.set $has? (i32.const 1)))) (local.set $base (struct.get $Pair $d (local.get $base-node))) (br $base-loop))) (block $target-done (loop $target-loop (br_if $target-done (ref.eq (local.get $target) (global.get $null))) (local.set $path-node (ref.cast (ref $Pair) (local.get $target))) (local.set $part (struct.get $Pair $a (local.get $path-node))) (if (local.get $has?) (then (local.set $result (call $path-join-bytes/convention (global.get $symbol:find-relative-path) (local.get $conv) (local.get $result) (local.get $part)))) (else (local.set $result (call $path-part->path/convention (global.get $symbol:find-relative-path) (local.get $conv) (local.get $part))) (local.set $has? (i32.const 1)))) (local.set $target (struct.get $Pair $d (local.get $path-node))) (br $target-loop))) (if (local.get $has?) (then (return (local.get $result)))) (call $path-part->path/convention (global.get $symbol:find-relative-path) (local.get $conv) (global.get $symbol:same))) ;; shrink-path-wrt : path? (listof path?) -> (or/c #f path?) ;; Return the shortest suffix that distinguishes path from the non-identical other paths. (func $shrink-path-wrt (type $Prim2) (param $path-raw (ref eq)) ;; path? (param $others (ref eq)) ;; listof path? (result (ref eq)) (local $path (ref $Path)) (local $other (ref $Path)) (local $conv (ref eq)) (local $path-rev (ref eq)) (local $other-rev (ref eq)) (local $node (ref $Pair)) (local $diff i32) (local $size i32) (local $any? i32) (if (i32.eqz (ref.test (ref $Path) (local.get $path-raw))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (local.set $conv (struct.get $Path $convention (local.get $path))) (if (i32.eqz (ref.eq (local.get $conv) (global.get $system-path-convention))) (then (call $raise-argument-error1 (global.get $symbol:shrink-path-wrt) (global.get $string:path?) (local.get $path-raw)) (unreachable))) (local.set $path-rev (call $reverse (call $explode-path (local.get $path)))) (local.set $size (i32.const 1)) (local.set $any? (i32.const 0)) (block $done (loop $loop (br_if $done (ref.eq (local.get $others) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $others))) (if (i32.eqz (ref.test (ref $Path) (struct.get $Pair $a (local.get $node)))) (then (call $raise-argument-error1 (global.get $symbol:shrink-path-wrt) (global.get $string:listof-path?) (local.get $others)) (unreachable))) (local.set $other (ref.cast (ref $Path) (struct.get $Pair $a (local.get $node)))) (if (i32.eqz (ref.eq (struct.get $Path $convention (local.get $other)) (global.get $system-path-convention))) (then (call $raise-argument-error1 (global.get $symbol:shrink-path-wrt) (global.get $string:listof-path?) (local.get $others)) (unreachable))) (local.set $other-rev (call $reverse (call $explode-path (local.get $other)))) (if (i32.eqz (call $path-parts-equal? (local.get $path-rev) (local.get $other-rev))) (then (local.set $any? (i32.const 1)) (local.set $diff (call $path-parts-suffix-diff-size (local.get $other-rev) (local.get $path-rev))) (if (i32.gt_u (local.get $diff) (local.get $size)) (then (local.set $size (local.get $diff)))))) (local.set $others (struct.get $Pair $d (local.get $node))) (br $loop))) (if (i32.eqz (local.get $any?)) (then (return (global.get $false)))) (call $build-path-from-reversed-prefix (global.get $symbol:shrink-path-wrt) (local.get $conv) (local.get $path-rev) (local.get $size))) ;; normal-case-path : (or/c path-string? path-for-some-system?) -> path-for-some-system? ;; Return Unix paths unchanged; for Windows, lowercase ASCII letters and use backslash separators. (func $normal-case-path (type $Prim1) (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (result (ref eq)) (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $src (ref $I8Array)) (local $dst (ref $I8Array)) (local $conv (ref eq)) (local $len i32) (local $i i32) (local $b i32) (local.set $path (ref.cast (ref $Path) (global.get $current-directory-path))) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw)))) (else (local.set $path (call $path-string->path/checked (global.get $symbol:normal-case-path) (local.get $path-raw))))) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (if (i32.eqz (ref.eq (local.get $conv) (global.get $symbol:windows))) (then (return (local.get $path)))) (local.set $src (struct.get $Bytes $bs (local.get $bytes))) (local.set $len (array.len (local.get $src))) (local.set $dst (array.new $I8Array (i32.const 0) (local.get $len))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $b (array.get_u $I8Array (local.get $src) (local.get $i))) (if (i32.eq (local.get $b) (i32.const 47)) (then (local.set $b (i32.const 92)))) (if (i32.and (i32.ge_u (local.get $b) (i32.const 65)) (i32.le_u (local.get $b) (i32.const 90))) (then (local.set $b (i32.add (local.get $b) (i32.const 32))))) (array.set $I8Array (local.get $dst) (local.get $i) (local.get $b)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (call $bytes->path (call $i8array->immutable-bytes (local.get $dst)) (local.get $conv))) ;; reroot-path : (or/c path-string? path-for-some-system?) (or/c path-string? path-for-some-system?) -> path-for-some-system? ;; Append the complete, cleansed, case-normalized path elements under root-path. ;; Windows drive and UNC rerooting are not modeled beyond the represented path elements. (func $reroot-path (type $Prim2) (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (param $root-raw (ref eq)) ;; path-string? or path-for-some-system? (result (ref eq)) (local $root (ref $Path)) (local $conv (ref eq)) (local $parts (ref eq)) (local $node (ref $Pair)) (local $part (ref eq)) (local $result (ref eq)) (local.set $root (ref.cast (ref $Path) (global.get $current-directory-path))) (if (ref.test (ref $Path) (local.get $root-raw)) (then (local.set $root (ref.cast (ref $Path) (local.get $root-raw)))) (else (local.set $root (call $path-string->path/checked (global.get $symbol:reroot-path) (local.get $root-raw))))) (local.set $conv (struct.get $Path $convention (local.get $root))) (local.set $result (local.get $root)) (local.set $parts (call $explode-path (call $normal-case-path (call $cleanse-path (call $path->complete-path (local.get $path-raw) (global.get $missing)))))) (if (i32.eqz (ref.eq (local.get $parts) (global.get $null))) (then (local.set $node (ref.cast (ref $Pair) (local.get $parts))) (local.set $part (struct.get $Pair $a (local.get $node))) (if (ref.test (ref $Path) (local.get $part)) (then (if (call $path-root-element? (ref.cast (ref $Path) (local.get $part))) (then (local.set $parts (struct.get $Pair $d (local.get $node))))))))) (block $done (loop $loop (br_if $done (ref.eq (local.get $parts) (global.get $null))) (local.set $node (ref.cast (ref $Pair) (local.get $parts))) (local.set $part (struct.get $Pair $a (local.get $node))) (local.set $result (call $path-join-bytes/convention (global.get $symbol:reroot-path) (local.get $conv) (local.get $result) (local.get $part))) (local.set $parts (struct.get $Pair $d (local.get $node))) (br $loop))) (local.get $result)) (func $string-or-bytes->bytes/checked (param $who (ref eq)) ;; symbol? (param $v (ref eq)) ;; (or/c string? bytes?) (result (ref $Bytes)) (if (ref.test (ref $Bytes) (local.get $v)) (then (return (ref.cast (ref $Bytes) (local.get $v))))) (if (ref.test (ref $String) (local.get $v)) (then (return (ref.cast (ref $Bytes) (call $string->bytes/utf-8 (ref.cast (ref $String) (local.get $v)) (global.get $false) (global.get $false) (global.get $false)))))) (call $raise-argument-error1 (local.get $who) (global.get $string:string-or-bytes) (local.get $v)) (unreachable)) (func $bytes-slice/unchecked (param $bytes (ref $Bytes)) (param $start i32) (param $end i32) (result (ref $Bytes)) (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8array-copy (struct.get $Bytes $bs (local.get $bytes)) (local.get $start) (local.get $end)))) (func $path-extension-start (param $path-bs (ref $Bytes)) (result i32) (local $arr (ref $I8Array)) (local $len i32) (local $elem-start i32) (local $i i32) (local $idx i32) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $len (array.len (local.get $arr))) (local.set $elem-start (i32.const 0)) (local.set $i (local.get $len)) (block $slash-done (loop $slash-loop (br_if $slash-done (i32.eqz (local.get $i))) (local.set $idx (i32.sub (local.get $i) (i32.const 1))) (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $idx)) (i32.const 47)) (then (local.set $elem-start (local.get $i)) (br $slash-done))) (local.set $i (local.get $idx)) (br $slash-loop))) (if (i32.eq (local.get $elem-start) (local.get $len)) (then (return (local.get $len)))) (if (i32.eq (i32.sub (local.get $len) (local.get $elem-start)) (i32.const 2)) (then (if (i32.and (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $elem-start)) (i32.const 46)) (i32.eq (array.get_u $I8Array (local.get $arr) (i32.add (local.get $elem-start) (i32.const 1))) (i32.const 46))) (then (return (local.get $len)))))) (local.set $i (local.get $len)) (block $dot-done (loop $dot-loop (br_if $dot-done (i32.le_u (local.get $i) (local.get $elem-start))) (local.set $idx (i32.sub (local.get $i) (i32.const 1))) (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $idx)) (i32.const 46)) (then (if (i32.gt_u (local.get $idx) (local.get $elem-start)) (then (return (local.get $idx))) (else (return (local.get $len)))))) (local.set $i (local.get $idx)) (br $dot-loop))) (local.get $len)) (func $path-suffix-start (param $path-bs (ref $Bytes)) (result i32) (local $arr (ref $I8Array)) (local $len i32) (local $elem-start i32) (local $i i32) (local $idx i32) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $len (array.len (local.get $arr))) (local.set $elem-start (i32.const 0)) (local.set $i (local.get $len)) (block $slash-done (loop $slash-loop (br_if $slash-done (i32.eqz (local.get $i))) (local.set $idx (i32.sub (local.get $i) (i32.const 1))) (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $idx)) (i32.const 47)) (then (local.set $elem-start (local.get $i)) (br $slash-done))) (local.set $i (local.get $idx)) (br $slash-loop))) (if (i32.eq (local.get $elem-start) (local.get $len)) (then (return (local.get $len)))) (if (i32.eq (i32.sub (local.get $len) (local.get $elem-start)) (i32.const 2)) (then (if (i32.and (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $elem-start)) (i32.const 46)) (i32.eq (array.get_u $I8Array (local.get $arr) (i32.add (local.get $elem-start) (i32.const 1))) (i32.const 46))) (then (return (local.get $len)))))) (local.set $i (local.get $len)) (block $dot-done (loop $dot-loop (br_if $dot-done (i32.le_u (local.get $i) (local.get $elem-start))) (local.set $idx (i32.sub (local.get $i) (i32.const 1))) (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $idx)) (i32.const 46)) (then (return (local.get $idx)))) (local.set $i (local.get $idx)) (br $dot-loop))) (local.get $len)) (func $path-final-element-start (param $path-bs (ref $Bytes)) (result i32) (local $arr (ref $I8Array)) (local $i i32) (local $idx i32) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $i (array.len (local.get $arr))) (block $done (loop $loop (br_if $done (i32.eqz (local.get $i))) (local.set $idx (i32.sub (local.get $i) (i32.const 1))) (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $idx)) (i32.const 47)) (then (return (local.get $i)))) (local.set $i (local.get $idx)) (br $loop))) (i32.const 0)) (func $path-byte-separator? (param $b i32) (param $conv (ref eq)) (result i32) (if (i32.eq (local.get $b) (i32.const 47)) (then (return (i32.const 1)))) (if (ref.eq (local.get $conv) (global.get $symbol:windows)) (then (if (i32.eq (local.get $b) (i32.const 92)) (then (return (i32.const 1)))))) (i32.const 0)) (func $path-final-element-start/convention (param $path-bs (ref $Bytes)) (param $conv (ref eq)) (result i32) (local $arr (ref $I8Array)) (local $i i32) (local $idx i32) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $i (array.len (local.get $arr))) (block $done (loop $loop (br_if $done (i32.eqz (local.get $i))) (local.set $idx (i32.sub (local.get $i) (i32.const 1))) (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (local.get $idx)) (local.get $conv)) (then (return (local.get $i)))) (local.set $i (local.get $idx)) (br $loop))) (i32.const 0)) (func $path-syntactic-directory? (param $path-bs (ref $Bytes)) (param $conv (ref eq)) (result i32) (local $arr (ref $I8Array)) (local $len i32) (local $elem-start i32) (local $elem-len i32) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $len (array.len (local.get $arr))) (if (i32.eqz (local.get $len)) (then (return (i32.const 0)))) (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.sub (local.get $len) (i32.const 1))) (local.get $conv)) (then (return (i32.const 1)))) (local.set $elem-start (call $path-final-element-start/convention (local.get $path-bs) (local.get $conv))) (local.set $elem-len (i32.sub (local.get $len) (local.get $elem-start))) (if (i32.eq (local.get $elem-len) (i32.const 1)) (then (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $elem-start)) (i32.const 46)) (then (return (i32.const 1)))))) (if (i32.eq (local.get $elem-len) (i32.const 2)) (then (if (i32.and (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $elem-start)) (i32.const 46)) (i32.eq (array.get_u $I8Array (local.get $arr) (i32.add (local.get $elem-start) (i32.const 1))) (i32.const 46))) (then (return (i32.const 1)))))) (i32.const 0)) ;; path-replace-extension : path-string? (or/c string? bytes?) -> path? ;; Replace the final Unix/browser path-element extension. (func $path-replace-extension (type $Prim2) (param $path-raw (ref eq)) ;; path-string? (param $ext-raw (ref eq)) ;; (or/c string? bytes?) (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $ext-bs (ref $Bytes)) (local $boundary i32) (local.set $path (call $path-string->path/checked (global.get $symbol:path-replace-extension) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $ext-bs (call $string-or-bytes->bytes/checked (global.get $symbol:path-replace-extension) (local.get $ext-raw))) (local.set $boundary (call $path-extension-start (local.get $path-bs))) (call $bytes->path (call $bytes-append/2 (call $bytes-slice/unchecked (local.get $path-bs) (i32.const 0) (local.get $boundary)) (local.get $ext-bs)) (global.get $missing))) ;; path-replace-suffix : path-string? (or/c string? bytes?) -> path? ;; Deprecated alias shape that treats a leading dot as a suffix separator. (func $path-replace-suffix (type $Prim2) (param $path-raw (ref eq)) ;; path-string? (param $ext-raw (ref eq)) ;; (or/c string? bytes?) (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $ext-bs (ref $Bytes)) (local $boundary i32) (local.set $path (call $path-string->path/checked (global.get $symbol:path-replace-suffix) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $ext-bs (call $string-or-bytes->bytes/checked (global.get $symbol:path-replace-suffix) (local.get $ext-raw))) (local.set $boundary (call $path-suffix-start (local.get $path-bs))) (call $bytes->path (call $bytes-append/2 (call $bytes-slice/unchecked (local.get $path-bs) (i32.const 0) (local.get $boundary)) (local.get $ext-bs)) (global.get $missing))) ;; path-add-extension : path-string? (or/c string? bytes?) [(or/c string? bytes?)] -> path? ;; Add an extension, preserving an existing extension via sep; default sep = #"_". (func $path-add-extension (type $Prim23) (param $path-raw (ref eq)) ;; path-string? (param $ext-raw (ref eq)) ;; (or/c string? bytes?) (param $sep-raw (ref eq)) ;; optional (or/c string? bytes?), default = #"_" (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $ext-bs (ref $Bytes)) (local $sep-bs (ref $Bytes)) (local $prefix-bs (ref $Bytes)) (local $suffix-bs (ref $Bytes)) (local $boundary i32) (local $len i32) (local.set $path (call $path-string->path/checked (global.get $symbol:path-add-extension) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $ext-bs (call $string-or-bytes->bytes/checked (global.get $symbol:path-add-extension) (local.get $ext-raw))) (local.set $sep-bs (if (result (ref $Bytes)) (ref.eq (local.get $sep-raw) (global.get $missing)) (then (ref.cast (ref $Bytes) (global.get $bytes:non-empty))) (else (call $string-or-bytes->bytes/checked (global.get $symbol:path-add-extension) (local.get $sep-raw))))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (local.set $boundary (call $path-extension-start (local.get $path-bs))) (if (i32.eq (local.get $boundary) (local.get $len)) (then (return (call $bytes->path (call $bytes-append/2 (local.get $path-bs) (local.get $ext-bs)) (global.get $missing))))) (local.set $prefix-bs (call $bytes-slice/unchecked (local.get $path-bs) (i32.const 0) (local.get $boundary))) (local.set $suffix-bs (call $bytes-slice/unchecked (local.get $path-bs) (i32.add (local.get $boundary) (i32.const 1)) (local.get $len))) (call $bytes->path (call $bytes-append/2 (call $bytes-append/2 (call $bytes-append/2 (local.get $prefix-bs) (local.get $sep-bs)) (local.get $suffix-bs)) (local.get $ext-bs)) (global.get $missing))) ;; path-add-suffix : path-string? (or/c string? bytes?) -> path? ;; Deprecated alias shape that treats a leading dot as a suffix separator. (func $path-add-suffix (type $Prim2) (param $path-raw (ref eq)) ;; path-string? (param $ext-raw (ref eq)) ;; (or/c string? bytes?) (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $ext-bs (ref $Bytes)) (local $prefix-bs (ref $Bytes)) (local $suffix-bs (ref $Bytes)) (local $boundary i32) (local $len i32) (local.set $path (call $path-string->path/checked (global.get $symbol:path-add-suffix) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $ext-bs (call $string-or-bytes->bytes/checked (global.get $symbol:path-add-suffix) (local.get $ext-raw))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (local.set $boundary (call $path-suffix-start (local.get $path-bs))) (if (i32.eq (local.get $boundary) (local.get $len)) (then (return (call $bytes->path (call $bytes-append/2 (local.get $path-bs) (local.get $ext-bs)) (global.get $missing))))) (local.set $prefix-bs (call $bytes-slice/unchecked (local.get $path-bs) (i32.const 0) (local.get $boundary))) (local.set $suffix-bs (call $bytes-slice/unchecked (local.get $path-bs) (i32.add (local.get $boundary) (i32.const 1)) (local.get $len))) (call $bytes->path (call $bytes-append/2 (call $bytes-append/2 (call $bytes-append/2 (local.get $prefix-bs) (global.get $bytes:non-empty)) (local.get $suffix-bs)) (local.get $ext-bs)) (global.get $missing))) ;; path-get-extension : path-string? -> (or/c bytes? #f) ;; Return the final path-element extension including the dot, or #f. (func $path-get-extension (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $boundary i32) (local $len i32) (local.set $path (call $path-string->path/checked (global.get $symbol:path-get-extension) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (local.set $boundary (call $path-extension-start (local.get $path-bs))) (if (i32.eq (local.get $boundary) (local.get $len)) (then (return (global.get $false)))) (call $bytes-slice/unchecked (local.get $path-bs) (local.get $boundary) (local.get $len))) ;; filename-extension : path-string? -> (or/c bytes? #f) ;; Deprecated variant of path-get-extension that omits the dot and counts leading dots. (func $filename-extension (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $boundary i32) (local $len i32) (local.set $path (call $path-string->path/checked (global.get $symbol:filename-extension) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (local.set $boundary (call $path-suffix-start (local.get $path-bs))) (if (i32.eq (local.get $boundary) (local.get $len)) (then (return (global.get $false)))) (call $bytes-slice/unchecked (local.get $path-bs) (i32.add (local.get $boundary) (i32.const 1)) (local.get $len))) ;; path-has-extension? : path-string? (or/c string? bytes?) -> boolean? ;; Check whether the final path element ends with ext but is not exactly ext. (func $path-has-extension? (type $Prim2) (param $path-raw (ref eq)) ;; path-string? (param $ext-raw (ref eq)) ;; (or/c string? bytes?) (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $ext-bs (ref $Bytes)) (local $path-arr (ref $I8Array)) (local $ext-arr (ref $I8Array)) (local $path-len i32) (local $ext-len i32) (local $elem-start i32) (local $elem-len i32) (local $offset i32) (local $i i32) (local.set $path (call $path-string->path/checked (global.get $symbol:path-has-extension?) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $ext-bs (call $string-or-bytes->bytes/checked (global.get $symbol:path-has-extension?) (local.get $ext-raw))) (local.set $path-arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $ext-arr (struct.get $Bytes $bs (local.get $ext-bs))) (local.set $path-len (array.len (local.get $path-arr))) (local.set $ext-len (array.len (local.get $ext-arr))) (if (i32.eqz (local.get $ext-len)) (then (return (global.get $false)))) (if (local.get $path-len) (then (if (i32.eq (array.get_u $I8Array (local.get $path-arr) (i32.sub (local.get $path-len) (i32.const 1))) (i32.const 47)) (then (return (global.get $false)))))) (local.set $elem-start (call $path-final-element-start (local.get $path-bs))) (local.set $elem-len (i32.sub (local.get $path-len) (local.get $elem-start))) (if (i32.ge_u (local.get $ext-len) (local.get $elem-len)) (then (return (global.get $false)))) (local.set $offset (i32.sub (local.get $path-len) (local.get $ext-len))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $ext-len))) (if (i32.ne (array.get_u $I8Array (local.get $path-arr) (i32.add (local.get $offset) (local.get $i))) (array.get_u $I8Array (local.get $ext-arr) (local.get $i))) (then (return (global.get $false)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (global.get $true)) ;; file-name-from-path : path-string? -> (or/c path? #f) ;; Return the final path element, or #f for syntactic directories. (func $file-name-from-path (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $elem-start i32) (local $elem-len i32) (local.set $path (call $path-string->path/checked (global.get $symbol:file-name-from-path) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $len (array.len (local.get $arr))) (if (local.get $len) (then (if (i32.eq (array.get_u $I8Array (local.get $arr) (i32.sub (local.get $len) (i32.const 1))) (i32.const 47)) (then (return (global.get $false)))))) (local.set $elem-start (call $path-final-element-start (local.get $path-bs))) (local.set $elem-len (i32.sub (local.get $len) (local.get $elem-start))) (if (i32.eq (local.get $elem-len) (i32.const 1)) (then (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $elem-start)) (i32.const 46)) (then (return (global.get $false)))))) (if (i32.eq (local.get $elem-len) (i32.const 2)) (then (if (i32.and (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $elem-start)) (i32.const 46)) (i32.eq (array.get_u $I8Array (local.get $arr) (i32.add (local.get $elem-start) (i32.const 1))) (i32.const 46))) (then (return (global.get $false)))))) (call $bytes->path (call $bytes-slice/unchecked (local.get $path-bs) (local.get $elem-start) (local.get $len)) (global.get $missing))) ;; split-path : (or/c path-string? path-for-some-system?) -> (values (or/c path-for-some-system? 'relative #f) (or/c path-for-some-system? 'up 'same) boolean?) ;; Syntactically split a path into base, name, and must-be-directory? values. (func $split-path (type $Prim1) (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $conv (ref eq)) (local $base (ref eq)) (local $name (ref eq)) (local $dir? (ref eq)) (local $len i32) (local $end i32) (local $elem-start i32) (local $elem-len i32) (local $i i32) (local $idx i32) (local.set $path (ref.cast (ref $Path) (global.get $current-directory-path))) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw)))) (else (local.set $path (call $path-string->path/checked (global.get $symbol:split-path) (local.get $path-raw))))) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $len (array.len (local.get $arr))) (local.set $end (local.get $len)) (local.set $dir? (global.get $false)) (if (call $path-syntactic-directory? (local.get $path-bs) (local.get $conv)) (then (local.set $dir? (global.get $true)))) ;; Simple root path: base is #f and name is the root path. (if (i32.eq (local.get $len) (i32.const 1)) (then (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.const 0)) (local.get $conv)) (then (return (array.new_fixed $Values 3 (global.get $false) (local.get $path) (global.get $true))))))) ;; Ignore one trailing separator when selecting the final element. (if (local.get $len) (then (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.sub (local.get $len) (i32.const 1))) (local.get $conv)) (then (local.set $end (i32.sub (local.get $len) (i32.const 1))))))) (local.set $elem-start (i32.const 0)) (local.set $i (local.get $end)) (block $done (loop $loop (br_if $done (i32.eqz (local.get $i))) (local.set $idx (i32.sub (local.get $i) (i32.const 1))) (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (local.get $idx)) (local.get $conv)) (then (local.set $elem-start (local.get $i)) (br $done))) (local.set $i (local.get $idx)) (br $loop))) (local.set $elem-len (i32.sub (local.get $end) (local.get $elem-start))) (local.set $base (if (result (ref eq)) (i32.eqz (local.get $elem-start)) (then (global.get $symbol:relative)) (else (call $bytes->path (call $bytes-slice/unchecked (local.get $path-bs) (i32.const 0) (local.get $elem-start)) (local.get $conv))))) (local.set $name (call $bytes->path (call $bytes-slice/unchecked (local.get $path-bs) (local.get $elem-start) (local.get $end)) (local.get $conv))) (if (i32.eq (local.get $elem-len) (i32.const 1)) (then (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $elem-start)) (i32.const 46)) (then (local.set $name (global.get $symbol:same)))))) (if (i32.eq (local.get $elem-len) (i32.const 2)) (then (if (i32.and (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $elem-start)) (i32.const 46)) (i32.eq (array.get_u $I8Array (local.get $arr) (i32.add (local.get $elem-start) (i32.const 1))) (i32.const 46))) (then (local.set $name (global.get $symbol:up)))))) (array.new_fixed $Values 3 (local.get $base) (local.get $name) (local.get $dir?))) ;; path-element? : any/c -> boolean? ;; Recognize single-element path values for any represented path convention. ;; explode-path : (or/c path-string? path-for-some-system?) -> (listof (or/c path-for-some-system? 'up 'same)) ;; Return the non-empty syntactic path elements as a list. (func $explode-path (type $Prim1) (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $conv (ref eq)) (local $acc (ref eq)) (local $part (ref eq)) (local $len i32) (local $start i32) (local $i i32) (local $part-len i32) (local $b i32) (local.set $path (ref.cast (ref $Path) (global.get $current-directory-path))) (local.set $acc (global.get $null)) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw)))) (else (local.set $path (call $path-string->path/checked (global.get $symbol:explode-path) (local.get $path-raw))))) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $len (array.len (local.get $arr))) (local.set $start (i32.const 0)) (local.set $i (i32.const 0)) (if (local.get $len) (then (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.const 0)) (local.get $conv)) (then (local.set $acc (struct.new $Pair (i32.const 0) (call $bytes->path (call $bytes-slice/unchecked (local.get $path-bs) (i32.const 0) (i32.const 1)) (local.get $conv)) (local.get $acc))) (local.set $start (i32.const 1)) (local.set $i (i32.const 1)))))) (block $done (loop $loop (if (i32.lt_u (local.get $i) (local.get $len)) (then (local.set $b (array.get_u $I8Array (local.get $arr) (local.get $i))) (if (i32.eqz (call $path-byte-separator? (local.get $b) (local.get $conv))) (then (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))))) (local.set $part-len (i32.sub (local.get $i) (local.get $start))) (if (local.get $part-len) (then (local.set $part (call $bytes->path (call $bytes-slice/unchecked (local.get $path-bs) (local.get $start) (local.get $i)) (local.get $conv))) (if (i32.eq (local.get $part-len) (i32.const 1)) (then (if (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $start)) (i32.const 46)) (then (local.set $part (global.get $symbol:same)))))) (if (i32.eq (local.get $part-len) (i32.const 2)) (then (if (i32.and (i32.eq (array.get_u $I8Array (local.get $arr) (local.get $start)) (i32.const 46)) (i32.eq (array.get_u $I8Array (local.get $arr) (i32.add (local.get $start) (i32.const 1))) (i32.const 46))) (then (local.set $part (global.get $symbol:up)))))) (local.set $acc (struct.new $Pair (i32.const 0) (local.get $part) (local.get $acc))))) (if (i32.ge_u (local.get $i) (local.get $len)) (then (br $done))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $start (local.get $i)) (br $loop))) (call $reverse (local.get $acc))) (func $path-element? (type $Prim1) (param $v (ref eq)) ;; any/c (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $arr (ref $I8Array)) (local $conv (ref eq)) (local $len i32) (local $elem-len i32) (local $i i32) (local $b i32) (if (i32.eqz (ref.test (ref $Path) (local.get $v))) (then (return (global.get $false)))) (local.set $path (ref.cast (ref $Path) (local.get $v))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $arr (struct.get $Bytes $bs (local.get $path-bs))) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $len (array.len (local.get $arr))) (local.set $elem-len (local.get $len)) (if (i32.eqz (local.get $elem-len)) (then (return (global.get $false)))) (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.const 0)) (local.get $conv)) (then (return (global.get $false)))) (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.sub (local.get $elem-len) (i32.const 1))) (local.get $conv)) (then (local.set $elem-len (i32.sub (local.get $elem-len) (i32.const 1))))) (if (i32.eqz (local.get $elem-len)) (then (return (global.get $false)))) (local.set $i (i32.const 0)) (block $done (loop $loop (br_if $done (i32.ge_u (local.get $i) (local.get $elem-len))) (local.set $b (array.get_u $I8Array (local.get $arr) (local.get $i))) (if (call $path-byte-separator? (local.get $b) (local.get $conv)) (then (return (global.get $false)))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (i32.eq (local.get $elem-len) (i32.const 1)) (then (if (i32.eq (array.get_u $I8Array (local.get $arr) (i32.const 0)) (i32.const 46)) (then (return (global.get $false)))))) (if (i32.eq (local.get $elem-len) (i32.const 2)) (then (if (i32.and (i32.eq (array.get_u $I8Array (local.get $arr) (i32.const 0)) (i32.const 46)) (i32.eq (array.get_u $I8Array (local.get $arr) (i32.const 1)) (i32.const 46))) (then (return (global.get $false)))))) (global.get $true)) (func $path-element-trimmed-bytes/checked (param $who (ref eq)) ;; symbol? (param $path-raw (ref eq)) ;; path-element? (result (ref $Bytes)) (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $arr (ref $I8Array)) (local $conv (ref eq)) (local $len i32) (local $end i32) (if (i32.eqz (ref.test (ref $Path) (local.get $path-raw))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:path-element?) (local.get $path-raw)) (unreachable))) (if (i32.eqz (ref.eq (call $path-element? (local.get $path-raw)) (global.get $true))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:path-element?) (local.get $path-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (local.set $arr (struct.get $Bytes $bs (local.get $bytes))) (local.set $conv (struct.get $Path $convention (local.get $path))) (local.set $len (array.len (local.get $arr))) (local.set $end (local.get $len)) (if (call $path-byte-separator? (array.get_u $I8Array (local.get $arr) (i32.sub (local.get $len) (i32.const 1))) (local.get $conv)) (then (local.set $end (i32.sub (local.get $len) (i32.const 1))))) (call $bytes-slice/unchecked (local.get $bytes) (i32.const 0) (local.get $end))) ;; path-element->bytes : path-element? -> bytes? ;; Convert a path element to bytes, without a trailing separator. (func $path-element->bytes (type $Prim1) (param $path-raw (ref eq)) ;; path-element? (result (ref eq)) ;; bytes? (call $path-element-trimmed-bytes/checked (global.get $symbol:path-element->bytes) (local.get $path-raw))) ;; path-element->string : path-element? -> string? ;; Convert a path element to a UTF-8 string, without a trailing separator. (func $path-element->string (type $Prim1) (param $path-raw (ref eq)) ;; path-element? (result (ref eq)) ;; string? (call $bytes->string/utf-8/checked (call $path-element-trimmed-bytes/checked (global.get $symbol:path-element->string) (local.get $path-raw)))) ;; path-only : path-string? -> (or/c path? #f) ;; Return a path without its final element, or #f for single non-directory elements. (func $path-only (type $Prim1) (param $path-raw (ref eq)) ;; path-string? or path-for-some-system? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $conv (ref eq)) (local $elem-start i32) (local.set $path (ref.cast (ref $Path) (global.get $current-directory-path))) (local.set $conv (global.get $system-path-convention)) (if (ref.test (ref $Path) (local.get $path-raw)) (then (local.set $path (ref.cast (ref $Path) (local.get $path-raw))) (local.set $conv (struct.get $Path $convention (local.get $path)))) (else (if (i32.eqz (ref.test (ref $String) (local.get $path-raw))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (ref.eq (call $non-empty-string-without-nuls (ref.cast (ref $String) (local.get $path-raw))) (global.get $true))) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (local.set $path (ref.cast (ref $Path) (call $string->path (ref.cast (ref $String) (local.get $path-raw))))) (local.set $conv (global.get $system-path-convention)))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (if (call $path-syntactic-directory? (local.get $path-bs) (local.get $conv)) (then (return (local.get $path)))) (local.set $elem-start (call $path-final-element-start/convention (local.get $path-bs) (local.get $conv))) (if (i32.eqz (local.get $elem-start)) (then (return (global.get $false)))) (call $bytes->path (call $bytes-slice/unchecked (local.get $path-bs) (i32.const 0) (local.get $elem-start)) (local.get $conv))) (func $vfs-path-stat-kind (param $who (ref eq)) ;; symbol? (currently for diagnostics) (param $path-raw (ref eq)) ;; path-string? (result i32) ;; 0 missing, 1 file, 2 directory (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $len i32) (local.set $path (call $path-string->path/checked (local.get $who) (local.get $path-raw))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $bytes)))) (if (i32.gt_u (local.get $len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $len (call $copy-bytes-to-memory (local.get $bytes) (global.get $memory-map:vfs-path-buffer-base))) (call $js-vfs-stat-kind (global.get $memory-map:vfs-path-buffer-base) (local.get $len))) (func $file-exists? (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (if (result (ref eq)) (i32.eq (call $vfs-path-stat-kind (global.get $symbol:file-exists?) (local.get $path-raw)) (i32.const 1)) (then (global.get $true)) (else (global.get $false)))) (func $directory-exists? (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (if (result (ref eq)) (i32.eq (call $vfs-path-stat-kind (global.get $symbol:directory-exists?) (local.get $path-raw)) (i32.const 2)) (then (global.get $true)) (else (global.get $false)))) ;; link-exists? : path-string? -> boolean? ;; Return #f in the current VFS model; links are not modeled yet. (func $link-exists? (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (drop (call $path-string->path/checked (global.get $symbol:link-exists?) (local.get $path-raw))) (global.get $false)) ;; file-or-directory-type : path-string? [any/c] -> (or/c 'file 'directory #f) ;; Report the VFS path kind; links are not modeled yet. (func $file-or-directory-type (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $must-exist-raw (ref eq)) ;; optional any/c, default = #f (result (ref eq)) (local $kind i32) (local.set $kind (call $vfs-path-stat-kind (global.get $symbol:file-or-directory-type) (local.get $path-raw))) (if (i32.eq (local.get $kind) (i32.const 1)) (then (return (global.get $symbol:file)))) (if (i32.eq (local.get $kind) (i32.const 2)) (then (return (global.get $symbol:directory)))) (if (i32.eqz (ref.eq (local.get $must-exist-raw) (global.get $false))) (then (if (i32.eqz (ref.eq (local.get $must-exist-raw) (global.get $missing))) (then (call $raise-vfs-file-error (global.get $string:vfs:type-failed)) (unreachable))))) (global.get $false)) ;; directory-list : [path-string?] [any/c] -> (listof path?) ;; Keywordless form of Racket's #:build? option; default #f. (func $directory-list (type $Prim02) (param $path-raw (ref eq)) ;; optional path-string?, default = (current-directory) (param $build-raw (ref eq)) ;; optional any/c, default = #f (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $names-raw (ref eq)) (local $names (ref $Vector)) (local $arr (ref $Array)) (local $entry (ref eq)) (local $entry-path (ref eq)) (local $path-len i32) (local $fasl-len i32) (local $i i32) (local $build? i32) (local $xs (ref eq)) (if (ref.eq (local.get $path-raw) (global.get $missing)) (then (local.set $path-raw (call $current-directory (global.get $missing))))) (local.set $build? (i32.and (i32.eqz (ref.eq (local.get $build-raw) (global.get $missing))) (i32.eqz (ref.eq (local.get $build-raw) (global.get $false))))) (local.set $path (call $path-string->path/checked (global.get $symbol:directory-list) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $fasl-len (call $js-vfs-list-directory (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len) (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (if (i32.lt_s (local.get $fasl-len) (i32.const -1)) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.lt_s (local.get $fasl-len) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:directory-list-failed)) (unreachable))) (local.set $names-raw (call $fasl-memory->s-exp (global.get $memory-map:vfs-file-buffer-base))) (if (i32.eqz (ref.test (ref $Vector) (local.get $names-raw))) (then (call $raise-vfs-file-error (global.get $string:vfs:directory-list-failed)) (unreachable))) (local.set $names (ref.cast (ref $Vector) (local.get $names-raw))) (local.set $arr (struct.get $Vector $arr (local.get $names))) (if (i32.eqz (array.len (local.get $arr))) (then (return (global.get $null)))) (local.set $xs (global.get $null)) (local.set $i (i32.sub (array.len (local.get $arr)) (i32.const 1))) (block $done (loop $build (local.set $entry (array.get $Array (local.get $arr) (local.get $i))) (if (i32.eqz (ref.test (ref $String) (local.get $entry))) (then (call $raise-vfs-file-error (global.get $string:vfs:directory-list-failed)) (unreachable))) (local.set $entry-path (call $string->path (ref.cast (ref $String) (local.get $entry)))) (if (local.get $build?) (then (local.set $entry-path (call $path-join-bytes/convention (global.get $symbol:directory-list) (struct.get $Path $convention (local.get $path)) (local.get $path) (local.get $entry-path))))) (local.set $xs (struct.new $Pair (i32.const 0) (local.get $entry-path) (local.get $xs))) (br_if $done (i32.eqz (local.get $i))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $build))) (local.get $xs)) ;; filesystem-root-list : -> (listof path?) ;; Return the mounted VFS roots as directory paths. (func $filesystem-root-list (type $Prim0) (result (ref eq)) (local $roots-raw (ref eq)) (local $roots (ref $Vector)) (local $arr (ref $Array)) (local $entry (ref eq)) (local $fasl-len i32) (local $i i32) (local $xs (ref eq)) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $fasl-len (call $js-vfs-root-list (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (if (i32.lt_s (local.get $fasl-len) (i32.const -1)) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.lt_s (local.get $fasl-len) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:root-list-failed)) (unreachable))) (local.set $roots-raw (call $fasl-memory->s-exp (global.get $memory-map:vfs-file-buffer-base))) (if (i32.eqz (ref.test (ref $Vector) (local.get $roots-raw))) (then (call $raise-vfs-file-error (global.get $string:vfs:root-list-failed)) (unreachable))) (local.set $roots (ref.cast (ref $Vector) (local.get $roots-raw))) (local.set $arr (struct.get $Vector $arr (local.get $roots))) (if (i32.eqz (array.len (local.get $arr))) (then (return (global.get $null)))) (local.set $xs (global.get $null)) (local.set $i (i32.sub (array.len (local.get $arr)) (i32.const 1))) (block $done (loop $build (local.set $entry (array.get $Array (local.get $arr) (local.get $i))) (if (i32.eqz (ref.test (ref $String) (local.get $entry))) (then (call $raise-vfs-file-error (global.get $string:vfs:root-list-failed)) (unreachable))) (local.set $xs (struct.new $Pair (i32.const 0) (call $string->path (local.get $entry)) (local.get $xs))) (br_if $done (i32.eqz (local.get $i))) (local.set $i (i32.sub (local.get $i) (i32.const 1))) (br $build))) (local.get $xs)) ;; file-or-directory-stat : path-string? [boolean?] -> hash-eq? ;; Return VFS stat metadata; as-link? is accepted but links are not modeled yet. (func $file-or-directory-stat (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $as-link-raw (ref eq)) ;; optional boolean?, default = #f (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $items-raw (ref eq)) (local $items (ref $Vector)) (local $arr (ref $Array)) (local $ht (ref eq)) (local $path-len i32) (local $fasl-len i32) (local $i i32) (if (i32.eqz (i32.or (ref.eq (local.get $as-link-raw) (global.get $missing)) (i32.or (ref.eq (local.get $as-link-raw) (global.get $false)) (ref.eq (local.get $as-link-raw) (global.get $true))))) (then (call $raise-argument-error1 (global.get $symbol:file-or-directory-stat) (global.get $string:boolean?) (local.get $as-link-raw)) (unreachable))) (local.set $path (call $path-string->path/checked (global.get $symbol:file-or-directory-stat) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $fasl-len (call $js-vfs-stat (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len) (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (if (i32.lt_s (local.get $fasl-len) (i32.const -1)) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.lt_s (local.get $fasl-len) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:stat-failed)) (unreachable))) (local.set $items-raw (call $fasl-memory->s-exp (global.get $memory-map:vfs-file-buffer-base))) (if (i32.eqz (ref.test (ref $Vector) (local.get $items-raw))) (then (call $raise-vfs-file-error (global.get $string:vfs:stat-failed)) (unreachable))) (local.set $items (ref.cast (ref $Vector) (local.get $items-raw))) (local.set $arr (struct.get $Vector $arr (local.get $items))) (if (i32.ne (i32.and (array.len (local.get $arr)) (i32.const 1)) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:stat-failed)) (unreachable))) (local.set $ht (call $make-empty-hasheq)) (local.set $i (i32.const 0)) (block $done (loop $build (br_if $done (i32.ge_u (local.get $i) (array.len (local.get $arr)))) (drop (call $hash-set! (local.get $ht) (array.get $Array (local.get $arr) (local.get $i)) (array.get $Array (local.get $arr) (i32.add (local.get $i) (i32.const 1))))) (local.set $i (i32.add (local.get $i) (i32.const 2))) (br $build))) (local.get $ht)) ;; file-or-directory-identity : path-string? [any/c] -> exact-positive-integer? ;; Return a stable VFS identity; as-link? is accepted but links are not modeled yet. (func $file-or-directory-identity (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $as-link-raw (ref eq)) ;; optional any/c, default = #f (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $id i32) (local.set $path (call $path-string->path/checked (global.get $symbol:file-or-directory-identity) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $id (call $js-vfs-identity (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len))) (if (i32.le_s (local.get $id) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:identity-failed)) (unreachable))) (ref.i31 (i32.shl (local.get $id) (i32.const 1)))) (func $file-size (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $len i32) (local $size i32) (local.set $path (call $path-string->path/checked (global.get $symbol:file-size) (local.get $path-raw))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $bytes)))) (if (i32.gt_u (local.get $len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $len (call $copy-bytes-to-memory (local.get $bytes) (global.get $memory-map:vfs-path-buffer-base))) (local.set $size (call $js-vfs-file-size (global.get $memory-map:vfs-path-buffer-base) (local.get $len))) (if (i32.lt_s (local.get $size) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:file-size-failed)) (unreachable))) (ref.i31 (i32.shl (local.get $size) (i32.const 1)))) ;; $vfs-read-file-bytes : symbol? path-string? -> bytes? ;; Read a VFS file through the static file transfer buffer. (func $vfs-read-file-bytes (param $who (ref eq)) ;; symbol? (param $path-raw (ref eq)) ;; path-string? (result (ref $Bytes)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $expected i32) (local $file-len i32) (local.set $path (call $path-string->path/checked (local.get $who) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $expected (call $js-vfs-file-size (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len))) (if (i32.lt_s (local.get $expected) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:read-file-failed)) (unreachable))) (if (i32.gt_u (local.get $expected) (global.get $memory-map:vfs-file-buffer-length)) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $file-len (call $js-vfs-read-file (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len) (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (if (i32.lt_s (local.get $file-len) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:read-file-failed)) (unreachable))) (if (i32.gt_u (local.get $file-len) (global.get $memory-map:vfs-file-buffer-length)) (then (call $raise-string-buffer-overflow) (unreachable))) (call $memory-range->immutable-bytes (global.get $memory-map:vfs-file-buffer-base) (local.get $file-len))) ;; file->bytes : path-string? [(or/c 'binary 'text)] -> bytes? ;; Keywordless form of Racket's #:mode option; text mode currently behaves like binary mode. (func $file->bytes (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (result (ref eq)) (local $mode (ref eq)) (local.set $mode (if (result (ref eq)) (ref.eq (local.get $mode-raw) (global.get $missing)) (then (global.get $symbol:binary)) (else (local.get $mode-raw)))) (if (i32.eqz (i32.or (ref.eq (local.get $mode) (global.get $symbol:binary)) (ref.eq (local.get $mode) (global.get $symbol:text)))) (then (call $raise-argument-error1 (global.get $symbol:file->bytes) (global.get $string:input-file-mode-flag) (local.get $mode-raw)) (unreachable))) (call $vfs-read-file-bytes (global.get $symbol:file->bytes) (local.get $path-raw))) ;; file->string : path-string? [(or/c 'binary 'text)] -> string? ;; Keywordless form of Racket's #:mode option; text mode currently behaves like binary mode. (func $file->string (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (result (ref eq)) (local $mode (ref eq)) (local.set $mode (if (result (ref eq)) (ref.eq (local.get $mode-raw) (global.get $missing)) (then (global.get $symbol:binary)) (else (local.get $mode-raw)))) (if (i32.eqz (i32.or (ref.eq (local.get $mode) (global.get $symbol:binary)) (ref.eq (local.get $mode) (global.get $symbol:text)))) (then (call $raise-argument-error1 (global.get $symbol:file->string) (global.get $string:input-file-mode-flag) (local.get $mode-raw)) (unreachable))) (call $bytes->string/utf-8/checked (call $vfs-read-file-bytes (global.get $symbol:file->string) (local.get $path-raw)))) ;; file->lines : path-string? [(or/c 'binary 'text)] [read-line-mode?] -> (listof string?) ;; Keywordless form of Racket's #:mode and #:line-mode options; default line mode = 'any. (func $file->lines (type $Prim13) (param $path-raw (ref eq)) ;; path-string? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $line-mode-raw (ref eq)) ;; optional read-line-mode?, default = 'any (result (ref eq)) (local $mode (ref eq)) (local $line-mode (ref eq)) (local $port (ref eq)) (local $line (ref eq)) (local $acc (ref eq)) (local.set $mode (if (result (ref eq)) (ref.eq (local.get $mode-raw) (global.get $missing)) (then (global.get $symbol:binary)) (else (local.get $mode-raw)))) (if (i32.eqz (i32.or (ref.eq (local.get $mode) (global.get $symbol:binary)) (ref.eq (local.get $mode) (global.get $symbol:text)))) (then (call $raise-argument-error1 (global.get $symbol:file->lines) (global.get $string:input-file-mode-flag) (local.get $mode-raw)) (unreachable))) (local.set $line-mode (if (result (ref eq)) (ref.eq (local.get $line-mode-raw) (global.get $missing)) (then (global.get $symbol:any)) (else (local.get $line-mode-raw)))) (local.set $port (call $open-input-file (local.get $path-raw) (local.get $mode) (global.get $missing))) (local.set $acc (global.get $null)) (block $done (loop $loop (local.set $line (call $read-line (local.get $port) (local.get $line-mode))) (br_if $done (ref.eq (local.get $line) (global.get $eof))) (local.set $acc (call $cons (local.get $line) (local.get $acc))) (br $loop))) (drop (call $close-input-port (local.get $port))) (call $reverse (local.get $acc))) ;; file->bytes-lines : path-string? [(or/c 'binary 'text)] [read-line-mode?] -> (listof bytes?) ;; Keywordless form of Racket's #:mode and #:line-mode options; default line mode = 'any. (func $file->bytes-lines (type $Prim13) (param $path-raw (ref eq)) ;; path-string? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $line-mode-raw (ref eq)) ;; optional read-line-mode?, default = 'any (result (ref eq)) (local $mode (ref eq)) (local $line-mode (ref eq)) (local $mode-code i32) (local $bytes (ref $Bytes)) (local $arr (ref $I8Array)) (local $len i32) (local $i i32) (local $start i32) (local $end i32) (local $b i32) (local $next i32) (local $acc (ref eq)) (local.set $mode (if (result (ref eq)) (ref.eq (local.get $mode-raw) (global.get $missing)) (then (global.get $symbol:binary)) (else (local.get $mode-raw)))) (if (i32.eqz (i32.or (ref.eq (local.get $mode) (global.get $symbol:binary)) (ref.eq (local.get $mode) (global.get $symbol:text)))) (then (call $raise-argument-error1 (global.get $symbol:file->bytes-lines) (global.get $string:input-file-mode-flag) (local.get $mode-raw)) (unreachable))) (local.set $line-mode (if (result (ref eq)) (ref.eq (local.get $line-mode-raw) (global.get $missing)) (then (global.get $symbol:any)) (else (local.get $line-mode-raw)))) (local.set $mode-code (i32.const -1)) (block (if (ref.eq (local.get $line-mode) (global.get $symbol:linefeed)) (then (local.set $mode-code (i32.const 0)) (br 1))) (if (ref.eq (local.get $line-mode) (global.get $symbol:return)) (then (local.set $mode-code (i32.const 1)) (br 1))) (if (ref.eq (local.get $line-mode) (global.get $symbol:return-linefeed)) (then (local.set $mode-code (i32.const 2)) (br 1))) (if (ref.eq (local.get $line-mode) (global.get $symbol:any)) (then (local.set $mode-code (i32.const 3)) (br 1))) (if (ref.eq (local.get $line-mode) (global.get $symbol:any-one)) (then (local.set $mode-code (i32.const 4)) (br 1))) (call $raise-read-line:bad-mode (local.get $line-mode)) (unreachable)) (local.set $bytes (call $vfs-read-file-bytes (global.get $symbol:file->bytes-lines) (local.get $path-raw))) (local.set $arr (struct.get $Bytes $bs (local.get $bytes))) (local.set $len (array.len (local.get $arr))) (local.set $i (i32.const 0)) (local.set $acc (global.get $null)) (block $done (loop $outer (br_if $done (i32.ge_u (local.get $i) (local.get $len))) (local.set $start (local.get $i)) (block $line-done (loop $inner (if (i32.ge_u (local.get $i) (local.get $len)) (then (local.set $end (local.get $len)) (local.set $acc (call $cons (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8array-copy (local.get $arr) (local.get $start) (local.get $end))) (local.get $acc))) (br $done))) (local.set $b (array.get_u $I8Array (local.get $arr) (local.get $i))) (if (i32.and (i32.eq (local.get $mode-code) (i32.const 0)) (i32.eq (local.get $b) (i32.const 10))) (then (local.set $end (local.get $i)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $acc (call $cons (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8array-copy (local.get $arr) (local.get $start) (local.get $end))) (local.get $acc))) (br $line-done))) (if (i32.and (i32.eq (local.get $mode-code) (i32.const 1)) (i32.eq (local.get $b) (i32.const 13))) (then (local.set $end (local.get $i)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $acc (call $cons (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8array-copy (local.get $arr) (local.get $start) (local.get $end))) (local.get $acc))) (br $line-done))) (if (i32.and (i32.eq (local.get $mode-code) (i32.const 2)) (i32.eq (local.get $b) (i32.const 13))) (then (if (i32.lt_u (i32.add (local.get $i) (i32.const 1)) (local.get $len)) (then (local.set $next (array.get_u $I8Array (local.get $arr) (i32.add (local.get $i) (i32.const 1)))) (if (i32.eq (local.get $next) (i32.const 10)) (then (local.set $end (local.get $i)) (local.set $i (i32.add (local.get $i) (i32.const 2))) (local.set $acc (call $cons (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8array-copy (local.get $arr) (local.get $start) (local.get $end))) (local.get $acc))) (br $line-done))))))) (if (i32.and (i32.eq (local.get $mode-code) (i32.const 3)) (i32.or (i32.eq (local.get $b) (i32.const 10)) (i32.eq (local.get $b) (i32.const 13)))) (then (local.set $end (local.get $i)) (if (i32.eq (local.get $b) (i32.const 13)) (then (if (i32.lt_u (i32.add (local.get $i) (i32.const 1)) (local.get $len)) (then (local.set $next (array.get_u $I8Array (local.get $arr) (i32.add (local.get $i) (i32.const 1)))) (if (i32.eq (local.get $next) (i32.const 10)) (then (local.set $i (i32.add (local.get $i) (i32.const 1))))))))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $acc (call $cons (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8array-copy (local.get $arr) (local.get $start) (local.get $end))) (local.get $acc))) (br $line-done))) (if (i32.and (i32.eq (local.get $mode-code) (i32.const 4)) (i32.or (i32.eq (local.get $b) (i32.const 10)) (i32.eq (local.get $b) (i32.const 13)))) (then (local.set $end (local.get $i)) (local.set $i (i32.add (local.get $i) (i32.const 1))) (local.set $acc (call $cons (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8array-copy (local.get $arr) (local.get $start) (local.get $end))) (local.get $acc))) (br $line-done))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $inner))) (br $outer))) (call $reverse (local.get $acc))) ;; display-value-to-port : any/c output-port? -> void? ;; Display raw bytes directly; otherwise use the runtime display formatter. (func $display-value-to-port (type $Prim2) (param $v (ref eq)) ;; any/c (param $port (ref eq)) ;; output-port? (result (ref eq)) (local $text (ref $String)) (if (ref.test (ref $Bytes) (local.get $v)) (then (drop (call $write-bytes (local.get $v) (local.get $port) (global.get $missing) (global.get $missing))) (return (global.get $void)))) (local.set $text (call $format/display (local.get $v))) (drop (call $write-string (local.get $text) (local.get $port) (global.get $missing) (global.get $missing))) (global.get $void)) ;; display-to-file : any/c path-string? [(or/c 'binary 'text)] [output-file-exists-flag?] -> void? ;; Keywordless form of Racket's #:mode and #:exists options; delegates options to open-output-file. (func $display-to-file (type $Prim24) (param $v (ref eq)) ;; any/c (param $path-raw (ref eq)) ;; path-string? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $exists-raw (ref eq)) ;; optional exists mode, default = 'error (result (ref eq)) (local $port (ref eq)) (local.set $port (call $open-output-file (local.get $path-raw) (local.get $mode-raw) (local.get $exists-raw))) (drop (call $display-value-to-port (local.get $v) (local.get $port))) (drop (call $close-output-port (local.get $port))) (global.get $void)) ;; display-lines-to-file : list? path-string? [any/c] [(or/c 'binary 'text)] [output-file-exists-flag?] -> void? ;; Keywordless form of Racket's #:separator, #:mode, and #:exists options; default separator = #"\n". (func $display-lines-to-file (type $Prim25) (param $lst-raw (ref eq)) ;; list? (param $path-raw (ref eq)) ;; path-string? (param $sep-raw (ref eq)) ;; optional any/c separator, default = #"\n" (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $exists-raw (ref eq)) ;; optional exists mode, default = 'error (result (ref eq)) (local $port (ref eq)) (local $node (ref eq)) (local $pair (ref $Pair)) (if (ref.eq (call $list? (local.get $lst-raw)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:display-lines-to-file) (global.get $string:list?) (local.get $lst-raw)) (unreachable))) (local.set $port (call $open-output-file (local.get $path-raw) (local.get $mode-raw) (local.get $exists-raw))) (local.set $node (local.get $lst-raw)) (block $done (loop $loop (br_if $done (ref.eq (local.get $node) (global.get $null))) (local.set $pair (ref.cast (ref $Pair) (local.get $node))) (drop (call $display-value-to-port (struct.get $Pair $a (local.get $pair)) (local.get $port))) (if (ref.eq (local.get $sep-raw) (global.get $missing)) (then (drop (call $write-byte (ref.i31 (i32.const 20)) (local.get $port)))) (else (drop (call $display-value-to-port (local.get $sep-raw) (local.get $port))))) (local.set $node (struct.get $Pair $d (local.get $pair))) (br $loop))) (drop (call $close-output-port (local.get $port))) (global.get $void)) (func $webracket-vfs-write-file (type $Prim2) (param $path-raw (ref eq)) ;; path-string? (param $bytes-raw (ref eq)) ;; bytes? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $bytes (ref $Bytes)) (local $bytes-len i32) (local $status i32) (local.set $path (call $path-string->path/checked (global.get $symbol:webracket-vfs-write-file) (local.get $path-raw))) (if (i32.eqz (ref.test (ref $Bytes) (local.get $bytes-raw))) (then (call $raise-check-bytes (local.get $bytes-raw)) (unreachable))) (local.set $bytes (ref.cast (ref $Bytes) (local.get $bytes-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (local.set $bytes-len (array.len (struct.get $Bytes $bs (local.get $bytes)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.gt_u (local.get $bytes-len) (global.get $memory-map:vfs-file-buffer-length)) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $bytes-len (call $copy-bytes-to-memory (local.get $bytes) (global.get $memory-map:vfs-file-buffer-base))) (local.set $status (call $js-vfs-write-file (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len) (global.get $memory-map:vfs-file-buffer-base) (local.get $bytes-len))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:write-file-failed)) (unreachable))) (global.get $void)) ;; delete-file : path-string? -> void? ;; Delete an existing VFS file; directories and missing paths fail. (func $delete-file (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $status i32) (local.set $path (call $path-string->path/checked (global.get $symbol:delete-file) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $status (call $js-vfs-delete-file (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:delete-file-failed)) (unreachable))) (global.get $void)) ;; make-directory : path-string? [integer?] -> void? ;; Create a VFS directory; optional permissions default to #o777 and are ignored. (func $make-directory (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $permissions-raw (ref eq)) ;; optional exact-integer?, default = #o777; ignored (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $status i32) (local.set $path (call $path-string->path/checked (global.get $symbol:make-directory) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $status (call $js-vfs-make-directory (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:make-directory-failed)) (unreachable))) (global.get $void)) ;; make-directory* : path-string? -> void? ;; Create a VFS directory and its explicit ancestors; existing directories are accepted. (func $make-directory* (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $status i32) (local.set $path (call $path-string->path/checked (global.get $symbol:make-directory*) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $status (call $js-vfs-make-directory* (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:make-directory*-failed)) (unreachable))) (global.get $void)) ;; make-parent-directory* : path-string? -> void? ;; Create a VFS path's explicit parent directories. (func $make-parent-directory* (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $status i32) (local.set $path (call $path-string->path/checked (global.get $symbol:make-parent-directory*) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $status (call $js-vfs-make-parent-directory* (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:make-parent-directory*-failed)) (unreachable))) (global.get $void)) ;; delete-directory : path-string? -> void? ;; Delete an existing empty VFS directory. (func $delete-directory (type $Prim1) (param $path-raw (ref eq)) ;; path-string? (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $status i32) (local.set $path (call $path-string->path/checked (global.get $symbol:delete-directory) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $status (call $js-vfs-delete-directory (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:delete-directory-failed)) (unreachable))) (global.get $void)) ;; delete-directory/files : path-string? [any/c] -> void? ;; Keywordless form of Racket's #:must-exist? option; default #t. (func $delete-directory/files (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $must-exist-raw (ref eq)) ;; optional any/c, default = #t (result (ref eq)) (local $path (ref $Path)) (local $path-bs (ref $Bytes)) (local $path-len i32) (local $kind i32) (local $status i32) (local.set $path (call $path-string->path/checked (global.get $symbol:delete-directory/files) (local.get $path-raw))) (local.set $path-bs (struct.get $Path $bytes (local.get $path))) (local.set $kind (call $vfs-path-stat-kind (global.get $symbol:delete-directory/files) (local.get $path-raw))) (if (i32.eq (local.get $kind) (i32.const 0)) (then (if (ref.eq (local.get $must-exist-raw) (global.get $false)) (then (return (global.get $void)))))) (local.set $path-len (array.len (struct.get $Bytes $bs (local.get $path-bs)))) (if (i32.gt_u (local.get $path-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $path-len (call $copy-bytes-to-memory (local.get $path-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $status (call $js-vfs-delete-directory/files (global.get $memory-map:vfs-path-buffer-base) (local.get $path-len))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:delete-directory/files-failed)) (unreachable))) (global.get $void)) ;; rename-file-or-directory : path-string? path-string? [any/c] -> void? ;; Rename a VFS file or directory within the same mounted backend. (func $rename-file-or-directory (type $Prim23) (param $old-raw (ref eq)) ;; path-string? (param $new-raw (ref eq)) ;; path-string? (param $exists-ok-raw (ref eq)) ;; optional any/c, default = #f (result (ref eq)) (local $old (ref $Path)) (local $new (ref $Path)) (local $old-bs (ref $Bytes)) (local $new-bs (ref $Bytes)) (local $old-len i32) (local $new-len i32) (local $exists? i32) (local $status i32) (local.set $old (call $path-string->path/checked (global.get $symbol:rename-file-or-directory) (local.get $old-raw))) (local.set $new (call $path-string->path/checked (global.get $symbol:rename-file-or-directory) (local.get $new-raw))) (local.set $old-bs (struct.get $Path $bytes (local.get $old))) (local.set $new-bs (struct.get $Path $bytes (local.get $new))) (local.set $old-len (array.len (struct.get $Bytes $bs (local.get $old-bs)))) (local.set $new-len (array.len (struct.get $Bytes $bs (local.get $new-bs)))) (if (i32.gt_u (local.get $old-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $old-raw)) (unreachable))) (if (i32.gt_u (local.get $new-len) (global.get $memory-map:vfs-file-buffer-length)) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $old-len (call $copy-bytes-to-memory (local.get $old-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $new-len (call $copy-bytes-to-memory (local.get $new-bs) (global.get $memory-map:vfs-file-buffer-base))) (local.set $exists? (if (result i32) (ref.eq (local.get $exists-ok-raw) (global.get $missing)) (then (i32.const 0)) (else (i32.eqz (ref.eq (local.get $exists-ok-raw) (global.get $false)))))) (local.set $status (call $js-vfs-rename (global.get $memory-map:vfs-path-buffer-base) (local.get $old-len) (global.get $memory-map:vfs-file-buffer-base) (local.get $new-len) (local.get $exists?))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:rename-failed)) (unreachable))) (global.get $void)) ;; copy-directory/files : path-string? path-string? [any/c] [any/c] -> void? ;; Keywordless form of #:keep-modify-seconds? and #:preserve-links?. ;; The VFS has no links; metadata preservation is backend-defined for now. (func $copy-directory/files (type $Prim24) (param $src-raw (ref eq)) ;; path-string? (param $dest-raw (ref eq)) ;; path-string? (param $keep-modify-seconds-raw (ref eq)) ;; optional any/c, default = #f; currently ignored (param $preserve-links-raw (ref eq)) ;; optional any/c, default = #f; currently ignored (result (ref eq)) (local $src (ref $Path)) (local $dest (ref $Path)) (local $src-bs (ref $Bytes)) (local $dest-bs (ref $Bytes)) (local $src-len i32) (local $dest-len i32) (local $status i32) (local.set $src (call $path-string->path/checked (global.get $symbol:copy-directory/files) (local.get $src-raw))) (local.set $dest (call $path-string->path/checked (global.get $symbol:copy-directory/files) (local.get $dest-raw))) (local.set $src-bs (struct.get $Path $bytes (local.get $src))) (local.set $dest-bs (struct.get $Path $bytes (local.get $dest))) (local.set $src-len (array.len (struct.get $Bytes $bs (local.get $src-bs)))) (local.set $dest-len (array.len (struct.get $Bytes $bs (local.get $dest-bs)))) (if (i32.gt_u (local.get $src-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $src-raw)) (unreachable))) (if (i32.gt_u (local.get $dest-len) (global.get $memory-map:vfs-file-buffer-length)) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $src-len (call $copy-bytes-to-memory (local.get $src-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $dest-len (call $copy-bytes-to-memory (local.get $dest-bs) (global.get $memory-map:vfs-file-buffer-base))) (local.set $status (call $js-vfs-copy-directory/files (global.get $memory-map:vfs-path-buffer-base) (local.get $src-len) (global.get $memory-map:vfs-file-buffer-base) (local.get $dest-len))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:copy-directory/files-failed)) (unreachable))) (global.get $void)) ;; copy-file : path-string? path-string? [any/c] -> void? ;; Copy a VFS file within the same mounted backend; keyword options are not supported yet. (func $copy-file (type $Prim23) (param $src-raw (ref eq)) ;; path-string? (param $dest-raw (ref eq)) ;; path-string? (param $exists-ok-raw (ref eq)) ;; optional any/c, default = #f (result (ref eq)) (local $src (ref $Path)) (local $dest (ref $Path)) (local $src-bs (ref $Bytes)) (local $dest-bs (ref $Bytes)) (local $src-len i32) (local $dest-len i32) (local $exists? i32) (local $status i32) (local.set $src (call $path-string->path/checked (global.get $symbol:copy-file) (local.get $src-raw))) (local.set $dest (call $path-string->path/checked (global.get $symbol:copy-file) (local.get $dest-raw))) (local.set $src-bs (struct.get $Path $bytes (local.get $src))) (local.set $dest-bs (struct.get $Path $bytes (local.get $dest))) (local.set $src-len (array.len (struct.get $Bytes $bs (local.get $src-bs)))) (local.set $dest-len (array.len (struct.get $Bytes $bs (local.get $dest-bs)))) (if (i32.gt_u (local.get $src-len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $src-raw)) (unreachable))) (if (i32.gt_u (local.get $dest-len) (global.get $memory-map:vfs-file-buffer-length)) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-file-buffer-base) (global.get $memory-map:vfs-file-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (local.set $src-len (call $copy-bytes-to-memory (local.get $src-bs) (global.get $memory-map:vfs-path-buffer-base))) (local.set $dest-len (call $copy-bytes-to-memory (local.get $dest-bs) (global.get $memory-map:vfs-file-buffer-base))) (local.set $exists? (if (result i32) (ref.eq (local.get $exists-ok-raw) (global.get $missing)) (then (i32.const 0)) (else (i32.eqz (ref.eq (local.get $exists-ok-raw) (global.get $false)))))) (local.set $status (call $js-vfs-copy-file (global.get $memory-map:vfs-path-buffer-base) (local.get $src-len) (global.get $memory-map:vfs-file-buffer-base) (local.get $dest-len) (local.get $exists?))) (if (i32.lt_s (local.get $status) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:copy-file-failed)) (unreachable))) (global.get $void)) ;; make-temporary-candidate : symbol? (or/c string? missing) (or/c path-string? #f missing) -> path? ;; Build a complete temporary path by replacing the first ~a in template with a counter. (func $make-temporary-candidate (param $who (ref eq)) ;; symbol? (param $template-raw (ref eq)) ;; optional string?, default = "rkttmp~a" (param $base-dir-raw (ref eq)) ;; optional (or/c path-string? #f), default = (find-system-path 'temp-dir) (result (ref eq)) (local $template-bs (ref $Bytes)) (local $template-ar (ref $I8Array)) (local $template-len i32) (local $i i32) (local $marker i32) (local $n i32) (local $digits (ref eq)) (local $digits-bs (ref eq)) (local $prefix (ref eq)) (local $suffix (ref eq)) (local $name-bs (ref eq)) (local $base-dir (ref eq)) (local.set $template-bs (ref.cast (ref $Bytes) (global.get $bytes:empty))) (local.set $template-ar (struct.get $Bytes $bs (local.get $template-bs))) (local.set $digits (global.get $false)) (local.set $digits-bs (global.get $bytes:empty)) (local.set $prefix (global.get $bytes:empty)) (local.set $suffix (global.get $bytes:empty)) (local.set $name-bs (global.get $bytes:empty)) (local.set $base-dir (global.get $false)) (if (ref.eq (local.get $template-raw) (global.get $missing)) (then (local.set $template-bs (ref.cast (ref $Bytes) (global.get $bytes:rkttmp-template)))) (else (if (i32.eqz (ref.test (ref $String) (local.get $template-raw))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:string?) (local.get $template-raw)) (unreachable))) (local.set $template-bs (ref.cast (ref $Bytes) (call $string->bytes/utf-8 (local.get $template-raw) (global.get $false) (global.get $false) (global.get $false)))))) (local.set $template-ar (struct.get $Bytes $bs (local.get $template-bs))) (local.set $template-len (array.len (local.get $template-ar))) (local.set $marker (i32.const -1)) (block $done (loop $loop (br_if $done (i32.ge_u (i32.add (local.get $i) (i32.const 1)) (local.get $template-len))) (if (i32.and (i32.eq (array.get_u $I8Array (local.get $template-ar) (local.get $i)) (i32.const 126)) ;; ~ (i32.eq (array.get_u $I8Array (local.get $template-ar) (i32.add (local.get $i) (i32.const 1))) (i32.const 97))) ;; a (then (local.set $marker (local.get $i)) (br $done))) (local.set $i (i32.add (local.get $i) (i32.const 1))) (br $loop))) (if (i32.lt_s (local.get $marker) (i32.const 0)) (then (call $raise-argument-error1 (local.get $who) (global.get $string:vfs:temporary-template) (local.get $template-raw)) (unreachable))) (global.set $temporary-file-counter (i32.add (global.get $temporary-file-counter) (i32.const 1))) (local.set $n (global.get $temporary-file-counter)) (local.set $digits (call $number->string (ref.i31 (i32.shl (local.get $n) (i32.const 1))) (global.get $missing))) (local.set $digits-bs (call $string->bytes/utf-8 (local.get $digits) (global.get $false) (global.get $false) (global.get $false))) (local.set $prefix (call $subbytes (local.get $template-bs) (ref.i31 (i32.shl (i32.const 0) (i32.const 1))) (ref.i31 (i32.shl (local.get $marker) (i32.const 1))))) (local.set $suffix (call $subbytes (local.get $template-bs) (ref.i31 (i32.shl (i32.add (local.get $marker) (i32.const 2)) (i32.const 1))) (ref.i31 (i32.shl (local.get $template-len) (i32.const 1))))) (local.set $name-bs (call $bytes-append/2 (call $bytes-append/2 (local.get $prefix) (local.get $digits-bs)) (local.get $suffix))) (if (call $path-bytes-absolute? (ref.cast (ref $Bytes) (local.get $name-bs))) (then (return (call $bytes->path (local.get $name-bs) (global.get $missing))))) (local.set $base-dir (if (result (ref eq)) (i32.or (ref.eq (local.get $base-dir-raw) (global.get $missing)) (ref.eq (local.get $base-dir-raw) (global.get $false))) (then (call $find-system-path (global.get $symbol:temp-dir))) (else (local.get $base-dir-raw)))) (call $path-join-bytes (local.get $base-dir) (call $bytes->path (local.get $name-bs) (global.get $missing)))) ;; make-temporary-file : [string?] [(or/c path-string? #f 'directory)] [(or/c path-string? #f)] -> complete-path? ;; Keywordless form of Racket's #:copy-from and #:base-dir options. ;; Template support currently recognizes one literal ~a placeholder. (func $make-temporary-file (type $Prim03) (param $template-raw (ref eq)) ;; optional string?, default = "rkttmp~a" (param $copy-from-raw (ref eq)) ;; optional (or/c path-string? #f 'directory), default = #f (param $base-dir-raw (ref eq)) ;; optional (or/c path-string? #f), default = #f (result (ref eq)) (local $copy-from (ref eq)) (local $candidate (ref eq)) (local $port (ref eq)) (local.set $copy-from (global.get $false)) (local.set $candidate (global.get $false)) (local.set $port (global.get $false)) (local.set $copy-from (if (result (ref eq)) (ref.eq (local.get $copy-from-raw) (global.get $missing)) (then (global.get $false)) (else (local.get $copy-from-raw)))) (block $done (loop $loop (local.set $candidate (call $make-temporary-candidate (global.get $symbol:make-temporary-file) (local.get $template-raw) (local.get $base-dir-raw))) (br_if $loop (i32.or (ref.eq (call $file-exists? (local.get $candidate)) (global.get $true)) (ref.eq (call $directory-exists? (local.get $candidate)) (global.get $true)))) (if (ref.eq (local.get $copy-from) (global.get $symbol:directory)) (then (drop (call $make-directory (local.get $candidate) (global.get $missing)))) (else (if (ref.eq (local.get $copy-from) (global.get $false)) (then (local.set $port (call $open-output-file (local.get $candidate) (global.get $symbol:binary) (global.get $symbol:error))) (drop (call $close-output-port (local.get $port)))) (else (drop (call $copy-file (local.get $copy-from) (local.get $candidate) (global.get $false))))))) (br $done))) (local.get $candidate)) ;; make-temporary-candidate* : symbol? bytes? bytes? (or/c path-string? #f missing) -> path? ;; Build a complete temporary path as bytes-append of prefix, counter, and suffix. (func $make-temporary-candidate* (param $who (ref eq)) ;; symbol? (param $prefix-raw (ref eq)) ;; bytes? (param $suffix-raw (ref eq)) ;; bytes? (param $base-dir-raw (ref eq)) ;; optional (or/c path-string? #f), default = (find-system-path 'temp-dir) (result (ref eq)) (local $prefix (ref $Bytes)) (local $suffix (ref $Bytes)) (local $digits (ref eq)) (local $digits-bs (ref eq)) (local $name-bs (ref eq)) (local $base-dir (ref eq)) (local $n i32) (local.set $prefix (ref.cast (ref $Bytes) (global.get $bytes:empty))) (local.set $suffix (ref.cast (ref $Bytes) (global.get $bytes:empty))) (local.set $digits (global.get $false)) (local.set $digits-bs (global.get $bytes:empty)) (local.set $name-bs (global.get $bytes:empty)) (local.set $base-dir (global.get $false)) (if (i32.eqz (ref.test (ref $Bytes) (local.get $prefix-raw))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:bytes?) (local.get $prefix-raw)) (unreachable))) (if (i32.eqz (ref.test (ref $Bytes) (local.get $suffix-raw))) (then (call $raise-argument-error1 (local.get $who) (global.get $string:bytes?) (local.get $suffix-raw)) (unreachable))) (local.set $prefix (ref.cast (ref $Bytes) (local.get $prefix-raw))) (local.set $suffix (ref.cast (ref $Bytes) (local.get $suffix-raw))) (global.set $temporary-file-counter (i32.add (global.get $temporary-file-counter) (i32.const 1))) (local.set $n (global.get $temporary-file-counter)) (local.set $digits (call $number->string (ref.i31 (i32.shl (local.get $n) (i32.const 1))) (global.get $missing))) (local.set $digits-bs (call $string->bytes/utf-8 (local.get $digits) (global.get $false) (global.get $false) (global.get $false))) (local.set $name-bs (call $bytes-append/2 (call $bytes-append/2 (local.get $prefix) (local.get $digits-bs)) (local.get $suffix))) (if (call $path-bytes-absolute? (ref.cast (ref $Bytes) (local.get $name-bs))) (then (return (call $bytes->path (local.get $name-bs) (global.get $missing))))) (local.set $base-dir (if (result (ref eq)) (i32.or (ref.eq (local.get $base-dir-raw) (global.get $missing)) (ref.eq (local.get $base-dir-raw) (global.get $false))) (then (call $find-system-path (global.get $symbol:temp-dir))) (else (local.get $base-dir-raw)))) (call $path-join-bytes (local.get $base-dir) (call $bytes->path (local.get $name-bs) (global.get $missing)))) ;; make-temporary-file* : bytes? bytes? [(or/c path-string? #f)] [(or/c path-string? #f)] -> complete-path? ;; Keywordless form of Racket's #:copy-from and #:base-dir options. (func $make-temporary-file* (type $Prim24) (param $prefix-raw (ref eq)) ;; bytes? (param $suffix-raw (ref eq)) ;; bytes? (param $copy-from-raw (ref eq)) ;; optional (or/c path-string? #f), default = #f (param $base-dir-raw (ref eq)) ;; optional (or/c path-string? #f), default = #f (result (ref eq)) (local $copy-from (ref eq)) (local $candidate (ref eq)) (local $port (ref eq)) (local.set $copy-from (global.get $false)) (local.set $candidate (global.get $false)) (local.set $port (global.get $false)) (local.set $copy-from (if (result (ref eq)) (ref.eq (local.get $copy-from-raw) (global.get $missing)) (then (global.get $false)) (else (local.get $copy-from-raw)))) (block $done (loop $loop (local.set $candidate (call $make-temporary-candidate* (global.get $symbol:make-temporary-file*) (local.get $prefix-raw) (local.get $suffix-raw) (local.get $base-dir-raw))) (br_if $loop (i32.or (ref.eq (call $file-exists? (local.get $candidate)) (global.get $true)) (ref.eq (call $directory-exists? (local.get $candidate)) (global.get $true)))) (if (ref.eq (local.get $copy-from) (global.get $false)) (then (local.set $port (call $open-output-file (local.get $candidate) (global.get $symbol:binary) (global.get $symbol:error))) (drop (call $close-output-port (local.get $port)))) (else (drop (call $copy-file (local.get $copy-from) (local.get $candidate) (global.get $false))))) (br $done))) (local.get $candidate)) ;; make-temporary-directory : [string?] [(or/c path-string? #f)] -> complete-path? ;; Keywordless form of Racket's #:base-dir option. (func $make-temporary-directory (type $Prim02) (param $template-raw (ref eq)) ;; optional string?, default = "rkttmp~a" (param $base-dir-raw (ref eq)) ;; optional (or/c path-string? #f), default = #f (result (ref eq)) (local $candidate (ref eq)) (local.set $candidate (global.get $false)) (block $done (loop $loop (local.set $candidate (call $make-temporary-candidate (global.get $symbol:make-temporary-directory) (local.get $template-raw) (local.get $base-dir-raw))) (br_if $loop (i32.or (ref.eq (call $file-exists? (local.get $candidate)) (global.get $true)) (ref.eq (call $directory-exists? (local.get $candidate)) (global.get $true)))) (drop (call $make-directory (local.get $candidate) (global.get $missing))) (br $done))) (local.get $candidate)) ;; make-temporary-directory* : bytes? bytes? [(or/c path-string? #f)] -> complete-path? ;; Keywordless form of Racket's #:base-dir option. (func $make-temporary-directory* (type $Prim23) (param $prefix-raw (ref eq)) ;; bytes? (param $suffix-raw (ref eq)) ;; bytes? (param $base-dir-raw (ref eq)) ;; optional (or/c path-string? #f), default = #f (result (ref eq)) (local $candidate (ref eq)) (local.set $candidate (global.get $false)) (block $done (loop $loop (local.set $candidate (call $make-temporary-candidate* (global.get $symbol:make-temporary-directory*) (local.get $prefix-raw) (local.get $suffix-raw) (local.get $base-dir-raw))) (br_if $loop (i32.or (ref.eq (call $file-exists? (local.get $candidate)) (global.get $true)) (ref.eq (call $directory-exists? (local.get $candidate)) (global.get $true)))) (drop (call $make-directory (local.get $candidate) (global.get $missing))) (br $done))) (local.get $candidate)) ;; file-or-directory-modify-seconds : path-string? [(or/c exact-nonnegative-integer? #f)] -> (or/c exact-nonnegative-integer? void?) ;; Get or set the VFS modification time; fail-thunk and negative seconds are not supported yet. (func $file-or-directory-modify-seconds (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $secs-raw (ref eq)) ;; optional (or/c exact-nonnegative-integer? #f), default = #f (result (ref eq)) (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $len i32) (local $has-secs i32) (local $secs i32) (local $mtime i32) (local.set $path (call $path-string->path/checked (global.get $symbol:file-or-directory-modify-seconds) (local.get $path-raw))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $bytes)))) (if (i32.gt_u (local.get $len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (ref.eq (local.get $secs-raw) (global.get $missing)) (then (local.set $has-secs (i32.const 0))) (else (if (ref.eq (local.get $secs-raw) (global.get $false)) (then (local.set $has-secs (i32.const 0))) (else (if (ref.eq (call $exact-nonnegative-integer? (local.get $secs-raw)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:file-or-directory-modify-seconds) (global.get $string:exact-nonnegative-integer?) (local.get $secs-raw)) (unreachable))) (local.set $has-secs (i32.const 1)) (local.set $secs (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $secs-raw))) (i32.const 1))))))) (local.set $len (call $copy-bytes-to-memory (local.get $bytes) (global.get $memory-map:vfs-path-buffer-base))) (local.set $mtime (call $js-vfs-modify-seconds (global.get $memory-map:vfs-path-buffer-base) (local.get $len) (local.get $has-secs) (local.get $secs))) (if (i32.lt_s (local.get $mtime) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:modify-seconds-failed)) (unreachable))) (if (result (ref eq)) (i32.eqz (local.get $has-secs)) (then (ref.i31 (i32.shl (local.get $mtime) (i32.const 1)))) (else (global.get $void)))) ;; file-or-directory-permissions : path-string? [(or/c #f 'bits (integer-in 0 65535))] -> (or/c list? fixnum? void?) ;; Get or set VFS permission bits; default mode returns '(read write execute)-style permissions. (func $file-or-directory-permissions (type $Prim12) (param $path-raw (ref eq)) ;; path-string? (param $mode-raw (ref eq)) ;; optional (or/c #f 'bits (integer-in 0 65535)), default = #f (result (ref eq)) (local $path (ref $Path)) (local $bytes (ref $Bytes)) (local $len i32) (local $set-mode? i32) (local $bits-mode? i32) (local $mode i32) (local $bits i32) (local $perms (ref eq)) (local.set $path (call $path-string->path/checked (global.get $symbol:file-or-directory-permissions) (local.get $path-raw))) (local.set $bytes (struct.get $Path $bytes (local.get $path))) (local.set $len (array.len (struct.get $Bytes $bs (local.get $bytes)))) (if (i32.gt_u (local.get $len) (global.get $memory-map:vfs-path-buffer-length)) (then (call $raise-path-expected (local.get $path-raw)) (unreachable))) (if (i32.eqz (call $linear-memory-range-available? (global.get $memory-map:vfs-path-buffer-base) (global.get $memory-map:vfs-path-buffer-length))) (then (call $raise-string-buffer-overflow) (unreachable))) (if (ref.eq (local.get $mode-raw) (global.get $missing)) (then (local.set $set-mode? (i32.const 0))) (else (if (ref.eq (local.get $mode-raw) (global.get $false)) (then (local.set $set-mode? (i32.const 0))) (else (if (ref.eq (local.get $mode-raw) (global.get $symbol:bits)) (then (local.set $set-mode? (i32.const 0)) (local.set $bits-mode? (i32.const 1))) (else (if (ref.eq (call $exact-nonnegative-integer? (local.get $mode-raw)) (global.get $false)) (then (call $raise-argument-error1 (global.get $symbol:file-or-directory-permissions) (global.get $string:permissions-mode) (local.get $mode-raw)) (unreachable))) (local.set $mode (i32.shr_s (i31.get_s (ref.cast (ref i31) (local.get $mode-raw))) (i32.const 1))) (if (i32.gt_u (local.get $mode) (i32.const 65535)) (then (call $raise-argument-error1 (global.get $symbol:file-or-directory-permissions) (global.get $string:permissions-mode) (local.get $mode-raw)) (unreachable))) (local.set $set-mode? (i32.const 1)))))))) (local.set $len (call $copy-bytes-to-memory (local.get $bytes) (global.get $memory-map:vfs-path-buffer-base))) (local.set $bits (call $js-vfs-permissions (global.get $memory-map:vfs-path-buffer-base) (local.get $len) (local.get $set-mode?) (local.get $mode))) (if (i32.lt_s (local.get $bits) (i32.const 0)) (then (call $raise-vfs-file-error (global.get $string:vfs:permissions-failed)) (unreachable))) (if (local.get $set-mode?) (then (return (global.get $void)))) (if (local.get $bits-mode?) (then (return (ref.i31 (i32.shl (local.get $bits) (i32.const 1)))))) (local.set $perms (global.get $null)) (if (i32.ne (i32.and (local.get $bits) (i32.const #o100)) (i32.const 0)) (then (local.set $perms (call $cons (global.get $symbol:execute) (local.get $perms))))) (if (i32.ne (i32.and (local.get $bits) (i32.const #o200)) (i32.const 0)) (then (local.set $perms (call $cons (global.get $symbol:write) (local.get $perms))))) (if (i32.ne (i32.and (local.get $bits) (i32.const #o400)) (i32.const 0)) (then (local.set $perms (call $cons (global.get $symbol:read) (local.get $perms))))) (local.get $perms)) ;; open-output-file : path-string? [(or/c 'binary 'text)] [(or/c 'error 'append 'replace 'truncate 'must-truncate 'truncate/replace)] -> output-port? ;; Keywordless form of Racket's #:mode and #:exists options. ;; Text mode is accepted but currently behaves like binary mode. ;; Update-style modes wait on random-access file-position support. (func $open-output-file (type $Prim13) (param $path-raw (ref eq)) ;; path-string? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $exists-raw (ref eq)) ;; optional exists mode, default = 'error (result (ref eq)) (local $path (ref $Path)) (local $bs (ref $Bytes)) (local $file-bs (ref $Bytes)) (local $loc (ref $Location)) (local $mode (ref eq)) (local $exists (ref eq)) (local $kind i32) (local $append? i32) (local $idx i32) (local $capacity i32) (local.set $path (call $path-string->path/checked (global.get $symbol:open-output-file) (local.get $path-raw))) (local.set $mode (if (result (ref eq)) (ref.eq (local.get $mode-raw) (global.get $missing)) (then (global.get $symbol:binary)) (else (local.get $mode-raw)))) (if (i32.eqz (i32.or (ref.eq (local.get $mode) (global.get $symbol:binary)) (ref.eq (local.get $mode) (global.get $symbol:text)))) (then (call $raise-argument-error1 (global.get $symbol:open-output-file) (global.get $string:output-file-mode-flag) (local.get $mode-raw)) (unreachable))) (local.set $exists (if (result (ref eq)) (ref.eq (local.get $exists-raw) (global.get $missing)) (then (global.get $symbol:error)) (else (local.get $exists-raw)))) (if (i32.eqz (i32.or (i32.or (ref.eq (local.get $exists) (global.get $symbol:error)) (ref.eq (local.get $exists) (global.get $symbol:append))) (i32.or (i32.or (ref.eq (local.get $exists) (global.get $symbol:replace)) (ref.eq (local.get $exists) (global.get $symbol:truncate))) (i32.or (ref.eq (local.get $exists) (global.get $symbol:must-truncate)) (ref.eq (local.get $exists) (global.get $symbol:truncate/replace)))))) (then (call $raise-argument-error1 (global.get $symbol:open-output-file) (global.get $string:output-file-exists-flag) (local.get $exists-raw)) (unreachable))) (local.set $kind (call $vfs-path-stat-kind (global.get $symbol:open-output-file) (local.get $path-raw))) (if (i32.eq (local.get $kind) (i32.const 2)) (then (call $raise-vfs-file-error (global.get $string:vfs:write-file-failed)) (unreachable))) (if (i32.and (ref.eq (local.get $exists) (global.get $symbol:error)) (i32.ne (local.get $kind) (i32.const 0))) (then (call $raise-vfs-file-error (global.get $string:vfs:write-file-failed)) (unreachable))) (if (i32.and (ref.eq (local.get $exists) (global.get $symbol:must-truncate)) (i32.ne (local.get $kind) (i32.const 1))) (then (call $raise-vfs-file-error (global.get $string:vfs:write-file-failed)) (unreachable))) (local.set $append? (ref.eq (local.get $exists) (global.get $symbol:append))) (local.set $capacity (i32.const 32)) (local.set $idx (i32.const 0)) (local.set $bs (struct.new $Bytes (i32.const 0) (i32.const 0) (call $i8make-array (i32.const 32) (i32.const 0)))) (if (i32.and (local.get $append?) (i32.eq (local.get $kind) (i32.const 1))) (then (local.set $file-bs (call $vfs-read-file-bytes (global.get $symbol:open-output-file) (local.get $path-raw))) (local.set $idx (array.len (struct.get $Bytes $bs (local.get $file-bs)))) (if (local.get $idx) (then (local.set $capacity (local.get $idx)) (local.set $bs (struct.new $Bytes (i32.const 0) (i32.const 0) (struct.get $Bytes $bs (local.get $file-bs)))))))) (local.set $loc (ref.cast (ref $Location) (call $make-initial-location))) (struct.new $OutputFilePort (i32.const 0) ;; $hash (local.get $path) ;; $name (i32.const 0) ;; $closed (local.get $bs) ;; $bytes (local.get $capacity) ;; $len (local.get $idx) ;; $idx (local.get $loc) ;; $loc (i32.const 0) ;; $utf8-len (i32.const 0) ;; $utf8-left (i32.const 0) ;; $utf8-bytes (local.get $path))) ;; $path ;; open-input-file : path-string? [(or/c 'binary 'text)] [any/c] -> input-port? ;; Keywordless form of Racket's #:mode and #:for-module? options. ;; Text mode is accepted but currently behaves like binary mode. (func $open-input-file (type $Prim13) (param $path-raw (ref eq)) ;; path-string? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $for-module-raw (ref eq)) ;; optional any/c, default = #f; currently ignored (result (ref eq)) (local $path (ref $Path)) (local $file-bs (ref $Bytes)) (local $mode (ref eq)) (local.set $path (call $path-string->path/checked (global.get $symbol:open-input-file) (local.get $path-raw))) (local.set $mode (if (result (ref eq)) (ref.eq (local.get $mode-raw) (global.get $missing)) (then (global.get $symbol:binary)) (else (local.get $mode-raw)))) (if (i32.eqz (i32.or (ref.eq (local.get $mode) (global.get $symbol:binary)) (ref.eq (local.get $mode) (global.get $symbol:text)))) (then (call $raise-argument-error1 (global.get $symbol:open-input-file) (global.get $string:input-file-mode-flag) (local.get $mode-raw)) (unreachable))) (local.set $file-bs (call $vfs-read-file-bytes (global.get $symbol:open-input-file) (local.get $path-raw))) (call $open-input-bytes (local.get $file-bs) (local.get $path))) ;; $call-with-input-file : path-string? procedure? [(or/c 'binary 'text)] -> any ;; Open a VFS file, pass its input port to proc, and return proc's result. (func $call-with-input-file (type $Prim23) (param $path-raw (ref eq)) ;; path-string? (param $proc (ref eq)) ;; procedure? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $port (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local.set $port (call $open-input-file (local.get $path-raw) (local.get $mode-raw) (global.get $missing))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new_fixed $Args 1 (local.get $port))) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (drop (call $close-input-port (local.get $port))) (local.get $res)) ;; $call-with-output-file : path-string? procedure? [(or/c 'binary 'text)] [exists-mode] -> any ;; Open a VFS output file, pass its output port to proc, close it, ;; and return proc's result. (func $call-with-output-file (type $Prim24) (param $path-raw (ref eq)) ;; path-string? (param $proc (ref eq)) ;; procedure? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $exists-raw (ref eq)) ;; optional exists mode, default = 'error (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $port (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local.set $port (call $open-output-file (local.get $path-raw) (local.get $mode-raw) (local.get $exists-raw))) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new_fixed $Args 1 (local.get $port))) (local.set $res (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv))) (drop (call $close-output-port (local.get $port))) (local.get $res)) ;; $call-with-current-input-port/thunk : input-port? procedure? -> any ;; Install port as the current input port while thunk runs, then restore it. (func $call-with-current-input-port/thunk (param $port (ref eq)) ;; input-port? (param $thunk (ref eq)) ;; procedure? (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $old (ref eq)) (local $res (ref eq)) (local $exn-val (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $thunk))) (then (call $raise-argument-error:procedure-expected (local.get $thunk)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $thunk))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 0))) (local.set $old (call $current-input-port (global.get $missing))) (drop (call $current-input-port (local.get $port))) (local.set $res (block $done (result (ref eq)) (block $handler-block (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler-block) (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv)) (br $done))) (local.set $exn-val) (drop (call $current-input-port (local.get $old))) (throw $exn (local.get $exn-val)) (unreachable))) (drop (call $current-input-port (local.get $old))) (local.get $res)) ;; $call-with-current-output-port/thunk : output-port? procedure? -> any ;; Install port as the current output port while thunk runs, then restore it. (func $call-with-current-output-port/thunk (param $port (ref eq)) ;; output-port? (param $thunk (ref eq)) ;; procedure? (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $args (ref $Args)) (local $old (ref eq)) (local $res (ref eq)) (local $exn-val (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $thunk))) (then (call $raise-argument-error:procedure-expected (local.get $thunk)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $thunk))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $args (array.new $Args (global.get $null) (i32.const 0))) (local.set $old (call $current-output-port (global.get $missing))) (drop (call $current-output-port (local.get $port))) (local.set $res (block $done (result (ref eq)) (block $handler-block (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler-block) (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv)) (br $done))) (local.set $exn-val) (drop (call $current-output-port (local.get $old))) (throw $exn (local.get $exn-val)) (unreachable))) (drop (call $current-output-port (local.get $old))) (local.get $res)) ;; $call-with-input-file* : path-string? procedure? [(or/c 'binary 'text)] -> any ;; Open a VFS file, pass its input port to proc, and close it on return or exception. (func $call-with-input-file* (type $Prim23) (param $path-raw (ref eq)) ;; path-string? (param $proc (ref eq)) ;; procedure? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $port (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local $exn-val (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $port (call $open-input-file (local.get $path-raw) (local.get $mode-raw) (global.get $missing))) (local.set $args (array.new_fixed $Args 1 (local.get $port))) (local.set $res (block $done (result (ref eq)) (block $handler-block (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler-block) (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv)) (br $done))) (local.set $exn-val) (drop (call $close-input-port (local.get $port))) (throw $exn (local.get $exn-val)) (unreachable))) (drop (call $close-input-port (local.get $port))) (local.get $res)) ;; $call-with-output-file* : path-string? procedure? [(or/c 'binary 'text)] [exists-mode] -> any ;; Open a VFS file, pass its output port to proc, and close it on return or exception. (func $call-with-output-file* (type $Prim24) (param $path-raw (ref eq)) ;; path-string? (param $proc (ref eq)) ;; procedure? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $exists-raw (ref eq)) ;; optional exists mode, default = 'error (result (ref eq)) (local $f (ref $Procedure)) (local $finv (ref $ProcedureInvoker)) (local $port (ref eq)) (local $args (ref $Args)) (local $res (ref eq)) (local $exn-val (ref eq)) (if (i32.eqz (ref.test (ref $Procedure) (local.get $proc))) (then (call $raise-argument-error:procedure-expected (local.get $proc)) (unreachable))) (local.set $f (ref.cast (ref $Procedure) (local.get $proc))) (local.set $finv (struct.get $Procedure $invoke (local.get $f))) (local.set $port (call $open-output-file (local.get $path-raw) (local.get $mode-raw) (local.get $exists-raw))) (local.set $args (array.new_fixed $Args 1 (local.get $port))) (local.set $res (block $done (result (ref eq)) (block $handler-block (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler-block) (call_ref $ProcedureInvoker (local.get $f) (local.get $args) (local.get $finv)) (br $done))) (local.set $exn-val) (drop (call $close-output-port (local.get $port))) (throw $exn (local.get $exn-val)) (unreachable))) (drop (call $close-output-port (local.get $port))) (local.get $res)) ;; $with-input-from-file : path-string? procedure? [(or/c 'binary 'text)] -> any ;; Install a VFS input file as the current input port while thunk runs. (func $with-input-from-file (type $Prim23) (param $path-raw (ref eq)) ;; path-string? (param $thunk (ref eq)) ;; procedure? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (result (ref eq)) (local $port (ref eq)) (local $res (ref eq)) (local $exn-val (ref eq)) (local.set $port (call $open-input-file (local.get $path-raw) (local.get $mode-raw) (global.get $missing))) (local.set $res (block $done (result (ref eq)) (block $handler-block (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler-block) (call $call-with-current-input-port/thunk (local.get $port) (local.get $thunk)) (br $done))) (local.set $exn-val) (drop (call $close-input-port (local.get $port))) (throw $exn (local.get $exn-val)) (unreachable))) (drop (call $close-input-port (local.get $port))) (local.get $res)) ;; $with-output-to-file : path-string? procedure? [(or/c 'binary 'text)] [exists-mode] -> any ;; Install a VFS output file as the current output port while thunk runs. (func $with-output-to-file (type $Prim24) (param $path-raw (ref eq)) ;; path-string? (param $thunk (ref eq)) ;; procedure? (param $mode-raw (ref eq)) ;; optional (or/c 'binary 'text), default = 'binary (param $exists-raw (ref eq)) ;; optional exists mode, default = 'error (result (ref eq)) (local $port (ref eq)) (local $res (ref eq)) (local $exn-val (ref eq)) (local.set $port (call $open-output-file (local.get $path-raw) (local.get $mode-raw) (local.get $exists-raw))) (local.set $res (block $done (result (ref eq)) (block $handler-block (result (ref eq)) (try_table (result (ref eq)) (catch $exn $handler-block) (call $call-with-current-output-port/thunk (local.get $port) (local.get $thunk)) (br $done))) (local.set $exn-val) (drop (call $close-output-port (local.get $port))) (throw $exn (local.get $exn-val)) (unreachable))) (drop (call $close-output-port (local.get $port))) (local.get $res)) ;;; ;;; FFI ;;; #;(type $External (sub $Heap (struct (field $hash (mut i32)) (field $v (ref null extern))))) ;; Moved up to import section #;(func $js_document_body (import "ffi" "js_document_body") (result externref)) #;(func $js-document-body (result (ref eq)) (struct.new $External (i32.const 0) (call $js-document-body/imported))) #;(func $js-make-text-node (result (ref eq)) (struct.new $External (i32.const 0) (call $js_make_text_node))) ;; Top level `(define-label ...) ,@dls ;; $entry ;; - called by the host (Node or Browser) to start computation ;; - the host expects `entry` to return an integer as result ;; - we convert the result value into a byte string ;; - store the byte string in $result-bytes ;; - we return the length of $result-bytes ;; - the host calls $get-bytes which copies the result-bytes ;; into the linear memory, where the host can read it. (global $system-path-convention (mut (ref eq)) (ref.i31 (i32.const 0))) (global $current-directory-path (mut (ref eq)) (ref.i31 (i32.const 0))) (global $current-directory-for-user-path (mut (ref eq)) (ref.i31 (i32.const 0))) (global $temporary-file-counter (mut i32) (i32.const 0)) (global $current-input-port-value (mut (ref eq)) (ref.i31 (i32.const 0))) (global $current-output-port-value (mut (ref eq)) (ref.i31 (i32.const 0))) (global $current-error-port-value (mut (ref eq)) (ref.i31 (i32.const 0))) (global $result-bytes (mut (ref eq)) (ref.i31 (i32.const 0))) ;; Struct-type properties provided by the runtime (global $prop:object-name (mut (ref eq)) (global.get $void)) (global $prop:procedure (mut (ref eq)) (global.get $void)) (global $prop:checked-procedure (mut (ref eq)) (global.get $void)) (global $prop:impersonator-of (mut (ref eq)) (global.get $void)) (global $prop:method-arity-error (mut (ref eq)) (global.get $void)) (global $prop:arity-string (mut (ref eq)) (global.get $void)) (global $prop:incomplete-arity (mut (ref eq)) (global.get $void)) (global $prop:authentic (mut (ref eq)) (global.get $void)) (global $prop:custom-write (mut (ref eq)) (global.get $void)) (global $prop:equal+hash (mut (ref eq)) (global.get $void)) (global $equal+hash-recur/equal (mut (ref eq)) (global.get $void)) (global $equal+hash-recur/equal-always (mut (ref eq)) (global.get $void)) (global $equal+hash-recur/hash (mut (ref eq)) (global.get $void)) (func $get-bytes (export "get_bytes") (result (ref $Bytes)) (ref.cast (ref $Bytes) (global.get $result-bytes))) (func $entry-uncaught-exception2 (param $e (ref eq)) (drop (call $js-log (call $format/display (local.get $e))))) (func $entry-uncaught-exception1 (param $e (ref eq)) (drop (call $js-log (local.get $e)))) (func $entry-uncaught-exception (param $e (ref eq)) (if (ref.eq (call $exn? (local.get $e)) (global.get $false)) (then (call $entry-uncaught-exception1 (local.get $e))) (else (call $entry-uncaught-exception2 (call $exn-message (local.get $e))))) (throw $exn (local.get $e))) (func $entry (export "entry") (result i32) ; Declare local variables (bound by let-values and letrec-values) (local $entry-exn (ref eq)) ,@(let () (define (Local x) (match x [(list v t) `(local ,(if (symbol? v) v (Var v)) ,t)] [(list v t init) `(local ,(if (symbol? v) v (Var v)) ,t)])) (define (Local* xs) (map Local xs)) (Local* (reverse entry-locals))) ;; Initialize global constants (global.set $flzero (call $i32->flonum (i32.const 0))) (global.set $flone (call $i32->flonum (i32.const 1))) (global.set $fltwo (call $i32->flonum (i32.const 2))) (global.set $flthree (call $i32->flonum (i32.const 3))) ;; Initialize global state (call $initialize-the-symbol-table) ; for interning (call $initialize-the-keyword-table) ; for interning ;; Initialize string constants used in the runtime ,@(initialize-runtime-string-constants) ;; Initialize bytes constants used in the runtime ,@(initialize-runtime-bytes-constants) ;; Initialize symbol constants used in the runtime ,@(initialize-runtime-symbol-constants) ;; Initialize struct-type properties provided by the runtime (global.set $prop:object-name (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:object-name)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:procedure (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:procedure)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:checked-procedure (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:checked-procedure)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:impersonator-of (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:impersonator-of)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:method-arity-error (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:method-arity-error)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:arity-string (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:arity-string)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:incomplete-arity (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:incomplete-arity)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:authentic (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:prop:authentic)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:custom-write (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:prop:custom-write)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $prop:equal+hash (ref.cast (ref eq) (call $make-struct-type-property-descriptor/checked (ref.cast (ref $Symbol) (global.get $symbol:prop:equal+hash)) (global.get $false) (global.get $null) (global.get $false) (global.get $false)))) (global.set $equal+hash-recur/equal (struct.new $Closure (i32.const 0) (global.get $false) (ref.i31 (i32.const 4)) (global.get $false) (ref.func $invoke-closure) (global.get $false) (ref.func $equal+hash-recur/equal) (array.new_fixed $Free 0))) (global.set $equal+hash-recur/equal-always (struct.new $Closure (i32.const 0) (global.get $false) (ref.i31 (i32.const 4)) (global.get $false) (ref.func $invoke-closure) (global.get $false) (ref.func $equal+hash-recur/equal-always) (array.new_fixed $Free 0))) (global.set $equal+hash-recur/hash (struct.new $Closure (i32.const 0) (global.get $false) (ref.i31 (i32.const 2)) (global.get $false) (ref.func $invoke-closure) (global.get $false) (ref.func $equal+hash-recur/hash) (array.new_fixed $Free 0))) ;; Default to the host platform's path convention (currently Unix) (global.set $system-path-convention (global.get $symbol:unix)) (global.set $current-directory-path (call $bytes->path (global.get $bytes:app-dir) (global.get $missing))) (global.set $current-directory-for-user-path (global.get $current-directory-path)) (drop (call $reset-current-input-port!)) (drop (call $reset-current-output-port!)) (drop (call $reset-current-error-port!)) (global.set $char-general-category-symbols (array.new_fixed $Array 30 (global.get $symbol:lu) (global.get $symbol:ll) (global.get $symbol:lt) (global.get $symbol:lm) (global.get $symbol:lo) (global.get $symbol:mn) (global.get $symbol:mc) (global.get $symbol:me) (global.get $symbol:nd) (global.get $symbol:nl) (global.get $symbol:no) (global.get $symbol:ps) (global.get $symbol:pe) (global.get $symbol:pi) (global.get $symbol:pf) (global.get $symbol:pd) (global.get $symbol:pc) (global.get $symbol:po) (global.get $symbol:sc) (global.get $symbol:sm) (global.get $symbol:sk) (global.get $symbol:so) (global.get $symbol:zs) (global.get $symbol:zp) (global.get $symbol:zl) (global.get $symbol:cc) (global.get $symbol:cf) (global.get $symbol:cs) (global.get $symbol:co) (global.get $symbol:cn))) ;; ;; Initialize realm symbols ;; (global.set $the-racket-realm ;; (call $string->symbol ;; (ref.cast (ref $String) ;; (global.get $string:racket)))) ;; (global.set $the-racket/primitive-realm ;; (call $string->symbol ;; (ref.cast ;; (ref $String) ;; (global.get $string:racket/primitive)))) (global.set $the-racket-realm (global.get $symbol:racket)) (global.set $the-racket/primitive-realm (global.get $symbol:racket/primitive)) ;; Initialize variables holding primitives ,@(initialize-primitives-as-globals) ,@(initialize-ffi-primitives-as-globals) ;; Initialize top-level variables. ;; These are all "boxed". ,@(let () (for/list ([v top-vars]) `(global.set ,(TopVar v) (struct.new $Boxed (global.get $undefined))) #;`(global.set ,(TopVar v) (struct.new $Boxed (global.get ,(TopVar v)))))) ;; Populate exception struct type descriptor bindings. ;; struct:exn, struct:exn:fail, etc ,@(for/list ([binding exception-struct-type-bindings]) (define name (car binding)) (define ensure (cadr binding)) (define $name (string->symbol (~a "$" (symbol->string name)))) (define $ensure ($ ensure)) `(global.set ,$name (call ,$ensure))) ;; Initialize the top-level namespace (global.set $top-level-namespace (ref.cast (ref $Namespace) (call $make-empty-namespace))) ; Initialize local variables ,@(let () (define (Init x) (match x [(list v t) (if (equal? t '(ref eq)) `(local.set ,(if (symbol? v) v (Var v)) ,(Undefined)) `(nop))] [(list v t init) `(local.set ,(if (symbol? v) v (Var v)) ,init)])) (define (Init* xs) (map Init xs)) (Init* entry-locals)) ; This runs `entry-body`. An exception handler is ; installed to catch exceptions not handled by the ; user program. (block $entry-done (local.set $entry-exn (block $entry-handler (result (ref eq)) (try_table (result (ref eq)) (catch $exn $entry-handler) ,entry-body (br $entry-done)))) (call $entry-uncaught-exception (local.get $entry-exn)) (unreachable)) ; Return the result (global.set $result-bytes (call $string->bytes/utf-8 (call $format/display (global.get ,result)) (global.get $false) ; ignored (global.get $zero) ; start = 0 (global.get $false))) ; end (call $copy-bytes-to-memory ; copy and return length as i32 (global.get $result-bytes) (i32.const 0))) )) (time-runtime-step "ffi-wrapper-filter" (λ () (define active-ffi-primitive-names (if (current-tree-shake?) (sort (filter (λ (pr) (hash-ref ffi-primitive-name-set pr #f)) (remove-duplicates used-primitives)) symboleq-set (map ffi-import-name active-ffi-foreigns))) (define active-ffi-func-name-set (list->eq-set (map (λ (f) ($ (foreign-racket-name f))) active-ffi-foreigns))) (set! active-ffi-imports-wat (for/list ([form (in-list (current-ffi-imports-wat))] #:do [(define name (module-func-name form))] #:when (and name (hash-ref active-ffi-import-name-set name #f))) form)) (set! active-ffi-funcs-wat (for/list ([form (in-list (current-ffi-funcs-wat))] #:do [(define name (module-func-name form))] #:when (and name (hash-ref active-ffi-func-name-set name #f))) form)))) (define full-module (time-runtime-step "tree-shake-build-module" build-runtime-module)) (define analysis (time-runtime-step "tree-shake-analyze" (λ () (analyze-runtime-primitives full-module)))) (define-values (retained-primitives retained-functions) (time-runtime-step "tree-shake-retain" (λ () (if (current-tree-shake?) (let loop ([retained-primitives (primitive-retained-set analysis)]) (define retained-functions (retained-function-set analysis retained-primitives)) (define next-primitives (sort (remove-duplicates (append retained-primitives (primitive-refs-for-functions analysis retained-functions))) symbol