hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/2025/day11.asm
blob: c3cd9308606fd325865a2f483f4e8b10ba198f3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
; Advent of Code 2025 - Day 11

bits 64
default rel

global _start

%include "utils/io.asm"

%define RAW_SIZE   262144
%define NCODES     17576        ; 26^3 possible 3-letter names
%define OUT_CODE   10003        ; encode("out")
%define YOU_CODE   16608        ; encode("you")
%define SVR_CODE   12731        ; encode("svr")
%define DAC_CODE   2030         ; encode("dac")
%define FFT_CODE   3529         ; encode("fft")
%define MAXEDGES   8192

section .data
    input_path db "input/day11.txt", 0

    label1     db "Part 1: ", 0
    label2     db "Part 2: ", 0

section .bss
    raw           resb RAW_SIZE
    adj_head      resq NCODES    ; first child index in `child`
    adj_cnt       resq NCODES    ; number of children
    child         resq MAXEDGES  ; flat child codes
    memo          resq NCODES    ; path counts, -1 = not computed
    blocked       resb NCODES    ; 1 = treat node as a dead end

    raw_len       resq 1
    t1            resq 1
    t2            resq 1

section .text

_start:
    mov     rdi, input_path
    lea     rsi, [raw]
    mov     rdx, RAW_SIZE
    call    read_file
    mov     [raw_len], rax

    ; memo = -1 everywhere
    mov     rax, -1
    lea     rdi, [memo]
    mov     rcx, NCODES
    rep     stosq

    ; parse "name: a b c" adjacency lines
    lea     rsi, [raw]
    mov     rax, [raw_len]
    lea     r13, [raw + rax]   ; end pointer
    xor     r12, r12           ; child write index
parse_line:
    cmp     rsi, r13
    jae     parse_done
    mov     al, [rsi]
    cmp     al, 10
    je      parse_skip
    cmp     al, 13
    je      parse_skip
    call    encode3            ; RAX = node code
    mov     r10, rax
    mov     [adj_head + r10*8], r12
skip_sep:
    mov     al, [rsi]
    cmp     al, ':'
    je      .adv
    cmp     al, ' '
    je      .adv
    jmp     children
.adv:
    inc     rsi
    jmp     skip_sep
children:
    xor     r11, r11           ; child count
child_loop:
    cmp     rsi, r13
    jae     child_done
    mov     al, [rsi]
    cmp     al, 10
    je      child_done
    cmp     al, 13
    je      child_done
    cmp     al, ' '
    je      child_space
    call    encode3
    mov     [child + r12*8], rax
    inc     r12
    inc     r11
    jmp     child_loop
child_space:
    inc     rsi
    jmp     child_loop
child_done:
    mov     [adj_cnt + r10*8], r11
skip_eol:
    cmp     rsi, r13
    jae     parse_done
    mov     al, [rsi]
    inc     rsi
    cmp     al, 10
    jne     skip_eol
    jmp     parse_line
parse_skip:
    inc     rsi
    jmp     parse_line
parse_done:

    ; part 1: paths you -> out
    call    clear_memo
    mov     rdi, YOU_CODE
    call    count
    mov     [t1], rax

    ; part 2: paths svr -> out visiting both dac and fft, via inclusion-exclusion
    call    clear_memo
    mov     rdi, SVR_CODE
    call    count
    mov     r12, rax                   ; N(all)

    mov     byte [blocked + DAC_CODE], 1
    call    clear_memo
    mov     rdi, SVR_CODE
    call    count
    mov     r13, rax                   ; N(avoid dac)
    mov     byte [blocked + DAC_CODE], 0

    mov     byte [blocked + FFT_CODE], 1
    call    clear_memo
    mov     rdi, SVR_CODE
    call    count
    mov     r14, rax                   ; N(avoid fft)
    mov     byte [blocked + FFT_CODE], 0

    mov     byte [blocked + DAC_CODE], 1
    mov     byte [blocked + FFT_CODE], 1
    call    clear_memo
    mov     rdi, SVR_CODE
    call    count                      ; N(avoid both) in rax
    mov     byte [blocked + DAC_CODE], 0
    mov     byte [blocked + FFT_CODE], 0

    sub     r12, r13
    sub     r12, r14
    add     r12, rax
    mov     [t2], r12

    mov     rcx, label1
    call    print_string
    mov     rax, [t1]
    call    print_int64
    call    print_newline

    mov     rcx, label2
    call    print_string
    mov     rax, [t2]
    call    print_int64
    call    print_newline

    mov     rax, SYS_EXIT
    xor     rdi, rdi
    syscall

; clear_memo: reset all memo entries to -1. Clobbers RAX, RCX, RDI.
clear_memo:
    mov     rax, -1
    lea     rdi, [memo]
    mov     rcx, NCODES
    rep     stosq
    ret

; encode3: read 3 lowercase letters at [RSI] into a base-26 code, advance RSI.
;   Output: RAX = code. Clobbers RAX, RDX.
encode3:
    movzx   eax, byte [rsi]
    sub     eax, 'a'
    imul    eax, eax, 676
    movzx   edx, byte [rsi + 1]
    sub     edx, 'a'
    imul    edx, edx, 26
    add     eax, edx
    movzx   edx, byte [rsi + 2]
    sub     edx, 'a'
    add     eax, edx
    add     rsi, 3
    ret

; count: number of paths from node RDI to out. Memoised DFS over the DAG.
;   Output: RAX. Clobbers RAX; preserves everything else via the stack.
count:
    cmp     rdi, OUT_CODE
    je      .one
    cmp     byte [blocked + rdi], 0
    jne     .zero
    mov     rax, [memo + rdi*8]
    cmp     rax, -1
    jne     .ret
    push    r12
    push    r13
    push    r14
    push    r15
    mov     r12, rdi
    mov     r13, [adj_head + r12*8]
    mov     r14, r13
    add     r14, [adj_cnt + r12*8]
    xor     r15, r15
.loop:
    cmp     r13, r14
    jge     .done
    mov     rdi, [child + r13*8]
    call    count
    add     r15, rax
    inc     r13
    jmp     .loop
.done:
    mov     [memo + r12*8], r15
    mov     rax, r15
    pop     r15
    pop     r14
    pop     r13
    pop     r12
    ret
.one:
    mov     rax, 1
    ret
.zero:
    xor     eax, eax
.ret:
    ret