Added tcp-listen/accept/connect/recv/send/close to Python, C, and asm. One examples/http-server.lsp runs identically in all three impls and serves HTTP/1.0 with routing, content-type, and content-length headers. asm additions: - SYS_SOCKET/BIND/LISTEN/ACCEPT/CONNECT/SETSOCKOPT syscalls - 6 tcp-* builtins using the existing port encoding (SPECIAL ≥ 1000) - bi_tcp_connect: dotted-quad IPv4 parser, no DNS dependency Defects fixed along the way (surfaced by the HTTP server): - string-append: was 2-arg only; now variadic (walks arg list twice) - number->string: was stubbed to VAL_VOID; now correctly writes digits into a heap-allocated string (incl. negative handling) - String-literal reader: \r and \0 escape sequences now handled (was silently dropping backslash, treating them as literal 'r' / '0') - tcp_accept: sockaddr buffer was 8 bytes, now 16 (was corrupting caller's stack when accept wrote full struct sockaddr_in) Pinocchio benchmark (tests/web-benchmark.sh): At concurrency=20, 1000 requests, serving a 1KB body: uncommonlisp Python 373 req/s uncommonlisp C 370 req/s uncommonlisp asm 370 req/s python3 http.server 381 req/s (stdlib reference) busybox httpd 382 req/s (production reference) All five converge within 3% — the client (curl fork/exec) is the bottleneck, not the server. Our single-threaded blocking servers are indistinguishable from battle-tested ones at this load. Binary sizes: uncommonlisp asm 45 KB (HTTP + everything else) busybox httpd 2.1 MB (multi-call binary) python3 8 MB (interpreter) The asm HTTP server is 46× smaller than busybox and 176× smaller than Python, serves from 7 Linux syscalls, and the entire protocol handler is 70 lines of portable Scheme. Test counts: 132 asm (up 1), rest unchanged. All green.
4792 lines
106 KiB
ArmAsm
4792 lines
106 KiB
ArmAsm
# uncommonlisp.s — A Scheme interpreter in pure x86_64 assembly
|
|
# No C. No libc. Just Linux syscalls and machine instructions.
|
|
#
|
|
# Value representation (tag in low 3 bits):
|
|
# Tag 0: integer (value in bits 3-63, arithmetic shift right to get)
|
|
# Tag 1: pair (pointer & ~7 -> 16-byte [car, cdr])
|
|
# Tag 2: symbol (pointer & ~7 -> byte length, then chars)
|
|
# Tag 3: closure (pointer & ~7 -> [params, body, env] 24 bytes)
|
|
# Tag 4: builtin (index in bits 3-63)
|
|
# Tag 5: special (0=nil, 8=true, 16=false, 24=void in bits 3+)
|
|
# Tag 6: string (pointer & ~7 -> 8-byte length, then chars)
|
|
#
|
|
# Global registers (callee-saved, never clobbered):
|
|
# %r15 = heap bump pointer
|
|
# %r14 = global environment (linked list of 24-byte nodes)
|
|
# %r13 = heap limit
|
|
#
|
|
# All functions follow a simple convention:
|
|
# - args in %rdi, %rsi, %rdx, %rcx
|
|
# - return in %rax
|
|
# - callee saves %rbx, %rbp, %r12-r15
|
|
# - caller saves %rdi, %rsi, %rdx, %rcx, %r8-r11
|
|
|
|
.equ SYS_READ, 0
|
|
.equ SYS_WRITE, 1
|
|
.equ SYS_OPEN, 2
|
|
.equ SYS_CLOSE, 3
|
|
.equ SYS_LSEEK, 8
|
|
.equ SYS_MMAP, 9
|
|
.equ SYS_MUNMAP,11
|
|
.equ SYS_SOCKET,41
|
|
.equ SYS_CONNECT,42
|
|
.equ SYS_ACCEPT,43
|
|
.equ SYS_BIND, 49
|
|
.equ SYS_LISTEN,50
|
|
.equ SYS_SETSOCKOPT,54
|
|
.equ SYS_EXIT, 60
|
|
|
|
.equ AF_INET, 2
|
|
.equ SOCK_STREAM,1
|
|
.equ SOL_SOCKET,1
|
|
.equ SO_REUSEADDR,2
|
|
|
|
.equ O_RDONLY, 0
|
|
.equ O_WRONLY, 1
|
|
.equ O_CREAT, 64
|
|
.equ O_TRUNC, 512
|
|
.equ SEEK_SET, 0
|
|
.equ SEEK_END, 2
|
|
|
|
.equ TAG_INT, 0
|
|
.equ TAG_PAIR, 1
|
|
.equ TAG_SYM, 2
|
|
.equ TAG_CLOSURE, 3
|
|
.equ TAG_BUILTIN, 4
|
|
.equ TAG_SPECIAL, 5
|
|
.equ TAG_STRING, 6
|
|
.equ TAG_MASK, 7
|
|
|
|
.equ SPECIAL_NIL, 0
|
|
.equ SPECIAL_TRUE, 1
|
|
.equ SPECIAL_FALSE, 2
|
|
.equ SPECIAL_VOID, 3
|
|
|
|
.equ VAL_NIL, ((SPECIAL_NIL << 3) | TAG_SPECIAL)
|
|
.equ VAL_TRUE, ((SPECIAL_TRUE << 3) | TAG_SPECIAL)
|
|
.equ VAL_FALSE, ((SPECIAL_FALSE << 3) | TAG_SPECIAL)
|
|
.equ VAL_VOID, ((SPECIAL_VOID << 3) | TAG_SPECIAL)
|
|
|
|
# Ports: encoded as SPECIAL values ≥ PORT_SPECIAL_BASE.
|
|
# Value layout: ((PORT_SPECIAL_BASE + fd) << 3) | TAG_SPECIAL
|
|
# fd is recovered via (val >> 3) - PORT_SPECIAL_BASE.
|
|
.equ PORT_SPECIAL_BASE, 1000
|
|
|
|
.equ HEAP_SIZE, 0x4000000 # 64 MB
|
|
|
|
# Builtin indices
|
|
.equ BI_ADD, 0
|
|
.equ BI_SUB, 1
|
|
.equ BI_MUL, 2
|
|
.equ BI_EQ, 3
|
|
.equ BI_LT, 4
|
|
.equ BI_GT, 5
|
|
.equ BI_CONS, 6
|
|
.equ BI_CAR, 7
|
|
.equ BI_CDR, 8
|
|
.equ BI_NULLP, 9
|
|
.equ BI_PAIRP, 10
|
|
.equ BI_NOT, 11
|
|
.equ BI_DISPLAY, 12
|
|
.equ BI_NEWLINE, 13
|
|
.equ BI_LIST, 14
|
|
.equ BI_LENGTH, 15
|
|
.equ BI_LE, 16
|
|
.equ BI_GE, 17
|
|
.equ BI_ZEROP, 18
|
|
.equ BI_MODULO, 19
|
|
.equ BI_NUMBERP, 20
|
|
.equ BI_EQVP, 21
|
|
.equ BI_EQUALP, 22
|
|
.equ BI_REMAINDER, 23
|
|
.equ BI_ABS, 24
|
|
.equ BI_MIN, 25
|
|
.equ BI_MAX, 26
|
|
.equ BI_BOOLP, 27
|
|
.equ BI_SYMBOLP, 28
|
|
.equ BI_STRINGP, 29
|
|
.equ BI_PROCP, 30
|
|
.equ BI_QUOTIENT, 31
|
|
.equ BI_NEGATIVEP, 32
|
|
.equ BI_POSITIVEP, 33
|
|
.equ BI_DIV, 34
|
|
.equ BI_ODDP, 35
|
|
.equ BI_EVENP, 36
|
|
.equ BI_APPEND, 37
|
|
.equ BI_REVERSE, 38
|
|
.equ BI_MAP, 39
|
|
.equ BI_FILTER, 40
|
|
.equ BI_FOLDL, 41
|
|
.equ BI_FOREACH, 42
|
|
.equ BI_APPLY, 43
|
|
.equ BI_MEMBER, 44
|
|
.equ BI_ASSOC, 45
|
|
.equ BI_WRITE, 46
|
|
.equ BI_STRLENGTH, 47
|
|
.equ BI_STRREF, 48
|
|
.equ BI_STRAPPEND, 49
|
|
.equ BI_STREQP, 50
|
|
.equ BI_NUMTOSTR, 51
|
|
.equ BI_STRTONUM, 52
|
|
.equ BI_CHARTOINT, 53
|
|
.equ BI_INTTOCHAR, 54
|
|
.equ BI_CHARALPHAP, 55
|
|
.equ BI_CHARNUMP, 56
|
|
.equ BI_VECTOR, 57
|
|
.equ BI_VECREF, 58
|
|
.equ BI_VECSET, 59
|
|
.equ BI_VECLEN, 60
|
|
.equ BI_VECP, 61
|
|
.equ BI_MAKEVEC, 62
|
|
.equ BI_VECTOLIST, 63
|
|
.equ BI_LISTTOVEC, 64
|
|
.equ BI_CHARP, 65
|
|
.equ BI_LISTP, 66
|
|
.equ BI_SUBSTR, 67
|
|
.equ BI_EXPT, 68
|
|
.equ BI_GCD, 69
|
|
.equ BI_INTEGERP, 70
|
|
.equ BI_PORTALSAVE, 71
|
|
.equ BI_PORTALRESUME, 72
|
|
.equ BI_LOAD, 73
|
|
.equ BI_OPENOUT, 74
|
|
.equ BI_CLOSEPORT, 75
|
|
.equ BI_PORTP, 76
|
|
.equ BI_WRITEFILE, 77
|
|
.equ BI_FILETOSTR, 78
|
|
.equ BI_TCPLISTEN, 79
|
|
.equ BI_TCPACCEPT, 80
|
|
.equ BI_TCPCONNECT, 81
|
|
.equ BI_TCPRECV, 82
|
|
.equ BI_TCPSEND, 83
|
|
.equ BI_TCPCLOSE, 84
|
|
.equ BI_COUNT, 85
|
|
|
|
# ============================================================
|
|
.data
|
|
# ============================================================
|
|
|
|
prompt_str: .ascii "uncommonlisp> "
|
|
.equ prompt_len, . - prompt_str
|
|
|
|
newline_ch: .byte 10
|
|
dquote_ch: .byte '"'
|
|
|
|
# Length-prefixed symbol names for special forms
|
|
sf_quote: .byte 5; .ascii "quote"
|
|
sf_if: .byte 2; .ascii "if"
|
|
sf_define: .byte 6; .ascii "define"
|
|
sf_setbang: .byte 4; .ascii "set!"
|
|
sf_lambda: .byte 6; .ascii "lambda"
|
|
sf_begin: .byte 5; .ascii "begin"
|
|
sf_let: .byte 3; .ascii "let"
|
|
sf_cond: .byte 4; .ascii "cond"
|
|
sf_and: .byte 3; .ascii "and"
|
|
sf_or: .byte 2; .ascii "or"
|
|
sf_else: .byte 4; .ascii "else"
|
|
|
|
# Builtin names (length-prefixed)
|
|
bn_add: .byte 1; .ascii "+"
|
|
bn_sub: .byte 1; .ascii "-"
|
|
bn_mul: .byte 1; .ascii "*"
|
|
bn_eq: .byte 1; .ascii "="
|
|
bn_lt: .byte 1; .ascii "<"
|
|
bn_gt: .byte 1; .ascii ">"
|
|
bn_cons: .byte 4; .ascii "cons"
|
|
bn_car: .byte 3; .ascii "car"
|
|
bn_cdr: .byte 3; .ascii "cdr"
|
|
bn_nullp: .byte 5; .ascii "null?"
|
|
bn_pairp: .byte 5; .ascii "pair?"
|
|
bn_not: .byte 3; .ascii "not"
|
|
bn_display: .byte 7; .ascii "display"
|
|
bn_newline: .byte 7; .ascii "newline"
|
|
bn_list: .byte 4; .ascii "list"
|
|
bn_length: .byte 6; .ascii "length"
|
|
bn_le: .byte 2; .ascii "<="
|
|
bn_ge: .byte 2; .ascii ">="
|
|
bn_zerop: .byte 5; .ascii "zero?"
|
|
bn_modulo: .byte 6; .ascii "modulo"
|
|
bn_numberp: .byte 7; .ascii "number?"
|
|
bn_eqvp: .byte 4; .ascii "eqv?"
|
|
bn_equalp: .byte 6; .ascii "equal?"
|
|
bn_remainder: .byte 9; .ascii "remainder"
|
|
bn_abs: .byte 3; .ascii "abs"
|
|
bn_min: .byte 3; .ascii "min"
|
|
bn_max: .byte 3; .ascii "max"
|
|
bn_boolp: .byte 8; .ascii "boolean?"
|
|
bn_symbolp: .byte 7; .ascii "symbol?"
|
|
bn_stringp: .byte 7; .ascii "string?"
|
|
bn_procp: .byte 10; .ascii "procedure?"
|
|
bn_quotient: .byte 8; .ascii "quotient"
|
|
bn_negativep: .byte 9; .ascii "negative?"
|
|
bn_positivep: .byte 9; .ascii "positive?"
|
|
bn_div: .byte 1; .ascii "/"
|
|
bn_oddp: .byte 4; .ascii "odd?"
|
|
bn_evenp: .byte 5; .ascii "even?"
|
|
bn_append: .byte 6; .ascii "append"
|
|
bn_reverse: .byte 7; .ascii "reverse"
|
|
bn_map: .byte 3; .ascii "map"
|
|
bn_filter: .byte 6; .ascii "filter"
|
|
bn_foldl: .byte 9; .ascii "fold-left"
|
|
bn_foreach: .byte 8; .ascii "for-each"
|
|
bn_apply: .byte 5; .ascii "apply"
|
|
bn_member: .byte 6; .ascii "member"
|
|
bn_assoc: .byte 5; .ascii "assoc"
|
|
bn_write: .byte 5; .ascii "write"
|
|
bn_strlength: .byte 13; .ascii "string-length"
|
|
bn_strref: .byte 10; .ascii "string-ref"
|
|
bn_strappend: .byte 13; .ascii "string-append"
|
|
bn_streqp: .byte 8; .ascii "string=?"
|
|
bn_numtostr: .byte 14; .ascii "number->string"
|
|
bn_strtonum: .byte 14; .ascii "string->number"
|
|
bn_chartoint: .byte 13; .ascii "char->integer"
|
|
bn_inttochar: .byte 13; .ascii "integer->char"
|
|
bn_charalphap: .byte 16; .ascii "char-alphabetic?"
|
|
bn_charnump: .byte 13; .ascii "char-numeric?"
|
|
bn_vector: .byte 6; .ascii "vector"
|
|
bn_vecref: .byte 10; .ascii "vector-ref"
|
|
bn_vecset: .byte 11; .ascii "vector-set!"
|
|
bn_veclen: .byte 13; .ascii "vector-length"
|
|
bn_vecp: .byte 7; .ascii "vector?"
|
|
bn_makevec: .byte 11; .ascii "make-vector"
|
|
bn_vectolist: .byte 12; .ascii "vector->list"
|
|
bn_listtovec: .byte 12; .ascii "list->vector"
|
|
bn_charp: .byte 5; .ascii "char?"
|
|
bn_listp: .byte 5; .ascii "list?"
|
|
bn_substr: .byte 9; .ascii "substring"
|
|
bn_expt: .byte 4; .ascii "expt"
|
|
bn_gcd: .byte 3; .ascii "gcd"
|
|
bn_integerp: .byte 8; .ascii "integer?"
|
|
bn_portalsave: .byte 11; .ascii "portal-save"
|
|
bn_portalresume:.byte 13; .ascii "portal-resume"
|
|
bn_load: .byte 4; .ascii "load"
|
|
bn_openout: .byte 16; .ascii "open-output-file"
|
|
bn_closeport: .byte 10; .ascii "close-port"
|
|
bn_portp: .byte 5; .ascii "port?"
|
|
bn_writefile: .byte 10; .ascii "write-file"
|
|
bn_filetostr: .byte 12; .ascii "file->string"
|
|
bn_tcplisten: .byte 10; .ascii "tcp-listen"
|
|
bn_tcpaccept: .byte 10; .ascii "tcp-accept"
|
|
bn_tcpconnect: .byte 11; .ascii "tcp-connect"
|
|
bn_tcprecv: .byte 8; .ascii "tcp-recv"
|
|
bn_tcpsend: .byte 8; .ascii "tcp-send"
|
|
bn_tcpclose: .byte 9; .ascii "tcp-close"
|
|
|
|
portal_magic: .ascii "ULPORTAL"
|
|
.equ PORTAL_MAGIC_LEN, 8
|
|
.equ PORTAL_HDR_SIZE, 48 # magic(8) + heap_size(8) + heap_base(8) + r14(8) + r15(8) + reserved(8)
|
|
|
|
# Builtin name table (pointers filled at init)
|
|
.align 8
|
|
bi_names:
|
|
.quad bn_add, bn_sub, bn_mul, bn_eq, bn_lt, bn_gt
|
|
.quad bn_cons, bn_car, bn_cdr, bn_nullp, bn_pairp, bn_not
|
|
.quad bn_display, bn_newline, bn_list, bn_length
|
|
.quad bn_le, bn_ge, bn_zerop, bn_modulo, bn_numberp
|
|
.quad bn_eqvp, bn_equalp, bn_remainder, bn_abs, bn_min, bn_max
|
|
.quad bn_boolp, bn_symbolp, bn_stringp, bn_procp, bn_quotient
|
|
.quad bn_negativep, bn_positivep
|
|
.quad bn_div, bn_oddp, bn_evenp, bn_append, bn_reverse
|
|
.quad bn_map, bn_filter, bn_foldl, bn_foreach, bn_apply
|
|
.quad bn_member, bn_assoc, bn_write
|
|
.quad bn_strlength, bn_strref, bn_strappend, bn_streqp
|
|
.quad bn_numtostr, bn_strtonum, bn_chartoint, bn_inttochar
|
|
.quad bn_charalphap, bn_charnump
|
|
.quad bn_vector, bn_vecref, bn_vecset, bn_veclen, bn_vecp
|
|
.quad bn_makevec, bn_vectolist, bn_listtovec
|
|
.quad bn_charp, bn_listp, bn_substr, bn_expt, bn_gcd, bn_integerp
|
|
.quad bn_portalsave, bn_portalresume, bn_load
|
|
.quad bn_openout, bn_closeport, bn_portp
|
|
.quad bn_writefile, bn_filetostr
|
|
.quad bn_tcplisten, bn_tcpaccept, bn_tcpconnect
|
|
.quad bn_tcprecv, bn_tcpsend, bn_tcpclose
|
|
|
|
# Error messages
|
|
err_unbound: .ascii "Error: unbound variable: "
|
|
.equ err_unbound_len, . - err_unbound
|
|
err_notproc: .ascii "Error: not a procedure\n"
|
|
.equ err_notproc_len, . - err_notproc
|
|
err_oom: .ascii "Error: out of memory\n"
|
|
.equ err_oom_len, . - err_oom
|
|
|
|
# Print strings
|
|
s_true: .ascii "#t"
|
|
s_false: .ascii "#f"
|
|
s_nil: .ascii "()"
|
|
s_void: .ascii "#<void>"
|
|
s_proc: .ascii "#<procedure>"
|
|
s_bi: .ascii "#<builtin>"
|
|
s_port: .ascii "#<port>"
|
|
s_lparen: .ascii "("
|
|
s_rparen: .ascii ")"
|
|
s_space: .ascii " "
|
|
s_dotsp: .ascii " . "
|
|
s_minus: .ascii "-"
|
|
s_hashparen: .ascii "#("
|
|
|
|
# Interned special form symbols (filled at init)
|
|
.align 8
|
|
sym_quote_val: .quad 0
|
|
sym_if_val: .quad 0
|
|
sym_define_val: .quad 0
|
|
sym_setbang_val:.quad 0
|
|
sym_lambda_val: .quad 0
|
|
sym_begin_val: .quad 0
|
|
sym_let_val: .quad 0
|
|
sym_cond_val: .quad 0
|
|
sym_and_val: .quad 0
|
|
sym_or_val: .quad 0
|
|
sym_else_val: .quad 0
|
|
|
|
# ============================================================
|
|
.bss
|
|
# ============================================================
|
|
.align 8
|
|
input_buf: .skip 65536
|
|
input_pos: .skip 8
|
|
input_end: .skip 8
|
|
input_buf_ptr: .skip 8 # points at active read buffer (stdin buf or mmap'd file)
|
|
input_is_file: .skip 8 # 0 = stdin (refill allowed), 1 = file (EOF at end)
|
|
output_fd: .skip 8 # active fd for printer syscalls (defaults to 1 = stdout)
|
|
is_tty: .skip 8
|
|
num_buf: .skip 64
|
|
sym_table: .skip 16384 # 2048 symbol pointers
|
|
sym_count: .skip 8
|
|
heap_base: .skip 8
|
|
# Hash table for O(1) symbol interning (MOAD-0001 fix)
|
|
# 1024 buckets, each a pointer to chain head (or 0 = empty)
|
|
# Chain nodes: 16 bytes [sym_ptr, next_ptr], allocated from heap
|
|
.equ SYM_HASH_BITS, 10
|
|
.equ SYM_HASH_SIZE, 1024
|
|
sym_hash_buckets: .skip 8192 # 1024 * 8 bytes
|
|
|
|
# ============================================================
|
|
.text
|
|
# ============================================================
|
|
.globl _start
|
|
|
|
# ============================================================
|
|
# _start: entry point
|
|
# ============================================================
|
|
_start:
|
|
# Allocate heap
|
|
movq $SYS_MMAP, %rax
|
|
xorq %rdi, %rdi
|
|
movq $HEAP_SIZE, %rsi
|
|
movq $3, %rdx # PROT_READ|PROT_WRITE
|
|
movq $0x22, %r10 # MAP_PRIVATE|MAP_ANONYMOUS
|
|
movq $-1, %r8
|
|
xorq %r9, %r9
|
|
syscall
|
|
movq %rax, %r15 # heap pointer
|
|
movq %rax, heap_base(%rip) # save base for portal
|
|
leaq HEAP_SIZE(%r15), %r13 # heap limit
|
|
|
|
# Init global env = 0 (empty)
|
|
xorq %r14, %r14
|
|
|
|
# Init input
|
|
movq $0, input_pos(%rip)
|
|
movq $0, input_end(%rip)
|
|
leaq input_buf(%rip), %rax
|
|
movq %rax, input_buf_ptr(%rip)
|
|
movq $0, input_is_file(%rip)
|
|
movq $1, output_fd(%rip)
|
|
|
|
# Check tty
|
|
movq $16, %rax # sys_ioctl
|
|
xorq %rdi, %rdi # stdin
|
|
movq $0x5401, %rsi # TCGETS
|
|
subq $256, %rsp
|
|
movq %rsp, %rdx
|
|
syscall
|
|
addq $256, %rsp
|
|
xorq %rcx, %rcx
|
|
testq %rax, %rax
|
|
sete %cl
|
|
movq %rcx, is_tty(%rip)
|
|
|
|
# Intern special forms
|
|
call init_special_forms
|
|
|
|
# Register builtins
|
|
call init_builtins
|
|
|
|
# Set up a generous stack area (16 KB stack frame for deep recursion)
|
|
# Actually the OS already gave us a stack, so we're fine.
|
|
|
|
# REPL
|
|
repl_top:
|
|
cmpq $0, is_tty(%rip)
|
|
je .repl_no_prompt
|
|
movq $SYS_WRITE, %rax
|
|
movq $1, %rdi
|
|
leaq prompt_str(%rip), %rsi
|
|
movq $prompt_len, %rdx
|
|
syscall
|
|
.repl_no_prompt:
|
|
call scheme_read
|
|
testq %rax, %rax
|
|
jz repl_exit # EOF
|
|
|
|
# Skip void
|
|
cmpq $VAL_VOID, %rax
|
|
je repl_top
|
|
|
|
# Eval
|
|
movq %rax, %rdi
|
|
movq %r14, %rsi
|
|
call eval
|
|
|
|
# Skip void results
|
|
cmpq $VAL_VOID, %rax
|
|
je repl_top
|
|
|
|
# Print
|
|
movq %rax, %rdi
|
|
call scheme_print
|
|
|
|
# Newline
|
|
movq $SYS_WRITE, %rax
|
|
movq $1, %rdi
|
|
leaq newline_ch(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
|
|
jmp repl_top
|
|
|
|
repl_exit:
|
|
movq $SYS_EXIT, %rax
|
|
xorq %rdi, %rdi
|
|
syscall
|
|
|
|
# ============================================================
|
|
# heap_alloc: allocate %rdi bytes, return pointer in %rax
|
|
# Bumps %r15. If out of space, mmap more.
|
|
# ============================================================
|
|
heap_alloc:
|
|
# Align size up to 8
|
|
addq $7, %rdi
|
|
andq $-8, %rdi
|
|
movq %r15, %rax
|
|
addq %rdi, %r15
|
|
cmpq %r13, %r15
|
|
jae .heap_grow
|
|
ret
|
|
.heap_grow:
|
|
# Allocate a new chunk
|
|
pushq %rax
|
|
pushq %rdi
|
|
movq $SYS_MMAP, %rax
|
|
xorq %rdi, %rdi
|
|
movq $HEAP_SIZE, %rsi
|
|
movq $3, %rdx
|
|
movq $0x22, %r10
|
|
movq $-1, %r8
|
|
xorq %r9, %r9
|
|
syscall
|
|
cmpq $-1, %rax
|
|
je die_oom
|
|
movq %rax, %r15
|
|
leaq HEAP_SIZE(%r15), %r13
|
|
popq %rdi
|
|
popq %rax # discard old pointer
|
|
movq %r15, %rax
|
|
addq %rdi, %r15
|
|
ret
|
|
|
|
die_oom:
|
|
movq $SYS_WRITE, %rax
|
|
movq $2, %rdi
|
|
leaq err_oom(%rip), %rsi
|
|
movq $err_oom_len, %rdx
|
|
syscall
|
|
movq $SYS_EXIT, %rax
|
|
movq $1, %rdi
|
|
syscall
|
|
|
|
# ============================================================
|
|
# Value constructors
|
|
# ============================================================
|
|
|
|
# make_int: %rdi = integer -> %rax = tagged
|
|
make_int:
|
|
movq %rdi, %rax
|
|
shlq $3, %rax
|
|
# TAG_INT = 0, no or needed
|
|
ret
|
|
|
|
# make_pair: %rdi = car, %rsi = cdr -> %rax = tagged pair
|
|
make_pair:
|
|
pushq %rdi
|
|
pushq %rsi
|
|
movq $16, %rdi
|
|
call heap_alloc
|
|
popq %rsi
|
|
popq %rdi
|
|
movq %rdi, (%rax)
|
|
movq %rsi, 8(%rax)
|
|
orq $TAG_PAIR, %rax
|
|
ret
|
|
|
|
# make_closure: %rdi = params, %rsi = body, %rdx = env -> %rax
|
|
make_closure:
|
|
pushq %rdi
|
|
pushq %rsi
|
|
pushq %rdx
|
|
movq $24, %rdi
|
|
call heap_alloc
|
|
popq %rdx
|
|
popq %rsi
|
|
popq %rdi
|
|
movq %rdi, (%rax) # params
|
|
movq %rsi, 8(%rax) # body
|
|
movq %rdx, 16(%rax) # env
|
|
orq $TAG_CLOSURE, %rax
|
|
ret
|
|
|
|
# make_builtin: %rdi = index -> %rax
|
|
make_builtin:
|
|
movq %rdi, %rax
|
|
shlq $3, %rax
|
|
orq $TAG_BUILTIN, %rax
|
|
ret
|
|
|
|
# ============================================================
|
|
# Environment: linked list of 24-byte nodes [sym, val, next]
|
|
# ============================================================
|
|
|
|
# env_define: %rdi=sym %rsi=val %rdx=env -> %rax = new env head
|
|
env_define:
|
|
pushq %rdi
|
|
pushq %rsi
|
|
pushq %rdx
|
|
movq $24, %rdi
|
|
call heap_alloc
|
|
popq %rdx
|
|
popq %rsi
|
|
popq %rdi
|
|
movq %rdi, (%rax)
|
|
movq %rsi, 8(%rax)
|
|
movq %rdx, 16(%rax)
|
|
ret
|
|
|
|
# env_lookup: %rdi=sym %rsi=env -> %rax = value (or die)
|
|
env_lookup:
|
|
movq %rsi, %rax
|
|
.env_lk_loop:
|
|
testq %rax, %rax
|
|
jz .env_lk_fail
|
|
cmpq %rdi, (%rax)
|
|
je .env_lk_found
|
|
movq 16(%rax), %rax
|
|
jmp .env_lk_loop
|
|
.env_lk_found:
|
|
movq 8(%rax), %rax
|
|
ret
|
|
.env_lk_fail:
|
|
# Print error
|
|
pushq %rdi
|
|
movq $SYS_WRITE, %rax
|
|
movq $2, %rdi
|
|
leaq err_unbound(%rip), %rsi
|
|
movq $err_unbound_len, %rdx
|
|
syscall
|
|
popq %rdi
|
|
# Print symbol name
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movzbq (%rax), %rdx
|
|
leaq 1(%rax), %rsi
|
|
movq $SYS_WRITE, %rax
|
|
movq $2, %rdi
|
|
syscall
|
|
movq $SYS_WRITE, %rax
|
|
movq $2, %rdi
|
|
leaq newline_ch(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
movq $SYS_EXIT, %rax
|
|
movq $1, %rdi
|
|
syscall
|
|
|
|
# env_set: %rdi=sym %rsi=val %rdx=env -> void (mutates)
|
|
env_set:
|
|
movq %rdx, %rax
|
|
.env_set_loop:
|
|
testq %rax, %rax
|
|
jz .env_lk_fail # reuse error
|
|
cmpq %rdi, (%rax)
|
|
je .env_set_found
|
|
movq 16(%rax), %rax
|
|
jmp .env_set_loop
|
|
.env_set_found:
|
|
movq %rsi, 8(%rax)
|
|
ret
|
|
|
|
# ============================================================
|
|
# Symbol interning — MOAD-0001: O(1) hash table lookup
|
|
# Symbols are stored as: 1 byte length, then chars (on heap)
|
|
# sym_table: flat array kept for iteration; sym_hash_buckets: hash chains for O(1) lookup
|
|
# intern_symbol: %rdi=str_ptr %rsi=len -> %rax = tagged symbol
|
|
# Hash function: djb2 (hash = 5381; for each byte: hash = hash*33 + byte)
|
|
# ============================================================
|
|
intern_symbol:
|
|
pushq %rbx
|
|
pushq %r12
|
|
pushq %rbp
|
|
movq %rdi, %rbx # string ptr
|
|
movq %rsi, %r12 # length
|
|
|
|
# Compute djb2 hash of string
|
|
movq $5381, %rax
|
|
xorq %rcx, %rcx
|
|
.isym_hash_loop:
|
|
cmpq %r12, %rcx
|
|
jge .isym_hash_done
|
|
movq %rax, %rdx
|
|
shlq $5, %rdx # hash << 5
|
|
addq %rdx, %rax # hash * 33
|
|
movzbq (%rbx,%rcx), %rdx
|
|
addq %rdx, %rax # + byte
|
|
incq %rcx
|
|
jmp .isym_hash_loop
|
|
.isym_hash_done:
|
|
# Mask to bucket index
|
|
andq $(SYM_HASH_SIZE - 1), %rax
|
|
movq %rax, %rbp # bucket index saved in %rbp
|
|
|
|
# Walk the chain at sym_hash_buckets[bucket]
|
|
leaq sym_hash_buckets(%rip), %rdi
|
|
movq (%rdi,%rbp,8), %r8 # chain head pointer (or 0)
|
|
.isym_chain_walk:
|
|
testq %r8, %r8
|
|
jz .isym_new
|
|
movq (%r8), %r9 # sym_ptr from chain node
|
|
movzbq (%r9), %r10 # candidate length
|
|
cmpq %r12, %r10
|
|
jne .isym_chain_next
|
|
# Compare bytes
|
|
leaq 1(%r9), %r10 # candidate chars
|
|
xorq %r11, %r11
|
|
.isym_chain_cmp:
|
|
cmpq %r12, %r11
|
|
jge .isym_chain_found
|
|
movb (%rbx,%r11), %al
|
|
cmpb (%r10,%r11), %al
|
|
jne .isym_chain_next
|
|
incq %r11
|
|
jmp .isym_chain_cmp
|
|
.isym_chain_found:
|
|
movq %r9, %rax
|
|
orq $TAG_SYM, %rax
|
|
popq %rbp
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
.isym_chain_next:
|
|
movq 8(%r8), %r8 # next pointer in chain
|
|
jmp .isym_chain_walk
|
|
.isym_new:
|
|
# Allocate symbol storage: 1 + length bytes
|
|
# NOTE: %r13 is the global heap limit — do NOT clobber it.
|
|
# Use the stack to save sym_ptr across the second heap_alloc.
|
|
leaq 1(%r12), %rdi
|
|
call heap_alloc
|
|
# %rax = sym_ptr; fill symbol: length byte + chars
|
|
movb %r12b, (%rax)
|
|
xorq %r8, %r8
|
|
.isym_copy:
|
|
cmpq %r12, %r8
|
|
jge .isym_copied
|
|
movb (%rbx,%r8), %cl
|
|
movb %cl, 1(%rax,%r8)
|
|
incq %r8
|
|
jmp .isym_copy
|
|
.isym_copied:
|
|
pushq %rax # save sym_ptr on stack
|
|
# Add to flat sym_table (for iteration)
|
|
leaq sym_table(%rip), %rdi
|
|
movq sym_count(%rip), %rcx
|
|
movq %rax, (%rdi,%rcx,8)
|
|
incq %rcx
|
|
movq %rcx, sym_count(%rip)
|
|
# Allocate hash chain node: 16 bytes [sym_ptr, next_ptr]
|
|
movq $16, %rdi
|
|
call heap_alloc
|
|
# Fill chain node: %rax = node_ptr, stack top = sym_ptr
|
|
popq %rcx # rcx = sym_ptr
|
|
movq %rcx, (%rax) # node->sym = sym_ptr
|
|
leaq sym_hash_buckets(%rip), %rdi
|
|
movq (%rdi,%rbp,8), %rdx # old chain head
|
|
movq %rdx, 8(%rax) # node->next = old head
|
|
movq %rax, (%rdi,%rbp,8) # bucket head = new node
|
|
# Return tagged symbol
|
|
movq %rcx, %rax
|
|
orq $TAG_SYM, %rax
|
|
popq %rbp
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
# intern_static: %rdi = pointer to length-prefixed static string -> %rax = tagged sym
|
|
intern_static:
|
|
movzbq (%rdi), %rsi
|
|
leaq 1(%rdi), %rdi
|
|
jmp intern_symbol
|
|
|
|
# ============================================================
|
|
# Input
|
|
# ============================================================
|
|
|
|
# read_char -> %rax (char or -1 for EOF)
|
|
read_char:
|
|
movq input_pos(%rip), %rax
|
|
cmpq input_end(%rip), %rax
|
|
jl .rc_have
|
|
# File-backed buffer: no refill, straight to EOF
|
|
cmpq $0, input_is_file(%rip)
|
|
jne .rc_eof
|
|
# Refill from stdin into input_buf
|
|
pushq %rbx
|
|
movq $SYS_READ, %rax
|
|
xorq %rdi, %rdi
|
|
leaq input_buf(%rip), %rsi
|
|
movq $65536, %rdx
|
|
syscall
|
|
popq %rbx
|
|
cmpq $0, %rax
|
|
jle .rc_eof
|
|
movq %rax, input_end(%rip)
|
|
movq $0, input_pos(%rip)
|
|
xorq %rax, %rax
|
|
.rc_have:
|
|
movq input_buf_ptr(%rip), %rcx
|
|
movzbq (%rcx,%rax), %rax
|
|
movq input_pos(%rip), %rcx
|
|
incq %rcx
|
|
movq %rcx, input_pos(%rip)
|
|
ret
|
|
.rc_eof:
|
|
movq $-1, %rax
|
|
ret
|
|
|
|
# peek_char -> %rax (char or -1)
|
|
peek_char:
|
|
movq input_pos(%rip), %rax
|
|
cmpq input_end(%rip), %rax
|
|
jl .pc_have
|
|
cmpq $0, input_is_file(%rip)
|
|
jne .pc_eof
|
|
pushq %rbx
|
|
movq $SYS_READ, %rax
|
|
xorq %rdi, %rdi
|
|
leaq input_buf(%rip), %rsi
|
|
movq $65536, %rdx
|
|
syscall
|
|
popq %rbx
|
|
cmpq $0, %rax
|
|
jle .pc_eof
|
|
movq %rax, input_end(%rip)
|
|
movq $0, input_pos(%rip)
|
|
xorq %rax, %rax
|
|
.pc_have:
|
|
movq input_buf_ptr(%rip), %rcx
|
|
movzbq (%rcx,%rax), %rax
|
|
ret
|
|
.pc_eof:
|
|
movq $-1, %rax
|
|
ret
|
|
|
|
# skip_ws: skip whitespace and ;-comments
|
|
skip_ws:
|
|
pushq %rbx
|
|
.sw_loop:
|
|
call peek_char
|
|
cmpq $-1, %rax
|
|
je .sw_done
|
|
cmpb $' ', %al
|
|
je .sw_eat
|
|
cmpb $'\t', %al
|
|
je .sw_eat
|
|
cmpb $'\n', %al
|
|
je .sw_eat
|
|
cmpb $'\r', %al
|
|
je .sw_eat
|
|
cmpb $';', %al
|
|
je .sw_comment
|
|
jmp .sw_done
|
|
.sw_eat:
|
|
call read_char
|
|
jmp .sw_loop
|
|
.sw_comment:
|
|
call read_char
|
|
cmpq $-1, %rax
|
|
je .sw_done
|
|
cmpb $'\n', %al
|
|
jne .sw_comment
|
|
jmp .sw_loop
|
|
.sw_done:
|
|
popq %rbx
|
|
ret
|
|
|
|
# ============================================================
|
|
# Reader
|
|
# scheme_read -> %rax = tagged value (0 for EOF)
|
|
# ============================================================
|
|
scheme_read:
|
|
pushq %rbx
|
|
pushq %r12
|
|
|
|
call skip_ws
|
|
call peek_char
|
|
cmpq $-1, %rax
|
|
je .sr_eof
|
|
|
|
cmpb $'(', %al
|
|
je .sr_list
|
|
cmpb $')', %al
|
|
je .sr_rparen
|
|
cmpb $'\'', %al
|
|
je .sr_quote
|
|
cmpb $'"', %al
|
|
je .sr_string
|
|
cmpb $'#', %al
|
|
je .sr_hash
|
|
cmpb $'-', %al
|
|
je .sr_maybe_neg
|
|
cmpb $'0', %al
|
|
jl .sr_symbol
|
|
cmpb $'9', %al
|
|
jle .sr_number
|
|
jmp .sr_symbol
|
|
|
|
.sr_eof:
|
|
xorq %rax, %rax
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sr_rparen:
|
|
call read_char
|
|
# Try reading again (skip stray rparen)
|
|
popq %r12
|
|
popq %rbx
|
|
jmp scheme_read
|
|
|
|
.sr_quote:
|
|
call read_char # eat '
|
|
call scheme_read # read datum
|
|
movq %rax, %rbx # save datum
|
|
# Build (quote datum): cons(datum, nil) then cons(quote_sym, that)
|
|
movq %rax, %rdi
|
|
movq $VAL_NIL, %rsi
|
|
call make_pair # (datum . ())
|
|
movq %rax, %rsi
|
|
movq sym_quote_val(%rip), %rdi
|
|
call make_pair # (quote datum)
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sr_hash:
|
|
call read_char # eat #
|
|
call read_char
|
|
cmpb $'t', %al
|
|
je .sr_true
|
|
cmpb $'f', %al
|
|
je .sr_false
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
.sr_true:
|
|
movq $VAL_TRUE, %rax
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
.sr_false:
|
|
movq $VAL_FALSE, %rax
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sr_maybe_neg:
|
|
call read_char # eat '-'
|
|
call peek_char
|
|
cmpq $-1, %rax
|
|
je .sr_minus_sym
|
|
cmpb $'0', %al
|
|
jl .sr_minus_sym
|
|
cmpb $'9', %al
|
|
jle .sr_neg_num
|
|
.sr_minus_sym:
|
|
# It's the symbol "-", read rest of symbol chars
|
|
subq $256, %rsp
|
|
movb $'-', (%rsp)
|
|
movq $1, %rbx # len = 1
|
|
jmp .sr_sym_rest
|
|
.sr_neg_num:
|
|
# Negative number
|
|
xorq %rbx, %rbx # accumulator
|
|
.sr_neg_digits:
|
|
call peek_char
|
|
cmpq $-1, %rax
|
|
je .sr_neg_done
|
|
cmpb $'0', %al
|
|
jl .sr_neg_done
|
|
cmpb $'9', %al
|
|
jg .sr_neg_done
|
|
call read_char
|
|
subq $'0', %rax
|
|
imulq $10, %rbx
|
|
addq %rax, %rbx
|
|
jmp .sr_neg_digits
|
|
.sr_neg_done:
|
|
negq %rbx
|
|
movq %rbx, %rdi
|
|
call make_int
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sr_number:
|
|
call read_char
|
|
subq $'0', %rax
|
|
movq %rax, %rbx # accumulator
|
|
.sr_num_loop:
|
|
call peek_char
|
|
cmpq $-1, %rax
|
|
je .sr_num_done
|
|
cmpb $'0', %al
|
|
jl .sr_num_done
|
|
cmpb $'9', %al
|
|
jg .sr_num_done
|
|
call read_char
|
|
subq $'0', %rax
|
|
imulq $10, %rbx
|
|
addq %rax, %rbx
|
|
jmp .sr_num_loop
|
|
.sr_num_done:
|
|
movq %rbx, %rdi
|
|
call make_int
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sr_symbol:
|
|
subq $256, %rsp
|
|
xorq %rbx, %rbx # length
|
|
.sr_sym_loop:
|
|
call peek_char
|
|
cmpq $-1, %rax
|
|
je .sr_sym_done
|
|
# Delimiters
|
|
cmpb $' ', %al
|
|
je .sr_sym_done
|
|
cmpb $'\t', %al
|
|
je .sr_sym_done
|
|
cmpb $'\n', %al
|
|
je .sr_sym_done
|
|
cmpb $'\r', %al
|
|
je .sr_sym_done
|
|
cmpb $'(', %al
|
|
je .sr_sym_done
|
|
cmpb $')', %al
|
|
je .sr_sym_done
|
|
cmpb $'"', %al
|
|
je .sr_sym_done
|
|
cmpb $';', %al
|
|
je .sr_sym_done
|
|
call read_char
|
|
movb %al, (%rsp,%rbx)
|
|
incq %rbx
|
|
cmpq $250, %rbx
|
|
jge .sr_sym_done
|
|
jmp .sr_sym_loop
|
|
.sr_sym_rest:
|
|
# Entry when we have partial symbol in buffer (e.g. "-")
|
|
jmp .sr_sym_loop
|
|
.sr_sym_done:
|
|
testq %rbx, %rbx
|
|
jz .sr_sym_empty
|
|
movq %rsp, %rdi
|
|
movq %rbx, %rsi
|
|
call intern_symbol
|
|
addq $256, %rsp
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
.sr_sym_empty:
|
|
addq $256, %rsp
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sr_string:
|
|
call read_char # eat opening "
|
|
subq $256, %rsp
|
|
xorq %rbx, %rbx # length
|
|
.sr_str_loop:
|
|
call read_char
|
|
cmpq $-1, %rax
|
|
je .sr_str_end
|
|
cmpb $'"', %al
|
|
je .sr_str_end
|
|
cmpb $'\\', %al
|
|
je .sr_str_esc
|
|
movb %al, (%rsp,%rbx)
|
|
incq %rbx
|
|
jmp .sr_str_loop
|
|
.sr_str_esc:
|
|
call read_char
|
|
cmpb $'n', %al
|
|
jne 1f
|
|
movb $10, (%rsp,%rbx)
|
|
incq %rbx
|
|
jmp .sr_str_loop
|
|
1: cmpb $'t', %al
|
|
jne 2f
|
|
movb $9, (%rsp,%rbx)
|
|
incq %rbx
|
|
jmp .sr_str_loop
|
|
2: cmpb $'r', %al
|
|
jne 3f
|
|
movb $13, (%rsp,%rbx)
|
|
incq %rbx
|
|
jmp .sr_str_loop
|
|
3: cmpb $'0', %al
|
|
jne 4f
|
|
movb $0, (%rsp,%rbx)
|
|
incq %rbx
|
|
jmp .sr_str_loop
|
|
4: movb %al, (%rsp,%rbx)
|
|
incq %rbx
|
|
jmp .sr_str_loop
|
|
.sr_str_end:
|
|
# Allocate: 8-byte length + data
|
|
pushq %rbx
|
|
leaq 8(%rbx), %rdi
|
|
call heap_alloc
|
|
popq %rbx
|
|
movq %rbx, (%rax) # 8-byte length
|
|
xorq %rcx, %rcx
|
|
.sr_str_copy:
|
|
cmpq %rbx, %rcx
|
|
jge .sr_str_done
|
|
movb (%rsp,%rcx), %dl
|
|
movb %dl, 8(%rax,%rcx)
|
|
incq %rcx
|
|
jmp .sr_str_copy
|
|
.sr_str_done:
|
|
addq $256, %rsp
|
|
orq $TAG_STRING, %rax
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
# Read a list after '(' has been consumed
|
|
.sr_list:
|
|
call read_char # eat '('
|
|
call .read_list_elems
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
# .read_list_elems -> %rax = list (proper or dotted)
|
|
.read_list_elems:
|
|
pushq %rbx
|
|
pushq %r12
|
|
|
|
call skip_ws
|
|
call peek_char
|
|
cmpq $-1, %rax
|
|
je .rle_nil
|
|
cmpb $')', %al
|
|
je .rle_close
|
|
|
|
# Check for dot
|
|
cmpb $'.', %al
|
|
je .rle_maybe_dot
|
|
|
|
# Read element
|
|
call scheme_read
|
|
movq %rax, %rbx # save element
|
|
|
|
# Read rest of list
|
|
call .read_list_elems
|
|
movq %rax, %rsi # rest
|
|
movq %rbx, %rdi # this element
|
|
call make_pair
|
|
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.rle_maybe_dot:
|
|
# Could be a dot or a symbol starting with dot
|
|
call read_char # eat '.'
|
|
call peek_char
|
|
cmpb $' ', %al
|
|
je .rle_dot
|
|
cmpb $'\t', %al
|
|
je .rle_dot
|
|
cmpb $'\n', %al
|
|
je .rle_dot
|
|
cmpb $')', %al
|
|
je .rle_dot
|
|
cmpq $-1, %rax
|
|
je .rle_dot
|
|
# Symbol starting with '.': read rest
|
|
subq $256, %rsp
|
|
movb $'.', (%rsp)
|
|
movq $1, %rbx
|
|
.rle_dot_sym_loop:
|
|
call peek_char
|
|
cmpq $-1, %rax
|
|
je .rle_dot_sym_done
|
|
cmpb $' ', %al
|
|
je .rle_dot_sym_done
|
|
cmpb $')', %al
|
|
je .rle_dot_sym_done
|
|
cmpb $'(', %al
|
|
je .rle_dot_sym_done
|
|
call read_char
|
|
movb %al, (%rsp,%rbx)
|
|
incq %rbx
|
|
jmp .rle_dot_sym_loop
|
|
.rle_dot_sym_done:
|
|
movq %rsp, %rdi
|
|
movq %rbx, %rsi
|
|
call intern_symbol
|
|
addq $256, %rsp
|
|
movq %rax, %rbx
|
|
call .read_list_elems
|
|
movq %rax, %rsi
|
|
movq %rbx, %rdi
|
|
call make_pair
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.rle_dot:
|
|
# Dotted pair: read one value, skip ws, expect ')'
|
|
call scheme_read
|
|
movq %rax, %rbx
|
|
call skip_ws
|
|
call peek_char
|
|
cmpb $')', %al
|
|
jne .rle_dot_ret
|
|
call read_char
|
|
.rle_dot_ret:
|
|
movq %rbx, %rax
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.rle_close:
|
|
call read_char # eat ')'
|
|
.rle_nil:
|
|
movq $VAL_NIL, %rax
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
# ============================================================
|
|
# Printer
|
|
# scheme_print: %rdi = value
|
|
# ============================================================
|
|
scheme_print:
|
|
pushq %rbx
|
|
pushq %r12
|
|
|
|
movq %rdi, %rbx
|
|
movq %rdi, %rax
|
|
andq $TAG_MASK, %rax
|
|
|
|
cmpq $TAG_INT, %rax
|
|
je .sp_int
|
|
cmpq $TAG_SPECIAL, %rax
|
|
je .sp_special
|
|
cmpq $TAG_SYM, %rax
|
|
je .sp_sym
|
|
cmpq $TAG_PAIR, %rax
|
|
je .sp_pair
|
|
cmpq $TAG_CLOSURE, %rax
|
|
je .sp_closure
|
|
cmpq $TAG_BUILTIN, %rax
|
|
je .sp_builtin
|
|
cmpq $TAG_STRING, %rax
|
|
je .sp_string
|
|
cmpq $7, %rax
|
|
je .sp_vector
|
|
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sp_int:
|
|
sarq $3, %rbx
|
|
movq %rbx, %rdi
|
|
call print_int64
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sp_special:
|
|
movq %rbx, %rax
|
|
shrq $3, %rax
|
|
cmpq $SPECIAL_NIL, %rax
|
|
je .sp_nil
|
|
cmpq $SPECIAL_TRUE, %rax
|
|
je .sp_true
|
|
cmpq $SPECIAL_FALSE, %rax
|
|
je .sp_false
|
|
cmpq $PORT_SPECIAL_BASE, %rax
|
|
jge .sp_port
|
|
# void
|
|
leaq s_void(%rip), %rsi
|
|
movq $7, %rdx
|
|
jmp .sp_write
|
|
.sp_port:
|
|
leaq s_port(%rip), %rsi
|
|
movq $7, %rdx
|
|
jmp .sp_write
|
|
.sp_nil:
|
|
leaq s_nil(%rip), %rsi
|
|
movq $2, %rdx
|
|
jmp .sp_write
|
|
.sp_true:
|
|
leaq s_true(%rip), %rsi
|
|
movq $2, %rdx
|
|
jmp .sp_write
|
|
.sp_false:
|
|
leaq s_false(%rip), %rsi
|
|
movq $2, %rdx
|
|
jmp .sp_write
|
|
|
|
.sp_write:
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
syscall
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sp_sym:
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movzbq (%rax), %rdx
|
|
leaq 1(%rax), %rsi
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
syscall
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sp_closure:
|
|
leaq s_proc(%rip), %rsi
|
|
movq $12, %rdx
|
|
jmp .sp_write
|
|
|
|
.sp_builtin:
|
|
leaq s_bi(%rip), %rsi
|
|
movq $10, %rdx
|
|
jmp .sp_write
|
|
|
|
.sp_string:
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %r12 # length
|
|
leaq 8(%rax), %rbx # data ptr
|
|
# Print: "..."
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq dquote_ch(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
movq %rbx, %rsi
|
|
movq %r12, %rdx
|
|
syscall
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq dquote_ch(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sp_pair:
|
|
# Print "("
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq s_lparen(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
# Print car
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi
|
|
pushq %rax
|
|
call scheme_print
|
|
popq %rax
|
|
movq 8(%rax), %rbx # cdr
|
|
.sp_pair_rest:
|
|
cmpq $VAL_NIL, %rbx
|
|
je .sp_pair_close
|
|
movq %rbx, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_PAIR, %rax
|
|
jne .sp_pair_dot
|
|
# Print " " then car
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq s_space(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi
|
|
pushq %rax
|
|
call scheme_print
|
|
popq %rax
|
|
movq 8(%rax), %rbx
|
|
jmp .sp_pair_rest
|
|
.sp_pair_dot:
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq s_dotsp(%rip), %rsi
|
|
movq $3, %rdx
|
|
syscall
|
|
movq %rbx, %rdi
|
|
call scheme_print
|
|
.sp_pair_close:
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq s_rparen(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.sp_vector:
|
|
# Print "#(" then elements separated by spaces, then ")"
|
|
# %rbx = tagged vector value
|
|
movq %rbx, %rax
|
|
andq $-8, %rax # untagged vector ptr
|
|
movq %rax, %rbx # %rbx = untagged vector ptr
|
|
movq (%rbx), %r12 # %r12 = length
|
|
# Print "#("
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq s_hashparen(%rip), %rsi
|
|
movq $2, %rdx
|
|
syscall
|
|
xorq %rcx, %rcx # index = 0
|
|
.sp_vec_loop:
|
|
cmpq %r12, %rcx
|
|
jge .sp_vec_close
|
|
# Print space before all but first element
|
|
testq %rcx, %rcx
|
|
jz .sp_vec_elem
|
|
pushq %rcx
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq s_space(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
popq %rcx
|
|
.sp_vec_elem:
|
|
pushq %rcx
|
|
pushq %rbx
|
|
pushq %r12
|
|
movq 8(%rbx,%rcx,8), %rdi # element at index
|
|
call scheme_print
|
|
popq %r12
|
|
popq %rbx
|
|
popq %rcx
|
|
incq %rcx
|
|
jmp .sp_vec_loop
|
|
.sp_vec_close:
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq s_rparen(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
# print_int64: %rdi = signed 64-bit integer
|
|
print_int64:
|
|
pushq %rbx
|
|
movq %rdi, %rax
|
|
testq %rax, %rax
|
|
jns .pi_pos
|
|
pushq %rax
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq s_minus(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
popq %rax
|
|
negq %rax
|
|
.pi_pos:
|
|
leaq num_buf(%rip), %rbx
|
|
addq $63, %rbx # end of buffer
|
|
movb $0, (%rbx) # sentinel
|
|
testq %rax, %rax
|
|
jnz .pi_digits
|
|
decq %rbx
|
|
movb $'0', (%rbx)
|
|
jmp .pi_out
|
|
.pi_digits:
|
|
testq %rax, %rax
|
|
jz .pi_out
|
|
xorq %rdx, %rdx
|
|
movq $10, %rcx
|
|
divq %rcx
|
|
addb $'0', %dl
|
|
decq %rbx
|
|
movb %dl, (%rbx)
|
|
jmp .pi_digits
|
|
.pi_out:
|
|
# Calculate length
|
|
leaq num_buf(%rip), %rax
|
|
addq $63, %rax
|
|
subq %rbx, %rax
|
|
movq %rax, %rdx # length
|
|
movq %rbx, %rsi # start
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
syscall
|
|
popq %rbx
|
|
ret
|
|
|
|
# ============================================================
|
|
# Evaluator
|
|
# eval: %rdi = expr, %rsi = env -> %rax = value
|
|
# Uses TCO: tail positions jump back to .eval_top
|
|
# ============================================================
|
|
eval:
|
|
pushq %rbx
|
|
pushq %rbp
|
|
pushq %r12
|
|
|
|
# %rbp = current env, %rdi = expr
|
|
movq %rsi, %rbp
|
|
|
|
.eval_top:
|
|
# TCO re-entry: %rdi = expr, %rbp = env
|
|
movq %rdi, %rax
|
|
andq $TAG_MASK, %rax
|
|
|
|
# Self-evaluating
|
|
cmpq $TAG_INT, %rax
|
|
je .ev_self
|
|
cmpq $TAG_STRING, %rax
|
|
je .ev_self
|
|
cmpq $TAG_SPECIAL, %rax
|
|
je .ev_self
|
|
cmpq $TAG_BUILTIN, %rax
|
|
je .ev_self
|
|
cmpq $TAG_CLOSURE, %rax
|
|
je .ev_self
|
|
|
|
# Symbol lookup
|
|
cmpq $TAG_SYM, %rax
|
|
je .ev_sym
|
|
|
|
# Must be a pair
|
|
cmpq $TAG_PAIR, %rax
|
|
jne .ev_self
|
|
|
|
# List: check for special forms
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rbx # car = operator position
|
|
movq 8(%rax), %r12 # cdr = args
|
|
|
|
# Is operator a symbol?
|
|
movq %rbx, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_SYM, %rax
|
|
jne .ev_app # not a symbol, just apply
|
|
|
|
# Check special forms by comparing tagged symbol values
|
|
cmpq sym_quote_val(%rip), %rbx
|
|
je .ev_quote
|
|
cmpq sym_if_val(%rip), %rbx
|
|
je .ev_if
|
|
cmpq sym_define_val(%rip), %rbx
|
|
je .ev_define
|
|
cmpq sym_setbang_val(%rip), %rbx
|
|
je .ev_setbang
|
|
cmpq sym_lambda_val(%rip), %rbx
|
|
je .ev_lambda
|
|
cmpq sym_begin_val(%rip), %rbx
|
|
je .ev_begin
|
|
cmpq sym_let_val(%rip), %rbx
|
|
je .ev_let
|
|
cmpq sym_cond_val(%rip), %rbx
|
|
je .ev_cond
|
|
cmpq sym_and_val(%rip), %rbx
|
|
je .ev_and
|
|
cmpq sym_or_val(%rip), %rbx
|
|
je .ev_or
|
|
|
|
# Not special form -> application
|
|
jmp .ev_app
|
|
|
|
.ev_self:
|
|
movq %rdi, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
.ev_sym:
|
|
movq %rbp, %rsi
|
|
# Also search global env
|
|
call env_lookup_both
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# env_lookup_both: %rdi=sym, %rsi=local_env
|
|
# Searches local first, then %r14 (global)
|
|
env_lookup_both:
|
|
pushq %rdi
|
|
pushq %rsi
|
|
# Search local
|
|
movq %rsi, %rax
|
|
.elb_local:
|
|
testq %rax, %rax
|
|
jz .elb_global
|
|
cmpq %rdi, (%rax)
|
|
je .elb_found
|
|
movq 16(%rax), %rax
|
|
jmp .elb_local
|
|
.elb_global:
|
|
movq %r14, %rax
|
|
.elb_global_loop:
|
|
testq %rax, %rax
|
|
jz .elb_fail
|
|
cmpq %rdi, (%rax)
|
|
je .elb_found
|
|
movq 16(%rax), %rax
|
|
jmp .elb_global_loop
|
|
.elb_found:
|
|
movq 8(%rax), %rax
|
|
popq %rsi
|
|
popq %rdi
|
|
ret
|
|
.elb_fail:
|
|
popq %rsi
|
|
popq %rdi
|
|
jmp .env_lk_fail
|
|
|
|
# ---- quote ----
|
|
.ev_quote:
|
|
# (quote X) -> X
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rax # car of args
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# ---- if ----
|
|
.ev_if:
|
|
# (if test then [else])
|
|
# %r12 = (test then [else])
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # test expr
|
|
movq 8(%rax), %rax # (then [else])
|
|
andq $-8, %rax
|
|
movq (%rax), %rbx # then expr
|
|
movq 8(%rax), %r12 # (else) or nil
|
|
|
|
# Eval test
|
|
pushq %rbx
|
|
pushq %r12
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %r12
|
|
popq %rbx
|
|
|
|
# False or nil -> else branch
|
|
cmpq $VAL_FALSE, %rax
|
|
je .ev_if_else
|
|
cmpq $VAL_NIL, %rax
|
|
je .ev_if_else
|
|
|
|
# True: TCO then
|
|
movq %rbx, %rdi
|
|
jmp .eval_top
|
|
|
|
.ev_if_else:
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_if_void
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # else expr
|
|
jmp .eval_top
|
|
.ev_if_void:
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# ---- define ----
|
|
.ev_define:
|
|
# %r12 = args: (var expr) or ((name params...) body...)
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rbx # first: var or (name params...)
|
|
movq 8(%rax), %r12 # rest
|
|
|
|
movq %rbx, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_PAIR, %rax
|
|
je .ev_define_func
|
|
|
|
# Simple: (define var expr) — or (define var) with no expr → VAL_VOID
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_define_no_expr
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # expr
|
|
pushq %rbx
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %rbx # var symbol
|
|
jmp .ev_define_bind
|
|
.ev_define_no_expr:
|
|
movq $VAL_VOID, %rax
|
|
.ev_define_bind:
|
|
movq %rbx, %rdi
|
|
movq %rax, %rsi
|
|
movq %r14, %rdx
|
|
call env_define
|
|
movq %rax, %r14 # update global env
|
|
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
.ev_define_func:
|
|
# (define (name p1 p2 ...) body...)
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rcx # name symbol
|
|
movq 8(%rax), %rdx # (p1 p2 ...) = params
|
|
|
|
# Make body: if single, use it; if multiple, wrap in (begin ...)
|
|
pushq %rcx # save name
|
|
pushq %rdx # save params
|
|
movq %r12, %rdi
|
|
call wrap_begin
|
|
movq %rax, %rsi # body
|
|
|
|
popq %rdi # params
|
|
movq %rbp, %rdx # env
|
|
call make_closure
|
|
movq %rax, %rsi # closure
|
|
popq %rdi # name
|
|
movq %r14, %rdx
|
|
call env_define
|
|
movq %rax, %r14
|
|
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# wrap_begin: %rdi = body list -> %rax = single expr or (begin ...)
|
|
wrap_begin:
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rcx # cdr
|
|
cmpq $VAL_NIL, %rcx
|
|
jne .wb_multi
|
|
# Single body
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rax
|
|
ret
|
|
.wb_multi:
|
|
pushq %rdi
|
|
movq sym_begin_val(%rip), %rdi
|
|
movq (%rsp), %rsi
|
|
call make_pair
|
|
addq $8, %rsp
|
|
ret
|
|
|
|
# ---- set! ----
|
|
.ev_setbang:
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rbx # var
|
|
movq 8(%rax), %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # expr
|
|
pushq %rbx
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %rbx
|
|
# Try local env first, then global
|
|
movq %rbx, %rdi
|
|
movq %rax, %rsi
|
|
movq %rbp, %rdx
|
|
call env_set_both
|
|
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# env_set_both: %rdi=sym %rsi=val %rdx=local_env
|
|
# Searches local first, then %r14 global
|
|
env_set_both:
|
|
movq %rdx, %rax
|
|
.esb_local:
|
|
testq %rax, %rax
|
|
jz .esb_global
|
|
cmpq %rdi, (%rax)
|
|
je .esb_found
|
|
movq 16(%rax), %rax
|
|
jmp .esb_local
|
|
.esb_global:
|
|
movq %r14, %rax
|
|
.esb_global_loop:
|
|
testq %rax, %rax
|
|
jz .esb_fail
|
|
cmpq %rdi, (%rax)
|
|
je .esb_found
|
|
movq 16(%rax), %rax
|
|
jmp .esb_global_loop
|
|
.esb_found:
|
|
movq %rsi, 8(%rax)
|
|
ret
|
|
.esb_fail:
|
|
jmp .env_lk_fail
|
|
|
|
# ---- lambda ----
|
|
.ev_lambda:
|
|
# (lambda (params...) body...)
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rcx # params list
|
|
movq 8(%rax), %rdi # body forms
|
|
|
|
pushq %rcx
|
|
call wrap_begin
|
|
movq %rax, %rsi # body
|
|
popq %rdi # params
|
|
movq %rbp, %rdx # capture env
|
|
call make_closure
|
|
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# ---- begin ----
|
|
.ev_begin:
|
|
# %r12 = body forms
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_begin_void
|
|
.ev_begin_loop:
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # current expr
|
|
movq 8(%rax), %r12 # rest
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_begin_tail
|
|
# Not last: eval and discard
|
|
pushq %r12
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %r12
|
|
jmp .ev_begin_loop
|
|
.ev_begin_tail:
|
|
# Last: TCO
|
|
jmp .eval_top
|
|
.ev_begin_void:
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# ---- let ----
|
|
.ev_let:
|
|
# (let ((v1 e1) ...) body...) or (let name ((v1 e1) ...) body...)
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rbx # first arg
|
|
movq 8(%rax), %r12 # rest
|
|
|
|
movq %rbx, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_SYM, %rax
|
|
je .ev_named_let
|
|
|
|
# Regular let: %rbx = bindings, %r12 = body
|
|
movq %rbp, %rcx # extended env starts as current env
|
|
# Process bindings
|
|
.ev_let_binds:
|
|
cmpq $VAL_NIL, %rbx
|
|
je .ev_let_body
|
|
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # (var expr) pair
|
|
movq 8(%rax), %rbx # rest bindings
|
|
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %r8 # var
|
|
movq 8(%rax), %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # expr
|
|
|
|
# Eval expr in ORIGINAL env
|
|
pushq %rbx
|
|
pushq %rcx
|
|
pushq %r8
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %r8 # var
|
|
popq %rcx # current extended env
|
|
popq %rbx # rest bindings
|
|
|
|
# Bind
|
|
pushq %rbx
|
|
movq %r8, %rdi
|
|
movq %rax, %rsi
|
|
movq %rcx, %rdx
|
|
call env_define
|
|
movq %rax, %rcx # updated env
|
|
popq %rbx
|
|
jmp .ev_let_binds
|
|
|
|
.ev_let_body:
|
|
# eval body in extended env, TCO
|
|
movq %rcx, %rbp # extended env
|
|
movq %r12, %rdi # body forms
|
|
call wrap_begin
|
|
movq %rax, %rdi
|
|
jmp .eval_top
|
|
|
|
# ---- named let ----
|
|
.ev_named_let:
|
|
# %rbx = name sym, %r12 = ((bindings...) body...)
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rcx # bindings list
|
|
movq 8(%rax), %r12 # body forms
|
|
|
|
# Collect params and eval init values
|
|
# We'll build two reversed lists, then reverse them
|
|
pushq %rbx # save loop name
|
|
pushq %r12 # save body forms
|
|
|
|
movq $VAL_NIL, %r8 # params acc (reversed)
|
|
movq $VAL_NIL, %r9 # vals acc (reversed)
|
|
|
|
.ev_nlet_collect:
|
|
cmpq $VAL_NIL, %rcx
|
|
je .ev_nlet_build
|
|
|
|
movq %rcx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # (var expr)
|
|
movq 8(%rax), %rcx # rest bindings
|
|
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # var
|
|
movq 8(%rax), %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rsi # expr
|
|
|
|
# Save state
|
|
pushq %rcx
|
|
pushq %r8
|
|
pushq %r9
|
|
pushq %rdi # var
|
|
|
|
# Eval init expr
|
|
movq %rsi, %rdi
|
|
movq %rbp, %rsi
|
|
call eval
|
|
|
|
popq %rdi # var
|
|
popq %r9 # vals acc
|
|
popq %r8 # params acc
|
|
popq %rcx # rest bindings
|
|
|
|
# cons var onto params
|
|
pushq %rax # save evaled value
|
|
pushq %rcx
|
|
pushq %r9
|
|
movq %r8, %rsi
|
|
call make_pair
|
|
movq %rax, %r8
|
|
popq %r9
|
|
popq %rcx
|
|
popq %rax
|
|
|
|
# cons val onto vals
|
|
pushq %rcx
|
|
pushq %r8
|
|
movq %rax, %rdi
|
|
movq %r9, %rsi
|
|
call make_pair
|
|
movq %rax, %r9
|
|
popq %r8
|
|
popq %rcx
|
|
|
|
jmp .ev_nlet_collect
|
|
|
|
.ev_nlet_build:
|
|
# Reverse params
|
|
pushq %r9
|
|
movq %r8, %rdi
|
|
call list_reverse
|
|
movq %rax, %r8 # params (correct order)
|
|
popq %rdi
|
|
call list_reverse
|
|
movq %rax, %r9 # vals (correct order)
|
|
|
|
popq %r12 # body forms
|
|
popq %rbx # loop name
|
|
|
|
# Wrap body
|
|
pushq %rbx
|
|
pushq %r8
|
|
pushq %r9
|
|
movq %r12, %rdi
|
|
call wrap_begin
|
|
movq %rax, %rsi # body
|
|
popq %r9
|
|
popq %rdi # params
|
|
popq %rbx # name
|
|
|
|
# Create closure
|
|
pushq %rbx
|
|
pushq %r9
|
|
movq %rbp, %rdx
|
|
call make_closure
|
|
popq %r9 # vals
|
|
popq %rbx # name
|
|
|
|
# Bind name to closure in env (for recursion)
|
|
pushq %rax # save closure
|
|
pushq %r9
|
|
movq %rbx, %rdi
|
|
movq %rax, %rsi
|
|
movq %rbp, %rdx
|
|
call env_define
|
|
movq %rax, %rbp # env with name bound
|
|
popq %r9
|
|
popq %rax
|
|
|
|
# Patch closure's captured env to include itself
|
|
movq %rax, %rcx
|
|
andq $-8, %rcx
|
|
movq %rbp, 16(%rcx)
|
|
|
|
# Now apply: bind params to vals
|
|
movq %rax, %rcx
|
|
andq $-8, %rcx
|
|
movq (%rcx), %rdi # params
|
|
movq 8(%rcx), %r12 # body
|
|
movq %rbp, %rsi # env
|
|
|
|
movq %r9, %rcx # vals
|
|
.ev_nlet_bind:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .ev_nlet_go
|
|
cmpq $VAL_NIL, %rcx
|
|
je .ev_nlet_go
|
|
|
|
# Get param and val
|
|
pushq %rdi
|
|
pushq %rcx
|
|
pushq %rsi
|
|
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # param sym
|
|
movq %rcx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rsi # val
|
|
popq %rdx # env
|
|
pushq %rdx
|
|
call env_define
|
|
movq %rax, %rsi # updated env
|
|
|
|
popq %rax # (discard, env now in %rsi)
|
|
popq %rcx
|
|
popq %rdi
|
|
|
|
# Advance
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi
|
|
movq %rcx, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rcx
|
|
jmp .ev_nlet_bind
|
|
|
|
.ev_nlet_go:
|
|
movq %rsi, %rbp
|
|
movq %r12, %rdi
|
|
jmp .eval_top
|
|
|
|
# ---- cond ----
|
|
.ev_cond:
|
|
# %r12 = clauses list
|
|
.ev_cond_loop:
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_cond_void
|
|
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rbx # first clause (test expr...)
|
|
movq 8(%rax), %r12 # rest clauses
|
|
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # test
|
|
movq 8(%rax), %rcx # exprs
|
|
|
|
# Check for else
|
|
cmpq sym_else_val(%rip), %rdi
|
|
je .ev_cond_else
|
|
|
|
# Eval test
|
|
pushq %rcx
|
|
pushq %r12
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %r12
|
|
popq %rcx
|
|
|
|
cmpq $VAL_FALSE, %rax
|
|
je .ev_cond_loop
|
|
cmpq $VAL_NIL, %rax
|
|
je .ev_cond_loop
|
|
|
|
# True: eval body (TCO for begin)
|
|
jmp .ev_cond_body
|
|
|
|
.ev_cond_else:
|
|
# Fall through to body
|
|
.ev_cond_body:
|
|
# %rcx = body exprs, treat like begin
|
|
movq %rcx, %r12
|
|
jmp .ev_begin
|
|
|
|
.ev_cond_void:
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# ---- and ----
|
|
.ev_and:
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_and_empty
|
|
.ev_and_loop:
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # current
|
|
movq 8(%rax), %r12 # rest
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_and_tail
|
|
pushq %r12
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %r12
|
|
cmpq $VAL_FALSE, %rax
|
|
je .ev_and_false
|
|
cmpq $VAL_NIL, %rax
|
|
je .ev_and_false
|
|
jmp .ev_and_loop
|
|
.ev_and_tail:
|
|
jmp .eval_top
|
|
.ev_and_empty:
|
|
movq $VAL_TRUE, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
.ev_and_false:
|
|
movq $VAL_FALSE, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# ---- or ----
|
|
.ev_or:
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_or_empty
|
|
.ev_or_loop:
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi
|
|
movq 8(%rax), %r12
|
|
cmpq $VAL_NIL, %r12
|
|
je .ev_or_tail
|
|
pushq %r12
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %r12
|
|
cmpq $VAL_FALSE, %rax
|
|
je .ev_or_loop
|
|
cmpq $VAL_NIL, %rax
|
|
je .ev_or_loop
|
|
# Truthy
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
.ev_or_tail:
|
|
jmp .eval_top
|
|
.ev_or_empty:
|
|
movq $VAL_FALSE, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# ---- application ----
|
|
.ev_app:
|
|
# %rbx = operator expr (in car position), %r12 = arg exprs
|
|
# We stored these from the pair destructuring above
|
|
|
|
# Eval operator
|
|
movq %rbx, %rdi
|
|
pushq %r12
|
|
movq %rbp, %rsi
|
|
call eval
|
|
popq %r12
|
|
movq %rax, %rbx # evaled operator
|
|
|
|
# Eval args list
|
|
movq %r12, %rdi
|
|
movq %rbp, %rsi
|
|
call eval_list
|
|
movq %rax, %r12 # evaled args
|
|
|
|
# Dispatch
|
|
movq %rbx, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_BUILTIN, %rax
|
|
je .app_builtin
|
|
cmpq $TAG_CLOSURE, %rax
|
|
je .app_closure
|
|
|
|
# Not a procedure
|
|
movq $SYS_WRITE, %rax
|
|
movq $2, %rdi
|
|
leaq err_notproc(%rip), %rsi
|
|
movq $err_notproc_len, %rdx
|
|
syscall
|
|
movq $SYS_EXIT, %rax
|
|
movq $1, %rdi
|
|
syscall
|
|
|
|
# eval_list: %rdi = expr_list, %rsi = env -> %rax = value_list
|
|
eval_list:
|
|
pushq %rbx
|
|
pushq %r12
|
|
pushq %rbp
|
|
movq %rsi, %rbp
|
|
|
|
cmpq $VAL_NIL, %rdi
|
|
je .el_nil
|
|
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rbx # car = first expr
|
|
movq 8(%rax), %r12 # cdr = rest
|
|
|
|
# Eval first
|
|
movq %rbx, %rdi
|
|
movq %rbp, %rsi
|
|
call eval
|
|
pushq %rax
|
|
|
|
# Eval rest
|
|
movq %r12, %rdi
|
|
movq %rbp, %rsi
|
|
call eval_list
|
|
movq %rax, %rsi
|
|
popq %rdi
|
|
call make_pair
|
|
|
|
popq %rbp
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
.el_nil:
|
|
movq $VAL_NIL, %rax
|
|
popq %rbp
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
# ---- apply closure ----
|
|
.app_closure:
|
|
# %rbx = closure (tagged), %r12 = arg values list
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # params
|
|
movq 8(%rax), %rcx # body
|
|
movq 16(%rax), %rsi # closure env
|
|
movq %r12, %rdx # arg vals
|
|
|
|
# Bind params to args
|
|
.ac_bind:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .ac_go
|
|
cmpq $VAL_NIL, %rdx
|
|
je .ac_go
|
|
|
|
pushq %rdi
|
|
pushq %rcx
|
|
pushq %rdx
|
|
pushq %rsi
|
|
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # param sym
|
|
movq %rdx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rsi # arg val
|
|
popq %rdx # env
|
|
pushq %rdx
|
|
call env_define
|
|
movq %rax, %rsi # updated env
|
|
|
|
popq %rax # discard
|
|
popq %rdx # arg vals
|
|
popq %rcx # body
|
|
popq %rdi # params
|
|
|
|
# Advance
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi
|
|
movq %rdx, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdx
|
|
jmp .ac_bind
|
|
|
|
.ac_go:
|
|
# TCO: eval body in extended env
|
|
movq %rsi, %rbp
|
|
movq %rcx, %rdi
|
|
jmp .eval_top
|
|
|
|
# ---- apply builtin ----
|
|
.app_builtin:
|
|
# %rbx = builtin (tagged), %r12 = arg values list
|
|
movq %rbx, %rax
|
|
shrq $3, %rax # index
|
|
|
|
# Jump table — shared entry point for apply_proc_raw dispatch
|
|
.app_builtin_dispatch:
|
|
cmpq $BI_ADD, %rax
|
|
je bi_add
|
|
cmpq $BI_SUB, %rax
|
|
je bi_sub
|
|
cmpq $BI_MUL, %rax
|
|
je bi_mul
|
|
cmpq $BI_EQ, %rax
|
|
je bi_eq
|
|
cmpq $BI_LT, %rax
|
|
je bi_lt
|
|
cmpq $BI_GT, %rax
|
|
je bi_gt
|
|
cmpq $BI_LE, %rax
|
|
je bi_le
|
|
cmpq $BI_GE, %rax
|
|
je bi_ge
|
|
cmpq $BI_CONS, %rax
|
|
je bi_cons
|
|
cmpq $BI_CAR, %rax
|
|
je bi_car
|
|
cmpq $BI_CDR, %rax
|
|
je bi_cdr
|
|
cmpq $BI_NULLP, %rax
|
|
je bi_nullp
|
|
cmpq $BI_PAIRP, %rax
|
|
je bi_pairp
|
|
cmpq $BI_NOT, %rax
|
|
je bi_not
|
|
cmpq $BI_DISPLAY, %rax
|
|
je bi_display
|
|
cmpq $BI_NEWLINE, %rax
|
|
je bi_newline
|
|
cmpq $BI_LIST, %rax
|
|
je bi_list
|
|
cmpq $BI_LENGTH, %rax
|
|
je bi_length
|
|
cmpq $BI_ZEROP, %rax
|
|
je bi_zerop
|
|
cmpq $BI_MODULO, %rax
|
|
je bi_modulo
|
|
cmpq $BI_REMAINDER, %rax
|
|
je bi_remainder
|
|
cmpq $BI_NUMBERP, %rax
|
|
je bi_numberp
|
|
cmpq $BI_EQVP, %rax
|
|
je bi_eqvp
|
|
cmpq $BI_EQUALP, %rax
|
|
je bi_equalp
|
|
cmpq $BI_ABS, %rax
|
|
je bi_abs
|
|
cmpq $BI_MIN, %rax
|
|
je bi_min
|
|
cmpq $BI_MAX, %rax
|
|
je bi_max
|
|
cmpq $BI_BOOLP, %rax
|
|
je bi_boolp
|
|
cmpq $BI_SYMBOLP, %rax
|
|
je bi_symbolp
|
|
cmpq $BI_STRINGP, %rax
|
|
je bi_stringp
|
|
cmpq $BI_PROCP, %rax
|
|
je bi_procp
|
|
cmpq $BI_QUOTIENT, %rax
|
|
je bi_quotient
|
|
cmpq $BI_NEGATIVEP, %rax
|
|
je bi_negativep
|
|
cmpq $BI_POSITIVEP, %rax
|
|
je bi_positivep
|
|
cmpq $BI_DIV, %rax
|
|
je bi_div
|
|
cmpq $BI_ODDP, %rax
|
|
je bi_oddp
|
|
cmpq $BI_EVENP, %rax
|
|
je bi_evenp
|
|
cmpq $BI_APPEND, %rax
|
|
je bi_append
|
|
cmpq $BI_REVERSE, %rax
|
|
je bi_reverse
|
|
cmpq $BI_MAP, %rax
|
|
je bi_map
|
|
cmpq $BI_FILTER, %rax
|
|
je bi_filter
|
|
cmpq $BI_FOLDL, %rax
|
|
je bi_foldl
|
|
cmpq $BI_FOREACH, %rax
|
|
je bi_foreach
|
|
cmpq $BI_APPLY, %rax
|
|
je bi_apply
|
|
cmpq $BI_MEMBER, %rax
|
|
je bi_member
|
|
cmpq $BI_ASSOC, %rax
|
|
je bi_assoc
|
|
cmpq $BI_WRITE, %rax
|
|
je bi_write
|
|
cmpq $BI_STRLENGTH, %rax
|
|
je bi_strlength
|
|
cmpq $BI_STRREF, %rax
|
|
je bi_strref
|
|
cmpq $BI_STRAPPEND, %rax
|
|
je bi_strappend
|
|
cmpq $BI_STREQP, %rax
|
|
je bi_streqp
|
|
cmpq $BI_NUMTOSTR, %rax
|
|
je bi_numtostr
|
|
cmpq $BI_STRTONUM, %rax
|
|
je bi_strtonum
|
|
cmpq $BI_CHARTOINT, %rax
|
|
je bi_chartoint
|
|
cmpq $BI_INTTOCHAR, %rax
|
|
je bi_inttochar
|
|
cmpq $BI_CHARALPHAP, %rax
|
|
je bi_charalphap
|
|
cmpq $BI_CHARNUMP, %rax
|
|
je bi_charnump
|
|
cmpq $BI_VECREF, %rax
|
|
je bi_vecref
|
|
cmpq $BI_VECSET, %rax
|
|
je bi_vecset
|
|
cmpq $BI_VECLEN, %rax
|
|
je bi_veclen
|
|
cmpq $BI_VECP, %rax
|
|
je bi_vecp
|
|
cmpq $BI_VECTOR, %rax
|
|
je bi_vector
|
|
cmpq $BI_MAKEVEC, %rax
|
|
je bi_makevec
|
|
cmpq $BI_VECTOLIST, %rax
|
|
je bi_vectolist
|
|
cmpq $BI_LISTTOVEC, %rax
|
|
je bi_listtovec
|
|
cmpq $BI_CHARP, %rax
|
|
je bi_charp
|
|
cmpq $BI_LISTP, %rax
|
|
je bi_listp
|
|
cmpq $BI_SUBSTR, %rax
|
|
je bi_substr
|
|
cmpq $BI_EXPT, %rax
|
|
je bi_expt
|
|
cmpq $BI_GCD, %rax
|
|
je bi_gcd
|
|
cmpq $BI_INTEGERP, %rax
|
|
je bi_integerp
|
|
cmpq $BI_PORTALSAVE, %rax
|
|
je bi_portal_save
|
|
cmpq $BI_PORTALRESUME, %rax
|
|
je bi_portal_resume
|
|
cmpq $BI_LOAD, %rax
|
|
je bi_load
|
|
cmpq $BI_OPENOUT, %rax
|
|
je bi_open_output_file
|
|
cmpq $BI_CLOSEPORT, %rax
|
|
je bi_close_port
|
|
cmpq $BI_PORTP, %rax
|
|
je bi_portp
|
|
cmpq $BI_WRITEFILE, %rax
|
|
je bi_write_file
|
|
cmpq $BI_FILETOSTR, %rax
|
|
je bi_file_to_string
|
|
cmpq $BI_TCPLISTEN, %rax
|
|
je bi_tcp_listen
|
|
cmpq $BI_TCPACCEPT, %rax
|
|
je bi_tcp_accept
|
|
cmpq $BI_TCPCONNECT, %rax
|
|
je bi_tcp_connect
|
|
cmpq $BI_TCPRECV, %rax
|
|
je bi_tcp_recv
|
|
cmpq $BI_TCPSEND, %rax
|
|
je bi_tcp_send
|
|
cmpq $BI_TCPCLOSE, %rax
|
|
je bi_close_port
|
|
|
|
movq $VAL_VOID, %rax
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
|
|
# Macro: get arg from %r12, store raw tagged val in dest, advance %r12
|
|
.macro GETARG dest
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %r12
|
|
movq (%rax), \dest
|
|
.endm
|
|
|
|
.macro RET_VAL
|
|
popq %r12
|
|
popq %rbp
|
|
popq %rbx
|
|
ret
|
|
.endm
|
|
|
|
bi_add:
|
|
xorq %rcx, %rcx
|
|
.ba_loop:
|
|
cmpq $VAL_NIL, %r12
|
|
je .ba_done
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
addq %rax, %rcx
|
|
jmp .ba_loop
|
|
.ba_done:
|
|
movq %rcx, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_sub:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
movq %rax, %rcx
|
|
cmpq $VAL_NIL, %r12
|
|
je .bs_neg
|
|
.bs_loop:
|
|
cmpq $VAL_NIL, %r12
|
|
je .bs_done
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
subq %rax, %rcx
|
|
jmp .bs_loop
|
|
.bs_neg:
|
|
negq %rcx
|
|
.bs_done:
|
|
movq %rcx, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_mul:
|
|
movq $1, %rcx
|
|
.bm_loop:
|
|
cmpq $VAL_NIL, %r12
|
|
je .bm_done
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
imulq %rax, %rcx
|
|
jmp .bm_loop
|
|
.bm_done:
|
|
movq %rcx, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
# Comparison helpers
|
|
.macro CMP_BI jmp_if
|
|
GETARG %rcx
|
|
sarq $3, %rcx
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
cmpq %rax, %rcx
|
|
\jmp_if .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
.endm
|
|
|
|
.cmp_true:
|
|
movq $VAL_TRUE, %rax
|
|
RET_VAL
|
|
.cmp_false:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_eq:
|
|
CMP_BI je
|
|
bi_lt:
|
|
CMP_BI jl
|
|
bi_gt:
|
|
CMP_BI jg
|
|
bi_le:
|
|
CMP_BI jle
|
|
bi_ge:
|
|
CMP_BI jge
|
|
|
|
bi_cons:
|
|
GETARG %rdi
|
|
GETARG %rsi
|
|
call make_pair
|
|
RET_VAL
|
|
|
|
bi_car:
|
|
GETARG %rdi
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rax
|
|
RET_VAL
|
|
|
|
bi_cdr:
|
|
GETARG %rdi
|
|
andq $-8, %rdi
|
|
movq 8(%rdi), %rax
|
|
RET_VAL
|
|
|
|
bi_nullp:
|
|
GETARG %rax
|
|
cmpq $VAL_NIL, %rax
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_pairp:
|
|
GETARG %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_PAIR, %rax
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_not:
|
|
GETARG %rax
|
|
cmpq $VAL_FALSE, %rax
|
|
je .cmp_true
|
|
cmpq $VAL_NIL, %rax
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# resolve_output_fd: optional port arg from %r12. %rax = fd (1 if none).
|
|
# Destructively consumes the port arg if present.
|
|
resolve_output_fd:
|
|
cmpq $VAL_NIL, %r12
|
|
je .rof_stdout
|
|
movq %r12, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rcx # port value
|
|
movq 8(%rax), %r12 # advance arg list
|
|
movq %rcx, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_SPECIAL, %rax
|
|
jne .rof_stdout
|
|
movq %rcx, %rax
|
|
shrq $3, %rax
|
|
cmpq $PORT_SPECIAL_BASE, %rax
|
|
jl .rof_stdout
|
|
subq $PORT_SPECIAL_BASE, %rax
|
|
ret
|
|
.rof_stdout:
|
|
movq $1, %rax
|
|
ret
|
|
|
|
bi_display:
|
|
GETARG %rbx # value to display
|
|
call resolve_output_fd # %rax = fd (stdout or port)
|
|
movq output_fd(%rip), %rcx
|
|
pushq %rcx
|
|
movq %rax, output_fd(%rip)
|
|
|
|
movq %rbx, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_STRING, %rax
|
|
je .bd_str
|
|
movq %rbx, %rdi
|
|
call scheme_print
|
|
jmp .bd_restore
|
|
.bd_str:
|
|
movq %rbx, %rdi
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rdx
|
|
leaq 8(%rdi), %rsi
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
syscall
|
|
.bd_restore:
|
|
popq %rax
|
|
movq %rax, output_fd(%rip)
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
bi_newline:
|
|
call resolve_output_fd
|
|
movq output_fd(%rip), %rcx
|
|
pushq %rcx
|
|
movq %rax, output_fd(%rip)
|
|
movq $SYS_WRITE, %rax
|
|
movq output_fd(%rip), %rdi
|
|
leaq newline_ch(%rip), %rsi
|
|
movq $1, %rdx
|
|
syscall
|
|
popq %rax
|
|
movq %rax, output_fd(%rip)
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
bi_list:
|
|
movq %r12, %rax
|
|
RET_VAL
|
|
|
|
bi_length:
|
|
GETARG %rdi
|
|
xorq %rcx, %rcx
|
|
.bl_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bl_done
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi
|
|
incq %rcx
|
|
jmp .bl_loop
|
|
.bl_done:
|
|
movq %rcx, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_zerop:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
testq %rax, %rax
|
|
jz .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_modulo:
|
|
GETARG %rcx
|
|
sarq $3, %rcx
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
movq %rax, %r8 # divisor
|
|
movq %rcx, %rax # dividend
|
|
cqo
|
|
idivq %r8
|
|
movq %rdx, %rdi # remainder
|
|
# modulo: result has sign of divisor
|
|
testq %rdi, %rdi
|
|
jz .bmod_done
|
|
movq %rdi, %rax
|
|
xorq %r8, %rax
|
|
testq %rax, %rax
|
|
jns .bmod_done
|
|
addq %r8, %rdi
|
|
.bmod_done:
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_remainder:
|
|
GETARG %rcx
|
|
sarq $3, %rcx
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
movq %rax, %r8
|
|
movq %rcx, %rax
|
|
cqo
|
|
idivq %r8
|
|
movq %rdx, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_numberp:
|
|
GETARG %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_INT, %rax
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_eqvp:
|
|
GETARG %rcx
|
|
GETARG %rax
|
|
cmpq %rax, %rcx
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_equalp:
|
|
GETARG %rdi
|
|
GETARG %rsi
|
|
call deep_equal
|
|
RET_VAL
|
|
|
|
# deep_equal: %rdi = a, %rsi = b. Returns VAL_TRUE or VAL_FALSE in %rax
|
|
deep_equal:
|
|
cmpq %rdi, %rsi
|
|
je .deq_true
|
|
# Both pairs? Recurse.
|
|
movq %rdi, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_PAIR, %rax
|
|
jne .deq_false
|
|
movq %rsi, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_PAIR, %rax
|
|
jne .deq_false
|
|
# Both pairs — compare car then cdr
|
|
pushq %rdi
|
|
pushq %rsi
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # car(a)
|
|
movq %rsi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rsi # car(b)
|
|
call deep_equal
|
|
cmpq $VAL_FALSE, %rax
|
|
popq %rsi
|
|
popq %rdi
|
|
je .deq_false
|
|
# car matched, now cdr
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi # cdr(a)
|
|
movq %rsi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rsi # cdr(b)
|
|
jmp deep_equal # tail call
|
|
.deq_true:
|
|
movq $VAL_TRUE, %rax
|
|
ret
|
|
.deq_false:
|
|
movq $VAL_FALSE, %rax
|
|
ret
|
|
|
|
bi_abs:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
testq %rax, %rax
|
|
jns 1f
|
|
negq %rax
|
|
1: movq %rax, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_min:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
movq %rax, %rcx
|
|
.bmin_loop:
|
|
cmpq $VAL_NIL, %r12
|
|
je .bmin_done
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
cmpq %rax, %rcx
|
|
jle .bmin_loop
|
|
movq %rax, %rcx
|
|
jmp .bmin_loop
|
|
.bmin_done:
|
|
movq %rcx, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_max:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
movq %rax, %rcx
|
|
.bmax_loop:
|
|
cmpq $VAL_NIL, %r12
|
|
je .bmax_done
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
cmpq %rax, %rcx
|
|
jge .bmax_loop
|
|
movq %rax, %rcx
|
|
jmp .bmax_loop
|
|
.bmax_done:
|
|
movq %rcx, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_boolp:
|
|
GETARG %rax
|
|
cmpq $VAL_TRUE, %rax
|
|
je .cmp_true
|
|
cmpq $VAL_FALSE, %rax
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_symbolp:
|
|
GETARG %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_SYM, %rax
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_stringp:
|
|
GETARG %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_STRING, %rax
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_procp:
|
|
GETARG %rax
|
|
movq %rax, %rcx
|
|
andq $TAG_MASK, %rcx
|
|
cmpq $TAG_CLOSURE, %rcx
|
|
je .cmp_true
|
|
cmpq $TAG_BUILTIN, %rcx
|
|
je .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_quotient:
|
|
GETARG %rcx
|
|
sarq $3, %rcx
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
movq %rax, %r8
|
|
movq %rcx, %rax
|
|
cqo
|
|
idivq %r8
|
|
movq %rax, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_negativep:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
testq %rax, %rax
|
|
js .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_positivep:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
testq %rax, %rax
|
|
jg .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# apply_proc_raw: %rdi = proc, %rsi = evaled_args_list → %rax = result
|
|
# Calls a procedure (builtin or closure) with already-evaluated arguments
|
|
apply_proc_raw:
|
|
pushq %rbx
|
|
pushq %r12
|
|
pushq %rbp
|
|
movq %rdi, %rbx # proc
|
|
movq %rsi, %r12 # args
|
|
|
|
movq %rbx, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_BUILTIN, %rax
|
|
je .apr_builtin
|
|
cmpq $TAG_CLOSURE, %rax
|
|
je .apr_closure
|
|
# Not callable
|
|
movq $VAL_VOID, %rax
|
|
popq %rbp
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.apr_builtin:
|
|
# Jump to builtin dispatch with %rbx = builtin, %r12 = args
|
|
movq %rbx, %rax
|
|
shrq $3, %rax
|
|
# Inline the dispatch — just call the apply_builtin section
|
|
# Actually, reuse the existing dispatch code by jumping
|
|
jmp .apr_bi_dispatch
|
|
|
|
.apr_closure:
|
|
# Closure: extract params, body, env. Bind args. Eval body.
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rcx # params
|
|
movq 8(%rax), %rdx # body
|
|
movq 16(%rax), %rbp # captured env
|
|
|
|
# Bind params to args
|
|
movq %r12, %rsi # args
|
|
.apr_bind:
|
|
cmpq $VAL_NIL, %rcx
|
|
je .apr_eval_body
|
|
cmpq $VAL_NIL, %rsi
|
|
je .apr_eval_body
|
|
|
|
# Extract param name and rest params
|
|
movq %rcx, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # param name (symbol)
|
|
movq 8(%rax), %rcx # rest params
|
|
|
|
# Extract arg value and rest args
|
|
movq %rsi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %r8 # rest args (cdr)
|
|
movq (%rax), %rsi # arg value (car)
|
|
|
|
pushq %r8 # save rest args
|
|
pushq %rcx # save rest params
|
|
pushq %rdx # save body
|
|
movq %rbp, %rdx # env
|
|
call env_define
|
|
movq %rax, %rbp
|
|
popq %rdx # restore body
|
|
popq %rcx # restore rest params
|
|
popq %rsi # restore rest args
|
|
jmp .apr_bind
|
|
|
|
.apr_eval_body:
|
|
# Body is already wrap_begin'd: single expr or (begin ...) form.
|
|
# Eval it directly.
|
|
movq %rdx, %rdi # body expression
|
|
movq %rbp, %rsi # env
|
|
call eval
|
|
popq %rbp
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|
|
|
|
.apr_bi_dispatch:
|
|
# Dispatch builtin by index — jump to main dispatch table
|
|
# RET_VAL pops %r12,%rbp,%rbx — matching our pushes in apply_proc_raw
|
|
movq %rbx, %rax
|
|
shrq $3, %rax
|
|
jmp .app_builtin_dispatch
|
|
|
|
# ── New builtins ─────────────────────────────────────────────
|
|
|
|
bi_div:
|
|
GETARG %rcx # first arg (dividend)
|
|
sarq $3, %rcx
|
|
GETARG %rdx # second arg (divisor)
|
|
sarq $3, %rdx
|
|
movq %rcx, %rax # dividend in rax
|
|
movq %rdx, %rcx # divisor in rcx
|
|
cqto # sign-extend rax → rdx:rax
|
|
idivq %rcx # rax = quotient
|
|
movq %rax, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_oddp:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
testq $1, %rax
|
|
jnz .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_evenp:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
testq $1, %rax
|
|
jz .cmp_true
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_append:
|
|
# (append lst1 lst2) — copy lst1, set last cdr to lst2
|
|
GETARG %rdi # lst1
|
|
GETARG %rsi # lst2 (stays in %r12 if more args, but we take 2)
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bapp_done_rsi
|
|
# Copy lst1
|
|
pushq %rsi
|
|
pushq %rbx
|
|
movq $VAL_NIL, %rbx # result tail
|
|
movq $0, %rcx # first pair ptr
|
|
.bapp_copy:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bapp_link
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
pushq %rdi
|
|
movq (%rax), %rdi # car
|
|
movq $VAL_NIL, %rsi
|
|
call make_pair
|
|
# if first, save as head
|
|
testq %rcx, %rcx
|
|
jnz .bapp_notfirst
|
|
movq %rax, %rcx # head
|
|
movq %rax, %rbx # tail
|
|
jmp .bapp_next
|
|
.bapp_notfirst:
|
|
# set tail's cdr
|
|
movq %rbx, %rdx
|
|
andq $-8, %rdx
|
|
movq %rax, 8(%rdx)
|
|
movq %rax, %rbx
|
|
.bapp_next:
|
|
popq %rdi
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi # cdr
|
|
jmp .bapp_copy
|
|
.bapp_link:
|
|
# set last cdr to lst2
|
|
# %rbx = last pair (tagged), %rcx = head (tagged)
|
|
# stack: [rbx_saved, rsi=lst2]
|
|
movq %rbx, %rax
|
|
andq $-8, %rax
|
|
popq %rbx # restore saved rbx
|
|
popq %rsi # lst2
|
|
movq %rsi, 8(%rax) # set last pair's cdr to lst2
|
|
movq %rcx, %rax # return head
|
|
RET_VAL
|
|
.bapp_done_rsi:
|
|
movq %rsi, %rax
|
|
RET_VAL
|
|
|
|
bi_reverse:
|
|
GETARG %rdi
|
|
call list_reverse
|
|
RET_VAL
|
|
|
|
bi_map:
|
|
# (map f lst) — apply f to each element, build result list
|
|
# NOTE: %r13 is the heap limit (global), must not be used as scratch.
|
|
# Stack slot for accumulator: [acc] at base of our frame.
|
|
GETARG %rbx # f (proc/closure/builtin)
|
|
GETARG %rdi # lst
|
|
subq $8, %rsp # allocate stack slot for acc
|
|
movq $VAL_NIL, (%rsp) # acc = NIL
|
|
.bmap_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bmap_done
|
|
pushq %rdi # save input list
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # car = element
|
|
# Build 1-element arg list: (element)
|
|
movq $VAL_NIL, %rsi
|
|
call make_pair
|
|
movq %rax, %rsi # arg list
|
|
pushq %rbx # save proc
|
|
movq %rbx, %rdi # proc
|
|
call apply_proc_raw
|
|
popq %rbx # restore proc
|
|
# cons result onto acc
|
|
# stack: [input_list] [acc]
|
|
movq %rax, %rdi # result value
|
|
movq 8(%rsp), %rsi # acc (past saved input_list)
|
|
call make_pair
|
|
movq %rax, 8(%rsp) # update acc
|
|
popq %rdi # restore input list
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi # cdr of input list
|
|
jmp .bmap_loop
|
|
.bmap_done:
|
|
popq %rdi # acc (from stack slot)
|
|
call list_reverse
|
|
RET_VAL
|
|
|
|
bi_filter:
|
|
# (filter pred lst)
|
|
# NOTE: %r13 is the heap limit (global), must not be used as scratch.
|
|
GETARG %rbx # pred
|
|
GETARG %rdi # lst
|
|
subq $8, %rsp # stack slot for acc
|
|
movq $VAL_NIL, (%rsp) # acc = NIL
|
|
.bfilt_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bfilt_done
|
|
pushq %rdi # save input list
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rcx # car = element
|
|
pushq %rcx # save element
|
|
# Call pred on element
|
|
movq %rcx, %rdi
|
|
movq $VAL_NIL, %rsi
|
|
call make_pair
|
|
movq %rax, %rsi # arg list
|
|
pushq %rbx # save pred
|
|
movq %rbx, %rdi # pred
|
|
call apply_proc_raw
|
|
popq %rbx # restore pred
|
|
popq %rcx # restore element
|
|
# Check if result is truthy (not #f)
|
|
cmpq $VAL_FALSE, %rax
|
|
je .bfilt_skip
|
|
# cons element onto acc
|
|
# stack: [input_list] [acc]
|
|
movq %rcx, %rdi
|
|
movq 8(%rsp), %rsi # acc (past saved input_list)
|
|
call make_pair
|
|
movq %rax, 8(%rsp) # update acc
|
|
.bfilt_skip:
|
|
popq %rdi # restore input list
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi # cdr
|
|
jmp .bfilt_loop
|
|
.bfilt_done:
|
|
popq %rdi # acc
|
|
call list_reverse
|
|
RET_VAL
|
|
|
|
bi_foldl:
|
|
# (fold-left f init lst)
|
|
# NOTE: %r13 is the heap limit (global), must not be used as scratch.
|
|
GETARG %rbx # f
|
|
GETARG %rcx # init (accumulator)
|
|
GETARG %rdi # lst
|
|
subq $8, %rsp # stack slot for acc
|
|
movq %rcx, (%rsp) # acc = init
|
|
.bfl_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bfl_done
|
|
pushq %rdi # save input list
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # car = element
|
|
# Build arg list: (list acc element) for fold-left convention
|
|
pushq %rdi # save element
|
|
movq $VAL_NIL, %rsi
|
|
call make_pair # (element)
|
|
movq %rax, %rsi
|
|
movq 16(%rsp), %rdi # acc (past element, input_list)
|
|
call make_pair # (acc element)
|
|
movq %rax, %rsi # arg list
|
|
pushq %rbx # save proc
|
|
movq %rbx, %rdi # proc
|
|
call apply_proc_raw
|
|
popq %rbx # restore proc
|
|
addq $8, %rsp # discard saved element
|
|
movq %rax, 8(%rsp) # update acc (past saved input_list)
|
|
popq %rdi # restore input list
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi # cdr
|
|
jmp .bfl_loop
|
|
.bfl_done:
|
|
popq %rax # acc = result
|
|
RET_VAL
|
|
|
|
bi_foreach:
|
|
# (for-each f lst) — like map but discard results
|
|
GETARG %rbx
|
|
GETARG %rdi
|
|
.bfe_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bfe_done
|
|
pushq %rdi
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi
|
|
movq $VAL_NIL, %rsi
|
|
call make_pair
|
|
movq %rax, %r12
|
|
pushq %rbx
|
|
movq %rbx, %rdi
|
|
movq %r12, %rsi
|
|
call apply_proc_raw
|
|
popq %rbx
|
|
popq %rdi
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi
|
|
jmp .bfe_loop
|
|
.bfe_done:
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
bi_apply:
|
|
# (apply f args-list)
|
|
GETARG %rbx # f
|
|
GETARG %rdi # args-list (already a proper list)
|
|
movq %rbx, %rdi
|
|
# Need to set up %rbx=proc, %r12=args then jump to apply path
|
|
# Actually just call apply_proc_raw
|
|
pushq %rbx
|
|
movq %rbx, %rdi
|
|
movq %r12, %rsi # remaining args = the list
|
|
call apply_proc_raw
|
|
popq %rbx
|
|
RET_VAL
|
|
|
|
bi_member:
|
|
# (member obj lst)
|
|
GETARG %rcx # obj
|
|
GETARG %rdi # lst
|
|
.bmem_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bmem_false
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
pushq %rdi
|
|
pushq %rcx
|
|
movq (%rax), %rdi # car
|
|
movq %rcx, %rsi
|
|
call deep_equal
|
|
popq %rcx
|
|
popq %rdi
|
|
cmpq $VAL_TRUE, %rax
|
|
je .bmem_found
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rdi
|
|
jmp .bmem_loop
|
|
.bmem_found:
|
|
movq %rdi, %rax # return the tail
|
|
RET_VAL
|
|
.bmem_false:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_assoc:
|
|
# (assoc key alist)
|
|
GETARG %rcx # key
|
|
GETARG %rdi # alist
|
|
.bassoc_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .bassoc_false
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdx # car = pair
|
|
movq 8(%rax), %rdi # cdr = rest
|
|
# car of the pair
|
|
movq %rdx, %rax
|
|
andq $-8, %rax
|
|
pushq %rdi
|
|
pushq %rcx
|
|
pushq %rdx
|
|
movq (%rax), %rdi # caar
|
|
movq %rcx, %rsi
|
|
call deep_equal
|
|
popq %rdx
|
|
popq %rcx
|
|
popq %rdi
|
|
cmpq $VAL_TRUE, %rax
|
|
je .bassoc_found
|
|
jmp .bassoc_loop
|
|
.bassoc_found:
|
|
movq %rdx, %rax
|
|
RET_VAL
|
|
.bassoc_false:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_write:
|
|
GETARG %rbx # value to write (quoted strings, etc.)
|
|
call resolve_output_fd
|
|
movq output_fd(%rip), %rcx
|
|
pushq %rcx
|
|
movq %rax, output_fd(%rip)
|
|
movq %rbx, %rdi
|
|
call scheme_print
|
|
popq %rax
|
|
movq %rax, output_fd(%rip)
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
bi_strlength:
|
|
GETARG %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rax # length (first 8 bytes of string obj)
|
|
movq %rax, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_strref:
|
|
GETARG %rax # string
|
|
andq $-8, %rax
|
|
movq %rax, %rcx # string ptr
|
|
GETARG %rax # index
|
|
sarq $3, %rax
|
|
movzbl 8(%rcx,%rax,1), %edi # byte at offset
|
|
# Return as 1-char string
|
|
movq %r15, %rax
|
|
movq $1, (%r15) # length
|
|
movb %dil, 8(%r15)
|
|
addq $16, %r15
|
|
orq $TAG_STRING, %rax
|
|
RET_VAL
|
|
|
|
bi_strappend:
|
|
# (string-append s1 s2 ...) — variadic
|
|
# Save arg list head for the second pass
|
|
pushq %r12 # original arg list head
|
|
|
|
# Pass 1: total length
|
|
xorq %rcx, %rcx # accumulator
|
|
movq %r12, %rax
|
|
.bsa_len_loop:
|
|
cmpq $VAL_NIL, %rax
|
|
je .bsa_alloc
|
|
movq %rax, %rbx
|
|
andq $-8, %rbx
|
|
movq (%rbx), %rdi # string value
|
|
andq $-8, %rdi
|
|
addq (%rdi), %rcx # += length
|
|
movq 8(%rbx), %rax # advance
|
|
jmp .bsa_len_loop
|
|
|
|
.bsa_alloc:
|
|
# Allocate string cell: 8 byte length + rcx bytes
|
|
movq %rcx, %rbx # save total length
|
|
movq %rcx, %rdi
|
|
addq $8, %rdi
|
|
call heap_alloc
|
|
movq %rax, %rbp # string object base
|
|
movq %rbx, (%rbp) # store length
|
|
leaq 8(%rbp), %r9 # dest cursor
|
|
|
|
# Pass 2: copy each arg's bytes
|
|
popq %r12 # restore arg list
|
|
pushq %rbp # save string obj
|
|
movq %r12, %rax
|
|
.bsa_copy_loop:
|
|
cmpq $VAL_NIL, %rax
|
|
je .bsa_done
|
|
movq %rax, %rbx
|
|
andq $-8, %rbx
|
|
movq (%rbx), %rdi # string value
|
|
movq 8(%rbx), %rax # save advance
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rcx # length
|
|
leaq 8(%rdi), %r10 # src bytes
|
|
.bsa_byte_loop:
|
|
testq %rcx, %rcx
|
|
jz .bsa_copy_loop
|
|
movb (%r10), %r8b
|
|
movb %r8b, (%r9)
|
|
incq %r10
|
|
incq %r9
|
|
decq %rcx
|
|
jmp .bsa_byte_loop
|
|
|
|
.bsa_done:
|
|
popq %rbp
|
|
movq %rbp, %rax
|
|
orq $TAG_STRING, %rax
|
|
RET_VAL
|
|
|
|
bi_streqp:
|
|
GETARG %rdi
|
|
GETARG %rsi
|
|
andq $-8, %rdi
|
|
andq $-8, %rsi
|
|
movq (%rdi), %rcx # len1
|
|
cmpq (%rsi), %rcx # len2
|
|
jne .cmp_false
|
|
leaq 8(%rdi), %rdi
|
|
leaq 8(%rsi), %rsi
|
|
.bseq_loop:
|
|
testq %rcx, %rcx
|
|
jz .cmp_true
|
|
movb (%rdi), %al
|
|
cmpb (%rsi), %al
|
|
jne .cmp_false
|
|
incq %rdi
|
|
incq %rsi
|
|
decq %rcx
|
|
jmp .bseq_loop
|
|
|
|
bi_numtostr:
|
|
# (number->string n) → string
|
|
# Write digits (reverse) into num_buf[end..], then copy into a heap cell.
|
|
GETARG %rax
|
|
sarq $3, %rax # untag int
|
|
leaq num_buf(%rip), %rdi
|
|
addq $63, %rdi # write backward from end
|
|
movb $0, (%rdi) # sentinel (not used for length)
|
|
movq %rdi, %rsi # save end pos
|
|
# Detect negative
|
|
xorq %r8, %r8 # negative flag
|
|
testq %rax, %rax
|
|
jns .bn2s_absloop
|
|
movq $1, %r8
|
|
negq %rax
|
|
.bn2s_absloop:
|
|
xorq %rdx, %rdx
|
|
movq $10, %rcx
|
|
divq %rcx # rax = q, rdx = r
|
|
addb $'0', %dl
|
|
decq %rdi
|
|
movb %dl, (%rdi)
|
|
testq %rax, %rax
|
|
jnz .bn2s_absloop
|
|
testq %r8, %r8
|
|
jz .bn2s_copy
|
|
decq %rdi
|
|
movb $'-', (%rdi)
|
|
.bn2s_copy:
|
|
# rdi = start of digit bytes, rsi = end (exclusive)
|
|
movq %rsi, %rcx
|
|
subq %rdi, %rcx # length
|
|
# Allocate string cell: 8 byte length + rcx bytes
|
|
pushq %rdi
|
|
pushq %rcx
|
|
movq %rcx, %rdi
|
|
addq $8, %rdi
|
|
call heap_alloc
|
|
popq %rcx
|
|
popq %rdi
|
|
movq %rcx, (%rax) # length
|
|
movq %rax, %rbx # save obj
|
|
leaq 8(%rax), %rdx
|
|
.bn2s_copyloop:
|
|
testq %rcx, %rcx
|
|
jz .bn2s_done
|
|
movb (%rdi), %r8b
|
|
movb %r8b, (%rdx)
|
|
incq %rdi
|
|
incq %rdx
|
|
decq %rcx
|
|
jmp .bn2s_copyloop
|
|
.bn2s_done:
|
|
movq %rbx, %rax
|
|
orq $TAG_STRING, %rax
|
|
RET_VAL
|
|
|
|
bi_strtonum:
|
|
GETARG %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rcx # length
|
|
leaq 8(%rax), %rdi # chars
|
|
# Parse integer
|
|
xorq %rax, %rax
|
|
xorq %rdx, %rdx # sign flag
|
|
cmpb $'-', (%rdi)
|
|
jne .bs2n_loop
|
|
movq $1, %rdx
|
|
incq %rdi
|
|
decq %rcx
|
|
.bs2n_loop:
|
|
testq %rcx, %rcx
|
|
jz .bs2n_done
|
|
movzbl (%rdi), %esi
|
|
subb $'0', %sil
|
|
cmpb $9, %sil
|
|
ja .bs2n_fail
|
|
imulq $10, %rax
|
|
addq %rsi, %rax
|
|
incq %rdi
|
|
decq %rcx
|
|
jmp .bs2n_loop
|
|
.bs2n_done:
|
|
testq %rdx, %rdx
|
|
jz .bs2n_pos
|
|
negq %rax
|
|
.bs2n_pos:
|
|
movq %rax, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
.bs2n_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_chartoint:
|
|
GETARG %rax
|
|
# char is stored as a 1-char string
|
|
andq $-8, %rax
|
|
movzbl 8(%rax), %edi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_inttochar:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
# Create 1-char string
|
|
movq %r15, %rcx
|
|
movq $1, (%r15)
|
|
movb %al, 8(%r15)
|
|
addq $16, %r15
|
|
movq %rcx, %rax
|
|
orq $TAG_STRING, %rax
|
|
RET_VAL
|
|
|
|
bi_charalphap:
|
|
GETARG %rax
|
|
andq $-8, %rax
|
|
movzbl 8(%rax), %eax
|
|
# Check a-z, A-Z
|
|
cmpb $'a', %al
|
|
jl .bca_upper
|
|
cmpb $'z', %al
|
|
jle .cmp_true
|
|
.bca_upper:
|
|
cmpb $'A', %al
|
|
jl .cmp_false
|
|
cmpb $'Z', %al
|
|
jle .cmp_true
|
|
jmp .cmp_false
|
|
|
|
bi_charnump:
|
|
GETARG %rax
|
|
andq $-8, %rax
|
|
movzbl 8(%rax), %eax
|
|
cmpb $'0', %al
|
|
jl .cmp_false
|
|
cmpb $'9', %al
|
|
jle .cmp_true
|
|
jmp .cmp_false
|
|
|
|
bi_charp:
|
|
# char? — true if it's a 1-char string
|
|
GETARG %rax
|
|
movq %rax, %rcx
|
|
andq $TAG_MASK, %rcx
|
|
cmpq $TAG_STRING, %rcx
|
|
jne .cmp_false
|
|
andq $-8, %rax
|
|
cmpq $1, (%rax)
|
|
je .cmp_true
|
|
jmp .cmp_false
|
|
|
|
bi_listp:
|
|
GETARG %rdi
|
|
.blistp_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .cmp_true
|
|
movq %rdi, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_PAIR, %rax
|
|
jne .cmp_false
|
|
andq $-8, %rdi
|
|
movq 8(%rdi), %rdi
|
|
jmp .blistp_loop
|
|
|
|
bi_integerp:
|
|
GETARG %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_INT, %rax
|
|
je .cmp_true
|
|
jmp .cmp_false
|
|
|
|
bi_expt:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
pushq %rax # save untagged base
|
|
GETARG %rcx
|
|
sarq $3, %rcx
|
|
popq %rax # restore untagged base
|
|
# base^exp by repeated multiplication
|
|
movq $1, %rdx
|
|
.bexpt_loop:
|
|
testq %rcx, %rcx
|
|
jz .bexpt_done
|
|
imulq %rax, %rdx
|
|
decq %rcx
|
|
jmp .bexpt_loop
|
|
.bexpt_done:
|
|
movq %rdx, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_gcd:
|
|
GETARG %rax
|
|
sarq $3, %rax
|
|
GETARG %rcx
|
|
sarq $3, %rcx
|
|
# Euclidean GCD
|
|
testq %rax, %rax
|
|
jns 1f
|
|
negq %rax
|
|
1: testq %rcx, %rcx
|
|
jns 2f
|
|
negq %rcx
|
|
2:
|
|
.bgcd_loop:
|
|
testq %rcx, %rcx
|
|
jz .bgcd_done
|
|
xorq %rdx, %rdx
|
|
divq %rcx
|
|
movq %rcx, %rax
|
|
movq %rdx, %rcx
|
|
jmp .bgcd_loop
|
|
.bgcd_done:
|
|
movq %rax, %rdi
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_vector:
|
|
# (vector e1 e2 ...) — build from remaining args in %r12
|
|
# Count args
|
|
movq %r12, %rdi
|
|
xorq %rcx, %rcx
|
|
movq %rdi, %rax
|
|
.bvec_count:
|
|
cmpq $VAL_NIL, %rax
|
|
je .bvec_alloc
|
|
incq %rcx
|
|
movq %rax, %rdx
|
|
andq $-8, %rdx
|
|
movq 8(%rdx), %rax
|
|
jmp .bvec_count
|
|
.bvec_alloc:
|
|
# Allocate: 8 bytes length + 8*count bytes
|
|
movq %r15, %rax # vector obj
|
|
movq %rcx, (%r15) # length
|
|
leaq 8(%r15,%rcx,8), %r15
|
|
# Fill elements from arg list
|
|
movq %rdi, %rdx # arg list
|
|
leaq 8(%rax), %rdi # elements start
|
|
.bvec_fill:
|
|
cmpq $VAL_NIL, %rdx
|
|
je .bvec_done2
|
|
movq %rdx, %rcx
|
|
andq $-8, %rcx
|
|
movq (%rcx), %rsi # car = element
|
|
movq %rsi, (%rdi)
|
|
addq $8, %rdi
|
|
movq 8(%rcx), %rdx # cdr
|
|
jmp .bvec_fill
|
|
.bvec_done2:
|
|
# Tag vector — use tag 7 (available)
|
|
orq $7, %rax
|
|
# Override r12 to empty (we consumed all args)
|
|
movq $VAL_NIL, %r12
|
|
RET_VAL
|
|
|
|
bi_makevec:
|
|
GETARG %rax
|
|
sarq $3, %rax # n
|
|
GETARG %rcx # fill value (or default 0)
|
|
movq %r15, %rdx # vector obj
|
|
movq %rax, (%r15) # length
|
|
leaq 8(%r15,%rax,8), %r15
|
|
# Fill
|
|
leaq 8(%rdx), %rdi
|
|
movq (%rdx), %rsi # count
|
|
.bmv_fill:
|
|
testq %rsi, %rsi
|
|
jz .bmv_done
|
|
movq %rcx, (%rdi)
|
|
addq $8, %rdi
|
|
decq %rsi
|
|
jmp .bmv_fill
|
|
.bmv_done:
|
|
movq %rdx, %rax
|
|
orq $7, %rax
|
|
RET_VAL
|
|
|
|
bi_vecref:
|
|
GETARG %rdi # vector (tagged)
|
|
andq $-8, %rdi # untag — use %rdi (not clobbered by GETARG)
|
|
GETARG %rcx # index (tagged int)
|
|
sarq $3, %rcx # untag index
|
|
movq 8(%rdi,%rcx,8), %rax # element
|
|
RET_VAL
|
|
|
|
bi_vecset:
|
|
GETARG %rdi # vector (tagged)
|
|
andq $-8, %rdi # untag — use %rdi (not clobbered by GETARG)
|
|
GETARG %rcx # index (tagged int)
|
|
sarq $3, %rcx # untag index
|
|
GETARG %rdx # value
|
|
movq %rdx, 8(%rdi,%rcx,8)
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
bi_veclen:
|
|
GETARG %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rdi # length
|
|
call make_int
|
|
RET_VAL
|
|
|
|
bi_vecp:
|
|
GETARG %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $7, %rax # vector tag
|
|
je .cmp_true
|
|
jmp .cmp_false
|
|
|
|
bi_vectolist:
|
|
GETARG %rax
|
|
andq $-8, %rax
|
|
movq (%rax), %rcx # length
|
|
leaq 8(%rax,%rcx,8), %rdi # end pointer
|
|
movq $VAL_NIL, %rsi # acc
|
|
.bv2l_loop:
|
|
testq %rcx, %rcx
|
|
jz .bv2l_done
|
|
subq $8, %rdi
|
|
pushq %rcx
|
|
pushq %rdi
|
|
movq (%rdi), %rdi # element
|
|
call make_pair
|
|
movq %rax, %rsi
|
|
popq %rdi
|
|
popq %rcx
|
|
decq %rcx
|
|
jmp .bv2l_loop
|
|
.bv2l_done:
|
|
movq %rsi, %rax
|
|
RET_VAL
|
|
|
|
bi_listtovec:
|
|
# Count list, then allocate and fill
|
|
GETARG %rdi
|
|
movq %rdi, %rsi # save list
|
|
xorq %rcx, %rcx
|
|
movq %rdi, %rax
|
|
.bl2v_count:
|
|
cmpq $VAL_NIL, %rax
|
|
je .bl2v_alloc
|
|
incq %rcx
|
|
movq %rax, %rdx
|
|
andq $-8, %rdx
|
|
movq 8(%rdx), %rax
|
|
jmp .bl2v_count
|
|
.bl2v_alloc:
|
|
movq %r15, %rax
|
|
movq %rcx, (%r15)
|
|
leaq 8(%r15,%rcx,8), %r15
|
|
leaq 8(%rax), %rdi
|
|
movq %rsi, %rdx
|
|
.bl2v_fill:
|
|
cmpq $VAL_NIL, %rdx
|
|
je .bl2v_done
|
|
movq %rdx, %rcx
|
|
andq $-8, %rcx
|
|
movq (%rcx), %rsi
|
|
movq %rsi, (%rdi)
|
|
addq $8, %rdi
|
|
movq 8(%rcx), %rdx
|
|
jmp .bl2v_fill
|
|
.bl2v_done:
|
|
orq $7, %rax
|
|
RET_VAL
|
|
|
|
bi_substr:
|
|
# (substring s start end)
|
|
GETARG %rax # string
|
|
andq $-8, %rax
|
|
movq %rax, %rdi # string ptr
|
|
GETARG %rax # start
|
|
sarq $3, %rax
|
|
movq %rax, %rcx # start
|
|
GETARG %rax # end
|
|
sarq $3, %rax
|
|
subq %rcx, %rax # length = end - start
|
|
movq %r15, %rdx # result
|
|
movq %rax, (%r15) # length
|
|
leaq 8(%r15), %rsi # dest
|
|
leaq 8(%rdi,%rcx,1), %rdi # src
|
|
movq %rax, %rcx
|
|
.bsub_copy:
|
|
testq %rcx, %rcx
|
|
jz .bsub_done
|
|
movb (%rdi), %al
|
|
movb %al, (%rsi)
|
|
incq %rdi
|
|
incq %rsi
|
|
decq %rcx
|
|
jmp .bsub_copy
|
|
.bsub_done:
|
|
movq (%rdx), %rax # length
|
|
addq $8, %rax
|
|
addq $7, %rax
|
|
andq $-8, %rax
|
|
addq %rax, %r15
|
|
movq %rdx, %rax
|
|
orq $TAG_STRING, %rax
|
|
RET_VAL
|
|
|
|
# ============================================================
|
|
# Portal: save/resume machine state to/from file
|
|
# Binary format: magic(8) + heap_size(8) + heap_base(8) +
|
|
# r14(8) + r15(8) + reserved(8) + heap_bytes
|
|
# Carry on USB to air-gapped machine. Resume from exact state.
|
|
# ============================================================
|
|
|
|
bi_portal_save:
|
|
# (portal-save "filename") — dump heap + state to file
|
|
GETARG %rdi # filename (string value)
|
|
andq $-8, %rdi # untag
|
|
movq (%rdi), %rcx # string length
|
|
leaq 8(%rdi), %rdi # string bytes
|
|
|
|
# Need null-terminated filename for sys_open
|
|
# Copy to stack
|
|
subq $256, %rsp
|
|
movq %rsp, %rsi # dest
|
|
pushq %rcx
|
|
.ps_copy_name:
|
|
testq %rcx, %rcx
|
|
jz .ps_name_done
|
|
movb (%rdi), %al
|
|
movb %al, (%rsi)
|
|
incq %rdi
|
|
incq %rsi
|
|
decq %rcx
|
|
jmp .ps_copy_name
|
|
.ps_name_done:
|
|
movb $0, (%rsi) # null terminate
|
|
popq %rcx
|
|
|
|
# Open file for writing
|
|
movq $SYS_OPEN, %rax
|
|
movq %rsp, %rdi # filename on stack
|
|
movq $(O_WRONLY | O_CREAT | O_TRUNC), %rsi
|
|
movq $0644, %rdx # mode
|
|
syscall
|
|
addq $256, %rsp # restore stack
|
|
testq %rax, %rax
|
|
js .ps_fail
|
|
movq %rax, %rbx # fd
|
|
|
|
# Write header to stack, then write it
|
|
subq $PORTAL_HDR_SIZE, %rsp
|
|
|
|
# Magic
|
|
leaq portal_magic(%rip), %rsi
|
|
movq (%rsi), %rax
|
|
movq %rax, (%rsp)
|
|
|
|
# Heap size = r15 - heap_base
|
|
movq heap_base(%rip), %rax
|
|
movq %r15, %rcx
|
|
subq %rax, %rcx # heap used bytes
|
|
movq %rcx, 8(%rsp) # heap_size
|
|
|
|
# Heap base
|
|
movq %rax, 16(%rsp) # heap_base
|
|
|
|
# r14 (global env)
|
|
movq %r14, 24(%rsp)
|
|
|
|
# r15 (bump pointer)
|
|
movq %r15, 32(%rsp)
|
|
|
|
# Reserved
|
|
movq $0, 40(%rsp)
|
|
|
|
# Write header
|
|
movq $SYS_WRITE, %rax
|
|
movq %rbx, %rdi # fd
|
|
movq %rsp, %rsi # header
|
|
movq $PORTAL_HDR_SIZE, %rdx
|
|
syscall
|
|
|
|
addq $PORTAL_HDR_SIZE, %rsp
|
|
|
|
# Write heap
|
|
movq $SYS_WRITE, %rax
|
|
movq %rbx, %rdi # fd
|
|
movq heap_base(%rip), %rsi # heap start
|
|
movq %r15, %rdx
|
|
subq %rsi, %rdx # heap used bytes
|
|
syscall
|
|
|
|
# Close
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
.ps_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
bi_portal_resume:
|
|
# (portal-resume "filename") — restore heap + state from file
|
|
GETARG %rdi # filename string
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rcx
|
|
leaq 8(%rdi), %rdi
|
|
|
|
# Copy filename to stack (null-terminated)
|
|
subq $256, %rsp
|
|
movq %rsp, %rsi
|
|
pushq %rcx
|
|
.pr_copy_name:
|
|
testq %rcx, %rcx
|
|
jz .pr_name_done
|
|
movb (%rdi), %al
|
|
movb %al, (%rsi)
|
|
incq %rdi
|
|
incq %rsi
|
|
decq %rcx
|
|
jmp .pr_copy_name
|
|
.pr_name_done:
|
|
movb $0, (%rsi)
|
|
popq %rcx
|
|
|
|
# Open file for reading
|
|
movq $SYS_OPEN, %rax
|
|
movq %rsp, %rdi
|
|
movq $O_RDONLY, %rsi
|
|
xorq %rdx, %rdx
|
|
syscall
|
|
addq $256, %rsp
|
|
testq %rax, %rax
|
|
js .pr_fail
|
|
movq %rax, %rbx # fd
|
|
|
|
# Read header — must get full PORTAL_HDR_SIZE bytes
|
|
subq $PORTAL_HDR_SIZE, %rsp
|
|
movq $SYS_READ, %rax
|
|
movq %rbx, %rdi
|
|
movq %rsp, %rsi
|
|
movq $PORTAL_HDR_SIZE, %rdx
|
|
syscall
|
|
cmpq $PORTAL_HDR_SIZE, %rax
|
|
jne .pr_bad_magic # short read → file is not a portal
|
|
|
|
# Verify magic
|
|
leaq portal_magic(%rip), %rdi
|
|
movq (%rsp), %rax
|
|
cmpq (%rdi), %rax
|
|
jne .pr_bad_magic
|
|
|
|
# Sanity check header fields before committing
|
|
movq 8(%rsp), %rcx # heap_size
|
|
testq %rcx, %rcx
|
|
jle .pr_bad_magic # zero or negative heap size → corrupt
|
|
movq 16(%rsp), %rdx # heap_base (must match our mmap)
|
|
testq %rdx, %rdx
|
|
jz .pr_bad_magic # null heap base → corrupt
|
|
|
|
# Header looks valid — commit to r14/r15 restore
|
|
movq 24(%rsp), %r14 # restore global env
|
|
movq 32(%rsp), %r15 # restore bump pointer
|
|
|
|
addq $PORTAL_HDR_SIZE, %rsp
|
|
|
|
# Remap heap at the saved base address so pointers are valid
|
|
pushq %rcx # save heap_size
|
|
pushq %rbx # save fd
|
|
movq $SYS_MMAP, %rax
|
|
movq %rdx, %rdi # saved heap_base as fixed addr
|
|
movq $HEAP_SIZE, %rsi # full heap size
|
|
movq $3, %rdx # PROT_READ|PROT_WRITE
|
|
movq $0x32, %r10 # MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED
|
|
movq $-1, %r8
|
|
xorq %r9, %r9
|
|
syscall
|
|
movq %rax, heap_base(%rip) # update heap_base
|
|
popq %rbx # restore fd
|
|
popq %rcx # restore heap_size
|
|
|
|
# Read heap data into the fixed-address heap
|
|
movq $SYS_READ, %rax
|
|
movq %rbx, %rdi # fd
|
|
movq heap_base(%rip), %rsi # heap at saved address
|
|
movq %rcx, %rdx # heap_size bytes
|
|
syscall
|
|
|
|
# Close
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
|
|
movq $VAL_TRUE, %rax
|
|
RET_VAL
|
|
|
|
.pr_bad_magic:
|
|
addq $PORTAL_HDR_SIZE, %rsp
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
.pr_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# ============================================================
|
|
# bi_load: (load "path") — read file, eval every form in r14 env
|
|
# Mmaps file into memory, swaps input_buf_ptr, loops scheme_read+eval,
|
|
# restores input state. Nestable — prior state saved on stack.
|
|
# ============================================================
|
|
bi_load:
|
|
GETARG %rdi # filename (string value)
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rcx # length
|
|
leaq 8(%rdi), %rdi # bytes
|
|
|
|
# Null-terminate filename on stack (256-byte slot)
|
|
subq $256, %rsp
|
|
movq %rsp, %rsi
|
|
pushq %rcx
|
|
.ld_copy:
|
|
testq %rcx, %rcx
|
|
jz .ld_copy_done
|
|
movb (%rdi), %al
|
|
movb %al, (%rsi)
|
|
incq %rdi
|
|
incq %rsi
|
|
decq %rcx
|
|
jmp .ld_copy
|
|
.ld_copy_done:
|
|
movb $0, (%rsi)
|
|
popq %rcx
|
|
|
|
# Open file
|
|
movq $SYS_OPEN, %rax
|
|
movq %rsp, %rdi
|
|
movq $O_RDONLY, %rsi
|
|
xorq %rdx, %rdx
|
|
syscall
|
|
addq $256, %rsp
|
|
testq %rax, %rax
|
|
js .ld_fail
|
|
movq %rax, %rbx # fd
|
|
|
|
# Size via lseek(fd, 0, SEEK_END)
|
|
movq $SYS_LSEEK, %rax
|
|
movq %rbx, %rdi
|
|
xorq %rsi, %rsi
|
|
movq $SEEK_END, %rdx
|
|
syscall
|
|
testq %rax, %rax
|
|
js .ld_close_fail
|
|
pushq %rax # save size at 0(%rsp)
|
|
|
|
# Empty file — nothing to eval, just close and return
|
|
testq %rax, %rax
|
|
jz .ld_empty
|
|
|
|
# Rewind: lseek(fd, 0, SEEK_SET)
|
|
movq $SYS_LSEEK, %rax
|
|
movq %rbx, %rdi
|
|
xorq %rsi, %rsi
|
|
movq $SEEK_SET, %rdx
|
|
syscall
|
|
|
|
# mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)
|
|
movq $SYS_MMAP, %rax
|
|
xorq %rdi, %rdi
|
|
movq 0(%rsp), %rsi # size
|
|
movq $1, %rdx # PROT_READ
|
|
movq $0x02, %r10 # MAP_PRIVATE
|
|
movq %rbx, %r8 # fd
|
|
xorq %r9, %r9 # offset
|
|
syscall
|
|
cmpq $-1, %rax
|
|
je .ld_mmap_fail
|
|
movq %rax, %rbp # mmap addr
|
|
|
|
# Close fd — mmap keeps page mapping
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
|
|
# Save current input state
|
|
movq input_buf_ptr(%rip), %rax
|
|
pushq %rax
|
|
movq input_pos(%rip), %rax
|
|
pushq %rax
|
|
movq input_end(%rip), %rax
|
|
pushq %rax
|
|
movq input_is_file(%rip), %rax
|
|
pushq %rax
|
|
# Stack layout now: [is_file][end][pos][buf_ptr][size]
|
|
# 0 8 16 24 32
|
|
|
|
# Install file as new input source
|
|
movq %rbp, input_buf_ptr(%rip)
|
|
movq $0, input_pos(%rip)
|
|
movq 32(%rsp), %rax # size
|
|
movq %rax, input_end(%rip)
|
|
movq $1, input_is_file(%rip)
|
|
|
|
# Loop: scheme_read + eval
|
|
.ld_loop:
|
|
call scheme_read
|
|
testq %rax, %rax
|
|
jz .ld_loop_done
|
|
movq %rax, %rdi # expr
|
|
movq %r14, %rsi # global env
|
|
call eval
|
|
jmp .ld_loop
|
|
|
|
.ld_loop_done:
|
|
# Restore input state
|
|
popq %rax
|
|
movq %rax, input_is_file(%rip)
|
|
popq %rax
|
|
movq %rax, input_end(%rip)
|
|
popq %rax
|
|
movq %rax, input_pos(%rip)
|
|
popq %rax
|
|
movq %rax, input_buf_ptr(%rip)
|
|
|
|
# munmap(addr, size)
|
|
popq %rsi # size
|
|
movq $SYS_MUNMAP, %rax
|
|
movq %rbp, %rdi
|
|
syscall
|
|
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
.ld_empty:
|
|
# File was size 0 — discard size from stack, close, return void
|
|
addq $8, %rsp
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
.ld_mmap_fail:
|
|
addq $8, %rsp # discard size
|
|
.ld_close_fail:
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
.ld_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# ============================================================
|
|
# Ports: output ports encoded as SPECIAL values ≥ PORT_SPECIAL_BASE.
|
|
# fd extraction: (val >> 3) - PORT_SPECIAL_BASE.
|
|
# ============================================================
|
|
|
|
# copy_fname_to_stack: %rdi=str_bytes, %rcx=len, %rsi=dest (256 bytes).
|
|
# Copies + null-terminates; advances registers. Preserves %rbx.
|
|
copy_fname_to_stack:
|
|
.cfs_loop:
|
|
testq %rcx, %rcx
|
|
jz .cfs_done
|
|
movb (%rdi), %al
|
|
movb %al, (%rsi)
|
|
incq %rdi
|
|
incq %rsi
|
|
decq %rcx
|
|
jmp .cfs_loop
|
|
.cfs_done:
|
|
movb $0, (%rsi)
|
|
ret
|
|
|
|
# bi_open_output_file: (open-output-file "path") → port value or #f on fail
|
|
bi_open_output_file:
|
|
GETARG %rdi
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rcx # len
|
|
leaq 8(%rdi), %rdi # bytes
|
|
|
|
subq $256, %rsp
|
|
movq %rsp, %rsi
|
|
call copy_fname_to_stack
|
|
|
|
movq $SYS_OPEN, %rax
|
|
movq %rsp, %rdi
|
|
movq $(O_WRONLY | O_CREAT | O_TRUNC), %rsi
|
|
movq $0644, %rdx
|
|
syscall
|
|
addq $256, %rsp
|
|
|
|
testq %rax, %rax
|
|
js .bof_fail
|
|
# Encode port: ((PORT_SPECIAL_BASE + fd) << 3) | TAG_SPECIAL
|
|
addq $PORT_SPECIAL_BASE, %rax
|
|
shlq $3, %rax
|
|
orq $TAG_SPECIAL, %rax
|
|
RET_VAL
|
|
.bof_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# bi_close_port: (close-port port) → void
|
|
bi_close_port:
|
|
GETARG %rax
|
|
movq %rax, %rcx
|
|
andq $TAG_MASK, %rcx
|
|
cmpq $TAG_SPECIAL, %rcx
|
|
jne .bcp_done
|
|
shrq $3, %rax
|
|
cmpq $PORT_SPECIAL_BASE, %rax
|
|
jl .bcp_done
|
|
subq $PORT_SPECIAL_BASE, %rax
|
|
movq %rax, %rdi # fd
|
|
movq $SYS_CLOSE, %rax
|
|
syscall
|
|
.bcp_done:
|
|
movq $VAL_VOID, %rax
|
|
RET_VAL
|
|
|
|
# bi_portp: (port? x) → #t or #f
|
|
bi_portp:
|
|
GETARG %rax
|
|
movq %rax, %rcx
|
|
andq $TAG_MASK, %rcx
|
|
cmpq $TAG_SPECIAL, %rcx
|
|
jne .bpp_false
|
|
shrq $3, %rax
|
|
cmpq $PORT_SPECIAL_BASE, %rax
|
|
jl .bpp_false
|
|
movq $VAL_TRUE, %rax
|
|
RET_VAL
|
|
.bpp_false:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# bi_write_file: (write-file "path" "content") → #t or #f
|
|
bi_write_file:
|
|
GETARG %rdi
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rcx # filename len
|
|
leaq 8(%rdi), %rdi # filename bytes
|
|
|
|
subq $256, %rsp
|
|
movq %rsp, %rsi
|
|
call copy_fname_to_stack
|
|
|
|
# Open for writing (truncate)
|
|
movq $SYS_OPEN, %rax
|
|
movq %rsp, %rdi
|
|
movq $(O_WRONLY | O_CREAT | O_TRUNC), %rsi
|
|
movq $0644, %rdx
|
|
syscall
|
|
addq $256, %rsp
|
|
testq %rax, %rax
|
|
js .bwf_fail
|
|
movq %rax, %rbx # fd
|
|
|
|
# Second arg: content string
|
|
GETARG %rdi
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rdx # length
|
|
leaq 8(%rdi), %rsi # bytes
|
|
movq $SYS_WRITE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
|
|
movq $VAL_TRUE, %rax
|
|
RET_VAL
|
|
.bwf_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# bi_file_to_string: (file->string "path") → string or #f
|
|
# Reads whole file into a heap-allocated string object.
|
|
bi_file_to_string:
|
|
GETARG %rdi
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rcx # filename len
|
|
leaq 8(%rdi), %rdi # filename bytes
|
|
|
|
subq $256, %rsp
|
|
movq %rsp, %rsi
|
|
call copy_fname_to_stack
|
|
|
|
movq $SYS_OPEN, %rax
|
|
movq %rsp, %rdi
|
|
movq $O_RDONLY, %rsi
|
|
xorq %rdx, %rdx
|
|
syscall
|
|
addq $256, %rsp
|
|
testq %rax, %rax
|
|
js .bfs_fail
|
|
movq %rax, %rbx # fd
|
|
|
|
# Size via lseek(fd, 0, SEEK_END)
|
|
movq $SYS_LSEEK, %rax
|
|
movq %rbx, %rdi
|
|
xorq %rsi, %rsi
|
|
movq $SEEK_END, %rdx
|
|
syscall
|
|
movq %rax, %rbp # file size
|
|
testq %rax, %rax
|
|
js .bfs_close_fail
|
|
|
|
# Rewind
|
|
movq $SYS_LSEEK, %rax
|
|
movq %rbx, %rdi
|
|
xorq %rsi, %rsi
|
|
movq $SEEK_SET, %rdx
|
|
syscall
|
|
|
|
# Allocate string cell [length | bytes...] on heap
|
|
# Heap object = 8 (length) + size bytes, aligned to 8
|
|
movq %rbp, %rdi
|
|
addq $8, %rdi
|
|
call heap_alloc # %rax = ptr
|
|
movq %rax, %r12 # save heap pointer
|
|
movq %rbp, (%r12) # store length
|
|
|
|
# Read file bytes into cell
|
|
leaq 8(%r12), %rsi
|
|
movq %rbp, %rdx
|
|
movq $SYS_READ, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
|
|
# Tag and return
|
|
movq %r12, %rax
|
|
orq $TAG_STRING, %rax
|
|
RET_VAL
|
|
|
|
.bfs_close_fail:
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
.bfs_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# ============================================================
|
|
# TCP sockets: fd encoded as port (same as file ports).
|
|
# tcp-recv = sys_read, tcp-send = sys_write, tcp-close = close-port.
|
|
# ============================================================
|
|
|
|
# encode_port: %rdi = fd → %rax = port value
|
|
encode_port:
|
|
movq %rdi, %rax
|
|
addq $PORT_SPECIAL_BASE, %rax
|
|
shlq $3, %rax
|
|
orq $TAG_SPECIAL, %rax
|
|
ret
|
|
|
|
# decode_port: %rdi = port value → %rax = fd (or -1 if not a port)
|
|
decode_port:
|
|
movq %rdi, %rax
|
|
andq $TAG_MASK, %rax
|
|
cmpq $TAG_SPECIAL, %rax
|
|
jne .dp_bad
|
|
movq %rdi, %rax
|
|
shrq $3, %rax
|
|
cmpq $PORT_SPECIAL_BASE, %rax
|
|
jl .dp_bad
|
|
subq $PORT_SPECIAL_BASE, %rax
|
|
ret
|
|
.dp_bad:
|
|
movq $-1, %rax
|
|
ret
|
|
|
|
# bi_tcp_listen: (tcp-listen port) → port or #f
|
|
bi_tcp_listen:
|
|
GETARG %rax
|
|
sarq $3, %rax # untag int → port number
|
|
movq %rax, %rbp # save port
|
|
|
|
# socket(AF_INET, SOCK_STREAM, 0)
|
|
movq $SYS_SOCKET, %rax
|
|
movq $AF_INET, %rdi
|
|
movq $SOCK_STREAM, %rsi
|
|
xorq %rdx, %rdx
|
|
syscall
|
|
testq %rax, %rax
|
|
js .tl_fail
|
|
movq %rax, %rbx # fd
|
|
|
|
# setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, 4)
|
|
subq $16, %rsp
|
|
movl $1, (%rsp)
|
|
movq $SYS_SETSOCKOPT, %rax
|
|
movq %rbx, %rdi
|
|
movq $SOL_SOCKET, %rsi
|
|
movq $SO_REUSEADDR, %rdx
|
|
movq %rsp, %r10
|
|
movq $4, %r8
|
|
syscall
|
|
addq $16, %rsp
|
|
|
|
# Build sockaddr_in on stack (16 bytes):
|
|
# [0:2] sin_family = AF_INET = 2
|
|
# [2:4] sin_port = htons(port)
|
|
# [4:8] sin_addr = 0 (INADDR_ANY)
|
|
# [8:16] padding = 0
|
|
subq $16, %rsp
|
|
movw $AF_INET, (%rsp)
|
|
# htons(port): swap bytes of lower 16 bits
|
|
movq %rbp, %rax
|
|
movw %ax, %cx
|
|
rolw $8, %cx
|
|
movw %cx, 2(%rsp)
|
|
movl $0, 4(%rsp)
|
|
movq $0, 8(%rsp)
|
|
|
|
# bind(fd, &addr, 16)
|
|
movq $SYS_BIND, %rax
|
|
movq %rbx, %rdi
|
|
movq %rsp, %rsi
|
|
movq $16, %rdx
|
|
syscall
|
|
addq $16, %rsp
|
|
testq %rax, %rax
|
|
js .tl_close_fail
|
|
|
|
# listen(fd, 128)
|
|
movq $SYS_LISTEN, %rax
|
|
movq %rbx, %rdi
|
|
movq $128, %rsi
|
|
syscall
|
|
testq %rax, %rax
|
|
js .tl_close_fail
|
|
|
|
movq %rbx, %rdi
|
|
call encode_port
|
|
RET_VAL
|
|
.tl_close_fail:
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
.tl_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# bi_tcp_accept: (tcp-accept server) → port or #f
|
|
bi_tcp_accept:
|
|
GETARG %rdi
|
|
call decode_port
|
|
cmpq $0, %rax
|
|
jl .ta_fail
|
|
movq %rax, %rbx # server fd
|
|
|
|
# accept(fd, &addr, &addrlen); reserve 32 bytes:
|
|
# [0..4] addrlen (in: 16, out: 16)
|
|
# [8..24] sockaddr_in (16 bytes)
|
|
subq $32, %rsp
|
|
movl $16, (%rsp)
|
|
movq $SYS_ACCEPT, %rax
|
|
movq %rbx, %rdi
|
|
leaq 8(%rsp), %rsi # sockaddr buffer (16 bytes)
|
|
movq %rsp, %rdx # addrlen ptr
|
|
syscall
|
|
addq $32, %rsp
|
|
testq %rax, %rax
|
|
js .ta_fail
|
|
movq %rax, %rdi
|
|
call encode_port
|
|
RET_VAL
|
|
.ta_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# bi_tcp_connect: (tcp-connect host port) → port or #f
|
|
# Only supports dotted-quad IPv4 addresses (no DNS).
|
|
bi_tcp_connect:
|
|
GETARG %rdi # host string
|
|
GETARG %rax # port int
|
|
sarq $3, %rax
|
|
movq %rax, %rbp # port number
|
|
|
|
# Parse dotted quad into 4-byte addr on stack
|
|
subq $8, %rsp # buf
|
|
movl $0, (%rsp)
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rcx # length
|
|
leaq 8(%rdi), %rdi # bytes
|
|
xorq %r8, %r8 # octet value
|
|
xorq %r9, %r9 # octet index (0..3)
|
|
.tc_parse:
|
|
testq %rcx, %rcx
|
|
jz .tc_store
|
|
movzbq (%rdi), %rax
|
|
cmpb $'.', %al
|
|
je .tc_dot
|
|
subb $'0', %al
|
|
cmpb $9, %al
|
|
ja .tc_fail_parse
|
|
imulq $10, %r8
|
|
addq %rax, %r8
|
|
incq %rdi
|
|
decq %rcx
|
|
jmp .tc_parse
|
|
.tc_dot:
|
|
movq %r9, %rax
|
|
movb %r8b, (%rsp,%rax)
|
|
incq %r9
|
|
cmpq $4, %r9
|
|
jge .tc_fail_parse
|
|
xorq %r8, %r8
|
|
incq %rdi
|
|
decq %rcx
|
|
jmp .tc_parse
|
|
.tc_store:
|
|
movq %r9, %rax
|
|
movb %r8b, (%rsp,%rax)
|
|
|
|
# socket()
|
|
movq $SYS_SOCKET, %rax
|
|
movq $AF_INET, %rdi
|
|
movq $SOCK_STREAM, %rsi
|
|
xorq %rdx, %rdx
|
|
syscall
|
|
testq %rax, %rax
|
|
js .tc_fail
|
|
movq %rax, %rbx # fd
|
|
|
|
# Build sockaddr_in
|
|
subq $16, %rsp
|
|
movw $AF_INET, (%rsp)
|
|
movq %rbp, %rax
|
|
movw %ax, %cx
|
|
rolw $8, %cx
|
|
movw %cx, 2(%rsp)
|
|
movl 16(%rsp), %eax # the parsed IP (dword at original buf)
|
|
movl %eax, 4(%rsp)
|
|
movq $0, 8(%rsp)
|
|
|
|
movq $SYS_CONNECT, %rax
|
|
movq %rbx, %rdi
|
|
movq %rsp, %rsi
|
|
movq $16, %rdx
|
|
syscall
|
|
addq $16, %rsp
|
|
addq $8, %rsp # discard parse buf
|
|
testq %rax, %rax
|
|
js .tc_close_fail
|
|
|
|
movq %rbx, %rdi
|
|
call encode_port
|
|
RET_VAL
|
|
|
|
.tc_close_fail:
|
|
movq $SYS_CLOSE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
jmp .tc_fail_noparsebuf
|
|
.tc_fail_parse:
|
|
.tc_fail:
|
|
addq $8, %rsp
|
|
.tc_fail_noparsebuf:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# bi_tcp_recv: (tcp-recv sock max) → string or #f
|
|
bi_tcp_recv:
|
|
GETARG %rdi
|
|
call decode_port
|
|
cmpq $0, %rax
|
|
jl .tr_fail
|
|
movq %rax, %rbx # fd
|
|
GETARG %rax # max bytes
|
|
sarq $3, %rax
|
|
cmpq $65536, %rax
|
|
jle .tr_ok
|
|
movq $65536, %rax # cap at 64 KB
|
|
.tr_ok:
|
|
movq %rax, %rbp # size
|
|
|
|
# Allocate string cell: 8-byte length + size bytes
|
|
movq %rbp, %rdi
|
|
addq $8, %rdi
|
|
call heap_alloc
|
|
movq %rax, %r12 # string object base
|
|
|
|
# read(fd, cell+8, size)
|
|
movq $SYS_READ, %rax
|
|
movq %rbx, %rdi
|
|
leaq 8(%r12), %rsi
|
|
movq %rbp, %rdx
|
|
syscall
|
|
testq %rax, %rax
|
|
js .tr_fail
|
|
movq %rax, (%r12) # actual length
|
|
|
|
movq %r12, %rax
|
|
orq $TAG_STRING, %rax
|
|
RET_VAL
|
|
.tr_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# bi_tcp_send: (tcp-send sock string) → int bytes written or #f
|
|
bi_tcp_send:
|
|
GETARG %rdi
|
|
call decode_port
|
|
cmpq $0, %rax
|
|
jl .ts_fail
|
|
movq %rax, %rbx # fd
|
|
|
|
GETARG %rdi # string
|
|
andq $-8, %rdi
|
|
movq (%rdi), %rdx # length
|
|
leaq 8(%rdi), %rsi # bytes
|
|
|
|
movq $SYS_WRITE, %rax
|
|
movq %rbx, %rdi
|
|
syscall
|
|
testq %rax, %rax
|
|
js .ts_fail
|
|
shlq $3, %rax # tag as int
|
|
RET_VAL
|
|
.ts_fail:
|
|
movq $VAL_FALSE, %rax
|
|
RET_VAL
|
|
|
|
# ============================================================
|
|
# list_reverse: %rdi = list -> %rax = reversed list
|
|
# ============================================================
|
|
list_reverse:
|
|
pushq %rbx
|
|
movq $VAL_NIL, %rbx # acc
|
|
.lr_loop:
|
|
cmpq $VAL_NIL, %rdi
|
|
je .lr_done
|
|
movq %rdi, %rax
|
|
andq $-8, %rax
|
|
movq 8(%rax), %rcx # cdr
|
|
movq (%rax), %rdi # car
|
|
pushq %rcx
|
|
movq %rbx, %rsi
|
|
call make_pair
|
|
movq %rax, %rbx
|
|
popq %rdi # continue with cdr
|
|
jmp .lr_loop
|
|
.lr_done:
|
|
movq %rbx, %rax
|
|
popq %rbx
|
|
ret
|
|
|
|
# ============================================================
|
|
# Initialization
|
|
# ============================================================
|
|
|
|
init_special_forms:
|
|
pushq %rbx
|
|
|
|
leaq sf_quote(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_quote_val(%rip)
|
|
|
|
leaq sf_if(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_if_val(%rip)
|
|
|
|
leaq sf_define(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_define_val(%rip)
|
|
|
|
leaq sf_setbang(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_setbang_val(%rip)
|
|
|
|
leaq sf_lambda(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_lambda_val(%rip)
|
|
|
|
leaq sf_begin(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_begin_val(%rip)
|
|
|
|
leaq sf_let(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_let_val(%rip)
|
|
|
|
leaq sf_cond(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_cond_val(%rip)
|
|
|
|
leaq sf_and(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_and_val(%rip)
|
|
|
|
leaq sf_or(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_or_val(%rip)
|
|
|
|
leaq sf_else(%rip), %rdi
|
|
call intern_static
|
|
movq %rax, sym_else_val(%rip)
|
|
|
|
popq %rbx
|
|
ret
|
|
|
|
init_builtins:
|
|
pushq %rbx
|
|
pushq %r12
|
|
|
|
leaq bi_names(%rip), %rbx
|
|
xorq %r12, %r12 # index
|
|
|
|
.ib_loop:
|
|
cmpq $BI_COUNT, %r12
|
|
jge .ib_done
|
|
|
|
movq (%rbx,%r12,8), %rdi # name pointer
|
|
call intern_static
|
|
pushq %rax # save symbol
|
|
|
|
movq %r12, %rdi
|
|
call make_builtin
|
|
movq %rax, %rsi # builtin val
|
|
|
|
popq %rdi # symbol
|
|
movq %r14, %rdx
|
|
call env_define
|
|
movq %rax, %r14
|
|
|
|
incq %r12
|
|
jmp .ib_loop
|
|
|
|
.ib_done:
|
|
popq %r12
|
|
popq %rbx
|
|
ret
|