Repository: matt-kempster/m2c
Pull request #286: Added a pattern for load load store store struct copy on PPC
Author: login: dbalatoni13
Created: 2025-03-20T20:49:01Z | Merged: 2025-03-23T21:51:23Z

With a new test case. If I'm not mistaken, this shouldn't have false posivites.

Being able to recognize struct copy with structs smaller than 8 bytes would require editing code somewhere else and in many cases having a correct context.

I wonder if the change to `parser` is fine.


Diff:
diff --git a/m2c/arch_ppc.py b/m2c/arch_ppc.py
index c6c9ac8e..9b06f621 100644
--- a/m2c/arch_ppc.py
+++ b/m2c/arch_ppc.py
@@ -265,6 +265,83 @@ def replace(self, m: AsmMatch) -> Optional[Replacement]:
         )
 
 
+class StructCopyPattern(AsmPattern):
+    """Recognizing struct copy when it starts with lwz lwz stw stw. Others
+    would cause false positves. Maybe we can find another way for those using
+    context?
+    This pattern appears on almost every GC and Wii MW compiler version when using C
+    and GC MW 1.0-1.2.5n when using C++.
+    """
+
+    pattern = make_pattern(
+        "lwz $a, I($s)",
+        "lwz $b, (I+4)($s)",
+        "stw $a, I($d)",
+        "stw $b, (I+4)($d)",
+    )
+
+    def match(self, matcher: AsmMatcher) -> Optional[Replacement]:
+        # Use the initial patterns first
+        m = matcher.try_match(self.pattern)
+        if m is None:
+            return None
+        i = 8
+        pattern_ext = self.pattern.copy()
+        while True:
+            pattern2 = make_pattern(
+                f"lwz $a, (I+{i})($s)",
+                f"lwz $b, (I+{i+4})($s)",
+                f"stw $a, (I+{i})($d)",
+                f"stw $b, (I+{i+4})($d)",
+            )
+
+            m2 = matcher.try_match(pattern_ext + pattern2)
+            if m2:
+                m = m2
+                i += 8
+                pattern_ext.extend(pattern2)
+            else:
+                # Unaligned struct
+                pattern_end_4b = make_pattern(
+                    f"lwz $b, (I+{i})($s)",
+                    f"stw $b, (I+{i})($d)",
+                )
+                m_end = matcher.try_match(pattern_ext + pattern_end_4b)
+                if m_end:
+                    m = m_end
+                    i += 4
+                    pattern_ext.extend(pattern_end_4b)
+
+                pattern_end_2b = make_pattern(
+                    f"lhz $b, (I+{i})($s)",
+                    f"sth $b, (I+{i})($d)",
+                )
+                m_end = matcher.try_match(pattern_ext + pattern_end_2b)
+                if m_end:
+                    m = m_end
+                    i += 2
+                    pattern_ext.extend(pattern_end_2b)
+
+                pattern_end_1b = make_pattern(
+                    f"lbz $b, (I+{i})($s)",
+                    f"stb $b, (I+{i})($d)",
+                )
+                m_end = matcher.try_match(pattern_ext + pattern_end_1b)
+                if m_end:
+                    m = m_end
+                    i += 1
+                break
+
+        return Replacement(
+            [
+                AsmInstruction(
+                    "structcopy.fictive", [m.regs["d"], m.regs["s"], AsmLiteral(i)]
+                )
+            ],
+            len(m.body),
+        )
+
+
 class FloatishToSintIrPattern(IrPattern):
     # This pattern handles converting either f32 or f64 into a signed int
     # The `fctiwz` instruction does all the work; this pattern is just to
@@ -926,7 +1003,8 @@ def eval_fn(s: NodeState, a: InstrArgs) -> None:
             # TODO: These are only supported in function prologues/epilogues
             eval_fn = None
         elif mnemonic in cls.instrs_no_dest:
-            assert not any(isinstance(a, (Register, AsmAddressMode)) for a in args)
+            assert not any(isinstance(a, (AsmAddressMode)) for a in args)
+            inputs = [r for r in args if isinstance(r, Register)]
             eval_fn = lambda s, a: s.write_statement(cls.instrs_no_dest[mnemonic](a))
         elif mnemonic.rstrip(".") in cls.instrs_destination_first:
             assert isinstance(args[0], Register)
@@ -1075,6 +1153,7 @@ def eval_fn(s: NodeState, a: InstrArgs) -> None:
         BoolCastPattern(),
         BranchCtrPattern(),
         FloatishToUintPattern(),
