Created
February 13, 2026 18:02
-
-
Save aydinnyunus/7beef428ca91fb7eb8aa2965086998d8 to your computer and use it in GitHub Desktop.
Vibe-Coding in Assembly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ; Redis-like Key-Value Store in x86-64 Assembly for macOS | |
| ; NASM syntax - Basic Redis server with SET, GET, DEL, PING commands | |
| extern _socket, _bind, _listen, _accept, _read, _write, _close, _exit, _printf, _htons | |
| extern _strcmp, _strlen, _strncmp, _sprintf | |
| section .data | |
| server_msg db "Redis-like server starting on port 6379...", 10 | |
| db "Connect with: redis-cli -p 6379", 10 | |
| db "Supported: PING, SET key value, GET key, DEL key, QUIT", 10, 0 | |
| ; RESP responses | |
| resp_ok db "+OK", 13, 10 | |
| resp_ok_len equ $ - resp_ok | |
| resp_pong db "+PONG", 13, 10 | |
| resp_pong_len equ $ - resp_pong | |
| resp_nil db "$-1", 13, 10 | |
| resp_nil_len equ $ - resp_nil | |
| resp_err_unknown db "-ERR unknown command", 13, 10 | |
| resp_err_unknown_len equ $ - resp_err_unknown | |
| ; Commands | |
| cmd_ping db "PING", 0 | |
| cmd_set db "SET", 0 | |
| cmd_get db "GET", 0 | |
| cmd_del db "DEL", 0 | |
| cmd_quit db "QUIT", 0 | |
| ; Socket address | |
| sockaddr: | |
| sin_family dw 2 | |
| sin_port dw 0 | |
| sin_addr dd 0 | |
| sin_zero dq 0 | |
| sockaddr_len equ $ - sockaddr | |
| ; Format strings (null-terminated!) | |
| fmt_bulk db "$%d", 13, 10, 0 | |
| fmt_int db ":%d", 13, 10, 0 | |
| crlf db 13, 10, 0 | |
| section .bss | |
| sock_fd resq 1 | |
| client_fd resq 1 | |
| buffer resb 8192 | |
| temp_key resb 256 | |
| temp_value resb 1024 | |
| response_buf resb 1024 | |
| saved_read_len resq 1 | |
| ; Simple key-value storage (100 entries) | |
| kv_keys resb 10000 ; Key storage (100 keys * 100 bytes) | |
| kv_values resb 100000 ; Value storage (100 values * 1000 bytes) | |
| kv_count resq 1 ; Number of entries | |
| section .text | |
| global _main | |
| %define AF_INET 2 | |
| %define SOCK_STREAM 1 | |
| %define IPPROTO_TCP 6 | |
| %define PORT 6379 | |
| %define MAX_KEYS 100 | |
| _main: | |
| push rbp | |
| mov rbp, rsp | |
| ; Initialize | |
| mov qword [rel kv_count], 0 | |
| ; Print message | |
| lea rdi, [rel server_msg] | |
| call _printf | |
| ; Setup port | |
| mov rdi, PORT | |
| call _htons | |
| mov [rel sockaddr + 2], ax | |
| ; Create socket | |
| mov rdi, AF_INET | |
| mov rsi, SOCK_STREAM | |
| mov rdx, IPPROTO_TCP | |
| call _socket | |
| cmp rax, 0 | |
| jl error_exit | |
| mov [rel sock_fd], rax | |
| ; Bind | |
| mov rdi, [rel sock_fd] | |
| lea rsi, [rel sockaddr] | |
| mov rdx, sockaddr_len | |
| call _bind | |
| cmp rax, 0 | |
| jl error_exit | |
| ; Listen | |
| mov rdi, [rel sock_fd] | |
| mov rsi, 5 | |
| call _listen | |
| cmp rax, 0 | |
| jl error_exit | |
| server_loop: | |
| ; Accept | |
| mov rdi, [rel sock_fd] | |
| mov rsi, 0 | |
| mov rdx, 0 | |
| call _accept | |
| cmp rax, 0 | |
| jl server_loop | |
| mov [rel client_fd], rax | |
| ; Handle client | |
| call handle_client | |
| ; Close | |
| mov rdi, [rel client_fd] | |
| call _close | |
| jmp server_loop | |
| handle_client: | |
| push rbp | |
| mov rbp, rsp | |
| .client_loop: | |
| ; Read | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel buffer] | |
| mov rdx, 8192 | |
| call _read | |
| cmp rax, 0 | |
| jle .client_done | |
| ; Save read length and null terminate | |
| mov [rel saved_read_len], rax | |
| lea rbx, [rel buffer] | |
| add rbx, rax | |
| mov byte [rbx], 0 | |
| ; Parse and execute | |
| call parse_resp | |
| jmp .client_loop | |
| .client_done: | |
| pop rbp | |
| ret | |
| parse_resp: | |
| push rbp | |
| mov rbp, rsp | |
| ; Check if RESP array (must start with '*') | |
| lea rbx, [rel buffer] | |
| cmp byte [rbx], '*' | |
| jne .parse_done | |
| ; Search for command strings in buffer (case-insensitive) | |
| ; Look for PING | |
| lea rdi, [rel buffer] | |
| mov rsi, [rel saved_read_len] | |
| lea rdx, [rel cmd_ping] | |
| call find_string_in_buffer | |
| cmp rax, 0 | |
| jne .found_ping | |
| ; Look for SET | |
| lea rdi, [rel buffer] | |
| mov rsi, [rel saved_read_len] | |
| lea rdx, [rel cmd_set] | |
| call find_string_in_buffer | |
| cmp rax, 0 | |
| jne .found_set | |
| ; Look for GET | |
| lea rdi, [rel buffer] | |
| mov rsi, [rel saved_read_len] | |
| lea rdx, [rel cmd_get] | |
| call find_string_in_buffer | |
| cmp rax, 0 | |
| jne .found_get | |
| ; Look for DEL | |
| lea rdi, [rel buffer] | |
| mov rsi, [rel saved_read_len] | |
| lea rdx, [rel cmd_del] | |
| call find_string_in_buffer | |
| cmp rax, 0 | |
| jne .found_del | |
| ; Look for QUIT | |
| lea rdi, [rel buffer] | |
| mov rsi, [rel saved_read_len] | |
| lea rdx, [rel cmd_quit] | |
| call find_string_in_buffer | |
| cmp rax, 0 | |
| jne .found_quit | |
| ; Unknown command | |
| jmp .unknown_cmd | |
| .found_ping: | |
| jmp .do_ping | |
| .found_set: | |
| jmp .do_set | |
| .found_get: | |
| jmp .do_get | |
| .found_del: | |
| jmp .do_del | |
| .found_quit: | |
| jmp .do_quit | |
| .unknown_cmd: | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel resp_err_unknown] | |
| mov rdx, resp_err_unknown_len | |
| call _write | |
| jmp .parse_done | |
| .do_ping: | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel resp_pong] | |
| mov rdx, resp_pong_len | |
| call _write | |
| jmp .parse_done | |
| .do_set: | |
| ; Extract key and value from RESP array | |
| call find_resp_command | |
| mov rbx, rax | |
| cmp rbx, 0 | |
| je .parse_done | |
| ; Find end of command string | |
| mov rcx, rbx | |
| .find_set_cmd_end: | |
| cmp byte [rcx], 13 | |
| je .found_set_cmd_end | |
| cmp byte [rcx], 0 | |
| je .parse_done | |
| inc rcx | |
| jmp .find_set_cmd_end | |
| .found_set_cmd_end: | |
| add rcx, 2 ; Skip \r\n | |
| ; Extract key (next bulk string) | |
| mov rdi, rcx | |
| call extract_resp_string_at | |
| cmp rax, 0 | |
| je .parse_done | |
| mov rsi, rax | |
| lea rdi, [rel temp_key] | |
| call copy_until_crlf | |
| ; Extract value (next bulk string after key) | |
| mov rdi, rsi | |
| call find_string_end | |
| mov rdi, rax | |
| call extract_resp_string_at | |
| cmp rax, 0 | |
| je .parse_done | |
| mov rsi, rax | |
| lea rdi, [rel temp_value] | |
| call copy_until_crlf | |
| ; Store key-value | |
| lea rdi, [rel temp_key] | |
| lea rsi, [rel temp_value] | |
| call kv_set | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel resp_ok] | |
| mov rdx, resp_ok_len | |
| call _write | |
| jmp .parse_done | |
| .do_get: | |
| call find_resp_command | |
| mov rbx, rax | |
| cmp rbx, 0 | |
| je .parse_done | |
| mov rcx, rbx | |
| .find_get_cmd_end: | |
| cmp byte [rcx], 13 | |
| je .found_get_cmd_end | |
| cmp byte [rcx], 0 | |
| je .parse_done | |
| inc rcx | |
| jmp .find_get_cmd_end | |
| .found_get_cmd_end: | |
| add rcx, 2 | |
| mov rdi, rcx | |
| call extract_resp_string_at | |
| cmp rax, 0 | |
| je .parse_done | |
| mov rsi, rax | |
| lea rdi, [rel temp_key] | |
| call copy_until_crlf | |
| lea rdi, [rel temp_key] | |
| call kv_get | |
| cmp rax, 0 | |
| je .get_not_found | |
| mov rdi, rax | |
| call send_bulk_string | |
| jmp .parse_done | |
| .get_not_found: | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel resp_nil] | |
| mov rdx, resp_nil_len | |
| call _write | |
| jmp .parse_done | |
| .do_del: | |
| call find_resp_command | |
| mov rbx, rax | |
| cmp rbx, 0 | |
| je .parse_done | |
| mov rcx, rbx | |
| .find_del_cmd_end: | |
| cmp byte [rcx], 13 | |
| je .found_del_cmd_end | |
| cmp byte [rcx], 0 | |
| je .parse_done | |
| inc rcx | |
| jmp .find_del_cmd_end | |
| .found_del_cmd_end: | |
| add rcx, 2 | |
| mov rdi, rcx | |
| call extract_resp_string_at | |
| cmp rax, 0 | |
| je .parse_done | |
| mov rsi, rax | |
| lea rdi, [rel temp_key] | |
| call copy_until_crlf | |
| lea rdi, [rel temp_key] | |
| call kv_del | |
| call send_integer | |
| jmp .parse_done | |
| .do_quit: | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel resp_ok] | |
| mov rdx, resp_ok_len | |
| call _write | |
| jmp .parse_done | |
| .parse_done: | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Find string in buffer (case-insensitive) | |
| ; rdi = buffer, rsi = buffer_len, rdx = search_string | |
| ; Returns: pointer to match or 0 if not found | |
| ; ============================================================ | |
| find_string_in_buffer: | |
| push rbp | |
| mov rbp, rsp | |
| push rbx | |
| push r12 | |
| push r13 | |
| push r14 | |
| push r15 | |
| mov r12, rdi ; buffer pointer (callee-saved) | |
| mov r13, rdx ; search string (callee-saved) | |
| mov r14, rsi ; buffer length (callee-saved) | |
| mov r15, 0 ; position (callee-saved) | |
| ; Get search string length once | |
| mov rdi, r13 | |
| call _strlen | |
| mov rbx, rax ; search string length (callee-saved) | |
| .find_str_loop: | |
| cmp r15, r14 | |
| jge .find_str_not_found | |
| ; Check if we have enough bytes left | |
| mov rax, r14 | |
| sub rax, r15 | |
| cmp rax, rbx | |
| jl .find_str_not_found | |
| ; Compare strings (case-insensitive) | |
| lea rdi, [r12 + r15] | |
| mov rsi, r13 | |
| mov rdx, rbx | |
| call compare_strings_case_insensitive | |
| cmp rax, 0 | |
| je .find_str_found | |
| inc r15 | |
| jmp .find_str_loop | |
| .find_str_found: | |
| lea rax, [r12 + r15] | |
| jmp .find_str_done | |
| .find_str_not_found: | |
| mov rax, 0 | |
| .find_str_done: | |
| pop r15 | |
| pop r14 | |
| pop r13 | |
| pop r12 | |
| pop rbx | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Compare strings case-insensitively | |
| ; rdi = str1, rsi = str2, rdx = length | |
| ; Returns: 0 if equal, 1 if not | |
| ; ============================================================ | |
| compare_strings_case_insensitive: | |
| push rbp | |
| mov rbp, rsp | |
| mov r8, rdx ; length | |
| mov r9, 0 ; index | |
| .cmp_loop: | |
| cmp r9, r8 | |
| jge .cmp_equal | |
| movzx eax, byte [rdi + r9] | |
| movzx ecx, byte [rsi + r9] | |
| ; Convert to uppercase | |
| cmp al, 'a' | |
| jl .al_ok | |
| cmp al, 'z' | |
| jg .al_ok | |
| sub al, 32 | |
| .al_ok: | |
| cmp cl, 'a' | |
| jl .cl_ok | |
| cmp cl, 'z' | |
| jg .cl_ok | |
| sub cl, 32 | |
| .cl_ok: | |
| cmp al, cl | |
| jne .cmp_not_equal | |
| inc r9 | |
| jmp .cmp_loop | |
| .cmp_equal: | |
| mov rax, 0 | |
| jmp .cmp_done | |
| .cmp_not_equal: | |
| mov rax, 1 | |
| .cmp_done: | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Find command in RESP array | |
| ; Format: *N\r\n$len\r\ncommand\r\n... | |
| ; Returns: pointer to command string or 0 | |
| ; ============================================================ | |
| find_resp_command: | |
| push rbp | |
| mov rbp, rsp | |
| lea rbx, [rel buffer] | |
| cmp byte [rbx], '*' | |
| jne .not_found_cmd | |
| inc rbx | |
| ; Skip array count digits | |
| .skip_count: | |
| mov al, [rbx] | |
| cmp al, 13 | |
| je .check_crlf_count | |
| inc rbx | |
| jmp .skip_count | |
| .check_crlf_count: | |
| inc rbx ; skip \r | |
| inc rbx ; skip \n | |
| ; Now at first '$' | |
| cmp byte [rbx], '$' | |
| jne .not_found_cmd | |
| inc rbx ; skip '$' | |
| ; Skip length digits | |
| .skip_length: | |
| mov al, [rbx] | |
| cmp al, 13 | |
| je .check_crlf_len | |
| inc rbx | |
| jmp .skip_length | |
| .check_crlf_len: | |
| inc rbx ; skip \r | |
| inc rbx ; skip \n | |
| mov rax, rbx ; pointer to command string | |
| jmp .find_cmd_done | |
| .not_found_cmd: | |
| mov rax, 0 | |
| .find_cmd_done: | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Extract RESP string at given position | |
| ; rdi = position in buffer (should point to '$') | |
| ; Returns: pointer to string data or 0 | |
| ; ============================================================ | |
| extract_resp_string_at: | |
| push rbp | |
| mov rbp, rsp | |
| mov rbx, rdi | |
| cmp byte [rbx], '$' | |
| jne .not_found_str | |
| inc rbx ; skip '$' | |
| ; Skip length digits | |
| .skip_len: | |
| mov al, [rbx] | |
| cmp al, 13 | |
| je .check_crlf_str | |
| inc rbx | |
| jmp .skip_len | |
| .check_crlf_str: | |
| inc rbx ; skip \r | |
| inc rbx ; skip \n | |
| mov rax, rbx ; pointer to string data | |
| jmp .extract_done | |
| .not_found_str: | |
| mov rax, 0 | |
| .extract_done: | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Find end of string (skip past \r\n) | |
| ; rdi = pointer to start of string | |
| ; Returns: pointer after \r\n | |
| ; ============================================================ | |
| find_string_end: | |
| push rbp | |
| mov rbp, rsp | |
| mov rbx, rdi | |
| .find_end_loop: | |
| mov al, [rbx] | |
| cmp al, 13 | |
| je .found_end | |
| cmp al, 0 | |
| je .found_end_null | |
| inc rbx | |
| jmp .find_end_loop | |
| .found_end: | |
| add rbx, 2 ; skip \r\n | |
| mov rax, rbx | |
| jmp .find_end_done | |
| .found_end_null: | |
| mov rax, rbx | |
| .find_end_done: | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Copy string until \r\n or null | |
| ; rdi = destination, rsi = source | |
| ; ============================================================ | |
| copy_until_crlf: | |
| push rbp | |
| mov rbp, rsp | |
| mov rbx, rsi | |
| mov rcx, rdi | |
| .copy_loop: | |
| mov al, [rbx] | |
| cmp al, 13 | |
| je .copy_done | |
| cmp al, 0 | |
| je .copy_done | |
| mov [rcx], al | |
| inc rbx | |
| inc rcx | |
| jmp .copy_loop | |
| .copy_done: | |
| mov byte [rcx], 0 | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Key-value store operations | |
| ; ============================================================ | |
| kv_set: | |
| push rbp | |
| mov rbp, rsp | |
| push r12 | |
| push r13 | |
| mov r12, rdi ; key | |
| mov r13, rsi ; value | |
| ; Check if key already exists | |
| mov rdi, r12 | |
| call kv_find | |
| cmp rax, -1 | |
| jne .kv_update | |
| ; New key | |
| mov rbx, [rel kv_count] | |
| cmp rbx, MAX_KEYS | |
| jge .kv_set_done | |
| ; Copy key to storage | |
| lea r8, [rel kv_keys] | |
| mov rax, rbx | |
| mov r9, 100 ; KEY_SIZE | |
| mul r9 | |
| add r8, rax | |
| mov rdi, r8 | |
| mov rsi, r12 | |
| call copy_string_simple | |
| ; Copy value to storage | |
| lea r8, [rel kv_values] | |
| mov rax, rbx | |
| mov r9, 1000 ; VALUE_SIZE | |
| mul r9 | |
| add r8, rax | |
| mov rdi, r8 | |
| mov rsi, r13 | |
| call copy_string_simple | |
| ; Increment count | |
| inc qword [rel kv_count] | |
| jmp .kv_set_done | |
| .kv_update: | |
| ; Update existing value | |
| lea r8, [rel kv_values] | |
| mov r9, 1000 | |
| mul r9 | |
| add r8, rax | |
| mov rdi, r8 | |
| mov rsi, r13 | |
| call copy_string_simple | |
| .kv_set_done: | |
| pop r13 | |
| pop r12 | |
| pop rbp | |
| ret | |
| copy_string_simple: | |
| push rbp | |
| mov rbp, rsp | |
| mov rcx, rdi | |
| mov rdx, rsi | |
| .copy_simple_loop: | |
| mov al, [rdx] | |
| cmp al, 0 | |
| je .copy_simple_done | |
| mov [rcx], al | |
| inc rcx | |
| inc rdx | |
| jmp .copy_simple_loop | |
| .copy_simple_done: | |
| mov byte [rcx], 0 | |
| pop rbp | |
| ret | |
| kv_get: | |
| push rbp | |
| mov rbp, rsp | |
| call kv_find | |
| cmp rax, -1 | |
| je .kv_get_not_found | |
| ; Return pointer to value | |
| lea r8, [rel kv_values] | |
| mov rbx, rax | |
| mov r9, 1000 | |
| mov rax, rbx | |
| mul r9 | |
| add r8, rax | |
| mov rax, r8 | |
| jmp .kv_get_done | |
| .kv_get_not_found: | |
| mov rax, 0 | |
| .kv_get_done: | |
| pop rbp | |
| ret | |
| kv_del: | |
| push rbp | |
| mov rbp, rsp | |
| push r12 | |
| call kv_find | |
| cmp rax, -1 | |
| je .kv_del_not_found | |
| ; Delete by shifting entries down | |
| mov r12, rax ; index to delete | |
| mov rbx, [rel kv_count] | |
| dec rbx ; new count | |
| ; Clear the key (set first byte to 0) | |
| lea r8, [rel kv_keys] | |
| mov rax, r12 | |
| mov r9, 100 | |
| mul r9 | |
| add r8, rax | |
| mov byte [r8], 0 | |
| ; Clear the value | |
| lea r8, [rel kv_values] | |
| mov rax, r12 | |
| mov r9, 1000 | |
| mul r9 | |
| add r8, rax | |
| mov byte [r8], 0 | |
| mov rax, 1 ; 1 key deleted | |
| jmp .kv_del_done | |
| .kv_del_not_found: | |
| mov rax, 0 | |
| .kv_del_done: | |
| pop r12 | |
| pop rbp | |
| ret | |
| kv_find: | |
| push rbp | |
| mov rbp, rsp | |
| push r12 | |
| mov r12, rdi ; key to find | |
| mov rbx, [rel kv_count] | |
| mov rcx, 0 | |
| .find_loop: | |
| cmp rcx, rbx | |
| jge .find_not_found | |
| push rcx | |
| push rbx | |
| ; Get key pointer | |
| lea r8, [rel kv_keys] | |
| mov rax, rcx | |
| mov r9, 100 | |
| mul r9 | |
| add r8, rax | |
| ; Skip deleted entries (first byte = 0) | |
| cmp byte [r8], 0 | |
| je .find_skip | |
| ; Compare | |
| mov rdi, r8 | |
| mov rsi, r12 | |
| call _strcmp | |
| pop rbx | |
| pop rcx | |
| cmp rax, 0 | |
| je .find_found | |
| inc rcx | |
| jmp .find_loop | |
| .find_skip: | |
| pop rbx | |
| pop rcx | |
| inc rcx | |
| jmp .find_loop | |
| .find_found: | |
| mov rax, rcx | |
| jmp .find_done | |
| .find_not_found: | |
| mov rax, -1 | |
| .find_done: | |
| pop r12 | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Send bulk string: $len\r\nstring\r\n | |
| ; rdi = pointer to null-terminated string | |
| ; ============================================================ | |
| send_bulk_string: | |
| push rbp | |
| mov rbp, rsp | |
| push r12 | |
| mov r12, rdi ; string pointer | |
| ; Get string length | |
| call _strlen | |
| mov rbx, rax | |
| ; Format header: $len\r\n | |
| lea rdi, [rel response_buf] | |
| lea rsi, [rel fmt_bulk] | |
| mov rdx, rbx | |
| call _sprintf | |
| ; Send header | |
| lea rdi, [rel response_buf] | |
| call _strlen | |
| mov rdx, rax | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel response_buf] | |
| call _write | |
| ; Send string content | |
| mov rdi, r12 | |
| call _strlen | |
| mov rdx, rax | |
| mov rdi, [rel client_fd] | |
| mov rsi, r12 | |
| call _write | |
| ; Send trailing \r\n | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel crlf] | |
| mov rdx, 2 | |
| call _write | |
| pop r12 | |
| pop rbp | |
| ret | |
| ; ============================================================ | |
| ; Send integer: :number\r\n | |
| ; rax = integer value | |
| ; ============================================================ | |
| send_integer: | |
| push rbp | |
| mov rbp, rsp | |
| ; Format: :number\r\n | |
| lea rdi, [rel response_buf] | |
| lea rsi, [rel fmt_int] | |
| mov rdx, rax | |
| call _sprintf | |
| ; Send | |
| lea rdi, [rel response_buf] | |
| call _strlen | |
| mov rdx, rax | |
| mov rdi, [rel client_fd] | |
| lea rsi, [rel response_buf] | |
| call _write | |
| pop rbp | |
| ret | |
| error_exit: | |
| mov rdi, 1 | |
| call _exit |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment