ACPI AML decompiler w/ CFG recovery and structured pseudocode
1#include "p.h"
2
3#include "mem.h"
4#include "nm.h"
5#include "op.h"
6#include "rd.h"
7#include "str.h"
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <stdarg.h>
12#include <string.h>
13
14#define MAX_DEPTH 64
15#define MAX_TERMS 200000u
16
17typedef struct parser {
18 tobi_diag_list *diag;
19 int strict;
20 char *scope;
21 tobi_ns *ns;
22 const char *source;
23 size_t terms;
24 size_t input_index;
25 int has_source;
26} parser;
27
28static void diag(parser *p, tobi_diag_level lvl, size_t off, const char *fmt, ...) {
29 va_list ap;
30 va_start(ap, fmt);
31 va_list cp;
32 va_copy(cp, ap);
33 int n = vsnprintf(NULL, 0, fmt, cp);
34 va_end(cp);
35 if (n < 0) {
36 n = 0;
37 }
38 char *msg = tobi_xmalloc((size_t)n + 1);
39 (void)vsnprintf(msg, (size_t)n + 1, fmt, ap);
40 va_end(ap);
41 tobi_diag_add_source(p->diag, lvl, off, p->source, p->input_index, p->has_source, "%s", msg);
42 free(msg);
43}
44
45static uint32_t le32_at(const uint8_t *d) {
46 return (uint32_t)d[0] | ((uint32_t)d[1] << 8) | ((uint32_t)d[2] << 16) | ((uint32_t)d[3] << 24);
47}
48
49static void copy_trim(char *dst, size_t dst_len, const uint8_t *src, size_t src_len) {
50 size_t n = src_len;
51 while (n > 0 && src[n - 1u] == ' ') {
52 n--;
53 }
54 if (n >= dst_len) {
55 n = dst_len - 1u;
56 }
57 memcpy(dst, src, n);
58 dst[n] = '\0';
59}
60
61static uint32_t hash32(const uint8_t *data, size_t len) {
62 uint32_t h = 2166136261u;
63 for (size_t i = 0; i < len; i++) {
64 h ^= data[i];
65 h *= 16777619u;
66 }
67 return h;
68}
69
70static int is_table_sig(const uint8_t *d) {
71 return memcmp(d, "DSDT", 4) == 0 || memcmp(d, "SSDT", 4) == 0;
72}
73
74static void input_meta_set_source(tobi_input_meta *meta, const char *source) {
75 free(meta->source);
76 meta->source = tobi_xstrdup(source && source[0] ? source : "raw");
77}
78
79static void input_meta_copy(tobi_input_meta *dst, const tobi_input_meta *src) {
80 *dst = *src;
81 dst->source = src->source ? tobi_xstrdup(src->source) : NULL;
82}
83
84static void input_meta_free(tobi_input_meta *meta) {
85 free(meta->source);
86 meta->source = NULL;
87}
88
89static void parser_free(parser *p) {
90 free(p->scope);
91}
92
93static tobi_ns_kind ns_kind_from_ir(tobi_ir_kind kind) {
94 switch (kind) {
95 case TOBI_IR_SCOPE: return TOBI_NS_SCOPE;
96 case TOBI_IR_DEVICE: return TOBI_NS_DEVICE;
97 case TOBI_IR_METHOD: return TOBI_NS_METHOD;
98 case TOBI_IR_PROCESSOR: return TOBI_NS_PROCESSOR;
99 case TOBI_IR_NAME: return TOBI_NS_NAME;
100 case TOBI_IR_ALIAS: return TOBI_NS_ALIAS;
101 case TOBI_IR_OPREGION: return TOBI_NS_OPREGION;
102 case TOBI_IR_FIELD:
103 case TOBI_IR_FIELD_ELEM: return TOBI_NS_FIELD;
104 case TOBI_IR_MUTEX: return TOBI_NS_MUTEX;
105 case TOBI_IR_EVENT: return TOBI_NS_EVENT;
106 default: return TOBI_NS_UNKNOWN;
107 }
108}
109
110static const char *region_space_name(unsigned space) {
111 switch (space) {
112 case 0x00: return "SystemMemory";
113 case 0x01: return "SystemIO";
114 case 0x02: return "PCI_Config";
115 case 0x03: return "EmbeddedControl";
116 case 0x04: return "SMBus";
117 case 0x05: return "SystemCMOS";
118 case 0x06: return "PCIBarTarget";
119 case 0x07: return "IPMI";
120 case 0x08: return "GPIO";
121 case 0x09: return "GenericSerialBus";
122 case 0x0a: return "PCC";
123 default: return "OEM";
124 }
125}
126
127static void ns_declare_path(parser *p, tobi_ns_kind kind, const char *path, size_t off,
128 unsigned args, unsigned flags, int external, const char *target) {
129 if (!p->ns || !path || path[0] == '\0') {
130 return;
131 }
132 int duplicate = 0;
133 tobi_ns_ent *ent = tobi_ns_add(p->ns, kind, path, p->scope, off, args, flags, external, target, &duplicate);
134 if (ent && p->has_source && (!duplicate || !ent->has_source)) {
135 tobi_ns_ent_set_source(ent, p->source, p->input_index);
136 }
137 if (duplicate) {
138 diag(p, p->strict ? TOBI_DIAG_ERROR : TOBI_DIAG_WARN, off,
139 "duplicate namespace object %s at %s", tobi_ns_kind_name(kind), path);
140 }
141}
142
143static void ns_set_region_range(parser *p, const char *path, uint64_t off, uint64_t len) {
144 tobi_ns_ent *e = tobi_ns_find(p->ns, path);
145 if (!e) {
146 return;
147 }
148 e->region_offset = off;
149 e->region_len = len;
150 e->has_region_range = 1;
151}
152
153static void ns_declare_name(parser *p, tobi_ns_kind kind, const char *name, size_t off,
154 unsigned args, unsigned flags, int external, const char *target) {
155 char *path = tobi_nm_resolve(p->scope, name);
156 ns_declare_path(p, kind, path, off, args, flags, external, target);
157 free(path);
158}
159
160static uint8_t table_sum8(const uint8_t *data, size_t len) {
161 uint8_t sum = 0;
162 for (size_t i = 0; i < len; i++) {
163 sum = (uint8_t)(sum + data[i]);
164 }
165 return sum;
166}
167
168static int detect_input(const uint8_t *data, size_t len, int strict, tobi_diag_list *diag_list,
169 tobi_input_meta *meta, const char *source, size_t input_index,
170 int has_source) {
171 memset(meta, 0, sizeof(*meta));
172 meta->aml_off = 0;
173 meta->aml_len = len;
174 memcpy(meta->signature, "RAW", 4);
175 meta->signature[4] = '\0';
176 input_meta_set_source(meta, has_source && source ? source : "raw");
177 if (len >= 4 && is_table_sig(data)) {
178 memcpy(meta->signature, data, 4);
179 meta->signature[4] = '\0';
180 meta->is_table = 1;
181 if (len < 36) {
182 tobi_diag_add_source(diag_list, TOBI_DIAG_ERROR, 0, source, input_index, has_source,
183 "ACPI table shorter than 36-byte header");
184 return !strict;
185 }
186 uint32_t table_len = le32_at(data + 4);
187 meta->table_len = table_len;
188 if (table_len < 36 || table_len > len) {
189 tobi_diag_add_source(diag_list, TOBI_DIAG_ERROR, 4, source, input_index, has_source,
190 "invalid ACPI table length %u for file size %zu", table_len, len);
191 return !strict;
192 }
193 meta->revision = data[8];
194 meta->checksum_byte = data[9];
195 copy_trim(meta->oem_id, sizeof(meta->oem_id), data + 10, 6);
196 copy_trim(meta->oem_table_id, sizeof(meta->oem_table_id), data + 16, 8);
197 meta->oem_revision = le32_at(data + 24);
198 copy_trim(meta->creator_id, sizeof(meta->creator_id), data + 28, 4);
199 meta->creator_revision = le32_at(data + 32);
200 meta->header_hash = hash32(data, 36);
201 uint8_t sum = table_sum8(data, table_len);
202 meta->checksum_sum = sum;
203 meta->checksum_valid = sum == 0;
204 if (sum != 0) {
205 tobi_diag_add_source(diag_list, strict ? TOBI_DIAG_ERROR : TOBI_DIAG_WARN, 9,
206 source, input_index, has_source,
207 "ACPI table checksum mismatch: byte sum is 0x%02x", sum);
208 if (strict) {
209 return 0;
210 }
211 }
212 meta->aml_off = 36;
213 meta->aml_len = (size_t)table_len - 36;
214 }
215 if (meta->aml_off <= len && meta->aml_len <= len - meta->aml_off) {
216 meta->aml_hash = hash32(data + meta->aml_off, meta->aml_len);
217 }
218 return 1;
219}
220
221static int pkg_window(parser *p, tobi_rd *rd, size_t op_start, size_t *body_start, size_t *body_len, size_t *whole_len) {
222 size_t len = 0;
223 size_t enc = 0;
224 size_t pkg_start = rd->pos;
225 if (!tobi_rd_pkg_len(rd, &len, &enc)) {
226 diag(p, TOBI_DIAG_ERROR, rd->base + pkg_start, "truncated AML package length");
227 return 0;
228 }
229 if (len < enc) {
230 diag(p, TOBI_DIAG_ERROR, rd->base + pkg_start, "invalid AML package length %zu smaller than encoded length %zu", len, enc);
231 return 0;
232 }
233 size_t end = pkg_start + len;
234 if (end > rd->len) {
235 diag(p, TOBI_DIAG_ERROR, rd->base + pkg_start, "AML package body extends past input");
236 end = rd->len;
237 }
238 *body_start = rd->pos;
239 *body_len = end >= rd->pos ? end - rd->pos : 0;
240 *whole_len = (rd->base + end) - op_start;
241 return 1;
242}
243
244static tobi_ir *unknown_node(parser *p, size_t off, uint32_t raw, size_t len, const char *why) {
245 tobi_ir *n = tobi_ir_new(TOBI_IR_UNKNOWN, off, len);
246 n->raw_op = raw;
247 tobi_ir_set_str(n, why);
248 diag(p, p->strict ? TOBI_DIAG_ERROR : TOBI_DIAG_WARN, off, "unsupported or malformed opcode 0x%x: %s", raw, why);
249 return n;
250}
251
252static int parse_term_list(parser *p, tobi_rd *rd, tobi_ir *parent, unsigned depth);
253static tobi_ir *parse_expr(parser *p, tobi_rd *rd, unsigned depth);
254static tobi_ir *parse_term(parser *p, tobi_rd *rd, unsigned depth);
255
256static int parse_namestring_diag(parser *p, tobi_rd *rd, char **name) {
257 size_t off = tobi_rd_off(rd);
258 if (!tobi_nm_namestring(rd, name)) {
259 diag(p, TOBI_DIAG_ERROR, off, "invalid or truncated AML namestring");
260 return 0;
261 }
262 return 1;
263}
264
265static tobi_ir *parse_block_from_reader(parser *p, tobi_rd *sub, unsigned depth, size_t off) {
266 tobi_ir *block = tobi_ir_new(TOBI_IR_BLOCK, off, tobi_rd_left(sub));
267 tobi_ir_set_scope(block, p->scope);
268 (void)parse_term_list(p, sub, block, depth + 1);
269 return block;
270}
271
272static tobi_ir *parse_pkg_named(parser *p, tobi_rd *rd, unsigned depth, tobi_ir_kind kind, const char *kind_name, size_t op_start) {
273 size_t body_start = 0;
274 size_t body_len = 0;
275 size_t whole_len = 0;
276 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
277 return unknown_node(p, op_start, rd->data[op_start - rd->base], 1, "bad package length");
278 }
279 tobi_rd body;
280 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
281 char *name = NULL;
282 if (!parse_namestring_diag(p, &body, &name)) {
283 rd->pos = body_start + body_len;
284 return unknown_node(p, op_start, rd->data[op_start - rd->base], whole_len, kind_name);
285 }
286 char *path = tobi_nm_resolve(p->scope, name);
287 tobi_ir *n = tobi_ir_new(kind, op_start, whole_len);
288 tobi_ir_set_name(n, name);
289 tobi_ir_set_path(n, path);
290 ns_declare_path(p, ns_kind_from_ir(kind), path, op_start, 0, 0, 0, NULL);
291 char *old_scope = p->scope;
292 p->scope = tobi_xstrdup(path);
293 tobi_ir *block = parse_block_from_reader(p, &body, depth, tobi_rd_off(&body));
294 free(p->scope);
295 p->scope = old_scope;
296 tobi_ir_add(n, block);
297 rd->pos = body_start + body_len;
298 free(name);
299 free(path);
300 return n;
301}
302
303static tobi_ir *parse_method(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) {
304 size_t body_start = 0;
305 size_t body_len = 0;
306 size_t whole_len = 0;
307 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
308 return unknown_node(p, op_start, 0x14, 1, "bad method package");
309 }
310 tobi_rd body;
311 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
312 char *name = NULL;
313 if (!parse_namestring_diag(p, &body, &name)) {
314 rd->pos = body_start + body_len;
315 return unknown_node(p, op_start, 0x14, whole_len, "bad method name");
316 }
317 uint8_t flags = 0;
318 if (!tobi_rd_u8(&body, &flags)) {
319 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated method flags");
320 rd->pos = body_start + body_len;
321 free(name);
322 return unknown_node(p, op_start, 0x14, whole_len, "bad method flags");
323 }
324 char *path = tobi_nm_resolve(p->scope, name);
325 tobi_ir *n = tobi_ir_new(TOBI_IR_METHOD, op_start, whole_len);
326 tobi_ir_set_name(n, name);
327 tobi_ir_set_path(n, path);
328 n->method_args = flags & 0x07u;
329 n->method_serialized = (flags >> 3) & 0x01u;
330 n->method_sync = (flags >> 4) & 0x0fu;
331 ns_declare_path(p, TOBI_NS_METHOD, path, op_start, n->method_args, flags, 0, NULL);
332 char *old_scope = p->scope;
333 p->scope = tobi_xstrdup(path);
334 tobi_ir *block = parse_block_from_reader(p, &body, depth, tobi_rd_off(&body));
335 free(p->scope);
336 p->scope = old_scope;
337 tobi_ir_add(n, block);
338 rd->pos = body_start + body_len;
339 free(name);
340 free(path);
341 return n;
342}
343
344static tobi_ir *parse_name(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) {
345 char *name = NULL;
346 if (!parse_namestring_diag(p, rd, &name)) {
347 return unknown_node(p, op_start, 0x08, 1, "bad NameOp name");
348 }
349 char *path = tobi_nm_resolve(p->scope, name);
350 tobi_ir *n = tobi_ir_new(TOBI_IR_NAME, op_start, 0);
351 tobi_ir_set_name(n, name);
352 tobi_ir_set_path(n, path);
353 ns_declare_path(p, TOBI_NS_NAME, path, op_start, 0, 0, 0, NULL);
354 tobi_ir *value = parse_expr(p, rd, depth + 1);
355 if (value) {
356 tobi_ir_add(n, value);
357 }
358 n->len = tobi_rd_off(rd) - op_start;
359 free(name);
360 free(path);
361 return n;
362}
363
364static tobi_ir *parse_alias(parser *p, tobi_rd *rd, size_t op_start) {
365 char *src = NULL;
366 char *dst = NULL;
367 if (!parse_namestring_diag(p, rd, &src) || !parse_namestring_diag(p, rd, &dst)) {
368 free(src);
369 free(dst);
370 return unknown_node(p, op_start, 0x06, 1, "bad AliasOp names");
371 }
372 tobi_ir *n = tobi_ir_new(TOBI_IR_ALIAS, op_start, tobi_rd_off(rd) - op_start);
373 tobi_ir_set_name(n, dst);
374 tobi_ir_set_str(n, src);
375 char *path = tobi_nm_resolve(p->scope, dst);
376 char *target = tobi_nm_resolve(p->scope, src);
377 tobi_ir_set_path(n, path);
378 ns_declare_path(p, TOBI_NS_ALIAS, path, op_start, 0, 0, 0, target);
379 free(target);
380 free(path);
381 free(src);
382 free(dst);
383 return n;
384}
385
386static tobi_ir *parse_external(parser *p, tobi_rd *rd, size_t op_start) {
387 char *name = NULL;
388 if (!parse_namestring_diag(p, rd, &name)) {
389 return unknown_node(p, op_start, 0x15, 1, "bad ExternalOp name");
390 }
391 uint8_t obj_type = 0;
392 uint8_t argc = 0;
393 if (!tobi_rd_u8(rd, &obj_type) || !tobi_rd_u8(rd, &argc)) {
394 free(name);
395 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "truncated ExternalOp metadata");
396 return unknown_node(p, op_start, 0x15, tobi_rd_off(rd) - op_start, "bad ExternalOp metadata");
397 }
398 tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, op_start, tobi_rd_off(rd) - op_start);
399 n->raw_op = 0x15;
400 n->value = argc;
401 tobi_ir_set_name(n, "external");
402 char *path = tobi_nm_resolve(p->scope, name);
403 tobi_ir_set_path(n, path);
404 tobi_ir *typ = tobi_ir_new(TOBI_IR_INTEGER, op_start + n->len - 2u, 1);
405 typ->value = obj_type;
406 tobi_ir_add(n, typ);
407 ns_declare_path(p, obj_type == 0x08u ? TOBI_NS_METHOD : TOBI_NS_EXTERNAL,
408 path, op_start, argc & 0x07u, obj_type, 1, NULL);
409 free(path);
410 free(name);
411 return n;
412}
413
414static void apply_field_meta(tobi_ir *e, uint64_t bit_off, uint64_t bit_len, uint8_t flags) {
415 e->bit_off = bit_off;
416 e->bit_len = bit_len;
417 e->access_type = flags & 0x0fu;
418 e->lock_rule = (flags >> 4) & 0x01u;
419 e->update_rule = (flags >> 5) & 0x03u;
420}
421
422static char *field_target_meta(parser *p, const char *prefix, const char *path) {
423 const tobi_ns_ent *e = tobi_ns_find_const(p->ns, path);
424 tobi_sb sb;
425 tobi_sb_init(&sb);
426 tobi_sb_add(&sb, prefix);
427 tobi_sb_add(&sb, path);
428 if (e && e->kind == TOBI_NS_OPREGION) {
429 tobi_sb_printf(&sb, " space=%s", region_space_name(e->flags));
430 if (e->has_region_range) {
431 tobi_sb_printf(&sb, " byte_offset=0x%llx byte_length=0x%llx",
432 (unsigned long long)e->region_offset,
433 (unsigned long long)e->region_len);
434 }
435 }
436 return tobi_sb_take(&sb);
437}
438
439static void parse_field_list(parser *p, tobi_rd *body, tobi_ir *n, uint8_t flags,
440 const tobi_ns_ent *region) {
441 uint64_t bit_pos = 0;
442 while (tobi_rd_left(body) > 0) {
443 size_t foff = tobi_rd_off(body);
444 uint8_t b = 0;
445 if (!tobi_rd_peek(body, &b)) {
446 break;
447 }
448 if (b == 0x00) {
449 (void)tobi_rd_u8(body, &b);
450 size_t bits = 0;
451 size_t enc = 0;
452 if (!tobi_rd_pkg_len(body, &bits, &enc)) {
453 diag(p, TOBI_DIAG_ERROR, foff, "truncated reserved field length");
454 break;
455 }
456 tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff);
457 tobi_ir_set_name(e, "<reserved>");
458 e->value = bits;
459 apply_field_meta(e, bit_pos, bits, flags);
460 tobi_ir_add(n, e);
461 bit_pos += bits;
462 continue;
463 }
464 if (b == 0x01) {
465 if (!tobi_rd_skip(body, 3)) {
466 diag(p, TOBI_DIAG_ERROR, foff, "truncated AccessField");
467 break;
468 }
469 tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff);
470 tobi_ir_set_name(e, "<access>");
471 tobi_ir_add(n, e);
472 continue;
473 }
474 if (b == 0x02) {
475 (void)tobi_rd_u8(body, &b);
476 char *conn = NULL;
477 if (!tobi_nm_namestring(body, &conn)) {
478 diag(p, TOBI_DIAG_ERROR, foff, "bad ConnectField name");
479 break;
480 }
481 tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff);
482 tobi_ir_set_name(e, "<connect>");
483 tobi_ir_set_str(e, conn);
484 tobi_ir_add(n, e);
485 free(conn);
486 continue;
487 }
488 if (b == 0x03) {
489 if (!tobi_rd_skip(body, 4)) {
490 diag(p, TOBI_DIAG_ERROR, foff, "truncated ExtendedAccessField");
491 break;
492 }
493 tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff);
494 tobi_ir_set_name(e, "<extended_access>");
495 tobi_ir_add(n, e);
496 continue;
497 }
498 char *fname = NULL;
499 if (!tobi_nm_nameseg(body, &fname)) {
500 diag(p, TOBI_DIAG_ERROR, foff, "bad field element");
501 (void)tobi_rd_skip(body, 1);
502 continue;
503 }
504 size_t bits = 0;
505 size_t enc = 0;
506 if (!tobi_rd_pkg_len(body, &bits, &enc)) {
507 diag(p, TOBI_DIAG_ERROR, foff, "truncated field element length");
508 free(fname);
509 break;
510 }
511 tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff);
512 tobi_ir_set_name(e, fname);
513 e->value = bits;
514 apply_field_meta(e, bit_pos, bits, flags);
515 if (n->target) {
516 tobi_sb target;
517 tobi_sb_init(&target);
518 tobi_sb_add(&target, n->target);
519 tobi_sb_printf(&target, " field_bit=%llu field_bytes=%llu",
520 (unsigned long long)bit_pos,
521 (unsigned long long)((bits + 7u) / 8u));
522 if (region && region->has_region_range &&
523 region->region_offset <= UINT64_MAX / 8u &&
524 bit_pos <= UINT64_MAX - (region->region_offset * 8u)) {
525 uint64_t abs_bit = (region->region_offset * 8u) + bit_pos;
526 tobi_sb_printf(&target, " absolute_bit=0x%llx absolute_byte=0x%llx absolute_bytes=0x%llx",
527 (unsigned long long)abs_bit,
528 (unsigned long long)(abs_bit / 8u),
529 (unsigned long long)((bits + 7u) / 8u));
530 }
531 tobi_ir_set_target(e, target.buf);
532 tobi_sb_free(&target);
533 }
534 ns_declare_name(p, TOBI_NS_FIELD, fname, foff, 0, 0, 0, NULL);
535 tobi_ir_add(n, e);
536 bit_pos += bits;
537 free(fname);
538 }
539}
540
541static tobi_ir *parse_field_like(parser *p, tobi_rd *rd, unsigned depth, size_t op_start, uint32_t raw, const char *label) {
542 size_t body_start = 0;
543 size_t body_len = 0;
544 size_t whole_len = 0;
545 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
546 return unknown_node(p, op_start, raw, 2, "bad field package");
547 }
548 tobi_rd body;
549 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
550 char *first = NULL;
551 if (!parse_namestring_diag(p, &body, &first)) {
552 rd->pos = body_start + body_len;
553 return unknown_node(p, op_start, raw, whole_len, "bad field name");
554 }
555 tobi_ir *n = tobi_ir_new(TOBI_IR_FIELD, op_start, whole_len);
556 tobi_ir_set_str(n, label);
557 const tobi_ns_ent *backing_region = NULL;
558 if (raw == 0x5b81u) {
559 tobi_ir_set_name(n, first);
560 char *path = tobi_ns_resolve_path(p->ns, p->scope, first);
561 char *target = field_target_meta(p, "region=", path);
562 tobi_ir_set_target(n, target);
563 backing_region = tobi_ns_find_const(p->ns, path);
564 free(target);
565 free(path);
566 } else if (raw == 0x5b86u) {
567 char *data = NULL;
568 if (!parse_namestring_diag(p, &body, &data)) {
569 free(first);
570 rd->pos = body_start + body_len;
571 return unknown_node(p, op_start, raw, whole_len, "bad IndexField data name");
572 }
573 tobi_sb nm;
574 tobi_sb_init(&nm);
575 tobi_sb_add(&nm, first);
576 tobi_sb_add(&nm, ",");
577 tobi_sb_add(&nm, data);
578 tobi_ir_set_name(n, nm.buf);
579 tobi_sb target;
580 tobi_sb_init(&target);
581 char *idx_path = tobi_ns_resolve_path(p->ns, p->scope, first);
582 char *data_path = tobi_ns_resolve_path(p->ns, p->scope, data);
583 char *idx_meta = field_target_meta(p, "index=", idx_path);
584 tobi_sb_add(&target, idx_meta);
585 tobi_sb_add(&target, " data=");
586 tobi_sb_add(&target, data_path);
587 tobi_ir_set_target(n, target.buf);
588 free(idx_meta);
589 free(idx_path);
590 free(data_path);
591 tobi_sb_free(&target);
592 tobi_sb_free(&nm);
593 free(data);
594 } else {
595 char *bank = NULL;
596 if (!parse_namestring_diag(p, &body, &bank)) {
597 free(first);
598 rd->pos = body_start + body_len;
599 return unknown_node(p, op_start, raw, whole_len, "bad BankField bank name");
600 }
601 tobi_sb nm;
602 tobi_sb_init(&nm);
603 tobi_sb_add(&nm, first);
604 tobi_sb_add(&nm, ",");
605 tobi_sb_add(&nm, bank);
606 tobi_ir_set_name(n, nm.buf);
607 tobi_sb target;
608 tobi_sb_init(&target);
609 char *region_path = tobi_ns_resolve_path(p->ns, p->scope, first);
610 char *bank_path = tobi_ns_resolve_path(p->ns, p->scope, bank);
611 char *region_meta = field_target_meta(p, "region=", region_path);
612 backing_region = tobi_ns_find_const(p->ns, region_path);
613 tobi_sb_add(&target, region_meta);
614 tobi_sb_add(&target, " bank=");
615 tobi_sb_add(&target, bank_path);
616 tobi_ir_set_target(n, target.buf);
617 free(region_meta);
618 free(region_path);
619 free(bank_path);
620 tobi_sb_free(&target);
621 tobi_sb_free(&nm);
622 free(bank);
623 tobi_ir *bank_value = parse_expr(p, &body, depth + 1);
624 if (bank_value) {
625 tobi_ir_add(n, bank_value);
626 }
627 }
628 uint8_t flags = 0;
629 if (!tobi_rd_u8(&body, &flags)) {
630 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated field flags");
631 }
632 n->value = flags;
633 n->access_type = flags & 0x0fu;
634 n->lock_rule = (flags >> 4) & 0x01u;
635 n->update_rule = (flags >> 5) & 0x03u;
636 parse_field_list(p, &body, n, flags, backing_region);
637 rd->pos = body_start + body_len;
638 free(first);
639 return n;
640}
641
642static tobi_ir *ref_from_name(parser *p, tobi_rd *rd, size_t owner_start, const char *why) {
643 size_t off = tobi_rd_off(rd);
644 char *name = NULL;
645 if (!parse_namestring_diag(p, rd, &name)) {
646 return unknown_node(p, owner_start, 0, tobi_rd_off(rd) - owner_start, why);
647 }
648 tobi_ir *n = tobi_ir_new(TOBI_IR_REF, off, tobi_rd_off(rd) - off);
649 tobi_ir_set_name(n, name);
650 char *path = tobi_ns_resolve_path(p->ns, p->scope, name);
651 tobi_ir_set_path(n, path);
652 free(path);
653 free(name);
654 return n;
655}
656
657static void add_expr_children(parser *p, tobi_rd *rd, unsigned depth, tobi_ir *n, unsigned count) {
658 for (unsigned i = 0; i < count && tobi_rd_left(rd) > 0; i++) {
659 tobi_ir *arg = parse_expr(p, rd, depth + 1);
660 if (arg) {
661 tobi_ir_add(n, arg);
662 }
663 }
664}
665
666static tobi_ir *parse_op_expr(parser *p, tobi_rd *rd, unsigned depth, size_t op_start,
667 uint32_t raw, const tobi_op_expr *info) {
668 tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, op_start, 0);
669 n->raw_op = raw;
670 tobi_ir_set_name(n, info->ir_name);
671 add_expr_children(p, rd, depth, n, info->arg_count);
672 add_expr_children(p, rd, depth, n, info->target_count);
673 for (unsigned i = 0; i < info->name_count && tobi_rd_left(rd) > 0; i++) {
674 tobi_ir *ref = ref_from_name(p, rd, op_start, "bad expression namestring");
675 if (ref) {
676 if (ref->path && ref->path[0] == '\\' && strncmp(info->ir_name, "create_", 7) == 0) {
677 ns_declare_path(p, TOBI_NS_FIELD, ref->path, ref->off, 0, raw, 0, NULL);
678 }
679 tobi_ir_add(n, ref);
680 }
681 }
682 n->len = tobi_rd_off(rd) - op_start;
683 return n;
684}
685
686static tobi_ir *parse_ext_load(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) {
687 tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, op_start, 0);
688 n->raw_op = 0x5b20u;
689 tobi_ir_set_name(n, "load");
690 tobi_ir *src = ref_from_name(p, rd, op_start, "bad LoadOp source name");
691 if (src) {
692 tobi_ir_add(n, src);
693 }
694 tobi_ir *target = parse_expr(p, rd, depth + 1);
695 if (target) {
696 tobi_ir_add(n, target);
697 }
698 n->len = tobi_rd_off(rd) - op_start;
699 return n;
700}
701
702static tobi_ir *parse_ext_pkg_scope(parser *p, tobi_rd *rd, unsigned depth, size_t op_start,
703 uint32_t raw, tobi_ir_kind kind, const char *kind_name,
704 unsigned prefix_bytes) {
705 size_t body_start = 0;
706 size_t body_len = 0;
707 size_t whole_len = 0;
708 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
709 return unknown_node(p, op_start, raw, 2, kind_name);
710 }
711 tobi_rd body;
712 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
713 char *name = NULL;
714 if (!parse_namestring_diag(p, &body, &name)) {
715 rd->pos = body_start + body_len;
716 return unknown_node(p, op_start, raw, whole_len, kind_name);
717 }
718 if (!tobi_rd_skip(&body, prefix_bytes)) {
719 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated %s metadata", kind_name);
720 rd->pos = body_start + body_len;
721 free(name);
722 return unknown_node(p, op_start, raw, whole_len, kind_name);
723 }
724 char *path = tobi_nm_resolve(p->scope, name);
725 tobi_ir *n = tobi_ir_new(kind, op_start, whole_len);
726 tobi_ir_set_name(n, name);
727 tobi_ir_set_path(n, path);
728 tobi_ir_set_str(n, kind_name);
729 ns_declare_path(p, ns_kind_from_ir(kind), path, op_start, 0, raw, 0, NULL);
730 char *old_scope = p->scope;
731 p->scope = tobi_xstrdup(path);
732 tobi_ir *block = parse_block_from_reader(p, &body, depth, tobi_rd_off(&body));
733 free(p->scope);
734 p->scope = old_scope;
735 tobi_ir_add(n, block);
736 rd->pos = body_start + body_len;
737 free(path);
738 free(name);
739 return n;
740}
741
742static tobi_ir *parse_ext(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) {
743 uint8_t ext = 0;
744 if (!tobi_rd_u8(rd, &ext)) {
745 return unknown_node(p, op_start, 0x5b, 1, "truncated extended opcode");
746 }
747 uint32_t raw = 0x5b00u | ext;
748 switch (ext) {
749 case 0x82:
750 return parse_pkg_named(p, rd, depth, TOBI_IR_DEVICE, "DeviceOp", op_start);
751 case 0x83: {
752 size_t body_start = 0;
753 size_t body_len = 0;
754 size_t whole_len = 0;
755 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
756 return unknown_node(p, op_start, raw, 2, "bad ProcessorOp package");
757 }
758 tobi_rd body;
759 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
760 char *name = NULL;
761 if (!parse_namestring_diag(p, &body, &name)) {
762 rd->pos = body_start + body_len;
763 return unknown_node(p, op_start, raw, whole_len, "bad processor name");
764 }
765 uint8_t proc_id = 0;
766 uint32_t pblk = 0;
767 uint8_t pblk_len = 0;
768 if (!tobi_rd_u8(&body, &proc_id) || !tobi_rd_u32(&body, &pblk) ||
769 !tobi_rd_u8(&body, &pblk_len)) {
770 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated ProcessorOp metadata");
771 rd->pos = body_start + body_len;
772 free(name);
773 return unknown_node(p, op_start, raw, whole_len, "bad ProcessorOp metadata");
774 }
775 char *path = tobi_nm_resolve(p->scope, name);
776 tobi_ir *n = tobi_ir_new(TOBI_IR_PROCESSOR, op_start, whole_len);
777 tobi_ir_set_name(n, name);
778 tobi_ir_set_path(n, path);
779 ns_declare_path(p, TOBI_NS_PROCESSOR, path, op_start, 0, 0, 0, NULL);
780 n->value = proc_id;
781 char *old_scope = p->scope;
782 p->scope = tobi_xstrdup(path);
783 tobi_ir_add(n, parse_block_from_reader(p, &body, depth, tobi_rd_off(&body)));
784 free(p->scope);
785 p->scope = old_scope;
786 rd->pos = body_start + body_len;
787 free(path);
788 free(name);
789 return n;
790 }
791 case 0x80: {
792 char *name = NULL;
793 if (!parse_namestring_diag(p, rd, &name)) {
794 return unknown_node(p, op_start, raw, 2, "bad OpRegion name");
795 }
796 uint8_t space = 0;
797 if (!tobi_rd_u8(rd, &space)) {
798 free(name);
799 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "truncated OpRegion space");
800 return unknown_node(p, op_start, raw, 2, "bad OpRegion");
801 }
802 tobi_ir *n = tobi_ir_new(TOBI_IR_OPREGION, op_start, 0);
803 tobi_ir_set_name(n, name);
804 char *path = tobi_nm_resolve(p->scope, name);
805 tobi_ir_set_path(n, path);
806 ns_declare_path(p, TOBI_NS_OPREGION, path, op_start, 0, space, 0, NULL);
807 n->value = space;
808 tobi_ir_set_str(n, region_space_name(space));
809 tobi_ir *off = parse_expr(p, rd, depth + 1);
810 tobi_ir *len = parse_expr(p, rd, depth + 1);
811 if (off) {
812 tobi_ir_add(n, off);
813 }
814 if (len) {
815 tobi_ir_add(n, len);
816 }
817 if (n->child_len >= 2 && n->child[0]->kind == TOBI_IR_INTEGER && n->child[1]->kind == TOBI_IR_INTEGER) {
818 ns_set_region_range(p, path, n->child[0]->value, n->child[1]->value);
819 }
820 n->len = tobi_rd_off(rd) - op_start;
821 free(path);
822 free(name);
823 return n;
824 }
825 case 0x81:
826 return parse_field_like(p, rd, depth, op_start, raw, "Field");
827 case 0x86:
828 return parse_field_like(p, rd, depth, op_start, raw, "IndexField");
829 case 0x87:
830 return parse_field_like(p, rd, depth, op_start, raw, "BankField");
831 case 0x84:
832 return parse_ext_pkg_scope(p, rd, depth, op_start, raw, TOBI_IR_SCOPE, "PowerResOp", 2);
833 case 0x85:
834 return parse_ext_pkg_scope(p, rd, depth, op_start, raw, TOBI_IR_SCOPE, "ThermalZoneOp", 0);
835 case 0x20:
836 return parse_ext_load(p, rd, depth, op_start);
837 case 0x30: {
838 tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, op_start, tobi_rd_off(rd) - op_start);
839 n->value = 2;
840 return n;
841 }
842 case 0x31: {
843 tobi_ir *n = tobi_ir_new(TOBI_IR_REF, op_start, tobi_rd_off(rd) - op_start);
844 n->raw_op = raw;
845 tobi_ir_set_name(n, "Debug");
846 return n;
847 }
848 case 0x33: {
849 tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, op_start, tobi_rd_off(rd) - op_start);
850 n->raw_op = raw;
851 tobi_ir_set_name(n, "timer");
852 return n;
853 }
854 case 0x88: {
855 char *name = NULL;
856 if (!parse_namestring_diag(p, rd, &name)) {
857 return unknown_node(p, op_start, raw, 2, "bad DataRegion name");
858 }
859 tobi_ir *n = tobi_ir_new(TOBI_IR_OPREGION, op_start, 0);
860 n->raw_op = raw;
861 tobi_ir_set_name(n, name);
862 char *path = tobi_nm_resolve(p->scope, name);
863 tobi_ir_set_path(n, path);
864 tobi_ir_set_str(n, "DataRegion");
865 ns_declare_path(p, TOBI_NS_OPREGION, path, op_start, 0, 0, 0, NULL);
866 add_expr_children(p, rd, depth, n, 3);
867 n->len = tobi_rd_off(rd) - op_start;
868 free(path);
869 free(name);
870 return n;
871 }
872 case 0x01:
873 case 0x02: {
874 char *name = NULL;
875 if (!parse_namestring_diag(p, rd, &name)) {
876 return unknown_node(p, op_start, raw, 2, ext == 0x01 ? "bad MutexOp" : "bad EventOp");
877 }
878 tobi_ir *n = tobi_ir_new(ext == 0x01 ? TOBI_IR_MUTEX : TOBI_IR_EVENT, op_start, 0);
879 tobi_ir_set_name(n, name);
880 char *path = tobi_nm_resolve(p->scope, name);
881 tobi_ir_set_path(n, path);
882 ns_declare_path(p, ext == 0x01 ? TOBI_NS_MUTEX : TOBI_NS_EVENT, path, op_start, 0, 0, 0, NULL);
883 if (ext == 0x01) {
884 uint8_t sync = 0;
885 if (!tobi_rd_u8(rd, &sync)) {
886 free(path);
887 free(name);
888 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "truncated MutexOp sync flags");
889 return unknown_node(p, op_start, raw, tobi_rd_off(rd) - op_start, "bad MutexOp sync flags");
890 }
891 n->value = sync;
892 }
893 n->len = tobi_rd_off(rd) - op_start;
894 free(path);
895 free(name);
896 return n;
897 }
898 default:
899 break;
900 }
901 const tobi_op_expr *info = tobi_op_expr_find((uint16_t)raw);
902 if (info) {
903 return parse_op_expr(p, rd, depth, op_start, raw, info);
904 }
905 return unknown_node(p, op_start, raw, 2, "unsupported extended opcode");
906}
907
908static tobi_ir *parse_if(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) {
909 size_t body_start = 0;
910 size_t body_len = 0;
911 size_t whole_len = 0;
912 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
913 return unknown_node(p, op_start, 0xa0, 1, "bad IfOp package");
914 }
915 tobi_rd body;
916 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
917 tobi_ir *n = tobi_ir_new(TOBI_IR_IF, op_start, whole_len);
918 tobi_ir *pred = parse_expr(p, &body, depth + 1);
919 if (pred) {
920 tobi_ir_add(n, pred);
921 }
922 tobi_ir_add(n, parse_block_from_reader(p, &body, depth, tobi_rd_off(&body)));
923 rd->pos = body_start + body_len;
924 uint8_t next = 0;
925 if (tobi_rd_peek(rd, &next) && next == 0xa1) {
926 size_t else_start = tobi_rd_off(rd);
927 (void)tobi_rd_u8(rd, &next);
928 size_t eb = 0;
929 size_t el = 0;
930 size_t ew = 0;
931 if (pkg_window(p, rd, else_start, &eb, &el, &ew)) {
932 tobi_rd er;
933 tobi_rd_init(&er, rd->data + eb, el, rd->base + eb);
934 tobi_ir_add(n, parse_block_from_reader(p, &er, depth, tobi_rd_off(&er)));
935 rd->pos = eb + el;
936 n->len = (rd->base + rd->pos) - op_start;
937 } else {
938 tobi_ir_add(n, unknown_node(p, else_start, 0xa1, 1, "bad ElseOp package"));
939 }
940 }
941 return n;
942}
943
944static tobi_ir *parse_while(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) {
945 size_t body_start = 0;
946 size_t body_len = 0;
947 size_t whole_len = 0;
948 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
949 return unknown_node(p, op_start, 0xa2, 1, "bad WhileOp package");
950 }
951 tobi_rd body;
952 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
953 tobi_ir *n = tobi_ir_new(TOBI_IR_WHILE, op_start, whole_len);
954 tobi_ir *pred = parse_expr(p, &body, depth + 1);
955 if (pred) {
956 tobi_ir_add(n, pred);
957 }
958 tobi_ir_add(n, parse_block_from_reader(p, &body, depth, tobi_rd_off(&body)));
959 rd->pos = body_start + body_len;
960 return n;
961}
962
963static tobi_ir *parse_string(parser *p, tobi_rd *rd, size_t op_start) {
964 size_t s = rd->pos;
965 while (rd->pos < rd->len && rd->data[rd->pos] != 0) {
966 rd->pos++;
967 }
968 size_t n = rd->pos - s;
969 if (rd->pos >= rd->len) {
970 diag(p, TOBI_DIAG_ERROR, rd->base + s, "unterminated AML string");
971 } else {
972 rd->pos++;
973 }
974 char *tmp = tobi_xstrndup((const char *)rd->data + s, n);
975 tobi_ir *node = tobi_ir_new(TOBI_IR_STRING, op_start, tobi_rd_off(rd) - op_start);
976 tobi_ir_set_str(node, tmp);
977 free(tmp);
978 return node;
979}
980
981static const char *small_res_name(unsigned name) {
982 switch (name) {
983 case 0x04: return "IRQ";
984 case 0x05: return "DMA";
985 case 0x06: return "StartDependentFn";
986 case 0x07: return "EndDependentFn";
987 case 0x08: return "IO";
988 case 0x09: return "FixedIO";
989 case 0x0e: return "VendorSmall";
990 case 0x0f: return "EndTag";
991 default: return NULL;
992 }
993}
994
995static const char *large_res_name(unsigned name) {
996 switch (name) {
997 case 0x01: return "Memory24";
998 case 0x02: return "GenericRegister";
999 case 0x04: return "VendorLarge";
1000 case 0x05: return "Memory32";
1001 case 0x06: return "FixedMemory32";
1002 case 0x07: return "DWordAddressSpace";
1003 case 0x08: return "WordAddressSpace";
1004 case 0x09: return "ExtendedIRQ";
1005 case 0x0a: return "QWordAddressSpace";
1006 case 0x0b: return "ExtendedAddressSpace";
1007 case 0x0c: return "GPIO";
1008 case 0x0e: return "SerialBus";
1009 default: return NULL;
1010 }
1011}
1012
1013static uint16_t res_u16(const uint8_t *p) {
1014 return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
1015}
1016
1017static uint32_t res_u32(const uint8_t *p) {
1018 return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
1019}
1020
1021static uint64_t res_u64(const uint8_t *p) {
1022 return (uint64_t)res_u32(p) | ((uint64_t)res_u32(p + 4) << 32);
1023}
1024
1025static void add_res_node(tobi_ir *parent, const char *label, size_t off, size_t len,
1026 uint32_t tag, size_t payload_len, const char *detail) {
1027 tobi_ir *r = tobi_ir_new(TOBI_IR_RESOURCE, off, len);
1028 r->raw_op = tag;
1029 r->value = payload_len;
1030 tobi_ir_set_name(r, label);
1031 if (detail) {
1032 tobi_ir_set_str(r, detail);
1033 }
1034 tobi_ir_add(parent, r);
1035}
1036
1037static char *small_res_detail(unsigned name, const uint8_t *p, size_t len) {
1038 tobi_sb sb;
1039 tobi_sb_init(&sb);
1040 if (name == 0x04 && len >= 2) {
1041 tobi_sb_printf(&sb, "irq_mask=0x%x", (unsigned)res_u16(p));
1042 if (len >= 3) {
1043 tobi_sb_printf(&sb, " flags=0x%x", (unsigned)p[2]);
1044 }
1045 } else if (name == 0x05 && len >= 2) {
1046 tobi_sb_printf(&sb, "dma_mask=0x%x flags=0x%x", (unsigned)p[0], (unsigned)p[1]);
1047 } else if (name == 0x08 && len >= 7) {
1048 tobi_sb_printf(&sb, "decode=0x%x min=0x%x max=0x%x align=%u length=%u",
1049 (unsigned)p[0], (unsigned)res_u16(p + 1), (unsigned)res_u16(p + 3),
1050 (unsigned)p[5], (unsigned)p[6]);
1051 } else if (name == 0x09 && len >= 3) {
1052 tobi_sb_printf(&sb, "base=0x%x length=%u", (unsigned)res_u16(p), (unsigned)p[2]);
1053 } else if (name == 0x0f && len >= 1) {
1054 tobi_sb_printf(&sb, "checksum=0x%x", (unsigned)p[0]);
1055 } else {
1056 tobi_sb_printf(&sb, "bytes=%zu", len);
1057 }
1058 return tobi_sb_take(&sb);
1059}
1060
1061static char *large_res_detail(unsigned name, const uint8_t *p, size_t len) {
1062 tobi_sb sb;
1063 tobi_sb_init(&sb);
1064 if (name == 0x02 && len >= 12) {
1065 tobi_sb_printf(&sb, "space=0x%x width=%u offset=%u access=%u address=0x%x",
1066 (unsigned)p[0], (unsigned)p[1], (unsigned)p[2], (unsigned)p[3],
1067 (unsigned)res_u64(p + 4));
1068 } else if ((name == 0x05 || name == 0x06) && len >= 9) {
1069 tobi_sb_printf(&sb, "write=0x%x min=0x%x max=0x%x align=0x%x length=0x%x",
1070 (unsigned)p[0], (unsigned)res_u32(p + 1), (unsigned)res_u32(p + 5),
1071 len >= 13 ? (unsigned)res_u32(p + 9) : 0u,
1072 len >= 17 ? (unsigned)res_u32(p + 13) : 0u);
1073 } else if (name == 0x07 && len >= 23) {
1074 tobi_sb_printf(&sb, "rtype=0x%x flags=0x%x gran=0x%x min=0x%x max=0x%x trans=0x%x length=0x%x",
1075 (unsigned)p[0], (unsigned)p[1], (unsigned)res_u32(p + 3),
1076 (unsigned)res_u32(p + 7), (unsigned)res_u32(p + 11),
1077 (unsigned)res_u32(p + 15), (unsigned)res_u32(p + 19));
1078 } else if (name == 0x08 && len >= 13) {
1079 tobi_sb_printf(&sb, "rtype=0x%x flags=0x%x gran=0x%x min=0x%x max=0x%x",
1080 (unsigned)p[0], (unsigned)p[1], (unsigned)res_u16(p + 3),
1081 (unsigned)res_u16(p + 5), (unsigned)res_u16(p + 7));
1082 } else if (name == 0x09 && len >= 2) {
1083 unsigned count = len >= 2 ? (unsigned)p[1] : 0u;
1084 tobi_sb_printf(&sb, "flags=0x%x interrupts=%u", (unsigned)p[0], count);
1085 } else if (name == 0x0a && len >= 43) {
1086 tobi_sb_printf(&sb, "rtype=0x%x flags=0x%x gran=0x%llx min=0x%llx max=0x%llx length=0x%llx",
1087 (unsigned)p[0], (unsigned)p[1], (unsigned long long)res_u64(p + 3),
1088 (unsigned long long)res_u64(p + 11), (unsigned long long)res_u64(p + 19),
1089 (unsigned long long)res_u64(p + 35));
1090 } else if (name == 0x0c && len >= 2) {
1091 tobi_sb_printf(&sb, "revision=%u type=0x%x", (unsigned)p[0], (unsigned)p[1]);
1092 } else if (name == 0x0e && len >= 2) {
1093 tobi_sb_printf(&sb, "revision=%u type=0x%x", (unsigned)p[0], (unsigned)p[1]);
1094 } else {
1095 tobi_sb_printf(&sb, "bytes=%zu", len);
1096 }
1097 return tobi_sb_take(&sb);
1098}
1099
1100static char *decode_resource_template(tobi_ir *parent, const uint8_t *data, size_t len, size_t base) {
1101 if (!data || len == 0) {
1102 return NULL;
1103 }
1104 tobi_sb sb;
1105 tobi_sb_init(&sb);
1106 size_t pos = 0;
1107 int any = 0;
1108 int ended = 0;
1109 while (pos < len) {
1110 uint8_t tag = data[pos++];
1111 if ((tag & 0x80u) != 0) {
1112 if (pos + 2 > len) {
1113 break;
1114 }
1115 unsigned name = tag & 0x7fu;
1116 size_t item_len = (size_t)data[pos] | ((size_t)data[pos + 1] << 8);
1117 pos += 2;
1118 if (item_len > len - pos) {
1119 break;
1120 }
1121 const char *label = large_res_name(name);
1122 if (!label) {
1123 label = "LargeItem";
1124 }
1125 char *detail = large_res_detail(name, data + pos, item_len);
1126 add_res_node(parent, label, base + pos - 3u, item_len + 3u, tag, item_len, detail);
1127 free(detail);
1128 if (any) {
1129 tobi_sb_add(&sb, "; ");
1130 }
1131 tobi_sb_printf(&sb, "%s(len=%zu)", label, item_len);
1132 any = 1;
1133 pos += item_len;
1134 } else {
1135 unsigned name = (tag >> 3) & 0x0fu;
1136 size_t item_len = tag & 0x07u;
1137 if (item_len > len - pos) {
1138 break;
1139 }
1140 const char *label = small_res_name(name);
1141 if (!label) {
1142 label = "SmallItem";
1143 }
1144 char *detail = small_res_detail(name, data + pos, item_len);
1145 add_res_node(parent, label, base + pos - 1u, item_len + 1u, tag, item_len, detail);
1146 free(detail);
1147 if (any) {
1148 tobi_sb_add(&sb, "; ");
1149 }
1150 tobi_sb_printf(&sb, "%s(len=%zu)", label, item_len);
1151 any = 1;
1152 pos += item_len;
1153 if (name == 0x0f) {
1154 ended = 1;
1155 break;
1156 }
1157 }
1158 }
1159 if (!any || !ended || pos != len) {
1160 tobi_sb_free(&sb);
1161 return NULL;
1162 }
1163 return tobi_sb_take(&sb);
1164}
1165
1166static tobi_ir *parse_buffer(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) {
1167 size_t body_start = 0;
1168 size_t body_len = 0;
1169 size_t whole_len = 0;
1170 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
1171 return unknown_node(p, op_start, 0x11, 1, "bad Buffer package");
1172 }
1173 tobi_rd body;
1174 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
1175 tobi_ir *n = tobi_ir_new(TOBI_IR_BUFFER, op_start, whole_len);
1176 tobi_ir *sz = parse_expr(p, &body, depth + 1);
1177 if (sz) {
1178 tobi_ir_add(n, sz);
1179 }
1180 tobi_sb hex;
1181 tobi_sb_init(&hex);
1182 const uint8_t *bytes = body.data + body.pos;
1183 size_t byte_len = tobi_rd_left(&body);
1184 tobi_hex(&hex, bytes, byte_len);
1185 char *res = decode_resource_template(n, bytes, byte_len, body.base + body.pos);
1186 if (res) {
1187 tobi_sb_add(&hex, " resources=");
1188 tobi_sb_add(&hex, res);
1189 free(res);
1190 }
1191 tobi_ir_set_str(n, hex.buf);
1192 tobi_sb_free(&hex);
1193 rd->pos = body_start + body_len;
1194 return n;
1195}
1196
1197static tobi_ir *parse_package(parser *p, tobi_rd *rd, unsigned depth, size_t op_start, int variable) {
1198 size_t body_start = 0;
1199 size_t body_len = 0;
1200 size_t whole_len = 0;
1201 if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) {
1202 return unknown_node(p, op_start, variable ? 0x13 : 0x12, 1, "bad Package");
1203 }
1204 tobi_rd body;
1205 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
1206 tobi_ir *n = tobi_ir_new(TOBI_IR_PACKAGE, op_start, whole_len);
1207 if (variable) {
1208 tobi_ir *cnt = parse_expr(p, &body, depth + 1);
1209 if (cnt) {
1210 tobi_ir_add(n, cnt);
1211 }
1212 } else {
1213 uint8_t count = 0;
1214 if (tobi_rd_u8(&body, &count)) {
1215 n->value = count;
1216 } else {
1217 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated package element count");
1218 }
1219 }
1220 while (tobi_rd_left(&body) > 0) {
1221 size_t before = body.pos;
1222 tobi_ir *e = parse_expr(p, &body, depth + 1);
1223 if (e) {
1224 tobi_ir_add(n, e);
1225 }
1226 if (body.pos == before) {
1227 (void)tobi_rd_skip(&body, 1);
1228 }
1229 }
1230 rd->pos = body_start + body_len;
1231 return n;
1232}
1233
1234static tobi_ir *parse_name_or_call(parser *p, tobi_rd *rd, unsigned depth) {
1235 size_t off = tobi_rd_off(rd);
1236 char *name = NULL;
1237 if (!parse_namestring_diag(p, rd, &name)) {
1238 return unknown_node(p, off, 0, 1, "bad namestring expression");
1239 }
1240 const tobi_ns_ent *ent = tobi_ns_lookup(p->ns, p->scope, name);
1241 int argc = ent && ent->kind == TOBI_NS_METHOD ? (int)ent->args : -1;
1242 char *path = ent ? tobi_xstrdup(ent->path) : tobi_nm_resolve(p->scope, name);
1243 tobi_ir *n = tobi_ir_new(argc >= 0 ? TOBI_IR_CALL : TOBI_IR_REF, off, 0);
1244 tobi_ir_set_name(n, name);
1245 tobi_ir_set_path(n, path);
1246 if (argc >= 0) {
1247 n->value = (uint64_t)argc;
1248 for (int i = 0; i < argc && tobi_rd_left(rd) > 0; i++) {
1249 tobi_ir *arg = parse_expr(p, rd, depth + 1);
1250 if (arg) {
1251 tobi_ir_add(n, arg);
1252 }
1253 }
1254 }
1255 n->len = tobi_rd_off(rd) - off;
1256 free(path);
1257 free(name);
1258 return n;
1259}
1260
1261static tobi_ir *parse_expr(parser *p, tobi_rd *rd, unsigned depth) {
1262 if (depth > MAX_DEPTH) {
1263 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "AML nesting too deep");
1264 return NULL;
1265 }
1266 size_t off = tobi_rd_off(rd);
1267 uint8_t op = 0;
1268 if (!tobi_rd_u8(rd, &op)) {
1269 diag(p, TOBI_DIAG_ERROR, off, "truncated AML expression");
1270 return NULL;
1271 }
1272 if (op == 0x00 || op == 0x01 || op == 0xff) {
1273 tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, 1);
1274 n->value = op == 0x00 ? 0 : (op == 0x01 ? 1 : UINT64_MAX);
1275 return n;
1276 }
1277 if (op == 0x0a) {
1278 uint8_t v = 0;
1279 if (!tobi_rd_u8(rd, &v)) {
1280 diag(p, TOBI_DIAG_ERROR, off, "truncated ByteConst");
1281 }
1282 tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, tobi_rd_off(rd) - off);
1283 n->value = v;
1284 return n;
1285 }
1286 if (op == 0x0b) {
1287 uint16_t v = 0;
1288 if (!tobi_rd_u16(rd, &v)) {
1289 diag(p, TOBI_DIAG_ERROR, off, "truncated WordConst");
1290 }
1291 tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, tobi_rd_off(rd) - off);
1292 n->value = v;
1293 return n;
1294 }
1295 if (op == 0x0c) {
1296 uint32_t v = 0;
1297 if (!tobi_rd_u32(rd, &v)) {
1298 diag(p, TOBI_DIAG_ERROR, off, "truncated DWordConst");
1299 }
1300 tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, tobi_rd_off(rd) - off);
1301 n->value = v;
1302 return n;
1303 }
1304 if (op == 0x0e) {
1305 uint64_t v = 0;
1306 if (!tobi_rd_u64(rd, &v)) {
1307 diag(p, TOBI_DIAG_ERROR, off, "truncated QWordConst");
1308 }
1309 tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, tobi_rd_off(rd) - off);
1310 n->value = v;
1311 return n;
1312 }
1313 if (op == 0x0d) {
1314 return parse_string(p, rd, off);
1315 }
1316 if (op == 0x5b) {
1317 return parse_ext(p, rd, depth, off);
1318 }
1319 if (op == 0x11) {
1320 return parse_buffer(p, rd, depth, off);
1321 }
1322 if (op == 0x12 || op == 0x13) {
1323 return parse_package(p, rd, depth, off, op == 0x13);
1324 }
1325 if (op >= 0x60 && op <= 0x67) {
1326 tobi_ir *n = tobi_ir_new(TOBI_IR_REF, off, 1);
1327 char name[8];
1328 (void)snprintf(name, sizeof(name), "Local%u", (unsigned)(op - 0x60));
1329 tobi_ir_set_name(n, name);
1330 return n;
1331 }
1332 if (op >= 0x68 && op <= 0x6e) {
1333 tobi_ir *n = tobi_ir_new(TOBI_IR_REF, off, 1);
1334 char name[8];
1335 (void)snprintf(name, sizeof(name), "Arg%u", (unsigned)(op - 0x68));
1336 tobi_ir_set_name(n, name);
1337 return n;
1338 }
1339 if (op == 0x70) {
1340 tobi_ir *n = tobi_ir_new(TOBI_IR_STORE, off, 0);
1341 tobi_ir *src = parse_expr(p, rd, depth + 1);
1342 tobi_ir *dst = parse_expr(p, rd, depth + 1);
1343 if (src) {
1344 tobi_ir_add(n, src);
1345 }
1346 if (dst) {
1347 tobi_ir_add(n, dst);
1348 }
1349 n->len = tobi_rd_off(rd) - off;
1350 return n;
1351 }
1352 const tobi_op_expr *info = tobi_op_expr_find(op);
1353 if (info) {
1354 return parse_op_expr(p, rd, depth, off, op, info);
1355 }
1356 rd->pos--;
1357 uint8_t b = 0;
1358 (void)tobi_rd_peek(rd, &b);
1359 if (b == '\\' || b == '^' || b == 0x2e || b == 0x2f || tobi_nm_is_lead(b)) {
1360 return parse_name_or_call(p, rd, depth);
1361 }
1362 (void)tobi_rd_u8(rd, &op);
1363 return unknown_node(p, off, op, 1, "unknown expression opcode");
1364}
1365
1366static tobi_ir *parse_return(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) {
1367 tobi_ir *n = tobi_ir_new(TOBI_IR_RETURN, op_start, 0);
1368 if (tobi_rd_left(rd) > 0) {
1369 tobi_ir *v = parse_expr(p, rd, depth + 1);
1370 if (v) {
1371 tobi_ir_add(n, v);
1372 }
1373 }
1374 n->len = tobi_rd_off(rd) - op_start;
1375 return n;
1376}
1377
1378static tobi_ir *parse_term(parser *p, tobi_rd *rd, unsigned depth) {
1379 if (depth > MAX_DEPTH) {
1380 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "AML nesting too deep");
1381 return NULL;
1382 }
1383 if (++p->terms > MAX_TERMS) {
1384 diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "too many AML terms");
1385 rd->pos = rd->len;
1386 return NULL;
1387 }
1388 size_t off = tobi_rd_off(rd);
1389 uint8_t op = 0;
1390 if (!tobi_rd_u8(rd, &op)) {
1391 return NULL;
1392 }
1393 switch (op) {
1394 case 0x06: return parse_alias(p, rd, off);
1395 case 0x08: return parse_name(p, rd, depth, off);
1396 case 0x10: return parse_pkg_named(p, rd, depth, TOBI_IR_SCOPE, "ScopeOp", off);
1397 case 0x14: return parse_method(p, rd, depth, off);
1398 case 0x15: return parse_external(p, rd, off);
1399 case 0x5b: return parse_ext(p, rd, depth, off);
1400 case 0x70:
1401 rd->pos--;
1402 return parse_expr(p, rd, depth);
1403 case 0xa0: return parse_if(p, rd, depth, off);
1404 case 0xa2: return parse_while(p, rd, depth, off);
1405 case 0xa3: {
1406 tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, off, 1);
1407 tobi_ir_set_name(n, "noop");
1408 return n;
1409 }
1410 case 0xa4: return parse_return(p, rd, depth, off);
1411 case 0xa5: return tobi_ir_new(TOBI_IR_BREAK, off, 1);
1412 case 0x9f: return tobi_ir_new(TOBI_IR_CONTINUE, off, 1);
1413 case 0xcc: {
1414 tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, off, 1);
1415 n->raw_op = 0xcc;
1416 tobi_ir_set_name(n, "breakpoint");
1417 return n;
1418 }
1419 default:
1420 rd->pos--;
1421 return parse_expr(p, rd, depth);
1422 }
1423}
1424
1425static int parse_term_list(parser *p, tobi_rd *rd, tobi_ir *parent, unsigned depth) {
1426 while (tobi_rd_left(rd) > 0) {
1427 size_t before = rd->pos;
1428 tobi_ir *n = parse_term(p, rd, depth);
1429 if (n) {
1430 tobi_ir_add(parent, n);
1431 }
1432 if (rd->pos == before) {
1433 uint8_t b = 0;
1434 if (tobi_rd_u8(rd, &b)) {
1435 tobi_ir_add(parent, unknown_node(p, rd->base + before, b, 1, "parser made no progress"));
1436 } else {
1437 break;
1438 }
1439 }
1440 }
1441 return 1;
1442}
1443
1444static int scan_pkg_window(tobi_rd *rd, size_t *body_start, size_t *body_len) {
1445 size_t pkg_start = rd->pos;
1446 size_t pkg_len = 0;
1447 size_t enc = 0;
1448 if (!tobi_rd_pkg_len(rd, &pkg_len, &enc) || pkg_len < enc) {
1449 return 0;
1450 }
1451 size_t end = pkg_start + pkg_len;
1452 if (end > rd->len || end < rd->pos) {
1453 return 0;
1454 }
1455 *body_start = rd->pos;
1456 *body_len = end - rd->pos;
1457 return 1;
1458}
1459
1460static void scan_methods(parser *p, tobi_rd *rd, unsigned depth) {
1461 if (depth > MAX_DEPTH) {
1462 return;
1463 }
1464 while (tobi_rd_left(rd) > 0) {
1465 size_t start = rd->pos;
1466 size_t abs_start = rd->base + start;
1467 uint8_t op = 0;
1468 if (!tobi_rd_u8(rd, &op)) {
1469 return;
1470 }
1471 if (op == 0x14 || op == 0x10) {
1472 size_t body_start = 0;
1473 size_t body_len = 0;
1474 if (!scan_pkg_window(rd, &body_start, &body_len)) {
1475 rd->pos = rd->len;
1476 return;
1477 }
1478 tobi_rd body;
1479 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
1480 char *name = NULL;
1481 if (!tobi_nm_namestring(&body, &name)) {
1482 rd->pos = body_start + body_len;
1483 continue;
1484 }
1485 char *path = tobi_nm_resolve(p->scope, name);
1486 if (op == 0x14) {
1487 uint8_t flags = 0;
1488 if (tobi_rd_u8(&body, &flags)) {
1489 ns_declare_path(p, TOBI_NS_METHOD, path, abs_start, flags & 0x07u, flags, 0, NULL);
1490 }
1491 } else {
1492 char *old_scope = p->scope;
1493 p->scope = tobi_xstrdup(path);
1494 scan_methods(p, &body, depth + 1);
1495 free(p->scope);
1496 p->scope = old_scope;
1497 }
1498 free(path);
1499 free(name);
1500 rd->pos = body_start + body_len;
1501 continue;
1502 }
1503 if (op == 0x15) {
1504 char *name = NULL;
1505 if (tobi_nm_namestring(rd, &name)) {
1506 uint8_t obj_type = 0;
1507 uint8_t argc = 0;
1508 if (tobi_rd_u8(rd, &obj_type) && tobi_rd_u8(rd, &argc) && obj_type == 0x08u) {
1509 char *path = tobi_nm_resolve(p->scope, name);
1510 ns_declare_path(p, TOBI_NS_METHOD, path, abs_start, argc & 0x07u, obj_type, 1, NULL);
1511 free(path);
1512 }
1513 free(name);
1514 }
1515 continue;
1516 }
1517 if (op == 0x5b) {
1518 uint8_t ext = 0;
1519 if (!tobi_rd_u8(rd, &ext)) {
1520 return;
1521 }
1522 if (ext == 0x82 || ext == 0x83 || ext == 0x84 || ext == 0x85) {
1523 size_t body_start = 0;
1524 size_t body_len = 0;
1525 if (!scan_pkg_window(rd, &body_start, &body_len)) {
1526 rd->pos = rd->len;
1527 return;
1528 }
1529 tobi_rd body;
1530 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
1531 char *name = NULL;
1532 if (tobi_nm_namestring(&body, &name)) {
1533 char *path = tobi_nm_resolve(p->scope, name);
1534 if (ext == 0x83) {
1535 (void)tobi_rd_skip(&body, 6);
1536 } else if (ext == 0x84) {
1537 (void)tobi_rd_skip(&body, 2);
1538 }
1539 char *old_scope = p->scope;
1540 p->scope = tobi_xstrdup(path);
1541 scan_methods(p, &body, depth + 1);
1542 free(p->scope);
1543 p->scope = old_scope;
1544 free(path);
1545 free(name);
1546 }
1547 rd->pos = body_start + body_len;
1548 continue;
1549 }
1550 if (ext == 0x81 || ext == 0x86 || ext == 0x87) {
1551 size_t body_start = 0;
1552 size_t body_len = 0;
1553 if (scan_pkg_window(rd, &body_start, &body_len)) {
1554 rd->pos = body_start + body_len;
1555 }
1556 continue;
1557 }
1558 }
1559 if (op == 0x11 || op == 0x12 || op == 0x13 || op == 0xa0 || op == 0xa1 || op == 0xa2) {
1560 size_t body_start = 0;
1561 size_t body_len = 0;
1562 if (scan_pkg_window(rd, &body_start, &body_len)) {
1563 if (op == 0xa0 || op == 0xa1 || op == 0xa2) {
1564 tobi_rd body;
1565 tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start);
1566 scan_methods(p, &body, depth + 1);
1567 }
1568 rd->pos = body_start + body_len;
1569 continue;
1570 }
1571 }
1572 if (rd->pos == start) {
1573 rd->pos++;
1574 }
1575 }
1576}
1577
1578static void sem_diag_source(parser *p, size_t off, const char *source, size_t input_index,
1579 int has_source, const char *fmt, ...) {
1580 va_list ap;
1581 va_start(ap, fmt);
1582 va_list cp;
1583 va_copy(cp, ap);
1584 int n = vsnprintf(NULL, 0, fmt, cp);
1585 va_end(cp);
1586 if (n < 0) {
1587 n = 0;
1588 }
1589 char *msg = tobi_xmalloc((size_t)n + 1);
1590 (void)vsnprintf(msg, (size_t)n + 1, fmt, ap);
1591 va_end(ap);
1592 tobi_diag_add_source(p->diag, p->strict ? TOBI_DIAG_ERROR : TOBI_DIAG_WARN, off,
1593 source, input_index, has_source, "%s", msg);
1594 free(msg);
1595}
1596
1597static void sem_diag_node(parser *p, const tobi_ir *n, const char *fmt, ...) {
1598 va_list ap;
1599 va_start(ap, fmt);
1600 va_list cp;
1601 va_copy(cp, ap);
1602 int sz = vsnprintf(NULL, 0, fmt, cp);
1603 va_end(cp);
1604 if (sz < 0) {
1605 sz = 0;
1606 }
1607 char *msg = tobi_xmalloc((size_t)sz + 1);
1608 (void)vsnprintf(msg, (size_t)sz + 1, fmt, ap);
1609 va_end(ap);
1610 sem_diag_source(p, n ? n->off : 0, n ? n->source : p->source,
1611 n ? n->input_index : p->input_index,
1612 n ? n->has_source : p->has_source, "%s", msg);
1613 free(msg);
1614}
1615
1616static int semantic_alias_cycle_from(const tobi_ns *ns, const tobi_ns_ent *start) {
1617 const tobi_ns_ent *cur = start;
1618 for (unsigned depth = 0; cur && cur->kind == TOBI_NS_ALIAS && cur->target; depth++) {
1619 if (depth > ns->len) {
1620 return 1;
1621 }
1622 const tobi_ns_ent *next = tobi_ns_find_const(ns, cur->target);
1623 if (!next) {
1624 return 0;
1625 }
1626 if (next == start) {
1627 return 1;
1628 }
1629 cur = next;
1630 }
1631 return 0;
1632}
1633
1634static int semantic_ref_is_value(const tobi_ir *parent) {
1635 if (!parent) {
1636 return 1;
1637 }
1638 if (parent->kind == TOBI_IR_EXPR && parent->name) {
1639 if (strncmp(parent->name, "create_", 7) == 0 || strcmp(parent->name, "cond_ref_of") == 0 ||
1640 strcmp(parent->name, "load") == 0 || strcmp(parent->name, "ref_of") == 0 ||
1641 strcmp(parent->name, "acquire") == 0 || strcmp(parent->name, "wait") == 0 ||
1642 strcmp(parent->name, "signal") == 0 || strcmp(parent->name, "reset") == 0 ||
1643 strcmp(parent->name, "release") == 0) {
1644 return 0;
1645 }
1646 }
1647 return 1;
1648}
1649
1650static void semantic_walk(parser *p, const tobi_ir *n, const tobi_ir *parent) {
1651 if (!n) {
1652 return;
1653 }
1654 if (n->kind == TOBI_IR_CALL && n->path && n->path[0] == '\\') {
1655 const tobi_ns_ent *ent = tobi_ns_find_const(p->ns, n->path);
1656 if (!ent || ent->kind != TOBI_NS_METHOD) {
1657 sem_diag_node(p, n, "unresolved AML method call %s", n->path);
1658 } else if (n->child_len != (size_t)ent->args) {
1659 sem_diag_node(p, n, "AML method call %s has %zu args but namespace declares %u",
1660 n->path, n->child_len, ent->args);
1661 }
1662 } else if (n->kind == TOBI_IR_REF && n->path && n->path[0] == '\\' && semantic_ref_is_value(parent)) {
1663 const tobi_ns_ent *ent = tobi_ns_find_const(p->ns, n->path);
1664 if (!ent) {
1665 sem_diag_node(p, n, "unresolved AML namespace reference %s", n->path);
1666 }
1667 }
1668 for (size_t i = 0; i < n->child_len; i++) {
1669 semantic_walk(p, n->child[i], n);
1670 }
1671}
1672
1673typedef struct method_state {
1674 parser *p;
1675 tobi_ir *method;
1676 uint16_t locals;
1677 uint16_t local_reads;
1678 uint16_t local_writes;
1679 uint16_t arg_reads;
1680 uint16_t arg_writes;
1681 unsigned warnings;
1682} method_state;
1683
1684static int ref_index(const tobi_ir *n, const char *prefix, unsigned max, unsigned *idx) {
1685 size_t plen = strlen(prefix);
1686 if (!n || n->kind != TOBI_IR_REF || !n->name || strncmp(n->name, prefix, plen) != 0) {
1687 return 0;
1688 }
1689 char c = n->name[plen];
1690 if (n->name[plen + 1u] != '\0' || c < '0' || c > (char)('0' + max)) {
1691 return 0;
1692 }
1693 *idx = (unsigned)(c - '0');
1694 return 1;
1695}
1696
1697static void method_mark_ref(method_state *st, tobi_ir *n, int write_target) {
1698 unsigned idx = 0;
1699 if (ref_index(n, "Local", 7, &idx)) {
1700 if (write_target) {
1701 st->locals |= (uint16_t)(1u << idx);
1702 st->local_writes |= (uint16_t)(1u << idx);
1703 tobi_ir_set_str(n, "method_symbol=local write=initialised");
1704 } else if ((st->locals & (uint16_t)(1u << idx)) == 0) {
1705 st->local_reads |= (uint16_t)(1u << idx);
1706 st->warnings++;
1707 sem_diag_node(st->p, n, "Local%u may be read before being initialised in method %s",
1708 idx, st->method->path ? st->method->path : st->method->name);
1709 tobi_ir_set_str(n, "method_symbol=local read=uninitialised");
1710 } else {
1711 st->local_reads |= (uint16_t)(1u << idx);
1712 tobi_ir_set_str(n, "method_symbol=local read=initialised");
1713 }
1714 } else if (ref_index(n, "Arg", 6, &idx)) {
1715 if (write_target) {
1716 st->arg_writes |= (uint16_t)(1u << idx);
1717 } else {
1718 st->arg_reads |= (uint16_t)(1u << idx);
1719 }
1720 if (idx >= st->method->method_args) {
1721 st->warnings++;
1722 sem_diag_node(st->p, n, "Arg%u %s exceeds declared argument count %u in method %s",
1723 idx, write_target ? "write" : "read",
1724 st->method->method_args, st->method->path ? st->method->path : st->method->name);
1725 tobi_ir_set_str(n, write_target ? "method_symbol=arg write=out_of_range" :
1726 "method_symbol=arg read=out_of_range");
1727 } else if (write_target) {
1728 tobi_ir_set_str(n, "method_symbol=arg write=declared");
1729 } else {
1730 tobi_ir_set_str(n, "method_symbol=arg read=declared");
1731 }
1732 }
1733}
1734
1735static void method_symbols_node(method_state *st, tobi_ir *n);
1736
1737static void method_symbols_block(method_state *st, tobi_ir *b) {
1738 if (!b) {
1739 return;
1740 }
1741 for (size_t i = 0; i < b->child_len; i++) {
1742 method_symbols_node(st, b->child[i]);
1743 }
1744}
1745
1746static void method_symbols_node(method_state *st, tobi_ir *n) {
1747 if (!n) {
1748 return;
1749 }
1750 if (n->kind == TOBI_IR_STORE) {
1751 if (n->child_len > 0) {
1752 method_symbols_node(st, n->child[0]);
1753 }
1754 if (n->child_len > 1) {
1755 unsigned idx = 0;
1756 if (ref_index(n->child[1], "Local", 7, &idx) || ref_index(n->child[1], "Arg", 6, &idx)) {
1757 method_mark_ref(st, n->child[1], 1);
1758 } else {
1759 method_symbols_node(st, n->child[1]);
1760 }
1761 }
1762 return;
1763 }
1764 if (n->kind == TOBI_IR_REF) {
1765 method_mark_ref(st, n, 0);
1766 return;
1767 }
1768 if (n->kind == TOBI_IR_EXPR && n->name) {
1769 size_t target_start = n->child_len;
1770 const tobi_op_expr *info = tobi_op_expr_find_name(n->name);
1771 if (info && info->target_count > 0 &&
1772 n->child_len >= (size_t)info->arg_count + info->target_count) {
1773 target_start = info->arg_count;
1774 }
1775 for (size_t i = 0; i < target_start; i++) {
1776 method_symbols_node(st, n->child[i]);
1777 }
1778 for (size_t i = target_start; i < n->child_len; i++) {
1779 unsigned idx = 0;
1780 if (ref_index(n->child[i], "Local", 7, &idx) || ref_index(n->child[i], "Arg", 6, &idx)) {
1781 method_mark_ref(st, n->child[i], 1);
1782 } else {
1783 method_symbols_node(st, n->child[i]);
1784 }
1785 }
1786 return;
1787 }
1788 if (n->kind == TOBI_IR_IF) {
1789 if (n->child_len > 0) {
1790 method_symbols_node(st, n->child[0]);
1791 }
1792 uint16_t base = st->locals;
1793 uint16_t then_locals = base;
1794 uint16_t else_locals = base;
1795 if (n->child_len > 1) {
1796 st->locals = base;
1797 method_symbols_block(st, n->child[1]);
1798 then_locals = st->locals;
1799 }
1800 if (n->child_len > 2) {
1801 st->locals = base;
1802 method_symbols_block(st, n->child[2]);
1803 else_locals = st->locals;
1804 st->locals = then_locals & else_locals;
1805 } else {
1806 st->locals = base;
1807 }
1808 return;
1809 }
1810 if (n->kind == TOBI_IR_WHILE) {
1811 if (n->child_len > 0) {
1812 method_symbols_node(st, n->child[0]);
1813 }
1814 uint16_t base = st->locals;
1815 if (n->child_len > 1) {
1816 st->locals = base;
1817 method_symbols_block(st, n->child[1]);
1818 }
1819 st->locals = base;
1820 return;
1821 }
1822 for (size_t i = 0; i < n->child_len; i++) {
1823 method_symbols_node(st, n->child[i]);
1824 }
1825}
1826
1827static void semantic_methods(parser *p, tobi_ir *n) {
1828 if (!n) {
1829 return;
1830 }
1831 if (n->kind == TOBI_IR_METHOD) {
1832 method_state st;
1833 memset(&st, 0, sizeof(st));
1834 st.p = p;
1835 st.method = n;
1836 if (n->child_len > 0) {
1837 method_symbols_block(&st, n->child[0]);
1838 }
1839 char summary[160];
1840 (void)snprintf(summary, sizeof(summary),
1841 "method_symbols args=%u local_reads=0x%x local_writes=0x%x final_locals=0x%x arg_reads=0x%x arg_writes=0x%x warnings=%u",
1842 n->method_args, st.local_reads, st.local_writes, st.locals,
1843 st.arg_reads, st.arg_writes, st.warnings);
1844 tobi_ir_set_target(n, summary);
1845 }
1846 for (size_t i = 0; i < n->child_len; i++) {
1847 semantic_methods(p, n->child[i]);
1848 }
1849}
1850
1851static void semantic_validate(parser *p, tobi_ir *root) {
1852 for (size_t i = 0; i < p->ns->len; i++) {
1853 const tobi_ns_ent *e = &p->ns->items[i];
1854 if (e->external && e->kind == TOBI_NS_METHOD) {
1855 sem_diag_source(p, e->off, e->source, e->input_index, e->has_source,
1856 "external AML method %s remains unresolved", e->path);
1857 }
1858 if (e->kind == TOBI_NS_ALIAS) {
1859 if (e->target && !tobi_ns_find_const(p->ns, e->target)) {
1860 sem_diag_source(p, e->off, e->source, e->input_index, e->has_source,
1861 "AML alias %s targets unresolved object %s", e->path, e->target);
1862 } else if (semantic_alias_cycle_from(p->ns, e)) {
1863 sem_diag_source(p, e->off, e->source, e->input_index, e->has_source,
1864 "AML alias cycle includes %s", e->path);
1865 }
1866 }
1867 }
1868 semantic_walk(p, root, NULL);
1869 semantic_methods(p, root);
1870}
1871
1872int tobi_parse(const uint8_t *data, size_t len, int strict, tobi_parse_result *out) {
1873 memset(out, 0, sizeof(*out));
1874 tobi_diag_init(&out->diag);
1875 tobi_ns_init(&out->ns);
1876 if (!detect_input(data, len, strict, &out->diag, &out->meta, "raw", 0, 1)) {
1877 return 0;
1878 }
1879 out->inputs = tobi_xcalloc(1u, sizeof(out->inputs[0]));
1880 input_meta_copy(&out->inputs[0], &out->meta);
1881 out->input_len = 1;
1882 out->root = tobi_ir_new(TOBI_IR_ROOT, out->meta.aml_off, out->meta.aml_len);
1883 tobi_ir_set_scope(out->root, "\\");
1884 tobi_ir_set_source(out->root, out->meta.source, 0);
1885 parser p;
1886 memset(&p, 0, sizeof(p));
1887 p.diag = &out->diag;
1888 p.strict = strict;
1889 p.ns = &out->ns;
1890 p.scope = tobi_xstrdup("\\");
1891 p.source = out->meta.source;
1892 p.input_index = 0;
1893 p.has_source = 1;
1894 tobi_rd rd;
1895 if (out->meta.aml_off <= len && out->meta.aml_len <= len - out->meta.aml_off) {
1896 tobi_rd scan;
1897 tobi_rd_init(&scan, data + out->meta.aml_off, out->meta.aml_len, out->meta.aml_off);
1898 scan_methods(&p, &scan, 0);
1899 tobi_rd_init(&rd, data + out->meta.aml_off, out->meta.aml_len, out->meta.aml_off);
1900 (void)parse_term_list(&p, &rd, out->root, 0);
1901 }
1902 semantic_validate(&p, out->root);
1903 tobi_ir_assign_ids(out->root);
1904 parser_free(&p);
1905 return !(strict && tobi_diag_has_error(&out->diag));
1906}
1907
1908int tobi_parse_multi(const tobi_parse_input *inputs, size_t count, int strict, tobi_parse_result *out) {
1909 memset(out, 0, sizeof(*out));
1910 tobi_diag_init(&out->diag);
1911 tobi_ns_init(&out->ns);
1912 memcpy(out->meta.signature, "MULT", 4);
1913 out->meta.signature[4] = '\0';
1914 input_meta_set_source(&out->meta, "multi");
1915 out->root = tobi_ir_new(TOBI_IR_ROOT, 0, 0);
1916 tobi_ir_set_scope(out->root, "\\");
1917 tobi_ir_set_source(out->root, "multi", 0);
1918 if (!inputs || count == 0) {
1919 tobi_diag_add(&out->diag, TOBI_DIAG_ERROR, 0, "no AML inputs");
1920 return 0;
1921 }
1922 tobi_input_meta *metas = tobi_xcalloc(count, sizeof(metas[0]));
1923 out->inputs = metas;
1924 out->input_len = count;
1925 int ok = 1;
1926 parser p;
1927 memset(&p, 0, sizeof(p));
1928 p.diag = &out->diag;
1929 p.strict = strict;
1930 p.ns = &out->ns;
1931 p.scope = tobi_xstrdup("\\");
1932 for (size_t i = 0; i < count; i++) {
1933 p.source = inputs[i].name;
1934 p.input_index = i;
1935 p.has_source = inputs[i].name != NULL;
1936 if (!detect_input(inputs[i].data, inputs[i].len, strict, &out->diag, &metas[i],
1937 inputs[i].name, i, inputs[i].name != NULL)) {
1938 ok = 0;
1939 continue;
1940 }
1941 out->meta.aml_len = tobi_xadd_size(out->meta.aml_len, metas[i].aml_len);
1942 if (metas[i].aml_off <= inputs[i].len && metas[i].aml_len <= inputs[i].len - metas[i].aml_off) {
1943 tobi_rd scan;
1944 tobi_rd_init(&scan, inputs[i].data + metas[i].aml_off, metas[i].aml_len, metas[i].aml_off);
1945 scan_methods(&p, &scan, 0);
1946 }
1947 }
1948 if (!strict || ok) {
1949 for (size_t i = 0; i < count; i++) {
1950 if (metas[i].aml_off > inputs[i].len || metas[i].aml_len > inputs[i].len - metas[i].aml_off) {
1951 continue;
1952 }
1953 p.source = inputs[i].name;
1954 p.input_index = i;
1955 p.has_source = inputs[i].name != NULL;
1956 tobi_ir *file = tobi_ir_new(TOBI_IR_BLOCK, metas[i].aml_off, metas[i].aml_len);
1957 tobi_ir_set_name(file, inputs[i].name ? inputs[i].name : "<input>");
1958 tobi_ir_set_scope(file, "\\");
1959 if (inputs[i].name) {
1960 tobi_ir_set_source(file, inputs[i].name, i);
1961 }
1962 tobi_rd rd;
1963 tobi_rd_init(&rd, inputs[i].data + metas[i].aml_off, metas[i].aml_len, metas[i].aml_off);
1964 (void)parse_term_list(&p, &rd, file, 0);
1965 tobi_ir_add(out->root, file);
1966 }
1967 }
1968 semantic_validate(&p, out->root);
1969 tobi_ir_assign_ids(out->root);
1970 parser_free(&p);
1971 return ok && !(strict && tobi_diag_has_error(&out->diag));
1972}
1973
1974void tobi_parse_result_free(tobi_parse_result *res) {
1975 tobi_ir_free(res->root);
1976 res->root = NULL;
1977 input_meta_free(&res->meta);
1978 for (size_t i = 0; i < res->input_len; i++) {
1979 input_meta_free(&res->inputs[i]);
1980 }
1981 free(res->inputs);
1982 res->inputs = NULL;
1983 res->input_len = 0;
1984 tobi_ns_free(&res->ns);
1985 tobi_diag_free(&res->diag);
1986}