+        StructCopyPattern(),
     ]
 
     instrs_ignore: Set[str] = {
@@ -1171,6 +1250,9 @@ def eval_fn(s: NodeState, a: InstrArgs) -> None:
     instrs_no_dest: StmtInstrMap = {
         "sync": lambda a: void_fn_op("M2C_SYNC", []),
         "isync": lambda a: void_fn_op("M2C_SYNC", []),
+        "structcopy.fictive": lambda a: void_fn_op(
+            "M2C_STRUCT_COPY", [a.reg(0), a.reg(1), a.imm(2)]
+        ),
     }
 
     instrs_dest_first_non_load: InstrMap = {
diff --git a/m2c_macros.h b/m2c_macros.h
index 19ce66b5..93c9eb51 100644
--- a/m2c_macros.h
+++ b/m2c_macros.h
@@ -47,5 +47,6 @@ typedef s64 M2C_UNK64;
 /* Memcpy patterns */
 #define M2C_MEMCPY_ALIGNED memcpy
 #define M2C_MEMCPY_UNALIGNED memcpy
+#define M2C_STRUCT_COPY memcpy
 
 #endif
diff --git a/tests/end_to_end/struct-copy2/mwcc-o0p-flags.txt b/tests/end_to_end/struct-copy2/mwcc-o0p-flags.txt
new file mode 100644
index 00000000..5b39f1ba
--- /dev/null
+++ b/tests/end_to_end/struct-copy2/mwcc-o0p-flags.txt
@@ -0,0 +1 @@
+--target ppc-mwcc-c -f test_0 -f test_1 -f test_2 -f test_3 -f test_4 -f test_5 -f test_6 -f test_7
diff --git a/tests/end_to_end/struct-copy2/mwcc-o0p-out.c b/tests/end_to_end/struct-copy2/mwcc-o0p-out.c
new file mode 100644
index 00000000..ea936349
--- /dev/null
+++ b/tests/end_to_end/struct-copy2/mwcc-o0p-out.c
@@ -0,0 +1,39 @@
+static ? d7;
+static ? s7;
+
+void test(void) {
+
+}
+
+void test_0(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 8);
+}
+
+void test_1(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 9);
+}
+
+void test_2(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xA);
+}
+
+void test_3(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xB);
+}
+
+void test_4(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xC);
+}
+
+void test_5(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xD);
+}
+
+void test_6(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xE);
+}
+
+void test_7(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xF);
+    M2C_STRUCT_COPY(&d7, &s7, 0xF);
+}
diff --git a/tests/end_to_end/struct-copy2/mwcc-o0p.s b/tests/end_to_end/struct-copy2/mwcc-o0p.s
new file mode 100644
index 00000000..94fe4a6b
--- /dev/null
+++ b/tests/end_to_end/struct-copy2/mwcc-o0p.s
@@ -0,0 +1,126 @@
+.include "macros.inc"
+
+.section .text  # 0x0 - 0x13c
+
+.global test_0
+test_0:
+/* 00000000 00000000  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000004 00000004  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000008 00000008  90 A3 00 00 */	stw r5, 0(r3)
+/* 0000000C 0000000C  90 03 00 04 */	stw r0, 4(r3)
+/* 00000010 00000010  4E 80 00 20 */	blr 
+
+.global test_1
+test_1:
+/* 00000014 00000014  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000018 00000018  80 04 00 04 */	lwz r0, 4(r4)
+/* 0000001C 0000001C  90 A3 00 00 */	stw r5, 0(r3)
+/* 00000020 00000020  90 03 00 04 */	stw r0, 4(r3)
+/* 00000024 00000024  88 04 00 08 */	lbz r0, 8(r4)
+/* 00000028 00000028  98 03 00 08 */	stb r0, 8(r3)
+/* 0000002C 0000002C  4E 80 00 20 */	blr 
+
+.global test_2
+test_2:
+/* 00000030 00000030  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000034 00000034  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000038 00000038  90 A3 00 00 */	stw r5, 0(r3)
+/* 0000003C 0000003C  90 03 00 04 */	stw r0, 4(r3)
+/* 00000040 00000040  A0 04 00 08 */	lhz r0, 8(r4)
+/* 00000044 00000044  B0 03 00 08 */	sth r0, 8(r3)
+/* 00000048 00000048  4E 80 00 20 */	blr 
+
+.global test_3
+test_3:
+/* 0000004C 0000004C  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000050 00000050  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000054 00000054  90 A3 00 00 */	stw r5, 0(r3)
+/* 00000058 00000058  90 03 00 04 */	stw r0, 4(r3)
+/* 0000005C 0000005C  A0 04 00 08 */	lhz r0, 8(r4)
+/* 00000060 00000060  B0 03 00 08 */	sth r0, 8(r3)
+/* 00000064 00000064  88 04 00 0A */	lbz r0, 0xa(r4)
+/* 00000068 00000068  98 03 00 0A */	stb r0, 0xa(r3)
+/* 0000006C 0000006C  4E 80 00 20 */	blr 
+
+.global test_4
+test_4:
+/* 00000070 00000070  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000074 00000074  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000078 00000078  90 A3 00 00 */	stw r5, 0(r3)
+/* 0000007C 0000007C  90 03 00 04 */	stw r0, 4(r3)
+/* 00000080 00000080  80 04 00 08 */	lwz r0, 8(r4)
+/* 00000084 00000084  90 03 00 08 */	stw r0, 8(r3)
+/* 00000088 00000088  4E 80 00 20 */	blr 
+
+.global test_5
+test_5:
+/* 0000008C 0000008C  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000090 00000090  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000094 00000094  90 A3 00 00 */	stw r5, 0(r3)
+/* 00000098 00000098  90 03 00 04 */	stw r0, 4(r3)
+/* 0000009C 0000009C  80 04 00 08 */	lwz r0, 8(r4)
+/* 000000A0 000000A0  90 03 00 08 */	stw r0, 8(r3)
+/* 000000A4 000000A4  88 04 00 0C */	lbz r0, 0xc(r4)
+/* 000000A8 000000A8  98 03 00 0C */	stb r0, 0xc(r3)
+/* 000000AC 000000AC  4E 80 00 20 */	blr 
+
+.global test_6
+test_6:
+/* 000000B0 000000B0  80 A4 00 00 */	lwz r5, 0(r4)
+/* 000000B4 000000B4  80 04 00 04 */	lwz r0, 4(r4)
+/* 000000B8 000000B8  90 A3 00 00 */	stw r5, 0(r3)
+/* 000000BC 000000BC  90 03 00 04 */	stw r0, 4(r3)
+/* 000000C0 000000C0  80 04 00 08 */	lwz r0, 8(r4)
+/* 000000C4 000000C4  90 03 00 08 */	stw r0, 8(r3)
+/* 000000C8 000000C8  A0 04 00 0C */	lhz r0, 0xc(r4)
+/* 000000CC 000000CC  B0 03 00 0C */	sth r0, 0xc(r3)
+/* 000000D0 000000D0  4E 80 00 20 */	blr 
+
+.global test_7
+test_7:
+/* 000000D4 000000D4  80 A4 00 00 */	lwz r5, 0(r4)
+/* 000000D8 000000D8  80 04 00 04 */	lwz r0, 4(r4)
+/* 000000DC 000000DC  90 A3 00 00 */	stw r5, 0(r3)
+/* 000000E0 000000E0  90 03 00 04 */	stw r0, 4(r3)
+/* 000000E4 000000E4  80 04 00 08 */	lwz r0, 8(r4)
+/* 000000E8 000000E8  90 03 00 08 */	stw r0, 8(r3)
+/* 000000EC 000000EC  A0 04 00 0C */	lhz r0, 0xc(r4)
+/* 000000F0 000000F0  B0 03 00 0C */	sth r0, 0xc(r3)
+/* 000000F4 000000F4  88 04 00 0E */	lbz r0, 0xe(r4)
+/* 000000F8 000000F8  98 03 00 0E */	stb r0, 0xe(r3)
+/* 000000FC 000000FC  3C A0 00 00 */	lis r5, d7@ha
+/* 00000100 00000100  38 E5 00 00 */	addi r7, r5, d7@l
+/* 00000104 00000104  3C A0 00 00 */	lis r5, s7@ha
+/* 00000108 00000108  38 C5 00 00 */	addi r6, r5, s7@l
+/* 0000010C 0000010C  80 A6 00 00 */	lwz r5, 0(r6)
+/* 00000110 00000110  80 06 00 04 */	lwz r0, 4(r6)
+/* 00000114 00000114  90 A7 00 00 */	stw r5, 0(r7)
+/* 00000118 00000118  90 07 00 04 */	stw r0, 4(r7)
+/* 0000011C 0000011C  80 06 00 08 */	lwz r0, 8(r6)
+/* 00000120 00000120  90 07 00 08 */	stw r0, 8(r7)
+/* 00000124 00000124  A0 06 00 0C */	lhz r0, 0xc(r6)
+/* 00000128 00000128  B0 07 00 0C */	sth r0, 0xc(r7)
+/* 0000012C 0000012C  88 06 00 0E */	lbz r0, 0xe(r6)
+/* 00000130 00000130  98 07 00 0E */	stb r0, 0xe(r7)
+/* 00000134 00000134  4E 80 00 20 */	blr 
+
+.global test
+test:
+/* 00000138 00000138  4E 80 00 20 */	blr 
+
+.section .bss  # 0x0 - 0x1f
+
+.global d7
+d7:
+	.word 0x00000000
+	.word 0x00000000
+	.word 0x00000000
+	.word 0x00000000
+
+.global s7
+s7:
+	.word 0x00000000
+	.word 0x00000000
+	.word 0x00000000
+	.bytes 0x0, 0x0, 0x0
+
diff --git a/tests/end_to_end/struct-copy2/mwcc-o4p-flags.txt b/tests/end_to_end/struct-copy2/mwcc-o4p-flags.txt
new file mode 100644
index 00000000..5b39f1ba
--- /dev/null
+++ b/tests/end_to_end/struct-copy2/mwcc-o4p-flags.txt
@@ -0,0 +1 @@
+--target ppc-mwcc-c -f test_0 -f test_1 -f test_2 -f test_3 -f test_4 -f test_5 -f test_6 -f test_7
diff --git a/tests/end_to_end/struct-copy2/mwcc-o4p-out.c b/tests/end_to_end/struct-copy2/mwcc-o4p-out.c
new file mode 100644
index 00000000..4c77b084
--- /dev/null
+++ b/tests/end_to_end/struct-copy2/mwcc-o4p-out.c
@@ -0,0 +1,47 @@
+static ? d7;
+static ? s7;
+
+void test(void) {
+
+}
+
+void test_0(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 8);
+}
+
+void test_1(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 9);
+}
+
+void test_2(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xA);
+}
+
+void test_3(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xB);
+}
+
+void test_4(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xC);
+}
+
+void test_5(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xD);
+}
+
+void test_6(s32 arg0, s32 arg1) {
+    M2C_STRUCT_COPY(arg0, arg1, 0xE);
+}
+
+void test_7(void *arg0, void *arg1) {
+    arg0->unk0 = (s32) arg1->unk0;
+    arg0->unk4 = (s32) arg1->unk4;
+    arg0->unk8 = (s32) arg1->unk8;
+    arg0->unkC = (u16) arg1->unkC;
+    arg0->unkE = (u8) arg1->unkE;
+    d7.unk0 = (s32) s7.unk0;
+    d7.unk4 = (s32) s7.unk4;
+    d7.unk8 = (s32) s7.unk8;
+    d7.unkC = (u16) s7.unkC;
+    d7.unkE = (u8) s7.unkE;
+}
diff --git a/tests/end_to_end/struct-copy2/mwcc-o4p.s b/tests/end_to_end/struct-copy2/mwcc-o4p.s
new file mode 100644
index 00000000..3832fd9c
--- /dev/null
+++ b/tests/end_to_end/struct-copy2/mwcc-o4p.s
@@ -0,0 +1,126 @@
+.include "macros.inc"
+
+.section .text  # 0x0 - 0x13c
+
+.global test_0
+test_0:
+/* 00000000 00000000  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000004 00000004  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000008 00000008  90 A3 00 00 */	stw r5, 0(r3)
+/* 0000000C 0000000C  90 03 00 04 */	stw r0, 4(r3)
+/* 00000010 00000010  4E 80 00 20 */	blr 
+
+.global test_1
+test_1:
+/* 00000014 00000014  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000018 00000018  80 04 00 04 */	lwz r0, 4(r4)
+/* 0000001C 0000001C  90 A3 00 00 */	stw r5, 0(r3)
+/* 00000020 00000020  90 03 00 04 */	stw r0, 4(r3)
+/* 00000024 00000024  88 04 00 08 */	lbz r0, 8(r4)
+/* 00000028 00000028  98 03 00 08 */	stb r0, 8(r3)
+/* 0000002C 0000002C  4E 80 00 20 */	blr 
+
+.global test_2
+test_2:
+/* 00000030 00000030  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000034 00000034  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000038 00000038  90 A3 00 00 */	stw r5, 0(r3)
+/* 0000003C 0000003C  90 03 00 04 */	stw r0, 4(r3)
+/* 00000040 00000040  A0 04 00 08 */	lhz r0, 8(r4)
+/* 00000044 00000044  B0 03 00 08 */	sth r0, 8(r3)
+/* 00000048 00000048  4E 80 00 20 */	blr 
+
+.global test_3
+test_3:
+/* 0000004C 0000004C  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000050 00000050  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000054 00000054  90 A3 00 00 */	stw r5, 0(r3)
+/* 00000058 00000058  90 03 00 04 */	stw r0, 4(r3)
+/* 0000005C 0000005C  A0 04 00 08 */	lhz r0, 8(r4)
+/* 00000060 00000060  B0 03 00 08 */	sth r0, 8(r3)
+/* 00000064 00000064  88 04 00 0A */	lbz r0, 0xa(r4)
+/* 00000068 00000068  98 03 00 0A */	stb r0, 0xa(r3)
+/* 0000006C 0000006C  4E 80 00 20 */	blr 
+
+.global test_4
+test_4:
+/* 00000070 00000070  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000074 00000074  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000078 00000078  90 A3 00 00 */	stw r5, 0(r3)
+/* 0000007C 0000007C  90 03 00 04 */	stw r0, 4(r3)
+/* 00000080 00000080  80 04 00 08 */	lwz r0, 8(r4)
+/* 00000084 00000084  90 03 00 08 */	stw r0, 8(r3)
+/* 00000088 00000088  4E 80 00 20 */	blr 
+
+.global test_5
+test_5:
+/* 0000008C 0000008C  80 A4 00 00 */	lwz r5, 0(r4)
+/* 00000090 00000090  80 04 00 04 */	lwz r0, 4(r4)
+/* 00000094 00000094  90 A3 00 00 */	stw r5, 0(r3)
+/* 00000098 00000098  90 03 00 04 */	stw r0, 4(r3)
+/* 0000009C 0000009C  80 04 00 08 */	lwz r0, 8(r4)
+/* 000000A0 000000A0  90 03 00 08 */	stw r0, 8(r3)
+/* 000000A4 000000A4  88 04 00 0C */	lbz r0, 0xc(r4)
+/* 000000A8 000000A8  98 03 00 0C */	stb r0, 0xc(r3)
+/* 000000AC 000000AC  4E 80 00 20 */	blr 
+
+.global test_6
+test_6:
+/* 000000B0 000000B0  80 A4 00 00 */	lwz r5, 0(r4)
+/* 000000B4 000000B4  80 04 00 04 */	lwz r0, 4(r4)
+/* 000000B8 000000B8  90 A3 00 00 */	stw r5, 0(r3)
+/* 000000BC 000000BC  90 03 00 04 */	stw r0, 4(r3)
+/* 000000C0 000000C0  80 04 00 08 */	lwz r0, 8(r4)
+/* 000000C4 000000C4  90 03 00 08 */	stw r0, 8(r3)
+/* 000000C8 000000C8  A0 04 00 0C */	lhz r0, 0xc(r4)
+/* 000000CC 000000CC  B0 03 00 0C */	sth r0, 0xc(r3)
+/* 000000D0 000000D0  4E 80 00 20 */	blr 
+
+.global test_7
+test_7:
+/* 000000D4 000000D4  81 04 00 00 */	lwz r8, 0(r4)
+/* 000000D8 000000D8  3C A0 00 00 */	lis r5, s7@ha
+/* 000000DC 000000DC  80 04 00 04 */	lwz r0, 4(r4)
+/* 000000E0 000000E0  3C C0 00 00 */	lis r6, d7@ha
+/* 000000E4 000000E4  38 E5 00 00 */	addi r7, r5, s7@l
+/* 000000E8 000000E8  91 03 00 00 */	stw r8, 0(r3)
+/* 000000EC 000000EC  39 06 00 00 */	addi r8, r6, d7@l
+/* 000000F0 000000F0  90 03 00 04 */	stw r0, 4(r3)
+/* 000000F4 000000F4  80 04 00 08 */	lwz r0, 8(r4)
+/* 000000F8 000000F8  90 03 00 08 */	stw r0, 8(r3)
+/* 000000FC 000000FC  A0 04 00 0C */	lhz r0, 0xc(r4)
+/* 00000100 00000100  B0 03 00 0C */	sth r0, 0xc(r3)
+/* 00000104 00000104  88 04 00 0E */	lbz r0, 0xe(r4)
+/* 00000108 00000108  98 03 00 0E */	stb r0, 0xe(r3)
+/* 0000010C 0000010C  80 C7 00 00 */	lwz r6, 0(r7)
+/* 00000110 00000110  80 A7 00 04 */	lwz r5, 4(r7)
+/* 00000114 00000114  80 87 00 08 */	lwz r4, 8(r7)
+/* 00000118 00000118  A0 67 00 0C */	lhz r3, 0xc(r7)
+/* 0000011C 0000011C  88 07 00 0E */	lbz r0, 0xe(r7)
+/* 00000120 00000120  90 C8 00 00 */	stw r6, 0(r8)
+/* 00000124 00000124  90 A8 00 04 */	stw r5, 4(r8)
+/* 00000128 00000128  90 88 00 08 */	stw r4, 8(r8)
+/* 0000012C 0000012C  B0 68 00 0C */	sth r3, 0xc(r8)
+/* 00000130 00000130  98 08 00 0E */	stb r0, 0xe(r8)
+/* 00000134 00000134  4E 80 00 20 */	blr 
+
+.global test
+test:
+/* 00000138 00000138  4E 80 00 20 */	blr 
+
+.section .bss  # 0x0 - 0x1f
+
+.global d7
+d7:
+	.word 0x00000000
+	.word 0x00000000
+	.word 0x00000000
+	.word 0x00000000
+
+.global s7
+s7:
+	.word 0x00000000
+	.word 0x00000000
+	.word 0x00000000
+	.bytes 0x0, 0x0, 0x0
+
diff --git a/tests/end_to_end/struct-copy2/orig.c b/tests/end_to_end/struct-copy2/orig.c
new file mode 100644
index 00000000..b7e9bef0
--- /dev/null
+++ b/tests/end_to_end/struct-copy2/orig.c
@@ -0,0 +1,94 @@
+#pragma push
+#pragma pack(1)
+
+typedef struct Test0 {
+	int a;
+	int b;
+} Test0;
+
+typedef struct Test1 {
+	int a;
+	int b;
+	char c;
+} Test1;
+
+typedef struct Test2 {
+	int a;
+	int b;
+	short c;
+} Test2;
+
+typedef struct Test3 {
+	int a;
+	int b;
+	short c;
+	char d;
+} Test3;
+
+typedef struct Test4 {
+	int a;
+	int b;
+	int c;
+} Test4;
+
+typedef struct Test5 {
+	int a;
+	int b;
+	int c;
+	char d;
+} Test5;
+
+typedef struct Test6 {
+	int a;
+	int b;
+	int c;
+	short d;
+} Test6;
+
+typedef struct Test7 {
+	int a;
+	int b;
+	int c;
+	short d;
+	char e;
+} Test7;
+
+Test7 s7;
+Test7 d7;
+
+#pragma pop
+
+void test_0(Test0 *a, Test0 *b) {
+	*a = *b;
+}
+
+void test_1(Test1 *a, Test1 *b) {
+	*a = *b;
+}
+
+void test_2(Test2 *a, Test2 *b) {
+	*a = *b;
+}
+
+void test_3(Test3 *a, Test3 *b) {
+	*a = *b;
+}
+
+void test_4(Test4 *a, Test4 *b) {
+	*a = *b;
+}
+
+void test_5(Test5 *a, Test5 *b) {
+	*a = *b;
+}
+
+void test_6(Test6 *a, Test6 *b) {
+	*a = *b;
+}
+
+void test_7(Test7 *a, Test7 *b) {
+	*a = *b;
+	d7 = s7;
+}
+
+void test() {}
\ No newline at end of file
diff --git a/tests/end_to_end/struct/mwcc-o4p-out.c b/tests/end_to_end/struct/mwcc-o4p-out.c
index 44eb3688..5f8d1d51 100644
--- a/tests/end_to_end/struct/mwcc-o4p-out.c
+++ b/tests/end_to_end/struct/mwcc-o4p-out.c
@@ -1,5 +1,4 @@
-void test(void *arg0, void *arg1) {
+void test(void *arg0, s32 arg1) {
     arg0->unk_4 = (s32) (arg0->unk_0 + arg0->unk_4);
-    arg1->unk_0 = (s32) arg0->unk_0;
-    arg1->unk_4 = (s32) arg0->unk_4;
+    M2C_STRUCT_COPY(arg1, arg0, 8);
 }


Reviews:
user: login: simonlindholm
body: Looks nice! This only covers lengths 0, 1, 2 and 4 (mod 8), what about 3/5/6/7? Is this pattern also used for memcpy? Should we include the length in the called intrinsic?
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T11:33:29Z
user: login: simonlindholm
body: Looks great. Can you add the intrinsic to m2c_macros.h as well, or switch to one of the MEMCPY macros that are already mentioned there?
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-22T08:21:19Z
user: login: dbalatoni13
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T08:34:10Z

Review comments:
diff_hunk: @@ -265,6 +265,81 @@ def replace(self, m: AsmMatch) -> Optional[Replacement]:
         )
 
 
