Add mwccgap (#1131)

OK. Here goes. Version 0.0.1 alpha of the MWCC Global Assembly Processor
(mwccgap). It's currently very simple/limited, but it appears to work
for `src/servant/tt_000/10E8.c`.

There is lot more that can be done to improve mwccgap - i.e. supporting
.rodata migration would be a good addition, but let's see how far we can
get with it in it's current state.

Note that the Makefile could do with some improvements - we don't nede
to use mwccgap for any C file that *dont* have INCLUDE_ASM macros (it's
a waste of time) so these could be ignored, i.e. for SSSV I do something
like this to find the files that need fixing up:
```
GLOBAL_ASM_C_FILES := $(shell $(GREP) GLOBAL_ASM $(SRC_DIR) </dev/null 2>/dev/null)
```
.. although this is perhaps too simple given that SOTN has a mix of PSP
and PSX functions (and therefore there may be INCLUDE_ASM for a PSX
function but none for PSP functions...
This commit is contained in:
Mark Street 2024-05-18 11:41:00 +01:00 committed by GitHub
parent 51ddf51bb3
commit b6a0ddbeb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 62 additions and 291 deletions

3
.gitmodules vendored
View File

@ -13,3 +13,6 @@
[submodule "tools/maspsx"]
path = tools/maspsx
url = https://github.com/mkst/maspsx.git
[submodule "tools/mwccgap"]
path = tools/mwccgap
url = https://github.com/mkst/mwccgap.git

View File

@ -15,12 +15,12 @@ LD := $(CROSS)ld
CPP := $(CROSS)cpp
OBJCOPY := $(CROSS)objcopy
AS_FLAGS += -Iinclude -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0
PSXCC_FLAGS := -quiet -mcpu=3000 -fgnu-linker -mgas -gcoff
PSXCC_FLAGS := -quiet -mcpu=3000 -fgnu-linker -mgas -gcoff
CC_FLAGS += -G0 -w -O2 -funsigned-char -fpeephole -ffunction-cse -fpcc-struct-return -fcommon -fverbose-asm -msoft-float -g
CPP_FLAGS += -Iinclude -Iinclude/psxsdk -undef -Wall -fno-builtin
CPP_FLAGS += -Dmips -D__GNUC__=2 -D__OPTIMIZE__ -D__mips__ -D__mips -Dpsx -D__psx__ -D__psx -D_PSYQ -D__EXTENSIONS__ -D_MIPSEL -D_LANGUAGE_C -DLANGUAGE_C -DNO_LOGS -DHACKS -DUSE_INCLUDE_ASM
CPP_FLAGS += -D_internal_version_$(VERSION) -DSOTN_STR
LD_FLAGS := -nostdlib --no-check-sections
LD_FLAGS := -nostdlib --no-check-sections
# Directories
ASM_DIR := asm/$(VERSION)
@ -62,7 +62,7 @@ M2C_DIR := $(TOOLS_DIR)/m2c
M2C_APP := $(M2C_DIR)/m2c.py
M2C := $(PYTHON) $(M2C_APP)
M2C_ARGS := -P 4
SOTNSTR := $(PYTHON) $(TOOLS_DIR)/sotn_str/sotn_str.py process
SOTNSTR := $(PYTHON) $(TOOLS_DIR)/sotn_str/sotn_str.py process
MASPSX_DIR := $(TOOLS_DIR)/maspsx
MASPSX_APP := $(MASPSX_DIR)/maspsx.py
MASPSX := $(PYTHON) $(MASPSX_APP) --expand-div --aspsx-version=2.34

View File

@ -1,16 +1,24 @@
GNUASPSP := mipsel-linux-gnu-as -I include/ -G0 -march=r6000 -mabi=eabi
MWASPSP := bin/wibo bin/asm_psp_elf.exe -gnu
ASPSP := $(GNUASPSP)
WIBO := bin/wibo
MWCCPSP := bin/mwccpsp.exe
GNULDPSP := mipsel-linux-gnu-ld
MWLDPSP := bin/wibo bin/mwldpsp.exe -partial -nostdlib -msgstyle gcc -sym full,elf -g
LDPSP := $(GNULDPSP)
GNUASPSP := mipsel-linux-gnu-as -I include/ -G0 -march=r6000 -mabi=eabi
MWASPSP := $(WIBO) bin/asm_psp_elf.exe -gnu
ASPSP := $(GNUASPSP)
PSP_BUILD_DIR := build/pspeu
CCPSP := MWCIncludes=bin/ bin/wibo bin/mwccpsp.exe
PSP_EU_TARGETS := tt_000
SPLAT_PIP := splat split
MWCPP_APP := python3 tools/mwcpp.py
GNULDPSP := mipsel-linux-gnu-ld
MWLDPSP := $(WIBO) bin/mwldpsp.exe -partial -nostdlib -msgstyle gcc -sym full,elf -g
LDPSP := $(GNULDPSP)
MWCCGAP_DIR := $(TOOLS_DIR)/mwccgap
MWCCGAP_APP := $(MWCCGAP_DIR)/mwccgap.py
MWCCGAP := $(PYTHON) $(MWCCGAP_APP)
PSP_BUILD_DIR := build/pspeu
CCPSP := MWCIncludes=bin/ $(WIBO) $(MWCCPSP)
PSP_EU_TARGETS := tt_000
SPLAT_PIP := splat split
MWCCPSP_FLAGS := -gccinc -Iinclude -D_internal_version_$(VERSION) -O0 -c -lang c -sdatathreshold 0
define list_src_files_psp
$(foreach dir,$(ASM_DIR)/$(1),$(wildcard $(dir)/**.s))
@ -27,15 +35,25 @@ build_pspeu: tt_000_psp
extract_pspeu: $(addprefix $(PSP_BUILD_DIR)/,$(addsuffix .ld,$(PSP_EU_TARGETS)))
bin/wibo:
$(WIBO):
wget -O $@ https://github.com/decompals/wibo/releases/download/0.6.13/wibo
sha256sum --check bin/wibo.sha256
chmod +x bin/wibo
bin/mwccpsp.exe: bin/wibo bin/mwccpsp_3.0.1_147
sha256sum --check $(WIBO).sha256
chmod +x $(WIBO)
$(MWCCPSP): $(WIBO) bin/mwccpsp_3.0.1_147
$(PSP_BUILD_DIR)/%.c.o: %.c bin/mwccpsp.exe
$(MWCCGAP_APP):
git submodule init $(MWCCGAP_DIR)
git submodule update $(MWCCGAP_DIR)
$(PSP_BUILD_DIR)/%.c.o: %.c $(MWCCPSP) $(MWCCGAP_APP)
mkdir -p $(dir $@)
$(MWCPP_APP) $< -o $<.post.c && (($(CCPSP) -gccinc -Iinclude -D_internal_version_$(VERSION) -O0 -c -lang c -sdatathreshold 0 -o $@ $<.post.c && rm $<.post.c) || (rm $<.post.c && exit 1))
if grep -q INCLUDE_ASM $<; then \
$(MWCCGAP) $< $@ --mwcc-path $(MWCCPSP) --use-wibo --wibo-path $(WIBO) --asm-dir-prefix asm/pspeu $(MWCCPSP_FLAGS) ; \
else \
$(CCPSP) $< -o $@ $(MWCCPSP_FLAGS) ; \
fi
$(PSP_BUILD_DIR)/asm/psp%.s.o: asm/psp%.s
mkdir -p $(dir $@)
$(ASPSP) -o $@ $<
@ -45,7 +63,7 @@ $(PSP_BUILD_DIR)/assets/servant/tt_000/header.bin.o: assets/servant/tt_000/heade
mkdir -p $(dir $@)
mipsel-linux-gnu-ld -r -b binary -o $@ $<
tt_000_psp: $(PSP_BUILD_DIR)/tt_000.bin
tt_000_psp: $(PSP_BUILD_DIR)/tt_000.bin $(PSP_BUILD_DIR)/assets/servant/tt_000/header.bin.o
$(PSP_BUILD_DIR)/tt_%.bin: $(PSP_BUILD_DIR)/tt_%.elf
$(OBJCOPY) -O binary $< $@
@ -53,6 +71,3 @@ $(PSP_BUILD_DIR)/tt_%.ld: $(CONFIG_DIR)/splat.pspeu.tt_%.yaml $(PSX_BASE_SYMS) $
$(SPLAT_PIP) $<
$(PSP_BUILD_DIR)/tt_%.elf: $(PSP_BUILD_DIR)/tt_%.ld $$(call list_o_files_psp,servant/tt_$$*)
$(call link,tt_$*,$@)
# cannot remove it for some reason? makefile bug?
_ignoreme_tt_000: $(PSP_BUILD_DIR)/src/servant/tt_000_psp/80.c.o $(PSP_BUILD_DIR)/asm/pspeu/servant/tt_000/data/4C80.data.s.o $(BUILD_DIR)/asm/pspeu/servant/tt_000/data/5E00.rodata.s.o

View File

@ -24,7 +24,13 @@ options:
- ".rodata"
- ".bss"
ld_bss_is_noload: True
# disasm_unknown: True
disasm_unknown: True
asm_inc_header: |
.set noat /* allow manual use of $at */
.set noreorder /* don't insert nops after branches */
.include "macro.inc"
sha1: c8c34ac1d46b31e2e5336df271aa2409f44c9d01
segments:
- [0x0, bin, header]
- name: tt_000

View File

@ -95,5 +95,5 @@ def apply(config, args):
apply_psx_base(config, version, name)
else:
apply_psx_bin(config, version, name)
config["arch"] = "mipsel"
config["arch"] = "mipsel:4000" if version == "pspeu" else "mipsel"
config["objdump_executable"] = "mipsel-linux-gnu-objdump"

View File

@ -5,6 +5,11 @@
#define SFX_BAT_SCREECH SOUND_BAT_SCREECH
#define SFX_BAT_NOTIFY SE_UI_OVERWRITE_MSG
#ifdef VERSION_PSP
#undef INCLUDE_ASM
#define INCLUDE_ASM(FOLDER, NAME)
#endif
#ifndef VERSION_PSP
s32 D_801748D8[0x80];
Collider D_80174AD8;
@ -67,18 +72,8 @@ ServantDesc g_ServantDesc = {
#endif
#ifdef VERSION_PSP
extern ServantDesc g_ServantDesc;
extern s32 D_80174D3C;
void DestroyEntity();
s32 func_80174864(void);
s32 func_801746A0(s32 arg0);
void ProcessEvent();
void CreateEventEntity(Entity* entityParent, s32 entityId, s32 params);
void func_80173F74();
void func_80173F30();
void DestroyEntity(Entity* entity);
#endif
void func_801710E8(Entity* entity, AnimationFrame* anim) {
@ -197,7 +192,10 @@ s32 func_801713C8(Entity* entity) {
return 0;
if (entity->hitPoints >= 0x7000)
return 0;
return entity->hitPoints > 0;
if (entity->hitPoints <= 0)
return 0;
return 1;
}
#endif

@ -1 +1 @@
Subproject commit e8bddb02576e73aecb90f813bfb452417c37b257
Subproject commit f5fc9026f0b164966ea7bc864335216f9f02ba02

1
tools/mwccgap Submodule

@ -0,0 +1 @@
Subproject commit 63417712bcf77aaa24b83e0e899493de42393ef7

View File

@ -1,120 +0,0 @@
#!/usr/bin/env python3
import argparse
import re
import sys
from typing import TextIO
r_lui = r"\s*lui\s*\$(.*),\s\%hi\((.*)\)"
r_lui_addiu_combo = r"\s*addiu\s*\$(.*),\s\$(.*),\s\%lo\((.*)\)"
r_hilo = r"\/\*\s*\w*\s*\w*\s*(\w*)\s*\*\/\s*\w*\s*\$\w*,.*(?:%hi|%lo).*"
r_hi = r"%hi\((.*)\)"
r_lo = r"%lo\((.*)\)\(|%lo\((.*)\)"
r_jalr = r"jalr\s*\$([a-z][a-z0-9])"
r_jlabel = r"jlabel \.(.*)"
def process_asm_line(asm_f: TextIO, line: str) -> str | None:
if line == "":
return None
# skip glabel
if line.startswith("glabel"):
return ""
# skip .size
if line.startswith(".size"):
return ""
match_jlabel = re.search(r_jlabel, line)
if match_jlabel:
label_name = match_jlabel.group(1)
line = f"{label_name}:\n"
# do not use '.' on label names
line = line.replace(".L", "L")
# remove trailing hash comment
if "#" in line:
line = line.split("#")[0] + "\n"
# jalr needs two arguments
jalr_match = re.search(r_jalr, line)
if jalr_match:
reg_name = jalr_match.group(1)
return line.replace(reg_name, f"ra, ${reg_name}")
r_hilo_match = re.search(r_hilo, line)
if r_hilo_match:
raw_data = r_hilo_match.group(1)
if len(raw_data) == 8:
return f".word 0x{raw_data[6:8]}{raw_data[4:6]}{raw_data[2:4]}{raw_data[0:2]}\n"
# return unpatched assembly line
return line
def include_asm(c_line: str, out: TextIO, version: str) -> None:
match = re.search(r'\s*INCLUDE_ASM\(\s*"(.*)",\s*(\w*)\)', c_line)
if match:
base_path = match.group(1)
func_name = match.group(2)
out.write(f"asm void {func_name}() {{\n")
with open(f"asm/{version}/{base_path}/{func_name}.s", "r") as f:
try:
while True:
line = f.readline()
patched_line = process_asm_line(f, line)
if patched_line == None:
break
out.write(patched_line)
except Exception as ex:
print(f"line {line} caused an exception")
raise ex
out.write(f"}}\n")
else:
out.write(c_line)
def process_file(file_name_in: str, out: TextIO, version: str) -> None:
with open(file_name_in, "r") as fin:
while True:
line = fin.readline()
if not line:
return None
if line.startswith("INCLUDE_ASM"):
try:
include_asm(line, out, version)
except Exception as ex:
print(f"{line} caused an exception")
raise
else:
out.write(line)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Pre-process Metrowerks C files to overcome certain compiler limitations"
)
parser.add_argument(
"--version", required=False, type=str, default="pspeu", help="Game version"
)
parser.add_argument(
"input",
type=str,
help="Input C file to pre-process",
)
parser.add_argument(
"-o",
"--output",
type=str,
help="Output file, stdout if not specified",
)
args = parser.parse_args()
if args.version == None:
args.version = "pspeu"
if args.output:
with open(args.output, "w") as fout:
process_file(args.input, fout, args.version)
else:
process_file(args.input, sys.stdout, args.version)

View File

@ -1,132 +0,0 @@
import os
import random
import shutil
import unittest
from mwcpp import process_file
class TestProcessFile(unittest.TestCase):
@classmethod
def setUp(self) -> None:
self.seed = f"{random.randint(0, 100000)}"
self.c_file_name = f"test_file_{self.seed}.c"
self.asm_dir_name = f"asm/{self.seed}"
self.output_name = f"test_processed_file_{self.seed}.c"
os.makedirs(f"{self.asm_dir_name}/ovl")
return super().setUp(self)
@classmethod
def tearDown(self) -> None:
shutil.rmtree(self.asm_dir_name)
os.remove(self.c_file_name)
os.remove(self.output_name)
return super().tearDown(self)
def helper_process_lines(self, assembly):
with open(self.c_file_name, "w") as f:
f.write(f'INCLUDE_ASM("ovl", func_name)')
with open(f"{self.asm_dir_name}/ovl/func_name.s", "w") as f:
f.write(assembly)
with open(self.output_name, "w") as f:
process_file(self.c_file_name, f, self.seed)
with open(self.output_name, "r") as f:
return "".join(f.readlines())
def test_process_basic_include_asm(self):
with open(self.c_file_name, "w") as f:
f.writelines(
[
"ignore this line\n",
"// this too\n" f'INCLUDE_ASM("ovl", func_name)\n',
f'// INCLUDE_ASM("ovl", func_name)\n',
"",
]
)
with open(f"{self.asm_dir_name}/ovl/func_name.s", "w") as f:
f.write("glabel function_name_should_be_skipped\n")
f.write("/* 0 */ nop\n")
f.write(".size this_part_should_be_skipped\n")
with open(self.output_name, "w") as f:
process_file(self.c_file_name, f, self.seed)
with open(self.output_name, "r") as f:
lines = "".join(f.readlines())
self.assertEqual(
lines,
"""ignore this line
// this too
asm void func_name() {
/* 0 */ nop
}
// INCLUDE_ASM("ovl", func_name)
""",
)
def test_fix_jalr(self):
self.assertEqual(
self.helper_process_lines(
"""jalr $v0
"""
),
"""asm void func_name() {
jalr $ra, $v0
}
""",
)
def test_fix_jlabel(self):
self.assertEqual(
self.helper_process_lines(
"""jlabel .LHELLO
"""
),
"""asm void func_name() {
LHELLO:
}
""",
)
def test_fix_lo_hi(self):
self.assertEqual(
self.helper_process_lines(
"""/* XXXX 09012348 DEADBEEF */ lui $v1, %hi(D_92EFFDE)
/* XXXX 09012344 BADC0FFE */ lh $a0, %lo(D_92EFFDE)
"""
),
"""asm void func_name() {
.word 0xEFBEADDE
.word 0xFE0FDCBA
}
""",
)
def test_fix_dot_on_label_names(self):
self.assertEqual(
self.helper_process_lines(
"""jmp .LABEL
.LABEL:
"""
),
"""asm void func_name() {
jmp LABEL
LABEL:
}
""",
)
def test_remove_trailing_hash_comment(self):
self.assertEqual(
self.helper_process_lines(
"""nop# comment
"""
),
"""asm void func_name() {
nop
}
""",
)
if __name__ == "__main__":
unittest.main()