hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/aoc-2025/day5.asm
blob: 4f395a0584b39e9e67f836312b9ed801ffba2ace (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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
; Advent of Code 2025 - Day 5

bits 64
default rel

global _start

%define SYS_EXIT   60
%define SYS_READ   0
%define SYS_WRITE  1
%define SYS_OPEN   2
%define SYS_CLOSE  3

%define O_RDONLY   0
%define STDOUT     1

%define RAW_SIZE   1048576  ; 1 MiB scratch for the raw file
%define MAX_RANGES 8192     ; plenty for the ~177 input ranges

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

    newline    db 10

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

section .bss
    raw           resb RAW_SIZE
    starts        resq MAX_RANGES  ; range/merged-interval starts
    ends          resq MAX_RANGES  ; range/merged-interval ends

    raw_len       resq 1
    n_ranges      resq 1  ; ranges parsed in section 1
    n_merged      resq 1  ; disjoint intervals after merge
    phase         resq 1  ; 0 = ranges, 1 = available IDs

    part1         resq 1
    part2         resq 1

    number_buffer resb 32

section .text

_start:
    mov     rax, SYS_OPEN
    mov     rdi, input_path
    mov     rsi, O_RDONLY
    xor     rdx, rdx
    syscall
    mov     r12, rax  ; fd

    xor     r13, r13
read_loop:
    mov     rax, SYS_READ
    mov     rdi, r12
    lea     rsi, [raw + r13]
    mov     rdx, RAW_SIZE
    sub     rdx, r13
    syscall
    cmp     rax, 0
    jle     read_done
    add     r13, rax
    jmp     read_loop
read_done:
    mov     [raw_len], r13

    mov     rax, SYS_CLOSE
    mov     rdi, r12
    syscall

    ; RSI = cursor, R14 = end pointer (both preserved across helper calls)
    lea     rsi, [raw]
    mov     rax, [raw_len]
    lea     r14, [raw + rax]
    mov     qword [phase], 0

scan_line:
    cmp     rsi, r14
    jae     scan_done
    mov     al, [rsi]
    cmp     al, 10  ; LF  -> blank line
    je      got_blank
    cmp     al, 13  ; CR  -> blank line
    je      got_blank

    cmp     qword [phase], 0
    jne     do_avail

    call    parse_u64  ; RAX = start
    mov     rcx, [n_ranges]
    mov     [starts + rcx*8], rax
    inc     rsi        ; skip '-'
    call    parse_u64  ; RAX = end
    mov     rcx, [n_ranges]
    mov     [ends + rcx*8], rax
    inc     qword [n_ranges]
    jmp     skip_eol

do_avail:
    call    parse_u64
    mov     rdi, rax
    call    is_fresh  ; RAX = 1 if fresh
    test    rax, rax
    jz      skip_eol
    inc     qword [part1]
    jmp     skip_eol

got_blank:
    ; the blank line separates the two sections
    cmp     qword [phase], 0
    jne     skip_eol
    call    finalize_ranges  ; sort + merge, compute part2
    mov     qword [phase], 1
    ; fall through

skip_eol:
    cmp     rsi, r14
    jae     scan_done
    mov     al, [rsi]
    inc     rsi
    cmp     al, 10  ; consume through the LF
    jne     skip_eol
    jmp     scan_line

scan_done:
    ; safety: if there was no blank line, still finalize the ranges
    cmp     qword [phase], 0
    jne     print_results
    call    finalize_ranges

print_results:
    mov     rcx, label1
    call    print_string
    mov     rax, [part1]
    call    print_int64
    call    print_newline

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

    mov     rax, SYS_EXIT
    xor     rdi, rdi
    syscall

; parse_u64: read decimal digits at [RSI], advancing RSI past them.
;   Output: RAX = value. Stops on the first non-digit. Clobbers RAX, RDX.
parse_u64:
    xor     rax, rax
.digit:
    mov     dl, [rsi]
    cmp     dl, '0'
    jl      .done
    cmp     dl, '9'
    jg      .done
    sub     dl, '0'
    movzx   rdx, dl
    imul    rax, rax, 10
    add     rax, rdx
    inc     rsi
    jmp     .digit
.done:
    ret

; is_fresh: test membership of an ID in the merged intervals.
;   Input:  RDI = id
;   Output: RAX = 1 if the id lies in any interval, else 0
;   Intervals are sorted ascending and disjoint, so we can stop early.
;   Clobbers: RAX, RCX
is_fresh:
    xor     rcx, rcx
.loop:
    cmp     rcx, [n_merged]
    jge     .no
    mov     rax, [starts + rcx*8]
    cmp     rdi, rax
    jl      .no  ; id below this start -> below all later ones
    mov     rax, [ends + rcx*8]
    cmp     rdi, rax
    jle     .yes  ; start <= id <= end
    inc     rcx
    jmp     .loop
.no:
    xor     eax, eax
    ret
.yes:
    mov     eax, 1
    ret

; finalize_ranges: insertion-sort the ranges by start, merge overlapping /
; touching ones in place, store the disjoint intervals back into starts/ends,
; set n_merged, and accumulate their total size into part2.
;   Clobbers: RAX, RCX, RDX, R8, R9, R10, R11, R12, R13
finalize_ranges:
    mov     r8, 1  ; i
.sort_i:
    cmp     r8, [n_ranges]
    jge     .sorted
    mov     r10, [starts + r8*8]  ; key_s
    mov     r11, [ends + r8*8]    ; key_e
    mov     r9, r8
    dec     r9  ; j = i - 1
.sort_j:
    cmp     r9, 0
    jl      .sort_place
    mov     rax, [starts + r9*8]
    cmp     rax, r10
    jle     .sort_place  ; starts[j] <= key -> in place
    mov     rdx, r9
    inc     rdx                    ; j + 1
    mov     [starts + rdx*8], rax  ; shift start
    mov     rcx, [ends + r9*8]
    mov     [ends + rdx*8], rcx  ; shift end
    dec     r9
    jmp     .sort_j
.sort_place:
    mov     rdx, r9
    inc     rdx
    mov     [starts + rdx*8], r10
    mov     [ends + rdx*8], r11
    inc     r8
    jmp     .sort_i
.sorted:

    cmp     qword [n_ranges], 0
    je      .empty

    mov     r10, [starts]  ; cur_lo
    mov     r11, [ends]    ; cur_hi
    mov     r8, 1          ; i
    xor     r12, r12       ; part2 accumulator
    xor     r13, r13       ; write index
.merge_loop:
    cmp     r8, [n_ranges]
    jge     .flush
    mov     rax, [starts + r8*8]  ; s
    mov     rcx, [ends + r8*8]    ; e
    cmp     rax, r11
    jg      .gap      ; s > cur_hi -> disjoint
    cmp     rcx, r11  ; overlap: cur_hi = max(cur_hi, e)
    jle     .no_extend
    mov     r11, rcx
.no_extend:
    inc     r8
    jmp     .merge_loop
.gap:
    call    .emit
    mov     r10, rax  ; begin new interval
    mov     r11, rcx
    inc     r8
    jmp     .merge_loop
.flush:
    call    .emit
    mov     [n_merged], r13
    mov     [part2], r12
    ret
.empty:
    mov     qword [n_merged], 0
    mov     qword [part2], 0
    ret

; local helper: emit [r10, r11] as merged interval r13, add its size to r12
.emit:
    mov     [starts + r13*8], r10
    mov     [ends + r13*8], r11
    inc     r13
    mov     rdx, r11
    sub     rdx, r10
    inc     rdx  ; end - start + 1
    add     r12, rdx
    ret

print_newline:
    mov     rax, SYS_WRITE
    mov     rdi, STDOUT
    mov     rsi, newline
    mov     rdx, 1
    syscall
    ret

print_int64:
    mov     rdi, number_buffer+31
    mov     byte [rdi], 0
    dec     rdi

    cmp     rax, 0
    jne     .conv

    mov     byte [rdi], '0'
    jmp     .done
.conv:
    mov     rbx, 10
.next:
    xor     rdx, rdx
    div     rbx
    add     dl, '0'
    mov     [rdi], dl
    dec     rdi
    test    rax, rax
    jnz     .next

    inc     rdi
.done:
    mov     rax, SYS_WRITE
    mov     rsi, rdi
    mov     rdi, STDOUT
    mov     rdx, number_buffer+31
    sub     rdx, rsi
    syscall
    ret

print_string:
    push    rcx
    push    rdx
    mov     rdx, 0
.length:
    cmp     byte [rcx+rdx], 0
    je      .submit
    inc     rdx
    jmp     .length
.submit:
    mov     rax, SYS_WRITE
    mov     rdi, STDOUT
    mov     rsi, rcx
    syscall
    pop     rdx
    pop     rcx
    ret