+class StructCopyPattern(AsmPattern):
+    """Recognizing struct copy when it starts with lwz lwz stw stw. Others
+    would cause false positves. Maybe we can find another way for those using
+    context?"""
+
+    pattern = make_pattern(
+        "lwz $a, I($s)",
+        "lwz $b, (I+4)($s)",
+        "stw $a, I($d)",
+        "stw $b, (I+4)($d)",
+    )
+
+    def match(self, matcher: AsmMatcher) -> Optional[Replacement]:
+        # Use the initial patterns first
+        m = matcher.try_match(self.pattern)
+        if m is None:
+            return None
+        i = 8
+        # Extend it only if necessary, increasing performance
path: m2c/arch_ppc.py
user: login: simonlindholm
body: ```suggestion
```

(I don't see what the alternative to extending it like this would be)
created_at: 2025-03-22T08:15:48Z
author_association: COLLABORATOR
diff_hunk: @@ -265,6 +265,81 @@ def replace(self, m: AsmMatch) -> Optional[Replacement]:
         )
 
 
+class StructCopyPattern(AsmPattern):
+    """Recognizing struct copy when it starts with lwz lwz stw stw. Others
+    would cause false positves. Maybe we can find another way for those using
+    context?"""
path: m2c/arch_ppc.py
user: login: simonlindholm
body: Can you write something here about what compiler versions/flags this applies to, to the extent you know about it? My first attempt at playing around with this pattern at decomp.me by picking compiler/compiler flags at random failed. :) (Of course, even then this is a good start and something we could extend in the future to cover more patterns as the need arises.)
created_at: 2025-03-22T08:17:36Z
author_association: COLLABORATOR
diff_hunk: @@ -265,6 +265,81 @@ def replace(self, m: AsmMatch) -> Optional[Replacement]:
         )
 
 
