diff --git a/include/struct_types.h b/include/struct_types.h index a601bcf2c4..7e3f454d7c 100644 --- a/include/struct_types.h +++ b/include/struct_types.h @@ -2736,7 +2736,8 @@ typedef struct Ent30D80 { /* 0x50 */ u8 unk50; /* 0x51 */ u8 unk51; } Ent30D80; -typedef struct { u8 _0[0x12]; u16 f12; } F20T; +typedef struct { u8 pad0[0x12]; u16 unk12; } Obj20_80178D40; +typedef Obj20_80178D40 F20T; typedef H16 H2; typedef struct { /* func_801457A4 buffer (hdr) */ s16 f0, f2, f4, f6, f8, fA; diff --git a/src/shared/engine_types.h b/src/shared/engine_types.h index 72d2cc42c2..f734733fe9 100644 --- a/src/shared/engine_types.h +++ b/src/shared/engine_types.h @@ -744,7 +744,6 @@ struct Entity_80167540 { /* 0x38 */ Mat_80167540 mat; }; typedef Blk20_8016DF5C Mat_8016A73C; -typedef struct { u8 pad0[0x12]; u16 unk12; } Obj20_80178D40; typedef struct Actor_80178D40 { s16 unk0; s16 unk2; diff --git a/src/shared/ov/func_80144B9C.h b/src/shared/ov/func_80144B9C.h index 4065f2cff6..41cd893759 100644 --- a/src/shared/ov/func_80144B9C.h +++ b/src/shared/ov/func_80144B9C.h @@ -26,7 +26,7 @@ * 2. STRUCT-MEMBER FOLD: at -O0, `struct->field` folds to `lw off(reg)`, but * `*(T*)(base+const)` AND array `b[k]` both MATERIALIZE an extra `addu`. All * bases (p10/p14/p18/p1C/hdr/ent) must be struct pointers with exact offsets. - * p10->f20 is a pointer-to-struct field: `p10->f20->f12` folds to `sh 0x12(reg)`. + * p10->f20 is a pointer-to-struct field: `p10->f20->unk12` folds to `sh 0x12(reg)`. * 3. Chained store `a->x = a->y = a->z = SRC;` reproduces the -O0 shared-value * multi-store (one load, base addresses computed left-to-right, stored C..A). * 4. Loop counter `for(i=0;i<8;i++)` (i++ NOT i=i+1): i++ emits the extra @@ -155,19 +155,19 @@ void func_80144B9C(void) { p10->f6 = p10->f88 = p10->f90 = p18->f20; p10->fA = p10->f8A = p10->f92 = p18->f22; p10->fE = p10->f8C = p10->f94 = p18->f24; - p10->f20->f12 = p18->f26; + p10->f20->unk12 = p18->f26; p18->f18 = 0; } else { p10->f6 = p10->f88 = p10->f90 = ent->f10; p10->fA = p10->f8A = p10->f92 = ent->f12; p10->fE = p10->f8C = p10->f94 = ent->f14; - p10->f20->f12 = ent->f16; + p10->f20->unk12 = ent->f16; } p10->f154 = p10->f15C = p10->f6; p10->f156 = p10->f15E = p10->fA; p10->f158 = p10->f160 = p10->fE; func_80015978((void *)((s32)p10 + 4), D_80126DB0); - D_80126DB6 = p10->f20->f12; + D_80126DB6 = p10->f20->unk12; if (*(s16 *)(p + 0xA3DA) == 0x1000 && (*(s16 *)(p + 0xA3DC) == 0x1000 || *(s16 *)(p + 0xA3DC) == 0x2000) && flag == 0) { ent->f18(); diff --git a/tools/type_census.py b/tools/type_census.py index fef9ba15e7..82d5ce63f9 100644 --- a/tools/type_census.py +++ b/tools/type_census.py @@ -1130,6 +1130,24 @@ def structs_reading(results, defs_all, td_all, dup_rows, dup_full_names, twins_t ALIGNED_RX = re.compile(r"aligned\s*\(\s*(\d+)\s*\)") +def n_types(defs): + """Distinct types among layout-class defs (P38 T5.1.c8): a def's tag and its typedef names are ONE type + (`typedef struct actor4c {…} Actor4C;` was counted as two names); names shared across defs join their types.""" + parent = {} + def find(x): + while parent.setdefault(x, x) != x: + parent[x] = parent[parent[x]] + x = parent[x] + return x + for d in defs: + own = list(d["names"]) + ([d["tag"]] if d["tag"] else []) + for n in own[1:]: + parent[find(n)] = find(own[0]) + if own: + find(own[0]) + return len({find(n) for n in parent}) + + def def_layout_hash(res, d, lay): """The def's layout hash: layout_hash(lay), re-keyed with the alignment when the def's own `packed` / `aligned(N)` moves it off the natural one (P38 T5.1.c6: R22 proved packed and natural twins different layouts — a2e73 → packed_word 70/218, @@ -1221,12 +1239,15 @@ def run_census(jobs, use_cache=True, out_dir=OUT_DIR_DEFAULT, want_sites=False): nm = set() for d in ds: nm |= names_of(d) - if len(nm) <= 1: + if n_types(ds) <= 1: # T5.1.c8: a def's tag and typedef name are one type, not two names continue sigs = collections.defaultdict(set) + sig_defs = collections.defaultdict(list) for d in ds: sg = sig_of(d) - sigs[("*",) if all(x == "*" for x in sg) else sg] |= names_of(d) + sk = ("*",) if all(x == "*" for x in sg) else sg + sigs[sk] |= names_of(d) + sig_defs[sk].append(d) # tier 1/2 merge: every wildcard-only definition joins the class; distinct meaningful signatures are separate types meaningful = {sg: nms for sg, nms in sigs.items() if sg != ("*",)} opaque = sigs.get(("*",), set()) @@ -1244,12 +1265,12 @@ def run_census(jobs, use_cache=True, out_dir=OUT_DIR_DEFAULT, want_sites=False): layout_twins.append(rec) # names WITHIN one meaningful camp are still duplicates of each other for sg, nms in meaningful.items(): - if len(nms) > 1: + if n_types(sig_defs[sg]) > 1: dup_layout_classes.append(dict(lhash=lh + ":" + hashlib.sha1(repr(sg).encode()).hexdigest()[:6], size=ds[0]["size"], n_names=len(nms), n_defs=sum(1 for d in ds if sig_of(d) == sg), canon=0, in_c=0, names=sorted(nms)[:40], tier=1)) dup_full_names[dup_layout_classes[-1]["lhash"]] = nms - if len(opaque) > 1: + if n_types(sig_defs[("*",)]) > 1: dup_layout_classes.append(dict(lhash=lh + ":opaque", size=ds[0]["size"], n_names=len(opaque), n_defs=0, canon=0, in_c=0, names=sorted(opaque)[:40], tier=2)) dup_full_names[lh + ":opaque"] = opaque @@ -1788,6 +1809,9 @@ def selftest(): ah = {d["names"][0]: def_layout_hash(ar, d, ar.layout_of_fields(d["fields"], d["kind"], packed=("packed" in d["attrs"]))) for d in ares["definitions"]} checks.append(("packed twin apart, natural aligned() together", ah["PkW"] != ah["NatW"] and ah["AlW"] == ah["NatW"])) + # T5.1.c8: a def's tag + typedef name are one type; two defs with different names are two + tres = walk_file("typedef struct actor4c { s32 a; } Actor4C;\ntypedef struct { s32 a; } Other4;\n", "src/ov_TEST/tg.c") + checks.append(("tag+typedef one type", n_types(tres["definitions"][:1]) == 1 and n_types(tres["definitions"]) == 2)) # T5.1: the --check-structs reading (liveness, allowlists, block frames) cres = walk_file(CANON_FIXTURE, "src/shared/engine_types.h") ures = walk_file(USE_FIXTURE, "src/ov_TEST/u.c")