Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/chunk.c
Line
Count
Source
1
#include "chunk.h"
2
#include "stackopcode.h"
3
#include <stdlib.h>
4
#include <stdio.h>
5
#include <string.h>
6
7
30.8k
static size_t chunk_fnv1a(const char *key) {
8
30.8k
    size_t hash = 14695981039346656037ULL;
9
445k
    for (const char *p = key; *p; p++) {
10
414k
        hash ^= (size_t)(unsigned char)*p;
11
414k
        hash *= 1099511628211ULL;
12
414k
    }
13
30.8k
    return hash;
14
30.8k
}
15
16
4.34k
Chunk *chunk_new(void) {
17
4.34k
    Chunk *c = calloc(1, sizeof(Chunk));
18
4.34k
    c->code_cap = 256;
19
4.34k
    c->code = malloc(c->code_cap);
20
4.34k
    c->const_cap = 32;
21
4.34k
    c->constants = malloc(c->const_cap * sizeof(LatValue));
22
4.34k
    c->const_hashes = calloc(c->const_cap, sizeof(size_t));
23
4.34k
    c->lines_cap = 256;
24
4.34k
    c->lines = malloc(c->lines_cap * sizeof(int));
25
4.34k
    return c;
26
4.34k
}
27
28
4.34k
void chunk_free(Chunk *c) {
29
4.34k
    if (!c) return;
30
4.34k
    free(c->code);
31
38.8k
    for (size_t i = 0; i < c->const_len; i++) {
32
        /* Recursively free compiled sub-chunks stored as VAL_CLOSURE constants */
33
34.4k
        if (c->constants[i].type == VAL_CLOSURE &&
34
34.4k
            c->constants[i].as.closure.body == NULL &&
35
34.4k
            c->constants[i].as.closure.native_fn != NULL) {
36
3.36k
            chunk_free((Chunk *)c->constants[i].as.closure.native_fn);
37
3.36k
            c->constants[i].as.closure.native_fn = NULL;
38
3.36k
        }
39
34.4k
        value_free(&c->constants[i]);
40
34.4k
    }
41
4.34k
    free(c->constants);
42
4.34k
    free(c->const_hashes);
43
4.34k
    free(c->lines);
44
29.5k
    for (size_t i = 0; i < c->local_name_cap; i++)
45
25.1k
        free(c->local_names[i]);
46
4.34k
    free(c->local_names);
47
4.34k
    free(c->name);
48
4.34k
    if (c->default_values) {
49
76
        for (int i = 0; i < c->default_count; i++)
50
39
            value_free(&c->default_values[i]);
51
37
        free(c->default_values);
52
37
    }
53
4.34k
    free(c->param_phases);
54
4.34k
    if (c->export_names) {
55
18
        for (size_t i = 0; i < c->export_count; i++)
56
12
            free(c->export_names[i]);
57
6
        free(c->export_names);
58
6
    }
59
4.34k
    free(c);
60
4.34k
}
61
62
8.92k
void chunk_set_local_name(Chunk *c, size_t slot, const char *name) {
63
12.0k
    while (c->local_name_cap <= slot) {
64
3.11k
        size_t new_cap = c->local_name_cap ? c->local_name_cap * 2 : 8;
65
3.11k
        c->local_names = realloc(c->local_names, new_cap * sizeof(char *));
66
28.3k
        for (size_t i = c->local_name_cap; i < new_cap; i++)
67
25.1k
            c->local_names[i] = NULL;
68
3.11k
        c->local_name_cap = new_cap;
69
3.11k
    }
70
8.92k
    free(c->local_names[slot]);
71
8.92k
    c->local_names[slot] = strdup(name);
72
8.92k
}
73
74
221k
size_t chunk_write(Chunk *c, uint8_t byte, int line) {
75
221k
    if (c->code_len >= c->code_cap) {
76
90
        c->code_cap *= 2;
77
90
        c->code = realloc(c->code, c->code_cap);
78
90
    }
79
221k
    if (c->lines_len >= c->lines_cap) {
80
90
        c->lines_cap *= 2;
81
90
        c->lines = realloc(c->lines, c->lines_cap * sizeof(int));
82
90
    }
83
221k
    size_t offset = c->code_len;
84
221k
    c->code[c->code_len++] = byte;
85
221k
    c->lines[c->lines_len++] = line;
86
221k
    return offset;
87
221k
}
88
89
34.4k
size_t chunk_add_constant(Chunk *c, LatValue val) {
90
34.4k
    if (c->const_len >= c->const_cap) {
91
168
        c->const_cap *= 2;
92
168
        c->constants = realloc(c->constants, c->const_cap * sizeof(LatValue));
93
168
        c->const_hashes = realloc(c->const_hashes, c->const_cap * sizeof(size_t));
94
168
    }
95
34.4k
    size_t idx = c->const_len++;
96
34.4k
    c->constants[idx] = val;
97
34.4k
    c->const_hashes[idx] = (val.type == VAL_STR && val.as.str_val) ?
98
30.8k
                            chunk_fnv1a(val.as.str_val) : 0;
99
34.4k
    return idx;
100
34.4k
}
101
102
0
void chunk_write_u16(Chunk *c, uint16_t val, int line) {
103
0
    chunk_write(c, (uint8_t)((val >> 8) & 0xff), line);
104
0
    chunk_write(c, (uint8_t)(val & 0xff), line);
105
0
}
106
107
0
static size_t simple_instruction(const char *name, size_t offset) {
108
0
    fprintf(stderr, "%s\n", name);
109
0
    return offset + 1;
110
0
}
111
112
0
static size_t byte_instruction(const char *name, const Chunk *c, size_t offset) {
113
0
    uint8_t slot = c->code[offset + 1];
114
0
    fprintf(stderr, "%-20s %4d\n", name, slot);
115
0
    return offset + 2;
116
0
}
117
118
0
static size_t constant_instruction(const char *name, const Chunk *c, size_t offset) {
119
0
    uint8_t idx = c->code[offset + 1];
120
0
    fprintf(stderr, "%-20s %4d '", name, idx);
121
0
    if (idx < c->const_len) {
122
0
        char *repr = value_repr(&c->constants[idx]);
123
0
        fprintf(stderr, "%s", repr);
124
0
        free(repr);
125
0
    }
126
0
    fprintf(stderr, "'\n");
127
0
    return offset + 2;
128
0
}
129
130
0
static size_t constant_instruction_16(const char *name, const Chunk *c, size_t offset) {
131
0
    uint16_t idx = (uint16_t)(c->code[offset + 1] << 8) | c->code[offset + 2];
132
0
    fprintf(stderr, "%-20s %4d '", name, idx);
133
0
    if (idx < c->const_len) {
134
0
        char *repr = value_repr(&c->constants[idx]);
135
0
        fprintf(stderr, "%s", repr);
136
0
        free(repr);
137
0
    }
138
0
    fprintf(stderr, "'\n");
139
0
    return offset + 3;
140
0
}
141
142
0
static size_t jump_instruction(const char *name, int sign, const Chunk *c, size_t offset) {
143
0
    uint16_t jump = (uint16_t)(c->code[offset + 1] << 8) | c->code[offset + 2];
144
0
    fprintf(stderr, "%-20s %4zu -> %zu\n", name, offset, offset + 3 + sign * jump);
145
0
    return offset + 3;
146
0
}
147
148
0
static size_t closure_instruction(const Chunk *c, size_t offset) {
149
0
    uint8_t fn_idx = c->code[offset + 1];
150
0
    uint8_t upvalue_count = c->code[offset + 2];
151
0
    fprintf(stderr, "%-20s %4d (upvalues: %d)\n", "OP_CLOSURE", fn_idx, upvalue_count);
152
0
    size_t pos = offset + 3;
153
0
    for (uint8_t i = 0; i < upvalue_count; i++) {
154
0
        uint8_t is_local = c->code[pos++];
155
0
        uint8_t index = c->code[pos++];
156
0
        fprintf(stderr, "     |                     %s %d\n",
157
0
                is_local ? "local" : "upvalue", index);
158
0
    }
159
0
    return pos;
160
0
}
161
162
0
static size_t invoke_instruction(const Chunk *c, size_t offset) {
163
0
    uint8_t method_idx = c->code[offset + 1];
164
0
    uint8_t arg_count = c->code[offset + 2];
165
0
    fprintf(stderr, "%-20s %4d '", "OP_INVOKE", method_idx);
166
0
    if (method_idx < c->const_len) {
167
0
        char *repr = value_repr(&c->constants[method_idx]);
168
0
        fprintf(stderr, "%s", repr);
169
0
        free(repr);
170
0
    }
171
0
    fprintf(stderr, "' (%d args)\n", arg_count);
172
0
    return offset + 3;
173
0
}
174
175
0
static size_t build_struct_instruction(const Chunk *c, size_t offset) {
176
0
    uint8_t name_idx = c->code[offset + 1];
177
0
    uint8_t field_count = c->code[offset + 2];
178
0
    fprintf(stderr, "%-20s %4d '", "OP_BUILD_STRUCT", name_idx);
179
0
    if (name_idx < c->const_len) {
180
0
        char *repr = value_repr(&c->constants[name_idx]);
181
0
        fprintf(stderr, "%s", repr);
182
0
        free(repr);
183
0
    }
184
0
    fprintf(stderr, "' (%d fields)\n", field_count);
185
0
    return offset + 3;
186
0
}
187
188
0
static size_t build_enum_instruction(const Chunk *c, size_t offset) {
189
0
    uint8_t enum_idx = c->code[offset + 1];
190
0
    uint8_t var_idx = c->code[offset + 2];
191
0
    uint8_t payload_count = c->code[offset + 3];
192
0
    fprintf(stderr, "%-20s enum=%d var=%d payload=%d\n",
193
0
            "OP_BUILD_ENUM", enum_idx, var_idx, payload_count);
194
0
    return offset + 4;
195
0
}
196
197
0
size_t chunk_disassemble_instruction(const Chunk *c, size_t offset) {
198
0
    fprintf(stderr, "%04zu ", offset);
199
0
    if (offset > 0 && c->lines[offset] == c->lines[offset - 1])
200
0
        fprintf(stderr, "   | ");
201
0
    else
202
0
        fprintf(stderr, "%4d ", c->lines[offset]);
203
204
0
    uint8_t op = c->code[offset];
205
0
    switch (op) {
206
0
        case OP_CONSTANT:      return constant_instruction("OP_CONSTANT", c, offset);
207
0
        case OP_NIL:           return simple_instruction("OP_NIL", offset);
208
0
        case OP_TRUE:          return simple_instruction("OP_TRUE", offset);
209
0
        case OP_FALSE:         return simple_instruction("OP_FALSE", offset);
210
0
        case OP_UNIT:          return simple_instruction("OP_UNIT", offset);
211
0
        case OP_POP:           return simple_instruction("OP_POP", offset);
212
0
        case OP_DUP:           return simple_instruction("OP_DUP", offset);
213
0
        case OP_SWAP:          return simple_instruction("OP_SWAP", offset);
214
0
        case OP_ADD:           return simple_instruction("OP_ADD", offset);
215
0
        case OP_SUB:           return simple_instruction("OP_SUB", offset);
216
0
        case OP_MUL:           return simple_instruction("OP_MUL", offset);
217
0
        case OP_DIV:           return simple_instruction("OP_DIV", offset);
218
0
        case OP_MOD:           return simple_instruction("OP_MOD", offset);
219
0
        case OP_NEG:           return simple_instruction("OP_NEG", offset);
220
0
        case OP_NOT:           return simple_instruction("OP_NOT", offset);
221
0
        case OP_BIT_AND:       return simple_instruction("OP_BIT_AND", offset);
222
0
        case OP_BIT_OR:        return simple_instruction("OP_BIT_OR", offset);
223
0
        case OP_BIT_XOR:       return simple_instruction("OP_BIT_XOR", offset);
224
0
        case OP_BIT_NOT:       return simple_instruction("OP_BIT_NOT", offset);
225
0
        case OP_LSHIFT:        return simple_instruction("OP_LSHIFT", offset);
226
0
        case OP_RSHIFT:        return simple_instruction("OP_RSHIFT", offset);
227
0
        case OP_EQ:            return simple_instruction("OP_EQ", offset);
228
0
        case OP_NEQ:           return simple_instruction("OP_NEQ", offset);
229
0
        case OP_LT:            return simple_instruction("OP_LT", offset);
230
0
        case OP_GT:            return simple_instruction("OP_GT", offset);
231
0
        case OP_LTEQ:          return simple_instruction("OP_LTEQ", offset);
232
0
        case OP_GTEQ:          return simple_instruction("OP_GTEQ", offset);
233
0
        case OP_CONCAT:        return simple_instruction("OP_CONCAT", offset);
234
0
        case OP_GET_LOCAL:     return byte_instruction("OP_GET_LOCAL", c, offset);
235
0
        case OP_SET_LOCAL:     return byte_instruction("OP_SET_LOCAL", c, offset);
236
0
        case OP_GET_GLOBAL:    return constant_instruction("OP_GET_GLOBAL", c, offset);
237
0
        case OP_SET_GLOBAL:    return constant_instruction("OP_SET_GLOBAL", c, offset);
238
0
        case OP_DEFINE_GLOBAL: return constant_instruction("OP_DEFINE_GLOBAL", c, offset);
239
0
        case OP_GET_UPVALUE:   return byte_instruction("OP_GET_UPVALUE", c, offset);
240
0
        case OP_SET_UPVALUE:   return byte_instruction("OP_SET_UPVALUE", c, offset);
241
0
        case OP_CLOSE_UPVALUE: return simple_instruction("OP_CLOSE_UPVALUE", offset);
242
0
        case OP_JUMP:          return jump_instruction("OP_JUMP", 1, c, offset);
243
0
        case OP_JUMP_IF_FALSE: return jump_instruction("OP_JUMP_IF_FALSE", 1, c, offset);
244
0
        case OP_JUMP_IF_TRUE:  return jump_instruction("OP_JUMP_IF_TRUE", 1, c, offset);
245
0
        case OP_JUMP_IF_NOT_NIL: return jump_instruction("OP_JUMP_IF_NOT_NIL", 1, c, offset);
246
0
        case OP_LOOP:          return jump_instruction("OP_LOOP", -1, c, offset);
247
0
        case OP_CALL:          return byte_instruction("OP_CALL", c, offset);
248
0
        case OP_CLOSURE:       return closure_instruction(c, offset);
249
0
        case OP_RETURN:        return simple_instruction("OP_RETURN", offset);
250
0
        case OP_ITER_INIT:     return simple_instruction("OP_ITER_INIT", offset);
251
0
        case OP_ITER_NEXT:     return jump_instruction("OP_ITER_NEXT", 1, c, offset);
252
0
        case OP_BUILD_ARRAY:   return byte_instruction("OP_BUILD_ARRAY", c, offset);
253
0
        case OP_ARRAY_FLATTEN: return simple_instruction("OP_ARRAY_FLATTEN", offset);
254
0
        case OP_BUILD_MAP:     return byte_instruction("OP_BUILD_MAP", c, offset);
255
0
        case OP_BUILD_TUPLE:   return byte_instruction("OP_BUILD_TUPLE", c, offset);
256
0
        case OP_BUILD_STRUCT:  return build_struct_instruction(c, offset);
257
0
        case OP_BUILD_RANGE:   return simple_instruction("OP_BUILD_RANGE", offset);
258
0
        case OP_BUILD_ENUM:    return build_enum_instruction(c, offset);
259
0
        case OP_INDEX:         return simple_instruction("OP_INDEX", offset);
260
0
        case OP_SET_INDEX:     return simple_instruction("OP_SET_INDEX", offset);
261
0
        case OP_GET_FIELD:     return constant_instruction("OP_GET_FIELD", c, offset);
262
0
        case OP_SET_FIELD:     return constant_instruction("OP_SET_FIELD", c, offset);
263
0
        case OP_INVOKE:        return invoke_instruction(c, offset);
264
0
        case OP_INVOKE_LOCAL: {
265
0
            uint8_t slot = c->code[offset + 1];
266
0
            uint8_t method_idx = c->code[offset + 2];
267
0
            uint8_t argc = c->code[offset + 3];
268
0
            fprintf(stderr, "%-20s slot=%d '", "OP_INVOKE_LOCAL", slot);
269
0
            if (method_idx < c->const_len) {
270
0
                char *repr = value_repr(&c->constants[method_idx]);
271
0
                fprintf(stderr, "%s", repr);
272
0
                free(repr);
273
0
            }
274
0
            fprintf(stderr, "' (%d args)\n", argc);
275
0
            return offset + 4;
276
0
        }
277
0
        case OP_INVOKE_GLOBAL: {
278
0
            uint8_t name_idx = c->code[offset + 1];
279
0
            uint8_t method_idx = c->code[offset + 2];
280
0
            uint8_t argc = c->code[offset + 3];
281
0
            fprintf(stderr, "%-20s '", "OP_INVOKE_GLOBAL");
282
0
            if (name_idx < c->const_len) {
283
0
                char *repr = value_repr(&c->constants[name_idx]);
284
0
                fprintf(stderr, "%s", repr);
285
0
                free(repr);
286
0
            }
287
0
            fprintf(stderr, "'.'");
288
0
            if (method_idx < c->const_len) {
289
0
                char *repr = value_repr(&c->constants[method_idx]);
290
0
                fprintf(stderr, "%s", repr);
291
0
                free(repr);
292
0
            }
293
0
            fprintf(stderr, "' (%d args)\n", argc);
294
0
            return offset + 4;
295
0
        }
296
0
        case OP_SET_INDEX_LOCAL: return byte_instruction("OP_SET_INDEX_LOCAL", c, offset);
297
0
        case OP_PUSH_EXCEPTION_HANDLER: return jump_instruction("OP_PUSH_EXCEPTION_HANDLER", 1, c, offset);
298
0
        case OP_POP_EXCEPTION_HANDLER: return simple_instruction("OP_POP_EXCEPTION_HANDLER", offset);
299
0
        case OP_THROW:         return simple_instruction("OP_THROW", offset);
300
0
        case OP_TRY_UNWRAP:    return simple_instruction("OP_TRY_UNWRAP", offset);
301
0
        case OP_DEFER_PUSH:    return jump_instruction("OP_DEFER_PUSH", 1, c, offset);
302
0
        case OP_DEFER_RUN:     return simple_instruction("OP_DEFER_RUN", offset);
303
0
        case OP_FREEZE:        return simple_instruction("OP_FREEZE", offset);
304
0
        case OP_THAW:          return simple_instruction("OP_THAW", offset);
305
0
        case OP_CLONE:         return simple_instruction("OP_CLONE", offset);
306
0
        case OP_MARK_FLUID:    return simple_instruction("OP_MARK_FLUID", offset);
307
0
        case OP_PRINT:         return byte_instruction("OP_PRINT", c, offset);
308
0
        case OP_IMPORT:        return byte_instruction("OP_IMPORT", c, offset);
309
0
        case OP_SCOPE: {
310
0
            uint8_t spawn_count = c->code[offset + 1];
311
0
            uint8_t sync_idx = c->code[offset + 2];
312
0
            fprintf(stderr, "%-20s spawns=%d sync=%d", "OP_SCOPE", spawn_count, sync_idx);
313
0
            for (uint8_t i = 0; i < spawn_count; i++)
314
0
                fprintf(stderr, " spawn[%d]=%d", i, c->code[offset + 3 + i]);
315
0
            fprintf(stderr, "\n");
316
0
            return offset + 3 + spawn_count;
317
0
        }
318
0
        case OP_SELECT: {
319
0
            uint8_t arm_count = c->code[offset + 1];
320
0
            fprintf(stderr, "%-20s arms=%d\n", "OP_SELECT", arm_count);
321
0
            size_t pos = offset + 2;
322
0
            for (uint8_t i = 0; i < arm_count; i++) {
323
0
                uint8_t flags = c->code[pos++];
324
0
                uint8_t chan_idx = c->code[pos++];
325
0
                uint8_t body_idx = c->code[pos++];
326
0
                uint8_t bind_idx = c->code[pos++];
327
0
                fprintf(stderr, "     |                     arm %d: flags=%02x chan=%d body=%d bind=%d\n",
328
0
                        i, flags, chan_idx, body_idx, bind_idx);
329
0
            }
330
0
            return pos;
331
0
        }
332
0
        case OP_INC_LOCAL:     return byte_instruction("OP_INC_LOCAL", c, offset);
333
0
        case OP_DEC_LOCAL:     return byte_instruction("OP_DEC_LOCAL", c, offset);
334
0
        case OP_ADD_INT:       return simple_instruction("OP_ADD_INT", offset);
335
0
        case OP_SUB_INT:       return simple_instruction("OP_SUB_INT", offset);
336
0
        case OP_MUL_INT:       return simple_instruction("OP_MUL_INT", offset);
337
0
        case OP_LT_INT:        return simple_instruction("OP_LT_INT", offset);
338
0
        case OP_LTEQ_INT:      return simple_instruction("OP_LTEQ_INT", offset);
339
0
        case OP_LOAD_INT8:     return byte_instruction("OP_LOAD_INT8", c, offset);
340
0
        case OP_CONSTANT_16:      return constant_instruction_16("OP_CONSTANT_16", c, offset);
341
0
        case OP_GET_GLOBAL_16:    return constant_instruction_16("OP_GET_GLOBAL_16", c, offset);
342
0
        case OP_SET_GLOBAL_16:    return constant_instruction_16("OP_SET_GLOBAL_16", c, offset);
343
0
        case OP_DEFINE_GLOBAL_16: return constant_instruction_16("OP_DEFINE_GLOBAL_16", c, offset);
344
0
        case OP_CLOSURE_16: {
345
0
            uint16_t idx = (uint16_t)(c->code[offset + 1] << 8) | c->code[offset + 2];
346
0
            uint8_t uvc = c->code[offset + 3];
347
0
            fprintf(stderr, "%-20s %4d (upvalues: %d)\n", "OP_CLOSURE_16", idx, uvc);
348
0
            return offset + 4 + uvc * 2;
349
0
        }
350
0
        case OP_RESET_EPHEMERAL: return simple_instruction("OP_RESET_EPHEMERAL", offset);
351
0
        case OP_SET_LOCAL_POP: return byte_instruction("OP_SET_LOCAL_POP", c, offset);
352
0
        case OP_HALT:          return simple_instruction("OP_HALT", offset);
353
0
        default:
354
0
            fprintf(stderr, "Unknown opcode %d\n", op);
355
0
            return offset + 1;
356
0
    }
357
0
}
358
359
0
void chunk_disassemble(const Chunk *c, const char *name) {
360
0
    fprintf(stderr, "== %s ==\n", name);
361
0
    for (size_t offset = 0; offset < c->code_len; )
362
0
        offset = chunk_disassemble_instruction(c, offset);
363
0
}