+class StructCopyPattern(AsmPattern):
+    """Recognizing struct copy when it starts with lwz lwz stw stw. Others
+    would cause false positves. Maybe we can find another way for those using
+    context?"""
path: m2c/arch_ppc.py
user: login: dbalatoni13
body: Yes, I'll experiment later today and update you.
created_at: 2025-03-22T08:34:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: dbalatoni13
created_at: 2025-03-21T17:05:51Z
body: You're right, I'm going to add 3,5,6 and 7 (mod 8).
According to my testing memcpy (the one with a loop) is generated if the struct's size is more than 64 bytes. That one requires an IR pattern I think, because the order of the instructions is different on -O0 and -O4. 
Sure, it would be a good idea to include the length. I forgot that the user doesn't necessarily know the type of the struct.
author_association: CONTRIBUTOR
user: login: dbalatoni13
created_at: 2025-03-21T20:21:16Z
body: Bss/data section struct copies have a different pattern in -O4, but I'm not interested in that currently.
author_association: CONTRIBUTOR
user: login: dbalatoni13
created_at: 2025-03-22T08:33:12Z
body: Uhm those are either aligned or unaligned. I'm not sure which to pick. With this approach it would be hard to differentiate the intrinsic, right? I guess it might be better to create that new one.
author_association: CONTRIBUTOR
user: login: simonlindholm
created_at: 2025-03-22T09:01:01Z
body: From my experiments with decomp.me it seemed like if the alignment of the struct was 1 (by having only char members and no int ones) it would use only byte-level loads/stores, so the 2x lwz/stw at the start signal that the alignment is >= 4 I guess? But a new intrinsic is also fine.
author_association: COLLABORATOR
user: login: dbalatoni13
created_at: 2025-03-22T09:07:11Z
body: I'll experiment with it, but in the test case I included, I set pack to 1 and it's using 2x lwz/stw. I wonder if that has the same effect as only having chars. Can you send a scratch?
author_association: CONTRIBUTOR
user: login: simonlindholm
created_at: 2025-03-22T09:32:56Z
body: Hesitant to create scratches for things that aren't actually real decomps (@ethteck it would be nice to have a compiler explorer mode), but I tried modifying https://decomp.me/scratch/eYyyU (a GameCube/Wii scratch with 4.3 build 151 (Wii MV 1.1) and -O4,p) adding the following at the end:
```c
#pragma push
#pragma pack(1)

typedef struct Test0 {
	int a;
	int b;
} Test0;

typedef struct Test0a {
	char a,b,c,d,e,f,g,h;
} Test0a;

#pragma pop

void test_0(Test0 *a, Test0 *b) {
	*a = *b;
}

void test_0_alt(Test0a *a, Test0a *b) {
	*a = *b;
}
```
and got the following output:
```asm
test_0:
lwz     r0,0(r4)
stw     r0,0(r3)
lwz     r0,4(r4)
stw     r0,4(r3)
blr     

test_0_alt:
lbz     r0,0(r4)
stb     r0,0(r3)
lbz     r0,1(r4)
stb     r0,1(r3)
lbz     r0,2(r4)
stb     r0,2(r3)
lbz     r0,3(r4)
stb     r0,3(r3)
lbz     r0,4(r4)
stb     r0,4(r3)
lbz     r0,5(r4)
stb     r0,5(r3)
lbz     r0,6(r4)
stb     r0,6(r3)
lbz     r0,7(r4)
stb     r0,7(r3)
blr     
```
author_association: COLLABORATOR
user: login: dbalatoni13
created_at: 2025-03-22T14:16:50Z
body: I see, I will check that out. Here's a scratch using Mario Party 4's preset: https://decomp.me/scratch/D20X1
Between GC 1.0 all the way till 2.7 there is no difference in the output in -O0,p. In -O4,p there are slight differences, but the lwz lwz stw stw are there in most of the functions along those versions. I think flags are not necessary for this to generate, will look into it though.
author_association: CONTRIBUTOR
user: login: dbalatoni13
created_at: 2025-03-23T03:20:54Z
body: > Looks great. Can you add the intrinsic to m2c_macros.h as well, or switch to one of the MEMCPY macros that are already mentioned there?

What should it be defined as? Simply memcpy?
author_association: CONTRIBUTOR
user: login: dbalatoni13
created_at: 2025-03-23T03:30:41Z
body: I checked out your scratch and came to the conclusion that the difference is caused by C++.
author_association: CONTRIBUTOR
user: login: simonlindholm
created_at: 2025-03-23T10:35:45Z
body: > What should it be defined as? Simply memcpy?

I think aligned memcpy is the right one, given that your scratch changes codegen to copy byte-by-byte if you redefine the structs to only contain chars.
author_association: COLLABORATOR
user: login: dbalatoni13
created_at: 2025-03-23T15:11:42Z
body: Okay. What you're describing is because of -O0,p btw, in -O4,p it still uses load load store store in that case.
author_association: CONTRIBUTOR
user: login: simonlindholm
created_at: 2025-03-23T16:28:42Z
body: > Okay. What you're describing is because of -O0,p btw, in -O4,p it still uses load load store store in that case.

Oh, okay. Then I guess a new M2C_STRUCT_COPY macro is fine, since we can't distinguish between aligned and unaligned. So your PR is good, it just needs to add the macro to m2c_macros.h.
author_association: COLLABORATOR
user: login: dbalatoni13
created_at: 2025-03-23T19:19:47Z
body: Okay. Should it be defined as dst = src?
author_association: CONTRIBUTOR
user: login: simonlindholm
created_at: 2025-03-23T21:35:43Z
body: Maybe define it as memcpy similar to M2C_MEMCPY_ALIGNED/UNALIGNED? Empty definition would also be fine
author_association: COLLABORATOR
user: login: dbalatoni13
created_at: 2025-03-23T21:37:44Z
body: Gotcha!
author_association: CONTRIBUTOR
user: login: dbalatoni13
created_at: 2025-03-27T05:42:49Z
body: I just realized that we have the source code of a similar version here: https://git.wuffs.org/MWCC/tree/compiler_and_linker/BackEnd/PowerPC/CodeGenerator/StructMoves.c?h=main
author_association: CONTRIBUTOR