mirror of
https://github.com/decompme/decomp.me.git
synced 2026-01-17 19:10:13 -06:00
Black (#380)
* Initial format * action * git subrepo pull --force backend/asm_differ subrepo: subdir: "backend/asm_differ" merged: "ee239a2b" upstream: origin: "https://github.com/simonlindholm/asm-differ" branch: "main" commit: "ee239a2b" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" * git subrepo pull --force backend/mips_to_c subrepo: subdir: "backend/mips_to_c" merged: "6704d61f" upstream: origin: "https://github.com/matt-kempster/mips_to_c" branch: "master" commit: "6704d61f" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" * vscode * fix * .
This commit is contained in:
8
.github/workflows/pr.yml
vendored
8
.github/workflows/pr.yml
vendored
@@ -159,3 +159,11 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
reporter: github-check
|
||||
workdir: backend
|
||||
black:
|
||||
name: black
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
src: "./backend"
|
||||
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"files.trimTrailingWhitespace": true,
|
||||
|
||||
// eslint
|
||||
"eslint.workingDirectories": [
|
||||
"frontend"
|
||||
@@ -22,7 +21,6 @@
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
|
||||
// stylelint
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "stylelint.vscode-stylelint",
|
||||
@@ -32,9 +30,12 @@
|
||||
"editor.defaultFormatter": "stylelint.vscode-stylelint",
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features",
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
"[python]": {
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
"python.formatting.provider": "black",
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[subrepo]
|
||||
remote = https://github.com/simonlindholm/asm-differ
|
||||
branch = main
|
||||
commit = 27e3449b145ac2807ded4a233f1edc33586c4b39
|
||||
parent = 38d47f81f5abcf2057b96da1a3d31843fd87d641
|
||||
commit = ee239a2b77d166c1050d4a9d88a63117d3c2860e
|
||||
parent = d4350758da88c20f355f6f6644624f9271dcd890
|
||||
method = merge
|
||||
cmdver = 0.4.3
|
||||
|
||||
18
backend/asm_differ/diff.py
generated
18
backend/asm_differ/diff.py
generated
@@ -109,6 +109,15 @@ if __name__ == "__main__":
|
||||
help="""Diff .o files rather than a whole binary. This makes it possible to
|
||||
see symbol names. (Recommended)""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--objfile",
|
||||
dest="objfile",
|
||||
type=str,
|
||||
help="""File path for an object file being diffed. When used
|
||||
the map file isn't searched for the function given. Useful for dynamically
|
||||
linked libraries."""
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--elf",
|
||||
@@ -388,6 +397,7 @@ class Config:
|
||||
|
||||
# Build/objdump options
|
||||
diff_obj: bool
|
||||
objfile: str
|
||||
make: bool
|
||||
source_old_binutils: bool
|
||||
diff_section: str
|
||||
@@ -472,6 +482,7 @@ def create_config(args: argparse.Namespace, project: ProjectSettings) -> Config:
|
||||
arch=arch,
|
||||
# Build/objdump options
|
||||
diff_obj=args.diff_obj,
|
||||
objfile=args.objfile,
|
||||
make=args.make,
|
||||
source_old_binutils=args.source_old_binutils,
|
||||
diff_section=args.diff_section,
|
||||
@@ -1301,7 +1312,10 @@ def dump_objfile(
|
||||
if start.startswith("0"):
|
||||
fail("numerical start address not supported with -o; pass a function name")
|
||||
|
||||
objfile, _ = search_map_file(start, project, config)
|
||||
objfile = config.objfile
|
||||
if not objfile:
|
||||
objfile, _ = search_map_file(start, project, config)
|
||||
|
||||
if not objfile:
|
||||
fail("Not able to find .o file for function.")
|
||||
|
||||
@@ -1958,8 +1972,6 @@ def process(dump: str, config: Config) -> List[Line]:
|
||||
branch_target = None
|
||||
if mnemonic in arch.branch_instructions:
|
||||
branch_target = int(row_parts[1].strip().split(",")[-1], 16)
|
||||
if mnemonic in arch.branch_likely_instructions:
|
||||
branch_target -= 4
|
||||
|
||||
output.append(
|
||||
Line(
|
||||
|
||||
@@ -7,6 +7,7 @@ from .models.scratch import Assembly, Asm, Scratch, CompilerConfig
|
||||
from .models.project import Project, ProjectMember, ProjectFunction, ProjectImportConfig
|
||||
from .models.github import GitHubUser, GitHubRepo, GitHubRepoBusyException
|
||||
|
||||
|
||||
class GitHubRepoAdmin(admin.ModelAdmin[GitHubRepo]):
|
||||
actions = ["pull", "reclone"]
|
||||
|
||||
@@ -23,6 +24,7 @@ class GitHubRepoAdmin(admin.ModelAdmin[GitHubRepo]):
|
||||
shutil.rmtree(repo.get_dir())
|
||||
repo.pull()
|
||||
|
||||
|
||||
admin.site.register(Profile)
|
||||
admin.site.register(GitHubUser)
|
||||
admin.site.register(Asm)
|
||||
|
||||
@@ -2,5 +2,5 @@ from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreappConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'coreapp'
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "coreapp"
|
||||
|
||||
@@ -23,6 +23,7 @@ class AsmDifferWrapper:
|
||||
arch=arch,
|
||||
# Build/objdump options
|
||||
diff_obj=True,
|
||||
objfile="",
|
||||
make=False,
|
||||
source_old_binutils=True,
|
||||
diff_section=".text",
|
||||
@@ -45,7 +46,9 @@ class AsmDifferWrapper:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_objdump_target_function_flags(sandbox: Sandbox, target_path, platform: str, label: Optional[str]) -> List[str]:
|
||||
def get_objdump_target_function_flags(
|
||||
sandbox: Sandbox, target_path, platform: str, label: Optional[str]
|
||||
) -> List[str]:
|
||||
if not label:
|
||||
return ["--start-address=0"]
|
||||
|
||||
@@ -80,7 +83,12 @@ class AsmDifferWrapper:
|
||||
return ["--start-address=0"]
|
||||
|
||||
@staticmethod
|
||||
def run_objdump(target_data: bytes, platform: str, config: asm_differ.Config, label: Optional[str]) -> str:
|
||||
def run_objdump(
|
||||
target_data: bytes,
|
||||
platform: str,
|
||||
config: asm_differ.Config,
|
||||
label: Optional[str],
|
||||
) -> str:
|
||||
flags = [
|
||||
"--disassemble",
|
||||
"--disassemble-zeroes",
|
||||
@@ -92,14 +100,19 @@ class AsmDifferWrapper:
|
||||
target_path = sandbox.path / "out.s"
|
||||
target_path.write_bytes(target_data)
|
||||
|
||||
flags += AsmDifferWrapper.get_objdump_target_function_flags(sandbox, target_path, platform, label)
|
||||
flags += AsmDifferWrapper.get_objdump_target_function_flags(
|
||||
sandbox, target_path, platform, label
|
||||
)
|
||||
|
||||
objdump_command = compiler_wrapper.get_objdump_command(platform)
|
||||
|
||||
if objdump_command:
|
||||
try:
|
||||
objdump_proc = sandbox.run_subprocess(
|
||||
[objdump_command] + config.arch.arch_flags + flags + [sandbox.rewrite_path(target_path)],
|
||||
[objdump_command]
|
||||
+ config.arch.arch_flags
|
||||
+ flags
|
||||
+ [sandbox.rewrite_path(target_path)],
|
||||
shell=True,
|
||||
env={
|
||||
"PATH": PATH,
|
||||
@@ -114,8 +127,13 @@ class AsmDifferWrapper:
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def get_dump(elf_object: bytes, platform: str, diff_label: Optional[str], arch:asm_differ.ArchSettings,
|
||||
config: asm_differ.Config) -> str:
|
||||
def get_dump(
|
||||
elf_object: bytes,
|
||||
platform: str,
|
||||
diff_label: Optional[str],
|
||||
arch: asm_differ.ArchSettings,
|
||||
config: asm_differ.Config,
|
||||
) -> str:
|
||||
compiler_arch = compiler_wrapper.CompilerWrapper.arch_from_platform(platform)
|
||||
|
||||
try:
|
||||
@@ -129,13 +147,17 @@ class AsmDifferWrapper:
|
||||
if len(elf_object) == 0:
|
||||
raise AssemblyError("Asm empty")
|
||||
|
||||
basedump = AsmDifferWrapper.run_objdump(elf_object, platform, config, diff_label)
|
||||
basedump = AsmDifferWrapper.run_objdump(
|
||||
elf_object, platform, config, diff_label
|
||||
)
|
||||
if not basedump:
|
||||
raise ObjdumpError("Error running objdump")
|
||||
|
||||
# Preprocess the dump
|
||||
try:
|
||||
basedump = asm_differ.preprocess_objdump_out(None, elf_object, basedump, config)
|
||||
basedump = asm_differ.preprocess_objdump_out(
|
||||
None, elf_object, basedump, config
|
||||
)
|
||||
except AssertionError as e:
|
||||
logger.exception("Error preprocessing dump")
|
||||
raise DiffError(f"Error preprocessing dump: {e}")
|
||||
@@ -145,7 +167,13 @@ class AsmDifferWrapper:
|
||||
return basedump
|
||||
|
||||
@staticmethod
|
||||
def diff(target_assembly: Assembly, platform: str, diff_label:Optional[str], compiled_elf: bytes, allow_target_only:bool = False) -> DiffResult:
|
||||
def diff(
|
||||
target_assembly: Assembly,
|
||||
platform: str,
|
||||
diff_label: Optional[str],
|
||||
compiled_elf: bytes,
|
||||
allow_target_only: bool = False,
|
||||
) -> DiffResult:
|
||||
compiler_arch = compiler_wrapper.CompilerWrapper.arch_from_platform(platform)
|
||||
|
||||
if compiler_arch == "dummy":
|
||||
@@ -160,9 +188,13 @@ class AsmDifferWrapper:
|
||||
|
||||
config = AsmDifferWrapper.create_config(arch)
|
||||
|
||||
basedump = AsmDifferWrapper.get_dump(bytes(target_assembly.elf_object), platform, diff_label, arch, config)
|
||||
basedump = AsmDifferWrapper.get_dump(
|
||||
bytes(target_assembly.elf_object), platform, diff_label, arch, config
|
||||
)
|
||||
try:
|
||||
mydump = AsmDifferWrapper.get_dump(compiled_elf, platform, diff_label, arch, config)
|
||||
mydump = AsmDifferWrapper.get_dump(
|
||||
compiled_elf, platform, diff_label, arch, config
|
||||
)
|
||||
except Exception as e:
|
||||
if allow_target_only:
|
||||
mydump = ""
|
||||
|
||||
@@ -34,9 +34,14 @@ if "microsoft" in uname().release.lower() and not settings.USE_SANDBOX_JAIL:
|
||||
else:
|
||||
WINE = "wine"
|
||||
|
||||
def load_compiler(id: str, cc: Optional[str], platform: Optional[str], base_id: Optional[str]=None) -> Optional[Dict[str, str]]:
|
||||
|
||||
def load_compiler(
|
||||
id: str, cc: Optional[str], platform: Optional[str], base_id: Optional[str] = None
|
||||
) -> Optional[Dict[str, str]]:
|
||||
if not cc or not platform:
|
||||
logger.error(f"Error: {id} {CONFIG_JSON} is missing 'cc' and/or 'platform' field(s), skipping.")
|
||||
logger.error(
|
||||
f"Error: {id} {CONFIG_JSON} is missing 'cc' and/or 'platform' field(s), skipping."
|
||||
)
|
||||
return None
|
||||
|
||||
if not base_id:
|
||||
@@ -56,6 +61,7 @@ def load_compiler(id: str, cc: Optional[str], platform: Optional[str], base_id:
|
||||
logger.debug(f"Config found but no binaries found for {id}, ignoring.")
|
||||
return None
|
||||
|
||||
|
||||
def load_compilers() -> Dict[str, Dict[str, str]]:
|
||||
ret: Dict[str, Dict[str, str]] = {}
|
||||
compilers_base = settings.BASE_DIR / "compilers"
|
||||
@@ -67,20 +73,31 @@ def load_compilers() -> Dict[str, Dict[str, str]]:
|
||||
try:
|
||||
config = json.load(f)
|
||||
except:
|
||||
logger.error(f"Error: Unable to parse {CONFIG_JSON} for {compiler_dir_name}")
|
||||
logger.error(
|
||||
f"Error: Unable to parse {CONFIG_JSON} for {compiler_dir_name}"
|
||||
)
|
||||
continue
|
||||
|
||||
if isinstance(config, dict):
|
||||
comp = load_compiler(compiler_dir_name, config.get("cc"), config.get("platform"))
|
||||
comp = load_compiler(
|
||||
compiler_dir_name, config.get("cc"), config.get("platform")
|
||||
)
|
||||
if comp:
|
||||
ret[compiler_dir_name] = comp
|
||||
elif isinstance(config, list):
|
||||
for list_def in config:
|
||||
if "name" not in list_def:
|
||||
logger.error(f"Error: {CONFIG_JSON} for {compiler_dir_name} is missing 'name' field")
|
||||
logger.error(
|
||||
f"Error: {CONFIG_JSON} for {compiler_dir_name} is missing 'name' field"
|
||||
)
|
||||
continue
|
||||
name = list_def["name"]
|
||||
comp = load_compiler(name, list_def.get("cc"), list_def.get("platform"), compiler_dir_name)
|
||||
comp = load_compiler(
|
||||
name,
|
||||
list_def.get("cc"),
|
||||
list_def.get("platform"),
|
||||
compiler_dir_name,
|
||||
)
|
||||
if comp:
|
||||
ret[name] = comp
|
||||
|
||||
@@ -104,18 +121,20 @@ class Platform:
|
||||
nm_cmd: Optional[str] = None
|
||||
supports_objdump_disassemble: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class CompilationResult:
|
||||
elf_object: bytes
|
||||
errors: str
|
||||
|
||||
|
||||
def load_platforms() -> Dict[str, Platform]:
|
||||
return {
|
||||
"dummy": Platform(
|
||||
"Dummy System",
|
||||
"DMY",
|
||||
"dummy",
|
||||
assemble_cmd='echo \"assembled("$INPUT")\" > "$OUTPUT"',
|
||||
assemble_cmd='echo "assembled("$INPUT")" > "$OUTPUT"',
|
||||
objdump_cmd="echo",
|
||||
nm_cmd="echo",
|
||||
asm_prelude="",
|
||||
@@ -157,7 +176,7 @@ def load_platforms() -> Dict[str, Platform]:
|
||||
.set noreorder
|
||||
.set gp=64
|
||||
|
||||
"""
|
||||
""",
|
||||
),
|
||||
"ps1": Platform(
|
||||
"PlayStation",
|
||||
@@ -180,7 +199,7 @@ def load_platforms() -> Dict[str, Platform]:
|
||||
.set noat
|
||||
.set noreorder
|
||||
|
||||
"""
|
||||
""",
|
||||
),
|
||||
"ps2": Platform(
|
||||
"PlayStation 2",
|
||||
@@ -203,7 +222,7 @@ def load_platforms() -> Dict[str, Platform]:
|
||||
.set noat
|
||||
.set noreorder
|
||||
|
||||
"""
|
||||
""",
|
||||
),
|
||||
"gc_wii": Platform(
|
||||
"GameCube / Wii",
|
||||
@@ -291,7 +310,7 @@ def load_platforms() -> Dict[str, Platform]:
|
||||
.set qr5, 5
|
||||
.set qr6, 6
|
||||
.set qr7, 7
|
||||
"""
|
||||
""",
|
||||
),
|
||||
"nds_arm9": Platform(
|
||||
"Nintendo DS",
|
||||
@@ -323,7 +342,7 @@ def load_platforms() -> Dict[str, Platform]:
|
||||
.endm
|
||||
.macro thumb_func_end name
|
||||
.endm
|
||||
"""
|
||||
""",
|
||||
),
|
||||
"gba": Platform(
|
||||
"Game Boy Advance",
|
||||
@@ -356,31 +375,35 @@ def load_platforms() -> Dict[str, Platform]:
|
||||
.endm
|
||||
.macro thumb_func_end name
|
||||
.endm
|
||||
"""
|
||||
""",
|
||||
),
|
||||
|
||||
}
|
||||
|
||||
|
||||
def get_assemble_cmd(platform: str) -> Optional[str]:
|
||||
if platform in _platforms:
|
||||
return _platforms[platform].assemble_cmd
|
||||
return None
|
||||
|
||||
|
||||
def get_nm_command(platform: str) -> Optional[str]:
|
||||
if platform in _platforms:
|
||||
return _platforms[platform].nm_cmd
|
||||
return None
|
||||
|
||||
|
||||
def get_objdump_command(platform: str) -> Optional[str]:
|
||||
if platform in _platforms:
|
||||
return _platforms[platform].objdump_cmd
|
||||
return None
|
||||
|
||||
|
||||
def supports_objdump_disassemble(platform: str) -> bool:
|
||||
if platform in _platforms:
|
||||
return _platforms[platform].supports_objdump_disassemble
|
||||
return False
|
||||
|
||||
|
||||
def _check_assembly_cache(*args: str) -> Tuple[Optional[Assembly], str]:
|
||||
hash = util.gen_hash(args)
|
||||
return Assembly.objects.filter(hash=hash).first(), hash
|
||||
@@ -407,7 +430,10 @@ class CompilerWrapper:
|
||||
|
||||
@staticmethod
|
||||
def available_compilers() -> Dict[str, Dict[str, Optional[str]]]:
|
||||
return {k: {"platform": CompilerWrapper.platform_from_compiler(k)} for k in CompilerWrapper.available_compiler_ids()}
|
||||
return {
|
||||
k: {"platform": CompilerWrapper.platform_from_compiler(k)}
|
||||
for k in CompilerWrapper.available_compiler_ids()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def available_platforms() -> OrderedDict[str, Dict[str, str]]:
|
||||
@@ -471,13 +497,18 @@ class CompilerWrapper:
|
||||
def filter_compile_errors(input: str) -> str:
|
||||
return (
|
||||
input.replace("wine: could not load kernel32.dll, status c0000135\n", "")
|
||||
.replace("wineserver: could not save registry branch to system.reg : Read-only file system\n", "")
|
||||
.replace(
|
||||
"wineserver: could not save registry branch to system.reg : Read-only file system\n",
|
||||
"",
|
||||
)
|
||||
.strip()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@lru_cache(maxsize=settings.COMPILATION_CACHE_SIZE) # type: ignore
|
||||
def compile_code(compiler: str, compiler_flags: str, code: str, context: str) -> CompilationResult:
|
||||
@lru_cache(maxsize=settings.COMPILATION_CACHE_SIZE) # type: ignore
|
||||
def compile_code(
|
||||
compiler: str, compiler_flags: str, code: str, context: str
|
||||
) -> CompilationResult:
|
||||
if compiler == "dummy":
|
||||
return CompilationResult(f"compiled({context}\n{code}".encode("UTF-8"), "")
|
||||
|
||||
@@ -493,13 +524,15 @@ class CompilerWrapper:
|
||||
with code_path.open("w") as f:
|
||||
f.write('#line 1 "ctx.c"\n')
|
||||
f.write(context)
|
||||
f.write('\n')
|
||||
f.write("\n")
|
||||
|
||||
f.write('#line 1 "src.c"\n')
|
||||
f.write(code)
|
||||
f.write('\n')
|
||||
f.write("\n")
|
||||
|
||||
compiler_path = CompilerWrapper.base_path() / _compilers[compiler]["basedir"]
|
||||
compiler_path = (
|
||||
CompilerWrapper.base_path() / _compilers[compiler]["basedir"]
|
||||
)
|
||||
|
||||
cc_cmd = _compilers[compiler]["cc"]
|
||||
|
||||
@@ -514,14 +547,15 @@ class CompilerWrapper:
|
||||
mounts=[compiler_path],
|
||||
shell=True,
|
||||
env={
|
||||
"PATH": PATH,
|
||||
"WINE": WINE,
|
||||
"INPUT": sandbox.rewrite_path(code_path),
|
||||
"OUTPUT": sandbox.rewrite_path(object_path),
|
||||
"COMPILER_DIR": sandbox.rewrite_path(compiler_path),
|
||||
"COMPILER_FLAGS": sandbox.quote_options(compiler_flags),
|
||||
"MWCIncludes": "/tmp",
|
||||
})
|
||||
"PATH": PATH,
|
||||
"WINE": WINE,
|
||||
"INPUT": sandbox.rewrite_path(code_path),
|
||||
"OUTPUT": sandbox.rewrite_path(object_path),
|
||||
"COMPILER_DIR": sandbox.rewrite_path(compiler_path),
|
||||
"COMPILER_FLAGS": sandbox.quote_options(compiler_flags),
|
||||
"MWCIncludes": "/tmp",
|
||||
},
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Compilation failed
|
||||
logging.debug("Compilation failed: " + e.stderr)
|
||||
@@ -560,7 +594,7 @@ class CompilerWrapper:
|
||||
hash=hash,
|
||||
arch=platform_cfg.arch,
|
||||
source_asm=asm,
|
||||
elf_object=f"assembled({asm.data})".encode("UTF-8")
|
||||
elf_object=f"assembled({asm.data})".encode("UTF-8"),
|
||||
)
|
||||
assembly.save()
|
||||
return assembly
|
||||
@@ -578,16 +612,19 @@ class CompilerWrapper:
|
||||
mounts=[],
|
||||
shell=True,
|
||||
env={
|
||||
"PATH": PATH,
|
||||
"INPUT": sandbox.rewrite_path(asm_path),
|
||||
"OUTPUT": sandbox.rewrite_path(object_path),
|
||||
})
|
||||
"PATH": PATH,
|
||||
"INPUT": sandbox.rewrite_path(asm_path),
|
||||
"OUTPUT": sandbox.rewrite_path(object_path),
|
||||
},
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise AssemblyError.from_process_error(e)
|
||||
|
||||
# Assembly failed
|
||||
if assemble_proc.returncode != 0:
|
||||
raise AssemblyError(f"Assembler failed with error code {assemble_proc.returncode}")
|
||||
raise AssemblyError(
|
||||
f"Assembler failed with error code {assemble_proc.returncode}"
|
||||
)
|
||||
|
||||
if not object_path.exists():
|
||||
raise AssemblyError("Assembler did not create an object file")
|
||||
@@ -605,4 +642,6 @@ class CompilerWrapper:
|
||||
_compilers = load_compilers()
|
||||
logger.info(f"Enabled {len(_compilers)} compiler(s): {', '.join(_compilers.keys())}")
|
||||
_platforms = load_platforms()
|
||||
logger.info(f"Available platform(s): {', '.join(CompilerWrapper.available_platforms().keys())}")
|
||||
logger.info(
|
||||
f"Available platform(s): {', '.join(CompilerWrapper.available_platforms().keys())}"
|
||||
)
|
||||
|
||||
@@ -3,13 +3,16 @@ import subprocess
|
||||
import tempfile
|
||||
from typing import List
|
||||
|
||||
|
||||
def c_file_to_context(root_dir: str, in_file: str, cpp_flags: List[str]) -> str:
|
||||
in_file = os.path.relpath(in_file, root_dir)
|
||||
cpp_command = ["gcc", "-E", "-P", "-dM", *cpp_flags, in_file]
|
||||
cpp_command2 = ["gcc", "-E", "-P", *cpp_flags, in_file]
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".c") as tmp:
|
||||
stock_macros = subprocess.check_output(["gcc", "-E", "-P", "-dM", tmp.name], cwd=root_dir, encoding="utf-8")
|
||||
stock_macros = subprocess.check_output(
|
||||
["gcc", "-E", "-P", "-dM", tmp.name], cwd=root_dir, encoding="utf-8"
|
||||
)
|
||||
|
||||
out_text = ""
|
||||
out_text += subprocess.check_output(cpp_command, cwd=root_dir, encoding="utf-8")
|
||||
|
||||
@@ -8,7 +8,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class DecompilerWrapper:
|
||||
@staticmethod
|
||||
def decompile(default_source_code: str, platform: str, asm: str, context: str, compiler: str) -> str:
|
||||
def decompile(
|
||||
default_source_code: str, platform: str, asm: str, context: str, compiler: str
|
||||
) -> str:
|
||||
if compiler == "dummy":
|
||||
return f"decompiled({asm})"
|
||||
|
||||
@@ -23,6 +25,8 @@ class DecompilerWrapper:
|
||||
logger.exception("Error running mips_to_c")
|
||||
ret = f"/* Internal error while running mips_to_c */\n{default_source_code}"
|
||||
else:
|
||||
ret = f"/* No decompiler yet implemented for {arch} */\n{default_source_code}"
|
||||
ret = (
|
||||
f"/* No decompiler yet implemented for {arch} */\n{default_source_code}"
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
@@ -6,9 +6,14 @@ from rest_framework.response import Response
|
||||
from typing import Optional, Callable
|
||||
from datetime import datetime
|
||||
|
||||
def condition(etag_func: Optional[Callable[..., Optional[str]]] = None, last_modified_func: Optional[Callable[..., Optional[datetime]]] = None) -> Callable[..., Callable[..., Response]]:
|
||||
|
||||
def condition(
|
||||
etag_func: Optional[Callable[..., Optional[str]]] = None,
|
||||
last_modified_func: Optional[Callable[..., Optional[datetime]]] = None,
|
||||
) -> Callable[..., Callable[..., Response]]:
|
||||
"""
|
||||
Handle Last-Modified and ETag headers.
|
||||
"""
|
||||
from django.views.decorators.http import condition
|
||||
|
||||
return method_decorator(condition(etag_func, last_modified_func))
|
||||
|
||||
@@ -5,6 +5,7 @@ from rest_framework.status import HTTP_400_BAD_REQUEST
|
||||
|
||||
from rest_framework.views import exception_handler
|
||||
|
||||
|
||||
def custom_exception_handler(exc, context):
|
||||
# Call REST framework's default exception handler first,
|
||||
# to get the standard error response.
|
||||
@@ -12,10 +13,11 @@ def custom_exception_handler(exc, context):
|
||||
|
||||
if isinstance(exc, SubprocessError):
|
||||
response = Response(
|
||||
data = {
|
||||
data={
|
||||
"code": exc.SUBPROCESS_NAME,
|
||||
"detail": exc.msg,
|
||||
}, status = HTTP_400_BAD_REQUEST
|
||||
},
|
||||
status=HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -8,9 +8,11 @@ from coreapp.sandbox import Sandbox
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class M2CError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class M2CWrapper:
|
||||
@staticmethod
|
||||
def get_triple(compiler: str, arch: str) -> str:
|
||||
@@ -32,7 +34,6 @@ class M2CWrapper:
|
||||
|
||||
return f"{t_arch}-{t_compiler}"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def decompile(asm: str, context: str, compiler: str, arch: str) -> str:
|
||||
with Sandbox() as sandbox:
|
||||
|
||||
@@ -11,16 +11,21 @@ from typing import Union, Optional, TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from .models.github import GitHubUser
|
||||
|
||||
|
||||
class AnonymousUser(auth.models.AnonymousUser):
|
||||
profile: Profile
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
class Request(DRFRequest):
|
||||
user: Union[User, AnonymousUser]
|
||||
profile: Profile
|
||||
|
||||
else:
|
||||
Request = DRFRequest
|
||||
|
||||
|
||||
def disable_csrf(get_response):
|
||||
def middleware(request: HttpRequest):
|
||||
setattr(request, "_dont_enforce_csrf_checks", True)
|
||||
@@ -28,6 +33,7 @@ def disable_csrf(get_response):
|
||||
|
||||
return middleware
|
||||
|
||||
|
||||
def set_user_profile(get_response):
|
||||
"""
|
||||
Makes sure that `request.profile` is always available, even for anonymous users.
|
||||
@@ -35,7 +41,10 @@ def set_user_profile(get_response):
|
||||
|
||||
def middleware(request: Request):
|
||||
# Skip if the request is from SSR
|
||||
if "User-Agent" in request.headers and "node-fetch" in request.headers["User-Agent"]:
|
||||
if (
|
||||
"User-Agent" in request.headers
|
||||
and "node-fetch" in request.headers["User-Agent"]
|
||||
):
|
||||
request.profile = Profile()
|
||||
return get_response(request)
|
||||
|
||||
|
||||
@@ -9,61 +9,109 @@ class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [ # type: ignore
|
||||
]
|
||||
dependencies = [] # type: ignore
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Asm',
|
||||
name="Asm",
|
||||
fields=[
|
||||
('hash', models.CharField(max_length=64, primary_key=True, serialize=False)),
|
||||
('data', models.TextField()),
|
||||
(
|
||||
"hash",
|
||||
models.CharField(max_length=64, primary_key=True, serialize=False),
|
||||
),
|
||||
("data", models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Assembly',
|
||||
name="Assembly",
|
||||
fields=[
|
||||
('hash', models.CharField(max_length=64, primary_key=True, serialize=False)),
|
||||
('time', models.DateTimeField(auto_now_add=True)),
|
||||
('arch', models.CharField(max_length=100)),
|
||||
('as_opts', models.TextField(blank=True, max_length=1000, null=True)),
|
||||
('elf_object', models.BinaryField(blank=True)),
|
||||
('source_asm', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coreapp.asm')),
|
||||
(
|
||||
"hash",
|
||||
models.CharField(max_length=64, primary_key=True, serialize=False),
|
||||
),
|
||||
("time", models.DateTimeField(auto_now_add=True)),
|
||||
("arch", models.CharField(max_length=100)),
|
||||
("as_opts", models.TextField(blank=True, max_length=1000, null=True)),
|
||||
("elf_object", models.BinaryField(blank=True)),
|
||||
(
|
||||
"source_asm",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="coreapp.asm"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Compilation',
|
||||
name="Compilation",
|
||||
fields=[
|
||||
('hash', models.CharField(max_length=64, primary_key=True, serialize=False)),
|
||||
('time', models.DateTimeField(auto_now_add=True)),
|
||||
('compiler', models.CharField(max_length=100)),
|
||||
('cc_opts', models.TextField(blank=True, max_length=1000, null=True)),
|
||||
('source_code', models.TextField()),
|
||||
('context', models.TextField(blank=True)),
|
||||
('elf_object', models.BinaryField(blank=True)),
|
||||
('stderr', models.TextField(blank=True, null=True)),
|
||||
(
|
||||
"hash",
|
||||
models.CharField(max_length=64, primary_key=True, serialize=False),
|
||||
),
|
||||
("time", models.DateTimeField(auto_now_add=True)),
|
||||
("compiler", models.CharField(max_length=100)),
|
||||
("cc_opts", models.TextField(blank=True, max_length=1000, null=True)),
|
||||
("source_code", models.TextField()),
|
||||
("context", models.TextField(blank=True)),
|
||||
("elf_object", models.BinaryField(blank=True)),
|
||||
("stderr", models.TextField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Profile',
|
||||
name="Profile",
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Scratch',
|
||||
name="Scratch",
|
||||
fields=[
|
||||
('slug', models.SlugField(default=gen_scratch_id, primary_key=True, serialize=False)),
|
||||
('creation_time', models.DateTimeField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('compiler', models.CharField(blank=True, max_length=100)),
|
||||
('cc_opts', models.TextField(blank=True, max_length=1000, null=True)),
|
||||
('source_code', models.TextField(blank=True)),
|
||||
('context', models.TextField(blank=True)),
|
||||
('original_context', models.TextField(blank=True)),
|
||||
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='coreapp.profile')),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='coreapp.scratch')),
|
||||
('target_assembly', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coreapp.assembly')),
|
||||
(
|
||||
"slug",
|
||||
models.SlugField(
|
||||
default=gen_scratch_id, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("creation_time", models.DateTimeField(auto_now_add=True)),
|
||||
("last_updated", models.DateTimeField(auto_now=True)),
|
||||
("compiler", models.CharField(blank=True, max_length=100)),
|
||||
("cc_opts", models.TextField(blank=True, max_length=1000, null=True)),
|
||||
("source_code", models.TextField(blank=True)),
|
||||
("context", models.TextField(blank=True)),
|
||||
("original_context", models.TextField(blank=True)),
|
||||
(
|
||||
"owner",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="coreapp.profile",
|
||||
),
|
||||
),
|
||||
(
|
||||
"parent",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="coreapp.scratch",
|
||||
),
|
||||
),
|
||||
(
|
||||
"target_assembly",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="coreapp.assembly",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,12 +6,12 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0001_initial'),
|
||||
("coreapp", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='assembly',
|
||||
name='as_opts',
|
||||
model_name="assembly",
|
||||
name="as_opts",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -9,39 +9,57 @@ import django.utils.timezone
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('coreapp', '0002_remove_assembly_as_opts'),
|
||||
("coreapp", "0002_remove_assembly_as_opts"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='GitHubUser',
|
||||
name="GitHubUser",
|
||||
fields=[
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='github', serialize=False, to='auth.user')),
|
||||
('github_id', models.PositiveIntegerField(editable=False, unique=True)),
|
||||
('access_token', models.CharField(max_length=100)),
|
||||
(
|
||||
"user",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
related_name="github",
|
||||
serialize=False,
|
||||
to="auth.user",
|
||||
),
|
||||
),
|
||||
("github_id", models.PositiveIntegerField(editable=False, unique=True)),
|
||||
("access_token", models.CharField(max_length=100)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'GitHub user',
|
||||
'verbose_name_plural': 'GitHub users',
|
||||
"verbose_name": "GitHub user",
|
||||
"verbose_name_plural": "GitHub users",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='creation_date',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
model_name="profile",
|
||||
name="creation_date",
|
||||
field=models.DateTimeField(
|
||||
auto_now_add=True, default=django.utils.timezone.now
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='last_request_date',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
model_name="profile",
|
||||
name="last_request_date",
|
||||
field=models.DateTimeField(
|
||||
auto_now_add=True, default=django.utils.timezone.now
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='user',
|
||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL),
|
||||
model_name="profile",
|
||||
name="user",
|
||||
field=models.OneToOneField(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="profile",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0003_github_profile'),
|
||||
("coreapp", "0003_github_profile"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='scratch',
|
||||
name='diff_label',
|
||||
model_name="scratch",
|
||||
name="diff_label",
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0004_scratch_diff_label'),
|
||||
("coreapp", "0004_scratch_diff_label"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='scratch',
|
||||
name='arch',
|
||||
model_name="scratch",
|
||||
name="arch",
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.migrations.operations.special
|
||||
|
||||
|
||||
def populate_name(apps, schema_editor):
|
||||
"""
|
||||
Populate the name field for all existing scratches
|
||||
@@ -16,32 +17,32 @@ def populate_name(apps, schema_editor):
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0005_scratch_arch'),
|
||||
("coreapp", "0005_scratch_arch"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='scratch',
|
||||
name='score',
|
||||
model_name="scratch",
|
||||
name="score",
|
||||
field=models.IntegerField(default=-1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scratch',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, default='', max_length=5000),
|
||||
model_name="scratch",
|
||||
name="description",
|
||||
field=models.TextField(blank=True, default="", max_length=5000),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scratch',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, default='', max_length=100),
|
||||
model_name="scratch",
|
||||
name="name",
|
||||
field=models.CharField(blank=True, default="", max_length=100),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=populate_name,
|
||||
reverse_code=django.db.migrations.operations.special.RunPython.noop,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='scratch',
|
||||
name='cc_opts',
|
||||
field=models.TextField(blank=True, default='', max_length=1000),
|
||||
model_name="scratch",
|
||||
name="cc_opts",
|
||||
field=models.TextField(blank=True, default="", max_length=1000),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0006_scratch_name_score_description'),
|
||||
("coreapp", "0006_scratch_name_score_description"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='scratch',
|
||||
old_name='arch',
|
||||
new_name='platform',
|
||||
model_name="scratch",
|
||||
old_name="arch",
|
||||
new_name="platform",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.migrations.operations.special
|
||||
|
||||
|
||||
def populate_compiler(apps, schema_editor):
|
||||
"""
|
||||
Populate the compiler field for all existing scratches
|
||||
@@ -12,38 +13,39 @@ def populate_compiler(apps, schema_editor):
|
||||
row.compiler = row.compiler if row.compiler else "ido7.1"
|
||||
row.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('coreapp', '0007_rename_arch_scratch_platform'),
|
||||
("coreapp", "0007_rename_arch_scratch_platform"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='scratch',
|
||||
name='max_score',
|
||||
model_name="scratch",
|
||||
name="max_score",
|
||||
field=models.IntegerField(default=-1),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='scratch',
|
||||
name='original_context',
|
||||
model_name="scratch",
|
||||
name="original_context",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='compilation',
|
||||
old_name='cc_opts',
|
||||
new_name='compiler_flags',
|
||||
model_name="compilation",
|
||||
old_name="cc_opts",
|
||||
new_name="compiler_flags",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='scratch',
|
||||
old_name='cc_opts',
|
||||
new_name='compiler_flags',
|
||||
model_name="scratch",
|
||||
old_name="cc_opts",
|
||||
new_name="compiler_flags",
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=populate_compiler,
|
||||
reverse_code=django.db.migrations.operations.special.RunPython.noop,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='scratch',
|
||||
name='compiler',
|
||||
model_name="scratch",
|
||||
name="compiler",
|
||||
field=models.CharField(max_length=100),
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,25 +6,27 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0008_scratch_max_score_squashed_0011_alter_scratch_compiler'),
|
||||
("coreapp", "0008_scratch_max_score_squashed_0011_alter_scratch_compiler"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='compilation',
|
||||
name='elf_object',
|
||||
model_name="compilation",
|
||||
name="elf_object",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='compilation',
|
||||
name='stderr',
|
||||
model_name="compilation",
|
||||
name="stderr",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='compilation',
|
||||
name='hash',
|
||||
model_name="compilation",
|
||||
name="hash",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='compilation',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False),
|
||||
model_name="compilation",
|
||||
name="id",
|
||||
field=models.BigAutoField(
|
||||
auto_created=True, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,11 +6,11 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0009_remove_crud_from_compilation'),
|
||||
("coreapp", "0009_remove_crud_from_compilation"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Compilation',
|
||||
name="Compilation",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0010_delete_compilation'),
|
||||
("coreapp", "0010_delete_compilation"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='scratch',
|
||||
name='diff_label',
|
||||
model_name="scratch",
|
||||
name="diff_label",
|
||||
field=models.CharField(blank=True, max_length=512, null=True),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0011_alter_scratch_diff_label'),
|
||||
("coreapp", "0011_alter_scratch_diff_label"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='scratch',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, default='', max_length=512),
|
||||
model_name="scratch",
|
||||
name="name",
|
||||
field=models.CharField(blank=True, default="", max_length=512),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.migrations.operations.special
|
||||
|
||||
|
||||
def populate_name(apps, schema_editor):
|
||||
"""
|
||||
Populate the name field for all existing scratches
|
||||
@@ -13,21 +14,22 @@ def populate_name(apps, schema_editor):
|
||||
row.name = "Untitled"
|
||||
row.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0012_alter_scratch_name'),
|
||||
("coreapp", "0012_alter_scratch_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='scratch',
|
||||
options={'ordering': ['-creation_time']},
|
||||
name="scratch",
|
||||
options={"ordering": ["-creation_time"]},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='scratch',
|
||||
name='name',
|
||||
field=models.CharField(default='Untitled', max_length=512),
|
||||
model_name="scratch",
|
||||
name="name",
|
||||
field=models.CharField(default="Untitled", max_length=512),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=populate_name,
|
||||
|
||||
@@ -7,98 +7,212 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('coreapp', '0013_scratch_name_default'),
|
||||
("coreapp", "0013_scratch_name_default"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CompilerConfig',
|
||||
name="CompilerConfig",
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('compiler', models.CharField(max_length=100)),
|
||||
('platform', models.CharField(max_length=100)),
|
||||
('compiler_flags', models.TextField(blank=True, default='', max_length=1000)),
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("compiler", models.CharField(max_length=100)),
|
||||
("platform", models.CharField(max_length=100)),
|
||||
(
|
||||
"compiler_flags",
|
||||
models.TextField(blank=True, default="", max_length=1000),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GitHubRepo',
|
||||
name="GitHubRepo",
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('owner', models.CharField(max_length=100)),
|
||||
('repo', models.CharField(max_length=100)),
|
||||
('branch', models.CharField(default='master', max_length=100)),
|
||||
('is_pulling', models.BooleanField(default=False)),
|
||||
('last_pulled', models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("owner", models.CharField(max_length=100)),
|
||||
("repo", models.CharField(max_length=100)),
|
||||
("branch", models.CharField(default="master", max_length=100)),
|
||||
("is_pulling", models.BooleanField(default=False)),
|
||||
("last_pulled", models.DateTimeField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'GitHub repo',
|
||||
'verbose_name_plural': 'GitHub repos',
|
||||
"verbose_name": "GitHub repo",
|
||||
"verbose_name_plural": "GitHub repos",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Project',
|
||||
name="Project",
|
||||
fields=[
|
||||
('slug', models.SlugField(primary_key=True, serialize=False)),
|
||||
('creation_time', models.DateTimeField(auto_now_add=True)),
|
||||
('icon_url', models.URLField()),
|
||||
('repo', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='coreapp.githubrepo')),
|
||||
("slug", models.SlugField(primary_key=True, serialize=False)),
|
||||
("creation_time", models.DateTimeField(auto_now_add=True)),
|
||||
("icon_url", models.URLField()),
|
||||
(
|
||||
"repo",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="coreapp.githubrepo",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='scratch',
|
||||
options={'ordering': ['-creation_time'], 'verbose_name_plural': 'Scratches'},
|
||||
name="scratch",
|
||||
options={
|
||||
"ordering": ["-creation_time"],
|
||||
"verbose_name_plural": "Scratches",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='scratch',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='coreapp.scratch'),
|
||||
model_name="scratch",
|
||||
name="parent",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="coreapp.scratch",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProjectMember',
|
||||
name="ProjectMember",
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coreapp.profile')),
|
||||
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coreapp.project')),
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"profile",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="coreapp.profile",
|
||||
),
|
||||
),
|
||||
(
|
||||
"project",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="coreapp.project",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProjectImportConfig',
|
||||
name="ProjectImportConfig",
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('display_name', models.CharField(blank=True, default='', max_length=512)),
|
||||
('src_dir', models.CharField(default='src', max_length=100)),
|
||||
('nonmatchings_dir', models.CharField(default='asm/nonmatchings', max_length=100)),
|
||||
('nonmatchings_glob', models.CharField(default='**/*.s', max_length=100)),
|
||||
('symbol_addrs_path', models.CharField(default='symbol_addrs.txt', max_length=100)),
|
||||
('compiler_config', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='coreapp.compilerconfig')),
|
||||
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coreapp.project')),
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"display_name",
|
||||
models.CharField(blank=True, default="", max_length=512),
|
||||
),
|
||||
("src_dir", models.CharField(default="src", max_length=100)),
|
||||
(
|
||||
"nonmatchings_dir",
|
||||
models.CharField(default="asm/nonmatchings", max_length=100),
|
||||
),
|
||||
(
|
||||
"nonmatchings_glob",
|
||||
models.CharField(default="**/*.s", max_length=100),
|
||||
),
|
||||
(
|
||||
"symbol_addrs_path",
|
||||
models.CharField(default="symbol_addrs.txt", max_length=100),
|
||||
),
|
||||
(
|
||||
"compiler_config",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="coreapp.compilerconfig",
|
||||
),
|
||||
),
|
||||
(
|
||||
"project",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="coreapp.project",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProjectFunction',
|
||||
name="ProjectFunction",
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('rom_address', models.IntegerField()),
|
||||
('creation_time', models.DateTimeField(auto_now_add=True)),
|
||||
('display_name', models.CharField(max_length=128)),
|
||||
('is_matched_in_repo', models.BooleanField(default=False)),
|
||||
('src_file', models.CharField(max_length=256)),
|
||||
('asm_file', models.CharField(max_length=256)),
|
||||
('import_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coreapp.projectimportconfig')),
|
||||
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coreapp.project')),
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("rom_address", models.IntegerField()),
|
||||
("creation_time", models.DateTimeField(auto_now_add=True)),
|
||||
("display_name", models.CharField(max_length=128)),
|
||||
("is_matched_in_repo", models.BooleanField(default=False)),
|
||||
("src_file", models.CharField(max_length=256)),
|
||||
("asm_file", models.CharField(max_length=256)),
|
||||
(
|
||||
"import_config",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="coreapp.projectimportconfig",
|
||||
),
|
||||
),
|
||||
(
|
||||
"project",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="coreapp.project",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scratch',
|
||||
name='project_function',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='coreapp.projectfunction'),
|
||||
model_name="scratch",
|
||||
name="project_function",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="coreapp.projectfunction",
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='projectmember',
|
||||
constraint=models.UniqueConstraint(fields=('project', 'profile'), name='unique_project_member'),
|
||||
model_name="projectmember",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("project", "profile"), name="unique_project_member"
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='projectfunction',
|
||||
constraint=models.UniqueConstraint(fields=('project', 'rom_address'), name='unique_project_function_addr'),
|
||||
model_name="projectfunction",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("project", "rom_address"), name="unique_project_function_addr"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -23,22 +23,26 @@ from .project import Project
|
||||
from ..middleware import Request
|
||||
import requests
|
||||
|
||||
API_CACHE_TIMEOUT = 60 * 60 # 1 hour
|
||||
API_CACHE_TIMEOUT = 60 * 60 # 1 hour
|
||||
|
||||
|
||||
class BadOAuthCodeException(APIException):
|
||||
status_code = status.HTTP_401_UNAUTHORIZED
|
||||
default_code = "bad_oauth_code"
|
||||
default_detail = "Invalid or expired GitHub OAuth verification code."
|
||||
|
||||
|
||||
class MissingOAuthScopeException(APIException):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_code = "missing_oauth_scope"
|
||||
|
||||
|
||||
class MalformedGitHubApiResponseException(APIException):
|
||||
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
default_code = "malformed_github_api_response"
|
||||
default_detail = "The GitHub API returned an malformed or unexpected response."
|
||||
|
||||
|
||||
class GitHubUser(models.Model):
|
||||
user = models.OneToOneField(
|
||||
User,
|
||||
@@ -78,14 +82,16 @@ class GitHubUser(models.Model):
|
||||
"client_secret": settings.GITHUB_CLIENT_SECRET,
|
||||
"code": oauth_code,
|
||||
},
|
||||
headers={ "Accept": "application/json" },
|
||||
headers={"Accept": "application/json"},
|
||||
).json()
|
||||
|
||||
error: Optional[str] = response.get("error")
|
||||
if error == "bad_verification_code":
|
||||
raise BadOAuthCodeException()
|
||||
elif error:
|
||||
raise MalformedGitHubApiResponseException(f"GitHub API login sent unknown error '{error}'.")
|
||||
raise MalformedGitHubApiResponseException(
|
||||
f"GitHub API login sent unknown error '{error}'."
|
||||
)
|
||||
|
||||
try:
|
||||
scope_str = str(response["scope"])
|
||||
@@ -106,7 +112,11 @@ class GitHubUser(models.Model):
|
||||
user = request.user
|
||||
|
||||
# make a new user if request.user already has a github account attached
|
||||
if user.is_anonymous or isinstance(user, User) and GitHubUser.objects.filter(user=user).get() is not None:
|
||||
if (
|
||||
user.is_anonymous
|
||||
or isinstance(user, User)
|
||||
and GitHubUser.objects.filter(user=user).get() is not None
|
||||
):
|
||||
user = User.objects.create_user(
|
||||
username=details.login,
|
||||
email=details.email,
|
||||
@@ -121,7 +131,9 @@ class GitHubUser(models.Model):
|
||||
gh_user.access_token = access_token
|
||||
gh_user.save()
|
||||
|
||||
profile: Profile = Profile.objects.filter(user=gh_user.user).first() or Profile()
|
||||
profile: Profile = (
|
||||
Profile.objects.filter(user=gh_user.user).first() or Profile()
|
||||
)
|
||||
profile.user = gh_user.user
|
||||
profile.last_request_date = now()
|
||||
profile.save()
|
||||
@@ -137,10 +149,12 @@ class GitHubUser(models.Model):
|
||||
|
||||
return gh_user
|
||||
|
||||
|
||||
class GitHubRepoBusyException(APIException):
|
||||
status_code = status.HTTP_409_CONFLICT
|
||||
default_detail = "This repository is currently being pulled."
|
||||
|
||||
|
||||
class GitHubRepo(models.Model):
|
||||
owner = models.CharField(max_length=100)
|
||||
repo = models.CharField(max_length=100)
|
||||
@@ -164,19 +178,30 @@ class GitHubRepo(models.Model):
|
||||
remote_url = f"https://github.com/{self.owner}/{self.repo}"
|
||||
|
||||
if repo_dir.exists():
|
||||
subprocess.run(["git", "remote", "set-url", "origin", remote_url], cwd=repo_dir)
|
||||
subprocess.run(
|
||||
["git", "remote", "set-url", "origin", remote_url], cwd=repo_dir
|
||||
)
|
||||
subprocess.run(["git", "fetch", "origin", self.branch], cwd=repo_dir)
|
||||
subprocess.run(["git", "reset", "--hard", f"origin/{self.branch}"], cwd=repo_dir)
|
||||
subprocess.run(
|
||||
["git", "reset", "--hard", f"origin/{self.branch}"], cwd=repo_dir
|
||||
)
|
||||
subprocess.run(["git", "pull"], cwd=repo_dir)
|
||||
else:
|
||||
repo_dir.mkdir(parents=True)
|
||||
subprocess.run([
|
||||
"git", "clone",
|
||||
remote_url,
|
||||
".",
|
||||
"--depth", "1",
|
||||
"-b", self.branch,
|
||||
], check=True, cwd=repo_dir)
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"clone",
|
||||
remote_url,
|
||||
".",
|
||||
"--depth",
|
||||
"1",
|
||||
"-b",
|
||||
self.branch,
|
||||
],
|
||||
check=True,
|
||||
cwd=repo_dir,
|
||||
)
|
||||
|
||||
self.last_pulled = now()
|
||||
self.save()
|
||||
@@ -208,6 +233,7 @@ class GitHubRepo(models.Model):
|
||||
def get_html_url(self):
|
||||
return f"https://github.com/{self.owner}/{self.repo}/tree/{self.branch}"
|
||||
|
||||
|
||||
# When a GitHubRepo is deleted, delete its directory
|
||||
@receiver(models.signals.pre_delete, sender=GitHubRepo)
|
||||
def delete_local_repo_dir(instance: GitHubRepo, **kwargs):
|
||||
|
||||
@@ -3,6 +3,7 @@ from django.contrib.auth.models import User
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Profile(models.Model):
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
last_request_date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@@ -13,6 +13,7 @@ from ..context import c_file_to_context
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
slug = models.SlugField(primary_key=True)
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
@@ -42,6 +43,7 @@ class Project(models.Model):
|
||||
def members(self) -> List["ProjectMember"]:
|
||||
return [m for m in ProjectMember.objects.filter(project=self)]
|
||||
|
||||
|
||||
class ProjectImportConfig(models.Model):
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
|
||||
@@ -74,9 +76,18 @@ class ProjectImportConfig(models.Model):
|
||||
src_dir, nonmatchings_dir, symbol_addrs_path = self.get_paths()
|
||||
|
||||
symbol_addrs = parse_symbol_addrs(symbol_addrs_path)
|
||||
asm_files = [Path(p) for p in glob(str(nonmatchings_dir.joinpath(self.nonmatchings_glob)), recursive=True)]
|
||||
asm_files = [
|
||||
Path(p)
|
||||
for p in glob(
|
||||
str(nonmatchings_dir.joinpath(self.nonmatchings_glob)), recursive=True
|
||||
)
|
||||
]
|
||||
|
||||
logger.info("Importing %d nonmatching asm files from %s", len(asm_files), nonmatchings_dir)
|
||||
logger.info(
|
||||
"Importing %d nonmatching asm files from %s",
|
||||
len(asm_files),
|
||||
nonmatchings_dir,
|
||||
)
|
||||
|
||||
for asm_file in asm_files:
|
||||
symbol_name = symbol_name_from_asm_file(asm_file)
|
||||
@@ -93,13 +104,19 @@ class ProjectImportConfig(models.Model):
|
||||
continue
|
||||
|
||||
# Search C file for this function (TODO: use configurable regex replace?)
|
||||
src_file = src_dir / asm_file.relative_to(nonmatchings_dir).parent.with_suffix(".c")
|
||||
src_file = src_dir / asm_file.relative_to(
|
||||
nonmatchings_dir
|
||||
).parent.with_suffix(".c")
|
||||
if not src_file.is_file():
|
||||
logger.warn(f"no C file found for '{asm_file}' (looked for '{src_file}')")
|
||||
logger.warn(
|
||||
f"no C file found for '{asm_file}' (looked for '{src_file}')"
|
||||
)
|
||||
continue
|
||||
|
||||
# Create or update ProjectFunction
|
||||
func: Optional[ProjectFunction] = ProjectFunction.objects.filter(project=self.project, rom_address=symbol.rom_address).first()
|
||||
func: Optional[ProjectFunction] = ProjectFunction.objects.filter(
|
||||
project=self.project, rom_address=symbol.rom_address
|
||||
).first()
|
||||
if func is not None:
|
||||
func.display_name = symbol.label
|
||||
func.is_matched_in_repo = False
|
||||
@@ -110,7 +127,6 @@ class ProjectImportConfig(models.Model):
|
||||
func = ProjectFunction(
|
||||
project=self.project,
|
||||
rom_address=symbol.rom_address,
|
||||
|
||||
display_name=symbol.label,
|
||||
is_matched_in_repo=False,
|
||||
src_file=str(src_file.relative_to(project_dir)),
|
||||
@@ -119,15 +135,18 @@ class ProjectImportConfig(models.Model):
|
||||
)
|
||||
func.save()
|
||||
|
||||
|
||||
class ProjectFunction(models.Model):
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE) # note: redundant w.r.t. import_config.project
|
||||
project = models.ForeignKey(
|
||||
Project, on_delete=models.CASCADE
|
||||
) # note: redundant w.r.t. import_config.project
|
||||
rom_address = models.IntegerField()
|
||||
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
display_name = models.CharField(max_length=128, blank=False)
|
||||
is_matched_in_repo = models.BooleanField(default=False)
|
||||
#complexity = models.IntegerField()
|
||||
# complexity = models.IntegerField()
|
||||
|
||||
src_file = models.CharField(max_length=256)
|
||||
asm_file = models.CharField(max_length=256)
|
||||
@@ -136,7 +155,9 @@ class ProjectFunction(models.Model):
|
||||
class Meta:
|
||||
constraints = [
|
||||
# ProjectFunctions are identified uniquely by (project, rom_address)
|
||||
models.UniqueConstraint(fields=["project", "rom_address"], name="unique_project_function_addr"),
|
||||
models.UniqueConstraint(
|
||||
fields=["project", "rom_address"], name="unique_project_function_addr"
|
||||
),
|
||||
]
|
||||
|
||||
def get_html_url(self) -> str:
|
||||
@@ -156,7 +177,9 @@ class ProjectFunction(models.Model):
|
||||
src_file = project_dir / Path(self.src_file)
|
||||
asm_file = project_dir / Path(self.asm_file)
|
||||
|
||||
source_code = "" # TODO: grab sourcecode from src_file's NON_MATCHING block, if any
|
||||
source_code = (
|
||||
"" # TODO: grab sourcecode from src_file's NON_MATCHING block, if any
|
||||
)
|
||||
|
||||
# TODO: make this more configurable or something
|
||||
cpp_flags = shlex.split(compiler_config.compiler_flags)
|
||||
@@ -177,7 +200,9 @@ class ProjectFunction(models.Model):
|
||||
|
||||
# Attempt to generate context (TODO: #361 so we don't have to do this)
|
||||
try:
|
||||
context = c_file_to_context(str(project_dir), str(src_file), cpp_flags=cpp_flags)
|
||||
context = c_file_to_context(
|
||||
str(project_dir), str(src_file), cpp_flags=cpp_flags
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"failed to generate context for {asm_file}: {e}")
|
||||
context = f"/* context generation failed: {e} */"
|
||||
@@ -185,17 +210,21 @@ class ProjectFunction(models.Model):
|
||||
with asm_file.open("r") as f:
|
||||
target_asm = f.read()
|
||||
|
||||
return create_scratch({
|
||||
"project": self.project.slug,
|
||||
"rom_address": self.rom_address,
|
||||
"diff_label": symbol_name_from_asm_file(asm_file),
|
||||
"target_asm": target_asm,
|
||||
"source_code": source_code,
|
||||
"context": context,
|
||||
"platform": compiler_config.platform,
|
||||
"compiler": compiler_config.compiler,
|
||||
"compiler_flags": compiler_config.compiler_flags,
|
||||
}, allow_project=True)
|
||||
return create_scratch(
|
||||
{
|
||||
"project": self.project.slug,
|
||||
"rom_address": self.rom_address,
|
||||
"diff_label": symbol_name_from_asm_file(asm_file),
|
||||
"target_asm": target_asm,
|
||||
"source_code": source_code,
|
||||
"context": context,
|
||||
"platform": compiler_config.platform,
|
||||
"compiler": compiler_config.compiler,
|
||||
"compiler_flags": compiler_config.compiler_flags,
|
||||
},
|
||||
allow_project=True,
|
||||
)
|
||||
|
||||
|
||||
class ProjectMember(models.Model):
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE)
|
||||
@@ -203,7 +232,9 @@ class ProjectMember(models.Model):
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=["project", "profile"], name="unique_project_member"),
|
||||
models.UniqueConstraint(
|
||||
fields=["project", "profile"], name="unique_project_member"
|
||||
),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -7,6 +7,7 @@ from .profile import Profile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def gen_scratch_id() -> str:
|
||||
ret = get_random_string(length=5)
|
||||
|
||||
@@ -15,6 +16,7 @@ def gen_scratch_id() -> str:
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class Asm(models.Model):
|
||||
hash = models.CharField(max_length=64, primary_key=True)
|
||||
data = models.TextField()
|
||||
@@ -22,6 +24,7 @@ class Asm(models.Model):
|
||||
def __str__(self):
|
||||
return self.data if len(self.data) < 20 else self.data[:17] + "..."
|
||||
|
||||
|
||||
class Assembly(models.Model):
|
||||
hash = models.CharField(max_length=64, primary_key=True)
|
||||
time = models.DateTimeField(auto_now_add=True)
|
||||
@@ -29,21 +32,27 @@ class Assembly(models.Model):
|
||||
source_asm = models.ForeignKey(Asm, on_delete=models.CASCADE)
|
||||
elf_object = models.BinaryField(blank=True)
|
||||
|
||||
|
||||
class CompilerConfig(models.Model):
|
||||
# TODO: validate compiler and platform
|
||||
compiler = models.CharField(max_length=100)
|
||||
platform = models.CharField(max_length=100)
|
||||
compiler_flags = models.TextField(max_length=1000, default="", blank=True)
|
||||
|
||||
|
||||
class Scratch(models.Model):
|
||||
slug = models.SlugField(primary_key=True, default=gen_scratch_id)
|
||||
name = models.CharField(max_length=512, default="Untitled", blank=False)
|
||||
description = models.TextField(max_length=5000, default="", blank=True)
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
last_updated = models.DateTimeField(auto_now=True)
|
||||
compiler = models.CharField(max_length=100) # TODO: reference a CompilerConfig
|
||||
platform = models.CharField(max_length=100, blank=True) # TODO: reference a CompilerConfig
|
||||
compiler_flags = models.TextField(max_length=1000, default="", blank=True) # TODO: reference a CompilerConfig
|
||||
compiler = models.CharField(max_length=100) # TODO: reference a CompilerConfig
|
||||
platform = models.CharField(
|
||||
max_length=100, blank=True
|
||||
) # TODO: reference a CompilerConfig
|
||||
compiler_flags = models.TextField(
|
||||
max_length=1000, default="", blank=True
|
||||
) # TODO: reference a CompilerConfig
|
||||
target_assembly = models.ForeignKey(Assembly, on_delete=models.CASCADE)
|
||||
source_code = models.TextField(blank=True)
|
||||
context = models.TextField(blank=True)
|
||||
@@ -52,10 +61,12 @@ class Scratch(models.Model):
|
||||
max_score = models.IntegerField(default=-1)
|
||||
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL)
|
||||
owner = models.ForeignKey(Profile, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
project_function = models.ForeignKey("ProjectFunction", null=True, blank=True, on_delete=models.SET_NULL) # The function, if any, that this scratch is an attempt of
|
||||
project_function = models.ForeignKey(
|
||||
"ProjectFunction", null=True, blank=True, on_delete=models.SET_NULL
|
||||
) # The function, if any, that this scratch is an attempt of
|
||||
|
||||
class Meta:
|
||||
ordering = ['-creation_time']
|
||||
ordering = ["-creation_time"]
|
||||
verbose_name_plural = "Scratches"
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -12,12 +12,13 @@ from .models.github import GitHubUser, GitHubRepo
|
||||
from .models.project import Project, ProjectFunction
|
||||
from .middleware import Request
|
||||
|
||||
def serialize_profile(request: Request, profile: Profile, small = False):
|
||||
|
||||
def serialize_profile(request: Request, profile: Profile, small=False):
|
||||
if profile.user is None:
|
||||
return {
|
||||
"url": None,
|
||||
"html_url": None,
|
||||
"is_you": profile == request.profile, # TODO(#245): remove
|
||||
"is_you": profile == request.profile, # TODO(#245): remove
|
||||
"is_anonymous": True,
|
||||
"id": profile.id,
|
||||
}
|
||||
@@ -30,7 +31,7 @@ def serialize_profile(request: Request, profile: Profile, small = False):
|
||||
small_obj = {
|
||||
"url": reverse("user-detail", args=[user.username], request=request),
|
||||
"html_url": profile.get_html_url(),
|
||||
"is_you": user == request.user, # TODO(#245): remove
|
||||
"is_you": user == request.user, # TODO(#245): remove
|
||||
"is_anonymous": False,
|
||||
"id": user.id,
|
||||
"username": user.username,
|
||||
@@ -48,19 +49,23 @@ def serialize_profile(request: Request, profile: Profile, small = False):
|
||||
"github_html_url": github_details.html_url if github_details else None,
|
||||
}
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ProfileFieldBaseClass = serializers.RelatedField[Profile, str, str]
|
||||
else:
|
||||
ProfileFieldBaseClass = serializers.RelatedField
|
||||
|
||||
|
||||
class ProfileField(ProfileFieldBaseClass):
|
||||
def to_representation(self, profile: Profile):
|
||||
return serialize_profile(self.context["request"], profile)
|
||||
|
||||
|
||||
class TerseProfileField(ProfileFieldBaseClass):
|
||||
def to_representation(self, profile: Profile):
|
||||
return serialize_profile(self.context["request"], profile, small=True)
|
||||
|
||||
|
||||
class UrlField(serializers.HyperlinkedIdentityField):
|
||||
"""
|
||||
Read-only field that takes the value returned by the model's get_url method.
|
||||
@@ -82,6 +87,7 @@ class UrlField(serializers.HyperlinkedIdentityField):
|
||||
|
||||
raise ImproperlyConfigured("UrlField does not support this type of model")
|
||||
|
||||
|
||||
class HtmlUrlField(UrlField):
|
||||
"""
|
||||
Read-only field that takes the value returned by the model's get_html_url method.
|
||||
@@ -94,6 +100,7 @@ class HtmlUrlField(UrlField):
|
||||
|
||||
raise ImproperlyConfigured("HtmlUrlField does not support this type of model")
|
||||
|
||||
|
||||
class ScratchCreateSerializer(serializers.Serializer[None]):
|
||||
name = serializers.CharField(allow_blank=True, required=False)
|
||||
compiler = serializers.CharField(allow_blank=True, required=True)
|
||||
@@ -101,43 +108,73 @@ class ScratchCreateSerializer(serializers.Serializer[None]):
|
||||
compiler_flags = serializers.CharField(allow_blank=True, required=False)
|
||||
source_code = serializers.CharField(allow_blank=True, required=False)
|
||||
target_asm = serializers.CharField(allow_blank=True)
|
||||
context = serializers.CharField(allow_blank=True) # type: ignore
|
||||
context = serializers.CharField(allow_blank=True) # type: ignore
|
||||
diff_label = serializers.CharField(allow_blank=True, required=False)
|
||||
|
||||
# ProjectFunction reference
|
||||
project = serializers.CharField(allow_blank=False, required=False)
|
||||
rom_address = serializers.IntegerField(required=False)
|
||||
|
||||
|
||||
class ScratchSerializer(serializers.HyperlinkedModelSerializer):
|
||||
slug = serializers.SlugField(read_only=True)
|
||||
url = UrlField()
|
||||
html_url = HtmlUrlField()
|
||||
parent = UrlField(target_field="parent") # type: ignore
|
||||
parent = UrlField(target_field="parent") # type: ignore
|
||||
owner = ProfileField(read_only=True)
|
||||
source_code = serializers.CharField(allow_blank=True, trim_whitespace=False)
|
||||
context = serializers.CharField(allow_blank=True, trim_whitespace=False) # type: ignore
|
||||
context = serializers.CharField(allow_blank=True, trim_whitespace=False) # type: ignore
|
||||
project = serializers.SerializerMethodField()
|
||||
project_function = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Scratch
|
||||
exclude = ["target_assembly"]
|
||||
read_only_fields = ["url", "html_url", "parent", "owner", "last_updated", "creation_time", "platform"]
|
||||
read_only_fields = [
|
||||
"url",
|
||||
"html_url",
|
||||
"parent",
|
||||
"owner",
|
||||
"last_updated",
|
||||
"creation_time",
|
||||
"platform",
|
||||
]
|
||||
|
||||
def get_project(self, scratch: Scratch):
|
||||
if hasattr(scratch, "project_function") and scratch.project_function is not None:
|
||||
return reverse("project-detail", args=[scratch.project_function.project.slug], request=self.context["request"]) # type: ignore
|
||||
if (
|
||||
hasattr(scratch, "project_function")
|
||||
and scratch.project_function is not None
|
||||
):
|
||||
return reverse("project-detail", args=[scratch.project_function.project.slug], request=self.context["request"]) # type: ignore
|
||||
|
||||
def get_project_function(self, scratch: Scratch):
|
||||
if hasattr(scratch, "project_function") and scratch.project_function is not None:
|
||||
return reverse("projectfunction-detail", args=[scratch.project_function.project.slug, scratch.project_function.id], request=self.context["request"]) # type: ignore
|
||||
if (
|
||||
hasattr(scratch, "project_function")
|
||||
and scratch.project_function is not None
|
||||
):
|
||||
return reverse("projectfunction-detail", args=[scratch.project_function.project.slug, scratch.project_function.id], request=self.context["request"]) # type: ignore
|
||||
|
||||
|
||||
class TerseScratchSerializer(ScratchSerializer):
|
||||
owner = TerseProfileField(read_only=True) # type: ignore
|
||||
owner = TerseProfileField(read_only=True) # type: ignore
|
||||
|
||||
class Meta:
|
||||
model = Scratch
|
||||
fields = ["url", "html_url", "owner", "last_updated", "creation_time", "platform", "compiler", "name", "score", "max_score", "project", "project_function"]
|
||||
fields = [
|
||||
"url",
|
||||
"html_url",
|
||||
"owner",
|
||||
"last_updated",
|
||||
"creation_time",
|
||||
"platform",
|
||||
"compiler",
|
||||
"name",
|
||||
"score",
|
||||
"max_score",
|
||||
"project",
|
||||
"project_function",
|
||||
]
|
||||
|
||||
|
||||
class GitHubRepoSerializer(serializers.ModelSerializer[GitHubRepo]):
|
||||
html_url = HtmlUrlField()
|
||||
@@ -147,6 +184,7 @@ class GitHubRepoSerializer(serializers.ModelSerializer[GitHubRepo]):
|
||||
exclude = ["id"]
|
||||
read_only_fields = ["last_pulled", "is_pulling"]
|
||||
|
||||
|
||||
class ProjectSerializer(serializers.ModelSerializer[Project]):
|
||||
url = HyperlinkedIdentityField(view_name="project-detail")
|
||||
html_url = HtmlUrlField()
|
||||
@@ -156,18 +194,25 @@ class ProjectSerializer(serializers.ModelSerializer[Project]):
|
||||
class Meta:
|
||||
model = Project
|
||||
exclude: List[str] = []
|
||||
depth = 1 # repo
|
||||
depth = 1 # repo
|
||||
|
||||
def get_members(self, project: Project):
|
||||
def get_url(user: User):
|
||||
return reverse("user-detail", args=[user.username], request=self.context["request"])
|
||||
return reverse(
|
||||
"user-detail", args=[user.username], request=self.context["request"]
|
||||
)
|
||||
|
||||
return [
|
||||
get_url(member.profile.user)
|
||||
for member in project.members()
|
||||
if member.profile.user is not None
|
||||
]
|
||||
|
||||
return [get_url(member.profile.user) for member in project.members() if member.profile.user is not None]
|
||||
|
||||
class ProjectFunctionSerializer(serializers.ModelSerializer[ProjectFunction]):
|
||||
url = SerializerMethodField()
|
||||
html_url = HtmlUrlField()
|
||||
project = HyperlinkedRelatedField(view_name="project-detail", read_only=True) # type: ignore
|
||||
project = HyperlinkedRelatedField(view_name="project-detail", read_only=True) # type: ignore
|
||||
attempts_count = SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
@@ -176,7 +221,11 @@ class ProjectFunctionSerializer(serializers.ModelSerializer[ProjectFunction]):
|
||||
read_only_fields = ["creation_time"]
|
||||
|
||||
def get_url(self, fn: ProjectFunction):
|
||||
return reverse("projectfunction-detail", args=[fn.project.slug, fn.id], request=self.context["request"])
|
||||
return reverse(
|
||||
"projectfunction-detail",
|
||||
args=[fn.project.slug, fn.id],
|
||||
request=self.context["request"],
|
||||
)
|
||||
|
||||
def get_attempts_count(self, fn: ProjectFunction):
|
||||
return Scratch.objects.filter(project_function=fn).count()
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
class Symbol:
|
||||
def __init__(self, label: str, ram_address: int, rom_address: Optional[int]):
|
||||
self.label = label
|
||||
self.ram_address = ram_address
|
||||
self.rom_address = rom_address
|
||||
|
||||
|
||||
def symbol_name_from_asm_file(asm_file: Path) -> Optional[str]:
|
||||
with asm_file.open("r") as f:
|
||||
lines = f.readlines()
|
||||
@@ -17,6 +19,7 @@ def symbol_name_from_asm_file(asm_file: Path) -> Optional[str]:
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def parse_symbol_addrs(file_path: Path) -> Dict[str, Symbol]:
|
||||
with open(file_path, "r") as f:
|
||||
lines = f.readlines()
|
||||
@@ -24,11 +27,18 @@ def parse_symbol_addrs(file_path: Path) -> Dict[str, Symbol]:
|
||||
symbol_addrs = {}
|
||||
|
||||
for line in lines:
|
||||
name = line[:line.find(" ")]
|
||||
name = line[: line.find(" ")]
|
||||
|
||||
attributes = line[line.find("//"):].split(" ")
|
||||
ram_addr = int(line[:line.find(";")].split("=")[1].strip(), base=0)
|
||||
rom_addr = next((int(attr.split(":")[1], base=0) for attr in attributes if attr.split(":")[0] == "rom"), None)
|
||||
attributes = line[line.find("//") :].split(" ")
|
||||
ram_addr = int(line[: line.find(";")].split("=")[1].strip(), base=0)
|
||||
rom_addr = next(
|
||||
(
|
||||
int(attr.split(":")[1], base=0)
|
||||
for attr in attributes
|
||||
if attr.split(":")[0] == "rom"
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
symbol_addrs[name] = Symbol(name, ram_addr, rom_addr)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from .models.scratch import Scratch, CompilerConfig
|
||||
from .models.github import GitHubUser, GitHubRepo
|
||||
from .models.project import Project, ProjectFunction, ProjectImportConfig
|
||||
|
||||
|
||||
def requiresCompiler(*compiler_ids: str):
|
||||
available = CompilerWrapper.available_compiler_ids()
|
||||
|
||||
@@ -32,7 +33,7 @@ def requiresCompiler(*compiler_ids: str):
|
||||
class BaseTestCase(APITestCase):
|
||||
# Create a scratch and return it as a DB object
|
||||
def create_scratch(self, partial: dict[str, str]) -> Scratch:
|
||||
response = self.client.post(reverse('scratch-list'), partial)
|
||||
response = self.client.post(reverse("scratch-list"), partial)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
scratch = Scratch.objects.get(slug=response.json()["slug"])
|
||||
assert scratch is not None
|
||||
@@ -40,47 +41,45 @@ class BaseTestCase(APITestCase):
|
||||
|
||||
def create_nop_scratch(self) -> Scratch:
|
||||
scratch_dict = {
|
||||
'compiler': 'dummy',
|
||||
'platform': 'dummy',
|
||||
'context': '',
|
||||
'target_asm': "jr $ra\nnop\n",
|
||||
"compiler": "dummy",
|
||||
"platform": "dummy",
|
||||
"context": "",
|
||||
"target_asm": "jr $ra\nnop\n",
|
||||
}
|
||||
return self.create_scratch(scratch_dict)
|
||||
|
||||
|
||||
class ScratchCreationTests(BaseTestCase):
|
||||
@requiresCompiler('ido7.1')
|
||||
@requiresCompiler("ido7.1")
|
||||
def test_accept_late_rodata(self):
|
||||
"""
|
||||
Ensure that .late_rodata (used in ASM_PROCESSOR) is accepted during scratch creation.
|
||||
"""
|
||||
scratch_dict = {
|
||||
'platform': 'n64',
|
||||
'compiler': 'ido7.1',
|
||||
'context': '',
|
||||
'target_asm':
|
||||
""".late_rodata
|
||||
"platform": "n64",
|
||||
"compiler": "ido7.1",
|
||||
"context": "",
|
||||
"target_asm": """.late_rodata
|
||||
glabel D_8092C224
|
||||
/* 000014 8092C224 3DCCCCCD */ .float 0.1
|
||||
|
||||
.text
|
||||
glabel func_80929D04
|
||||
jr $ra
|
||||
nop"""
|
||||
nop""",
|
||||
}
|
||||
self.create_scratch(scratch_dict)
|
||||
|
||||
@requiresCompiler('ido5.3')
|
||||
@requiresCompiler("ido5.3")
|
||||
def test_n64_func(self):
|
||||
"""
|
||||
Ensure that functions with t6/t7 registers can be assembled.
|
||||
"""
|
||||
scratch_dict = {
|
||||
'platform': 'n64',
|
||||
'compiler': 'ido5.3',
|
||||
'context': 'typedef unsigned char u8;',
|
||||
'target_asm':
|
||||
"""
|
||||
"platform": "n64",
|
||||
"compiler": "ido5.3",
|
||||
"context": "typedef unsigned char u8;",
|
||||
"target_asm": """
|
||||
.text
|
||||
glabel func_8019B378
|
||||
lui $t6, %hi(sOcarinaSongAppendPos)
|
||||
@@ -88,7 +87,7 @@ lbu $t6, %lo(sOcarinaSongAppendPos)($t6)
|
||||
lui $at, %hi(D_801D702C)
|
||||
jr $ra
|
||||
sb $t6, %lo(D_801D702C)($at)
|
||||
"""
|
||||
""",
|
||||
}
|
||||
self.create_scratch(scratch_dict)
|
||||
|
||||
@@ -97,38 +96,39 @@ sb $t6, %lo(D_801D702C)($at)
|
||||
Ensure that we can create scratches with the dummy platform and compiler
|
||||
"""
|
||||
scratch_dict = {
|
||||
'platform': 'dummy',
|
||||
'compiler': 'dummy',
|
||||
'context': 'typedef unsigned char u8;',
|
||||
'target_asm': 'this is some test asm',
|
||||
"platform": "dummy",
|
||||
"compiler": "dummy",
|
||||
"context": "typedef unsigned char u8;",
|
||||
"target_asm": "this is some test asm",
|
||||
}
|
||||
self.create_scratch(scratch_dict)
|
||||
|
||||
@requiresCompiler('ido7.1')
|
||||
@requiresCompiler("ido7.1")
|
||||
def test_max_score(self):
|
||||
"""
|
||||
Ensure that max_score is available upon scratch creation even if the initial compialtion fails
|
||||
"""
|
||||
scratch_dict = {
|
||||
'platform': 'n64',
|
||||
'compiler': 'ido7.1',
|
||||
'context': 'this aint cod',
|
||||
'target_asm': ".text\nglabel func_80929D04\njr $ra\nnop"
|
||||
"platform": "n64",
|
||||
"compiler": "ido7.1",
|
||||
"context": "this aint cod",
|
||||
"target_asm": ".text\nglabel func_80929D04\njr $ra\nnop",
|
||||
}
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
self.assertEqual(scratch.max_score, 200)
|
||||
|
||||
|
||||
class ScratchModificationTests(BaseTestCase):
|
||||
@requiresCompiler('gcc2.8.1', 'ido5.3')
|
||||
@requiresCompiler("gcc2.8.1", "ido5.3")
|
||||
def test_update_scratch_score(self):
|
||||
"""
|
||||
Ensure that a scratch's score gets updated when the code changes.
|
||||
"""
|
||||
scratch_dict = {
|
||||
'compiler': 'gcc2.8.1',
|
||||
'platform': 'n64',
|
||||
'context': '',
|
||||
'target_asm': "jr $ra"
|
||||
"compiler": "gcc2.8.1",
|
||||
"platform": "n64",
|
||||
"context": "",
|
||||
"target_asm": "jr $ra",
|
||||
}
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
slug = scratch.slug
|
||||
@@ -136,33 +136,35 @@ class ScratchModificationTests(BaseTestCase):
|
||||
self.assertGreater(scratch.score, 0)
|
||||
|
||||
# Obtain ownership of the scratch
|
||||
response = self.client.post(reverse('scratch-claim', kwargs={'pk': slug}))
|
||||
response = self.client.post(reverse("scratch-claim", kwargs={"pk": slug}))
|
||||
|
||||
# Update the scratch's code and compiler output
|
||||
scratch_patch = {
|
||||
'source_code': "int func() { return 2; }",
|
||||
'compiler': 'ido5.3'
|
||||
"source_code": "int func() { return 2; }",
|
||||
"compiler": "ido5.3",
|
||||
}
|
||||
|
||||
response = self.client.patch(reverse('scratch-detail', kwargs={'pk': slug}), scratch_patch)
|
||||
response = self.client.patch(
|
||||
reverse("scratch-detail", kwargs={"pk": slug}), scratch_patch
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
scratch = Scratch.objects.get(slug=slug)
|
||||
assert(scratch is not None)
|
||||
assert scratch is not None
|
||||
self.assertEqual(scratch.score, 200)
|
||||
|
||||
@requiresCompiler('gcc2.8.1')
|
||||
@requiresCompiler("gcc2.8.1")
|
||||
def test_update_scratch_score_on_compile_get(self):
|
||||
"""
|
||||
Ensure that a scratch's score gets updated on a GET to compile
|
||||
"""
|
||||
scratch_dict = {
|
||||
'compiler': 'gcc2.8.1',
|
||||
'compiler_flags': '-O2',
|
||||
'platform': 'n64',
|
||||
'context': '',
|
||||
'target_asm': 'jr $ra\nli $v0,2',
|
||||
'source_code': 'int func() { return 2; }'
|
||||
"compiler": "gcc2.8.1",
|
||||
"compiler_flags": "-O2",
|
||||
"platform": "n64",
|
||||
"context": "",
|
||||
"target_asm": "jr $ra\nli $v0,2",
|
||||
"source_code": "int func() { return 2; }",
|
||||
}
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
|
||||
@@ -173,39 +175,39 @@ class ScratchModificationTests(BaseTestCase):
|
||||
self.assertEqual(scratch.score, -1)
|
||||
slug = scratch.slug
|
||||
|
||||
response = self.client.get(reverse('scratch-compile', kwargs={'pk': slug}))
|
||||
response = self.client.get(reverse("scratch-compile", kwargs={"pk": slug}))
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
scratch = Scratch.objects.get(slug=slug)
|
||||
assert(scratch is not None)
|
||||
assert scratch is not None
|
||||
self.assertEqual(scratch.score, 0)
|
||||
|
||||
@requiresCompiler('ido7.1')
|
||||
@requiresCompiler("ido7.1")
|
||||
def test_create_scratch_score(self):
|
||||
"""
|
||||
Ensure that a scratch's score gets set upon creation.
|
||||
"""
|
||||
scratch_dict = {
|
||||
'platform': 'n64',
|
||||
'compiler': 'ido7.1',
|
||||
'context': '',
|
||||
'target_asm': 'jr $ra\nli $v0,2',
|
||||
'source_code': 'int func() { return 2; }'
|
||||
"platform": "n64",
|
||||
"compiler": "ido7.1",
|
||||
"context": "",
|
||||
"target_asm": "jr $ra\nli $v0,2",
|
||||
"source_code": "int func() { return 2; }",
|
||||
}
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
self.assertEqual(scratch.score, 0)
|
||||
|
||||
@requiresCompiler('ido7.1')
|
||||
@requiresCompiler("ido7.1")
|
||||
def test_update_scratch_score_does_not_affect_last_updated(self):
|
||||
"""
|
||||
Ensure that a scratch's last_updated field does not get updated when the max_score changes.
|
||||
"""
|
||||
scratch_dict = {
|
||||
'platform': 'n64',
|
||||
'compiler': 'ido7.1',
|
||||
'context': '',
|
||||
'target_asm': 'jr $ra\nli $v0,2',
|
||||
'source_code': 'int func() { return 2; }'
|
||||
"platform": "n64",
|
||||
"compiler": "ido7.1",
|
||||
"context": "",
|
||||
"target_asm": "jr $ra\nli $v0,2",
|
||||
"source_code": "int func() { return 2; }",
|
||||
}
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
scratch.max_score = -1
|
||||
@@ -217,33 +219,36 @@ class ScratchModificationTests(BaseTestCase):
|
||||
self.assertEqual(scratch.max_score, 200)
|
||||
self.assertEqual(prev_last_updated, scratch.last_updated)
|
||||
|
||||
|
||||
class ScratchForkTests(BaseTestCase):
|
||||
def test_fork_scratch(self):
|
||||
"""
|
||||
Ensure that a scratch's fork maintains the relevant properties of its parent
|
||||
"""
|
||||
scratch_dict = {
|
||||
'compiler': 'dummy',
|
||||
'platform': 'dummy',
|
||||
'context': '',
|
||||
'target_asm': 'glabel meow\njr $ra',
|
||||
'diff_label': 'meow',
|
||||
'name': 'cat scratch',
|
||||
"compiler": "dummy",
|
||||
"platform": "dummy",
|
||||
"context": "",
|
||||
"target_asm": "glabel meow\njr $ra",
|
||||
"diff_label": "meow",
|
||||
"name": "cat scratch",
|
||||
}
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
|
||||
slug = scratch.slug
|
||||
|
||||
fork_dict = {
|
||||
'compiler': 'dummy',
|
||||
'platform': 'dummy',
|
||||
'compiler_flags': '-O2',
|
||||
'source_code': 'int func() { return 2; }',
|
||||
'context': '',
|
||||
"compiler": "dummy",
|
||||
"platform": "dummy",
|
||||
"compiler_flags": "-O2",
|
||||
"source_code": "int func() { return 2; }",
|
||||
"context": "",
|
||||
}
|
||||
|
||||
# Create a fork of the scratch
|
||||
response = self.client.post(reverse('scratch-fork', kwargs={'pk': slug}), fork_dict)
|
||||
response = self.client.post(
|
||||
reverse("scratch-fork", kwargs={"pk": slug}), fork_dict
|
||||
)
|
||||
print(response.json())
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
@@ -260,42 +265,44 @@ class ScratchForkTests(BaseTestCase):
|
||||
|
||||
|
||||
class CompilationTests(BaseTestCase):
|
||||
@requiresCompiler('gcc2.8.1')
|
||||
@requiresCompiler("gcc2.8.1")
|
||||
def test_simple_compilation(self):
|
||||
"""
|
||||
Ensure that we can run a simple compilation via the api
|
||||
"""
|
||||
scratch_dict = {
|
||||
'compiler': 'gcc2.8.1',
|
||||
'platform': 'n64',
|
||||
'context': '',
|
||||
'target_asm': 'glabel func_80929D04\njr $ra\nnop'
|
||||
"compiler": "gcc2.8.1",
|
||||
"platform": "n64",
|
||||
"context": "",
|
||||
"target_asm": "glabel func_80929D04\njr $ra\nnop",
|
||||
}
|
||||
|
||||
# Test that we can create a scratch
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
|
||||
compile_dict = {
|
||||
'slug': scratch.slug,
|
||||
'compiler': 'gcc2.8.1',
|
||||
'compiler_flags': '-mips2 -O2',
|
||||
'source_code': 'int add(int a, int b){\nreturn a + b;\n}\n'
|
||||
"slug": scratch.slug,
|
||||
"compiler": "gcc2.8.1",
|
||||
"compiler_flags": "-mips2 -O2",
|
||||
"source_code": "int add(int a, int b){\nreturn a + b;\n}\n",
|
||||
}
|
||||
|
||||
# Test that we can compile a scratch
|
||||
response = self.client.post(reverse("scratch-compile", kwargs={'pk': scratch.slug}), compile_dict)
|
||||
response = self.client.post(
|
||||
reverse("scratch-compile", kwargs={"pk": scratch.slug}), compile_dict
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
@requiresCompiler('gcc2.8.1')
|
||||
@requiresCompiler("gcc2.8.1")
|
||||
def test_giant_compilation(self):
|
||||
"""
|
||||
Ensure that we can compile a giant file
|
||||
"""
|
||||
scratch_dict = {
|
||||
'compiler': 'gcc2.8.1',
|
||||
'platform': 'n64',
|
||||
'context': '',
|
||||
'target_asm': 'glabel func_80929D04\njr $ra\nnop'
|
||||
"compiler": "gcc2.8.1",
|
||||
"platform": "n64",
|
||||
"context": "",
|
||||
"target_asm": "glabel func_80929D04\njr $ra\nnop",
|
||||
}
|
||||
|
||||
# Test that we can create a scratch
|
||||
@@ -306,43 +313,67 @@ class CompilationTests(BaseTestCase):
|
||||
context += "extern int test_symbol_to_be_used_in_a_test;\n"
|
||||
|
||||
compile_dict = {
|
||||
'slug': scratch.slug,
|
||||
'compiler': 'gcc2.8.1',
|
||||
'compiler_flags': '-mips2 -O2',
|
||||
'source_code': 'int add(int a, int b){\nreturn a + b;\n}\n',
|
||||
'context': context,
|
||||
"slug": scratch.slug,
|
||||
"compiler": "gcc2.8.1",
|
||||
"compiler_flags": "-mips2 -O2",
|
||||
"source_code": "int add(int a, int b){\nreturn a + b;\n}\n",
|
||||
"context": context,
|
||||
}
|
||||
|
||||
# Test that we can compile a scratch
|
||||
response = self.client.post(reverse("scratch-compile", kwargs={'pk': scratch.slug}), compile_dict)
|
||||
response = self.client.post(
|
||||
reverse("scratch-compile", kwargs={"pk": scratch.slug}), compile_dict
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.assertEqual(len(response.json()["errors"]), 0)
|
||||
|
||||
@requiresCompiler('ido5.3')
|
||||
@requiresCompiler("ido5.3")
|
||||
def test_ido_line_endings(self):
|
||||
"""
|
||||
Ensure that compilations with \\r\\n line endings succeed
|
||||
"""
|
||||
result = CompilerWrapper.compile_code("ido5.3", "-mips2 -O2", "int dog = 5;", "extern char libvar1;\r\nextern char libvar2;\r\n")
|
||||
self.assertGreater(len(result.elf_object), 0, "The compilation result should be non-null")
|
||||
result = CompilerWrapper.compile_code(
|
||||
"ido5.3",
|
||||
"-mips2 -O2",
|
||||
"int dog = 5;",
|
||||
"extern char libvar1;\r\nextern char libvar2;\r\n",
|
||||
)
|
||||
self.assertGreater(
|
||||
len(result.elf_object), 0, "The compilation result should be non-null"
|
||||
)
|
||||
|
||||
@requiresCompiler('ido5.3')
|
||||
@requiresCompiler("ido5.3")
|
||||
def test_ido_kpic(self):
|
||||
"""
|
||||
Ensure that ido compilations including -KPIC produce different code
|
||||
"""
|
||||
result_non_shared = CompilerWrapper.compile_code("ido5.3", "-mips2 -O2", "int dog = 5;", "")
|
||||
result_kpic = CompilerWrapper.compile_code("ido5.3", "-mips2 -O2 -KPIC", "int dog = 5;", "")
|
||||
self.assertNotEqual(result_non_shared.elf_object, result_kpic.elf_object, "The compilation result should be different")
|
||||
result_non_shared = CompilerWrapper.compile_code(
|
||||
"ido5.3", "-mips2 -O2", "int dog = 5;", ""
|
||||
)
|
||||
result_kpic = CompilerWrapper.compile_code(
|
||||
"ido5.3", "-mips2 -O2 -KPIC", "int dog = 5;", ""
|
||||
)
|
||||
self.assertNotEqual(
|
||||
result_non_shared.elf_object,
|
||||
result_kpic.elf_object,
|
||||
"The compilation result should be different",
|
||||
)
|
||||
|
||||
@requiresCompiler('mwcc_247_92')
|
||||
@requiresCompiler("mwcc_247_92")
|
||||
def test_mwcc_wine(self):
|
||||
"""
|
||||
Ensure that we can invoke mwcc through wine
|
||||
"""
|
||||
result = CompilerWrapper.compile_code("mwcc_247_92", "-str reuse -inline on -fp off -O0", "int func(void) { return 5; }", "extern char libvar1;\r\nextern char libvar2;\r\n")
|
||||
self.assertGreater(len(result.elf_object), 0, "The compilation result should be non-null")
|
||||
result = CompilerWrapper.compile_code(
|
||||
"mwcc_247_92",
|
||||
"-str reuse -inline on -fp off -O0",
|
||||
"int func(void) { return 5; }",
|
||||
"extern char libvar1;\r\nextern char libvar2;\r\n",
|
||||
)
|
||||
self.assertGreater(
|
||||
len(result.elf_object), 0, "The compilation result should be non-null"
|
||||
)
|
||||
|
||||
def test_dummy_compiler(self):
|
||||
"""
|
||||
@@ -350,72 +381,102 @@ class CompilationTests(BaseTestCase):
|
||||
"""
|
||||
|
||||
result = CompilerWrapper.compile_code("dummy", "", "sample text 123", "")
|
||||
self.assertGreater(len(result.elf_object), 0, "The compilation result should be non-null")
|
||||
self.assertGreater(
|
||||
len(result.elf_object), 0, "The compilation result should be non-null"
|
||||
)
|
||||
|
||||
|
||||
class DecompilationTests(BaseTestCase):
|
||||
@requiresCompiler('gcc2.8.1')
|
||||
@requiresCompiler("gcc2.8.1")
|
||||
def test_default_decompilation(self):
|
||||
"""
|
||||
Ensure that a scratch's initial decompilation makes sense
|
||||
"""
|
||||
scratch_dict = {
|
||||
'compiler': 'gcc2.8.1',
|
||||
'platform': 'n64',
|
||||
'context': '',
|
||||
'target_asm': 'glabel return_2\njr $ra\nli $v0,2',
|
||||
"compiler": "gcc2.8.1",
|
||||
"platform": "n64",
|
||||
"context": "",
|
||||
"target_asm": "glabel return_2\njr $ra\nli $v0,2",
|
||||
}
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
self.assertEqual(scratch.source_code, "? return_2(void) {\n return 2;\n}\n")
|
||||
|
||||
@requiresCompiler('gcc2.8.1')
|
||||
@requiresCompiler("gcc2.8.1")
|
||||
def test_decompile_endpoint(self):
|
||||
"""
|
||||
Ensure that the decompile endpoint works
|
||||
"""
|
||||
scratch_dict = {
|
||||
'compiler': 'gcc2.8.1',
|
||||
'platform': 'n64',
|
||||
'context': 'typedef int s32;',
|
||||
'target_asm': 'glabel return_2\njr $ra\nli $v0,2',
|
||||
"compiler": "gcc2.8.1",
|
||||
"platform": "n64",
|
||||
"context": "typedef int s32;",
|
||||
"target_asm": "glabel return_2\njr $ra\nli $v0,2",
|
||||
}
|
||||
scratch = self.create_scratch(scratch_dict)
|
||||
|
||||
response = self.client.post(reverse("scratch-decompile", kwargs={'pk': scratch.slug}))
|
||||
self.assertEqual(response.json()["decompilation"], "? return_2(void) {\n return 2;\n}\n")
|
||||
response = self.client.post(
|
||||
reverse("scratch-decompile", kwargs={"pk": scratch.slug})
|
||||
)
|
||||
self.assertEqual(
|
||||
response.json()["decompilation"], "? return_2(void) {\n return 2;\n}\n"
|
||||
)
|
||||
|
||||
# Provide context and see that the decompilation changes
|
||||
response = self.client.post(reverse("scratch-decompile", kwargs={'pk': scratch.slug}), data={"context": "s32 return_2(void);"})
|
||||
self.assertEqual(response.json()["decompilation"], "s32 return_2(void) {\n return 2;\n}\n")
|
||||
response = self.client.post(
|
||||
reverse("scratch-decompile", kwargs={"pk": scratch.slug}),
|
||||
data={"context": "s32 return_2(void);"},
|
||||
)
|
||||
self.assertEqual(
|
||||
response.json()["decompilation"], "s32 return_2(void) {\n return 2;\n}\n"
|
||||
)
|
||||
|
||||
|
||||
class M2CTests(TestCase):
|
||||
"""
|
||||
Ensure that pointers are next to types (left style)
|
||||
"""
|
||||
|
||||
def test_left_pointer_style(self):
|
||||
c_code = M2CWrapper.decompile("""
|
||||
c_code = M2CWrapper.decompile(
|
||||
"""
|
||||
glabel func
|
||||
li $t6,1
|
||||
jr $ra
|
||||
sw $t6,0($a0)
|
||||
""", "", "ido", "mips")
|
||||
""",
|
||||
"",
|
||||
"ido",
|
||||
"mips",
|
||||
)
|
||||
|
||||
self.assertTrue("s32*" in c_code, "The decompiled c code should have a left-style pointer, was instead:\n" + c_code)
|
||||
self.assertTrue(
|
||||
"s32*" in c_code,
|
||||
"The decompiled c code should have a left-style pointer, was instead:\n"
|
||||
+ c_code,
|
||||
)
|
||||
|
||||
"""
|
||||
Ensure that we can decompile ppc code
|
||||
"""
|
||||
|
||||
def test_ppc(self):
|
||||
c_code = M2CWrapper.decompile("""
|
||||
c_code = M2CWrapper.decompile(
|
||||
"""
|
||||
.global func_800B43A8
|
||||
func_800B43A8:
|
||||
xor r0, r3, r3
|
||||
subf r3, r4, r0
|
||||
blr
|
||||
""", "", "mwcc", "ppc")
|
||||
""",
|
||||
"",
|
||||
"mwcc",
|
||||
"ppc",
|
||||
)
|
||||
|
||||
self.assertEqual("s32 func_800B43A8(s32 arg0, s32 arg1) {\n return (arg0 ^ arg0) - arg1;\n}\n", c_code)
|
||||
self.assertEqual(
|
||||
"s32 func_800B43A8(s32 arg0, s32 arg1) {\n return (arg0 ^ arg0) - arg1;\n}\n",
|
||||
c_code,
|
||||
)
|
||||
|
||||
|
||||
class UserTests(BaseTestCase):
|
||||
@@ -453,7 +514,7 @@ class UserTests(BaseTestCase):
|
||||
"followers": 0,
|
||||
"following": 0,
|
||||
"created_at": "2021-08-23T20:56:16Z",
|
||||
"updated_at": "2021-08-23T21:00:04Z"
|
||||
"updated_at": "2021-08-23T21:00:04Z",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -477,16 +538,34 @@ class UserTests(BaseTestCase):
|
||||
Ensure that a user is created upon sign-in with GitHub.
|
||||
"""
|
||||
|
||||
responses.add(responses.POST, "https://github.com/login/oauth/access_token", json={
|
||||
"access_token": "__mock__",
|
||||
"scope": "public_repo",
|
||||
}, status=200)
|
||||
responses.add(responses.GET, "https://api.github.com:443/user", json=self.GITHUB_USER, status=200)
|
||||
responses.add(responses.GET, f"https://api.github.com:443/user/{self.GITHUB_USER['id']}", json=self.GITHUB_USER, status=200)
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"https://github.com/login/oauth/access_token",
|
||||
json={
|
||||
"access_token": "__mock__",
|
||||
"scope": "public_repo",
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"https://api.github.com:443/user",
|
||||
json=self.GITHUB_USER,
|
||||
status=200,
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://api.github.com:443/user/{self.GITHUB_USER['id']}",
|
||||
json=self.GITHUB_USER,
|
||||
status=200,
|
||||
)
|
||||
|
||||
response = self.client.post(self.current_user_url, {
|
||||
"code": "__mock__",
|
||||
})
|
||||
response = self.client.post(
|
||||
self.current_user_url,
|
||||
{
|
||||
"code": "__mock__",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(Profile.objects.count(), 1)
|
||||
@@ -502,17 +581,35 @@ class UserTests(BaseTestCase):
|
||||
# log in as the user
|
||||
self.test_github_login()
|
||||
|
||||
responses.add(responses.POST, "https://github.com/login/oauth/access_token", json={
|
||||
"access_token": "__mock__",
|
||||
"scope": "public_repo",
|
||||
}, status=200)
|
||||
responses.add(responses.GET, "https://api.github.com:443/user", json=self.GITHUB_USER, status=200)
|
||||
responses.add(responses.GET, f"https://api.github.com:443/user/{self.GITHUB_USER['id']}", json=self.GITHUB_USER, status=200)
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"https://github.com/login/oauth/access_token",
|
||||
json={
|
||||
"access_token": "__mock__",
|
||||
"scope": "public_repo",
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"https://api.github.com:443/user",
|
||||
json=self.GITHUB_USER,
|
||||
status=200,
|
||||
)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
f"https://api.github.com:443/user/{self.GITHUB_USER['id']}",
|
||||
json=self.GITHUB_USER,
|
||||
status=200,
|
||||
)
|
||||
|
||||
# log in as the user again
|
||||
response = self.client.post(self.current_user_url, {
|
||||
"code": "__mock__",
|
||||
})
|
||||
response = self.client.post(
|
||||
self.current_user_url,
|
||||
{
|
||||
"code": "__mock__",
|
||||
},
|
||||
)
|
||||
|
||||
# check there is only one user created
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
@@ -533,7 +630,7 @@ class UserTests(BaseTestCase):
|
||||
response = self.client.get(self.current_user_url)
|
||||
self.assertEqual(response.json()["is_anonymous"], False)
|
||||
|
||||
self.assertEqual(Profile.objects.count(), 1) # logged-in
|
||||
self.assertEqual(Profile.objects.count(), 1) # logged-in
|
||||
|
||||
# log out
|
||||
response = self.client.post(self.current_user_url, {})
|
||||
@@ -541,7 +638,7 @@ class UserTests(BaseTestCase):
|
||||
self.assertEqual(response.json()["is_you"], True)
|
||||
self.assertEqual(response.json()["is_anonymous"], True)
|
||||
|
||||
self.assertEqual(Profile.objects.count(), 2) # logged-out
|
||||
self.assertEqual(Profile.objects.count(), 2) # logged-out
|
||||
|
||||
for i in range(3):
|
||||
# verify we are logged out
|
||||
@@ -557,12 +654,15 @@ class UserTests(BaseTestCase):
|
||||
"""
|
||||
Create a scratch anonymously, claim it, then log in and verify that the scratch owner is your logged-in user.
|
||||
"""
|
||||
response = self.client.post("/api/scratch", {
|
||||
'compiler': 'dummy',
|
||||
'platform': 'dummy',
|
||||
'context': '',
|
||||
'target_asm': "jr $ra\nnop\n"
|
||||
})
|
||||
response = self.client.post(
|
||||
"/api/scratch",
|
||||
{
|
||||
"compiler": "dummy",
|
||||
"platform": "dummy",
|
||||
"context": "",
|
||||
"target_asm": "jr $ra\nnop\n",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
slug = response.json()["slug"]
|
||||
|
||||
@@ -573,7 +673,9 @@ class UserTests(BaseTestCase):
|
||||
self.assertTrue(response.json()["success"])
|
||||
|
||||
response = self.client.get(f"/api/scratch/{slug}")
|
||||
self.assertEqual(response.json()["owner"]["username"], self.GITHUB_USER["login"])
|
||||
self.assertEqual(
|
||||
response.json()["owner"]["username"], self.GITHUB_USER["login"]
|
||||
)
|
||||
self.assertEqual(response.json()["owner"]["is_you"], True)
|
||||
|
||||
|
||||
@@ -607,7 +709,10 @@ class ScratchDetailTests(BaseTestCase):
|
||||
last_modified = response.headers.get("Last-Modified")
|
||||
|
||||
# should be unmodified
|
||||
response = self.client.get(reverse("scratch-detail", args=[scratch.slug]), HTTP_IF_MODIFIED_SINCE=last_modified)
|
||||
response = self.client.get(
|
||||
reverse("scratch-detail", args=[scratch.slug]),
|
||||
HTTP_IF_MODIFIED_SINCE=last_modified,
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_304_NOT_MODIFIED)
|
||||
|
||||
# Last-Modified is only granular to the second
|
||||
@@ -620,7 +725,10 @@ class ScratchDetailTests(BaseTestCase):
|
||||
self.assertNotEqual(scratch.last_updated, old_last_updated)
|
||||
|
||||
# should now be modified
|
||||
response = self.client.get(reverse("scratch-detail", args=[scratch.slug]), HTTP_IF_MODIFIED_SINCE=last_modified)
|
||||
response = self.client.get(
|
||||
reverse("scratch-detail", args=[scratch.slug]),
|
||||
HTTP_IF_MODIFIED_SINCE=last_modified,
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_double_claim(self):
|
||||
@@ -712,7 +820,7 @@ class RequestTests(APITestCase):
|
||||
Ensure that we create a profile for a normal request
|
||||
"""
|
||||
|
||||
response = self.client.get(reverse('compilers'))
|
||||
response = self.client.get(reverse("compilers"))
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.assertEqual(Profile.objects.count(), 1)
|
||||
@@ -722,7 +830,7 @@ class RequestTests(APITestCase):
|
||||
Ensure that we don't create profiles for node-fetch requests (SSR)
|
||||
"""
|
||||
|
||||
response = self.client.get(reverse('compilers'), HTTP_USER_AGENT='node-fetch')
|
||||
response = self.client.get(reverse("compilers"), HTTP_USER_AGENT="node-fetch")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.assertEqual(Profile.objects.count(), 0)
|
||||
@@ -764,7 +872,7 @@ class ProjectTests(TestCase):
|
||||
mock_subprocess.assert_called_once()
|
||||
self.assertListEqual(
|
||||
mock_subprocess.call_args.args[0][:3],
|
||||
["git", "clone", "https://github.com/decompme/example-project"]
|
||||
["git", "clone", "https://github.com/decompme/example-project"],
|
||||
)
|
||||
mock_mkdir.assert_called_once_with(parents=True)
|
||||
|
||||
@@ -792,19 +900,25 @@ class ProjectTests(TestCase):
|
||||
(dir / "src").mkdir(parents=True)
|
||||
asm_file = dir / "asm" / "nonmatchings" / "section" / "test.s"
|
||||
with asm_file.open("w") as f:
|
||||
f.writelines([
|
||||
"glabel test\n",
|
||||
"jr $ra\n",
|
||||
"nop\n",
|
||||
])
|
||||
f.writelines(
|
||||
[
|
||||
"glabel test\n",
|
||||
"jr $ra\n",
|
||||
"nop\n",
|
||||
]
|
||||
)
|
||||
with (dir / "src" / "section.c").open("w") as f:
|
||||
f.writelines([
|
||||
"typedef int s32;\n",
|
||||
])
|
||||
f.writelines(
|
||||
[
|
||||
"typedef int s32;\n",
|
||||
]
|
||||
)
|
||||
with (dir / "symbol_addrs.txt").open("w") as f:
|
||||
f.writelines([
|
||||
"test = 0x80240000; // type:func rom:0x1000\n",
|
||||
])
|
||||
f.writelines(
|
||||
[
|
||||
"test = 0x80240000; // type:func rom:0x1000\n",
|
||||
]
|
||||
)
|
||||
|
||||
# configure the import
|
||||
compiler_config = CompilerConfig(
|
||||
|
||||
@@ -3,11 +3,19 @@ from django.urls import path
|
||||
from coreapp.views import compilers, scratch, user, project
|
||||
|
||||
urlpatterns = [
|
||||
path('compilers', compilers.CompilersDetail.as_view(), name='compilers'),
|
||||
path("compilers", compilers.CompilersDetail.as_view(), name="compilers"),
|
||||
*scratch.router.urls,
|
||||
*project.router.urls,
|
||||
path('user', user.CurrentUser.as_view(), name="current-user"),
|
||||
path('user/scratches', user.CurrentUserScratchList.as_view(), name="current-user-scratches"),
|
||||
path('users/<slug:username>', user.user, name="user-detail"),
|
||||
path('users/<slug:username>/scratches', user.UserScratchList.as_view(), name="user-scratches"),
|
||||
path("user", user.CurrentUser.as_view(), name="current-user"),
|
||||
path(
|
||||
"user/scratches",
|
||||
user.CurrentUserScratchList.as_view(),
|
||||
name="current-user-scratches",
|
||||
),
|
||||
path("users/<slug:username>", user.user, name="user-detail"),
|
||||
path(
|
||||
"users/<slug:username>/scratches",
|
||||
user.UserScratchList.as_view(),
|
||||
name="user-scratches",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,8 @@ from typing import Tuple
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_startup_time = int(time.time())
|
||||
logger.info('Startup time: %s', _startup_time)
|
||||
logger.info("Startup time: %s", _startup_time)
|
||||
|
||||
|
||||
def gen_hash(key: Tuple[str, ...]) -> str:
|
||||
return hashlib.sha256(str(key + (_startup_time,)).encode('utf-8')).hexdigest()
|
||||
return hashlib.sha256(str(key + (_startup_time,)).encode("utf-8")).hexdigest()
|
||||
|
||||
@@ -8,6 +8,7 @@ from ..decorators.django import condition
|
||||
|
||||
boot_time = now()
|
||||
|
||||
|
||||
class CompilersDetail(APIView):
|
||||
@condition(last_modified_func=lambda request: boot_time)
|
||||
def head(self, request: Request):
|
||||
@@ -15,9 +16,11 @@ class CompilersDetail(APIView):
|
||||
|
||||
@condition(last_modified_func=lambda request: boot_time)
|
||||
def get(self, request: Request):
|
||||
return Response({
|
||||
# compiler_ids is used by the permuter
|
||||
"compiler_ids": CompilerWrapper.available_compiler_ids(),
|
||||
"compilers": CompilerWrapper.available_compilers(),
|
||||
"platforms": CompilerWrapper.available_platforms(),
|
||||
})
|
||||
return Response(
|
||||
{
|
||||
# compiler_ids is used by the permuter
|
||||
"compiler_ids": CompilerWrapper.available_compiler_ids(),
|
||||
"compilers": CompilerWrapper.available_compilers(),
|
||||
"platforms": CompilerWrapper.available_platforms(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,25 +15,34 @@ import django_filters
|
||||
from ..models.scratch import Scratch
|
||||
from ..models.project import Project, ProjectFunction
|
||||
from ..models.github import GitHubRepo, GitHubRepoBusyException
|
||||
from ..serializers import ProjectFunctionSerializer, ProjectSerializer, ScratchSerializer, TerseScratchSerializer
|
||||
from ..serializers import (
|
||||
ProjectFunctionSerializer,
|
||||
ProjectSerializer,
|
||||
ScratchSerializer,
|
||||
TerseScratchSerializer,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NotProjectMaintainer(APIException):
|
||||
status_code = status.HTTP_403_FORBIDDEN
|
||||
default_detail = "You must be a project maintainer to perform this action."
|
||||
|
||||
|
||||
class ProjectPagination(CursorPagination):
|
||||
ordering="-creation_time"
|
||||
page_size=20
|
||||
page_size_query_param="page_size"
|
||||
max_page_size=100
|
||||
ordering = "-creation_time"
|
||||
page_size = 20
|
||||
page_size_query_param = "page_size"
|
||||
max_page_size = 100
|
||||
|
||||
|
||||
class ProjectFunctionPagination(CursorPagination):
|
||||
ordering="-creation_time"
|
||||
page_size=20
|
||||
page_size_query_param="page_size"
|
||||
max_page_size=100
|
||||
ordering = "-creation_time"
|
||||
page_size = 20
|
||||
page_size_query_param = "page_size"
|
||||
max_page_size = 100
|
||||
|
||||
|
||||
class ProjectViewSet(
|
||||
mixins.RetrieveModelMixin,
|
||||
@@ -44,7 +53,7 @@ class ProjectViewSet(
|
||||
pagination_class = ProjectPagination
|
||||
serializer_class = ProjectSerializer
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
@action(detail=True, methods=["POST"])
|
||||
def pull(self, request, pk):
|
||||
project: Project = self.get_object()
|
||||
repo: GitHubRepo = project.repo
|
||||
@@ -56,8 +65,12 @@ class ProjectViewSet(
|
||||
t = Thread(target=GitHubRepo.pull, args=(project.repo,))
|
||||
t.start()
|
||||
|
||||
repo.is_pulling = True # Respond with is_pulling=True; the thread will save is_pulling=True to the DB
|
||||
return Response(ProjectSerializer(project, context={ "request": request }).data, status=status.HTTP_202_ACCEPTED)
|
||||
repo.is_pulling = True # Respond with is_pulling=True; the thread will save is_pulling=True to the DB
|
||||
return Response(
|
||||
ProjectSerializer(project, context={"request": request}).data,
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
|
||||
class ProjectFunctionViewSet(
|
||||
mixins.RetrieveModelMixin,
|
||||
@@ -67,22 +80,31 @@ class ProjectFunctionViewSet(
|
||||
pagination_class = ProjectFunctionPagination
|
||||
serializer_class = ProjectFunctionSerializer
|
||||
|
||||
filter_fields = ['rom_address', 'is_matched_in_repo']
|
||||
filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.SearchFilter]
|
||||
search_fields = ['display_name']
|
||||
filter_fields = ["rom_address", "is_matched_in_repo"]
|
||||
filter_backends = [
|
||||
django_filters.rest_framework.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
]
|
||||
search_fields = ["display_name"]
|
||||
|
||||
def get_queryset(self):
|
||||
return ProjectFunction.objects.filter(project=self.kwargs["parent_lookup_slug"])
|
||||
|
||||
@action(detail=True, methods=['GET', 'POST'])
|
||||
@action(detail=True, methods=["GET", "POST"])
|
||||
def attempts(self, request, **kwargs):
|
||||
fn: ProjectFunction = self.get_object()
|
||||
project: Project = fn.project
|
||||
repo: GitHubRepo = project.repo
|
||||
|
||||
if request.method == "GET":
|
||||
attempts = Scratch.objects.filter(project_function=fn).order_by("-last_updated")
|
||||
return Response(TerseScratchSerializer(attempts, many=True, context={ "request": request }).data)
|
||||
attempts = Scratch.objects.filter(project_function=fn).order_by(
|
||||
"-last_updated"
|
||||
)
|
||||
return Response(
|
||||
TerseScratchSerializer(
|
||||
attempts, many=True, context={"request": request}
|
||||
).data
|
||||
)
|
||||
elif request.method == "POST":
|
||||
if repo.is_pulling:
|
||||
raise GitHubRepoBusyException()
|
||||
@@ -92,12 +114,20 @@ class ProjectFunctionViewSet(
|
||||
scratch.owner = request.profile
|
||||
scratch.save()
|
||||
|
||||
return Response(ScratchSerializer(scratch, context={ "request": request }).data, status=status.HTTP_201_CREATED)
|
||||
return Response(
|
||||
ScratchSerializer(scratch, context={"request": request}).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
else:
|
||||
raise Exception("Unsupported method")
|
||||
|
||||
|
||||
router = ExtendedSimpleRouter(trailing_slash=False)
|
||||
(
|
||||
router.register(r'projects', ProjectViewSet)
|
||||
.register(r'functions', ProjectFunctionViewSet, basename='projectfunction', parents_query_lookups=['slug'])
|
||||
router.register(r"projects", ProjectViewSet).register(
|
||||
r"functions",
|
||||
ProjectFunctionViewSet,
|
||||
basename="projectfunction",
|
||||
parents_query_lookups=["slug"],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -22,7 +22,11 @@ from ..middleware import Request
|
||||
from ..models.scratch import Asm, Scratch
|
||||
from ..models.project import Project, ProjectFunction
|
||||
from ..models.github import GitHubRepo, GitHubRepoBusyException
|
||||
from ..serializers import ScratchCreateSerializer, ScratchSerializer, TerseScratchSerializer
|
||||
from ..serializers import (
|
||||
ScratchCreateSerializer,
|
||||
ScratchSerializer,
|
||||
TerseScratchSerializer,
|
||||
)
|
||||
from ..asm_diff_wrapper import AsmDifferWrapper
|
||||
from ..compiler_wrapper import CompilationResult, CompilerWrapper, DiffResult
|
||||
from ..decompiler_wrapper import DecompilerWrapper
|
||||
@@ -30,22 +34,40 @@ from ..error import CompilationError, DiffError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProjectNotMemberException(APIException):
|
||||
status_code = status.HTTP_403_FORBIDDEN
|
||||
default_detail = "You must be a maintainer of the project to perform this action."
|
||||
|
||||
|
||||
def get_db_asm(request_asm) -> Asm:
|
||||
h = hashlib.sha256(request_asm.encode()).hexdigest()
|
||||
asm, _ = Asm.objects.get_or_create(hash=h, defaults={
|
||||
"data": request_asm,
|
||||
})
|
||||
asm, _ = Asm.objects.get_or_create(
|
||||
hash=h,
|
||||
defaults={
|
||||
"data": request_asm,
|
||||
},
|
||||
)
|
||||
return asm
|
||||
|
||||
def compile_scratch(scratch: Scratch) -> CompilationResult:
|
||||
return CompilerWrapper.compile_code(scratch.compiler, scratch.compiler_flags, scratch.source_code, scratch.context)
|
||||
|
||||
def diff_compilation(scratch: Scratch, compilation: CompilationResult, allow_target_only:bool = False) -> DiffResult:
|
||||
return AsmDifferWrapper.diff(scratch.target_assembly, scratch.platform, scratch.diff_label, compilation.elf_object, allow_target_only=allow_target_only)
|
||||
def compile_scratch(scratch: Scratch) -> CompilationResult:
|
||||
return CompilerWrapper.compile_code(
|
||||
scratch.compiler, scratch.compiler_flags, scratch.source_code, scratch.context
|
||||
)
|
||||
|
||||
|
||||
def diff_compilation(
|
||||
scratch: Scratch, compilation: CompilationResult, allow_target_only: bool = False
|
||||
) -> DiffResult:
|
||||
return AsmDifferWrapper.diff(
|
||||
scratch.target_assembly,
|
||||
scratch.platform,
|
||||
scratch.diff_label,
|
||||
compilation.elf_object,
|
||||
allow_target_only=allow_target_only,
|
||||
)
|
||||
|
||||
|
||||
def update_scratch_score(scratch: Scratch, diff: DiffResult):
|
||||
"""
|
||||
@@ -57,7 +79,8 @@ def update_scratch_score(scratch: Scratch, diff: DiffResult):
|
||||
if score != scratch.score or max_score != scratch.max_score:
|
||||
scratch.score = score
|
||||
scratch.max_score = max_score
|
||||
scratch.save(update_fields=['score', 'max_score'])
|
||||
scratch.save(update_fields=["score", "max_score"])
|
||||
|
||||
|
||||
def compile_scratch_update_score(scratch: Scratch) -> None:
|
||||
"""
|
||||
@@ -81,20 +104,22 @@ def compile_scratch_update_score(scratch: Scratch) -> None:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def scratch_last_modified(request: Request, pk: Optional[str] = None) -> Optional[datetime]:
|
||||
def scratch_last_modified(
|
||||
request: Request, pk: Optional[str] = None
|
||||
) -> Optional[datetime]:
|
||||
scratch: Optional[Scratch] = Scratch.objects.filter(slug=pk).first()
|
||||
if scratch:
|
||||
return scratch.last_updated
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def scratch_etag(request: Request, pk: Optional[str] = None) -> Optional[str]:
|
||||
scratch: Optional[Scratch] = Scratch.objects.filter(slug=pk).first()
|
||||
if scratch:
|
||||
# We hash the Accept header too to avoid the following situation:
|
||||
# - DEBUG is enabled
|
||||
# - Developer visits /api/scratch/:slug manually, seeing the DRF HTML page
|
||||
# - Developer visits /api/scratch/:slug manually, seeing the DRF HTML page
|
||||
# - **Browsers caches the page**
|
||||
# - Developer visits /scratch/:slug
|
||||
# - The frontend JS fetches /api/scratch/:slug
|
||||
@@ -103,7 +128,11 @@ def scratch_etag(request: Request, pk: Optional[str] = None) -> Optional[str]:
|
||||
else:
|
||||
return None
|
||||
|
||||
scratch_condition = condition(last_modified_func=scratch_last_modified, etag_func=scratch_etag)
|
||||
|
||||
scratch_condition = condition(
|
||||
last_modified_func=scratch_last_modified, etag_func=scratch_etag
|
||||
)
|
||||
|
||||
|
||||
def family_etag(request: Request, pk: Optional[str] = None) -> Optional[str]:
|
||||
scratch: Optional[Scratch] = Scratch.objects.filter(slug=pk).first()
|
||||
@@ -117,6 +146,7 @@ def family_etag(request: Request, pk: Optional[str] = None) -> Optional[str]:
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def update_needs_recompile(partial: Dict[str, Any]) -> bool:
|
||||
recompile_params = ["compiler", "compiler_flags", "source_code", "context"]
|
||||
|
||||
@@ -126,6 +156,7 @@ def update_needs_recompile(partial: Dict[str, Any]) -> bool:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
|
||||
create_ser = ScratchCreateSerializer(data=data)
|
||||
create_ser.is_valid(raise_exception=True)
|
||||
@@ -139,7 +170,10 @@ def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
|
||||
|
||||
if platform:
|
||||
if CompilerWrapper.platform_from_compiler(compiler) != platform:
|
||||
raise APIException(f"Compiler {compiler} is not compatible with platform {platform}", str(status.HTTP_400_BAD_REQUEST))
|
||||
raise APIException(
|
||||
f"Compiler {compiler} is not compatible with platform {platform}",
|
||||
str(status.HTTP_400_BAD_REQUEST),
|
||||
)
|
||||
else:
|
||||
platform = CompilerWrapper.platform_from_compiler(compiler)
|
||||
|
||||
@@ -161,7 +195,9 @@ def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
|
||||
source_code = data.get("source_code")
|
||||
if not source_code:
|
||||
default_source_code = f"void {diff_label or 'func'}(void) {{\n // ...\n}}\n"
|
||||
source_code = DecompilerWrapper.decompile(default_source_code, platform, asm.data, context, compiler)
|
||||
source_code = DecompilerWrapper.decompile(
|
||||
default_source_code, platform, asm.data, context, compiler
|
||||
)
|
||||
|
||||
compiler_flags = data.get("compiler_flags", "")
|
||||
if compiler and compiler_flags:
|
||||
@@ -181,32 +217,42 @@ def create_scratch(data: Dict[str, Any], allow_project=False) -> Scratch:
|
||||
if repo.is_pulling:
|
||||
raise GitHubRepoBusyException()
|
||||
|
||||
project_function = ProjectFunction.objects.filter(project=project_obj, rom_address=rom_address).first()
|
||||
project_function = ProjectFunction.objects.filter(
|
||||
project=project_obj, rom_address=rom_address
|
||||
).first()
|
||||
if not project_function:
|
||||
raise serializers.ValidationError("Function with given rom address does not exist in project")
|
||||
raise serializers.ValidationError(
|
||||
"Function with given rom address does not exist in project"
|
||||
)
|
||||
else:
|
||||
project_function = None
|
||||
|
||||
ser = ScratchSerializer(data={
|
||||
"name": name,
|
||||
"compiler": compiler,
|
||||
"compiler_flags": compiler_flags,
|
||||
"context": context,
|
||||
"diff_label": diff_label,
|
||||
"source_code": source_code,
|
||||
})
|
||||
ser = ScratchSerializer(
|
||||
data={
|
||||
"name": name,
|
||||
"compiler": compiler,
|
||||
"compiler_flags": compiler_flags,
|
||||
"context": context,
|
||||
"diff_label": diff_label,
|
||||
"source_code": source_code,
|
||||
}
|
||||
)
|
||||
ser.is_valid(raise_exception=True)
|
||||
scratch = ser.save(target_assembly=assembly, platform=platform, project_function=project_function)
|
||||
scratch = ser.save(
|
||||
target_assembly=assembly, platform=platform, project_function=project_function
|
||||
)
|
||||
|
||||
compile_scratch_update_score(scratch)
|
||||
|
||||
return scratch
|
||||
|
||||
|
||||
class ScratchPagination(CursorPagination):
|
||||
ordering="-last_updated"
|
||||
page_size=10
|
||||
page_size_query_param="page_size"
|
||||
max_page_size=100
|
||||
ordering = "-last_updated"
|
||||
page_size = 10
|
||||
page_size_query_param = "page_size"
|
||||
max_page_size = 100
|
||||
|
||||
|
||||
class ScratchViewSet(
|
||||
mixins.CreateModelMixin,
|
||||
@@ -217,9 +263,12 @@ class ScratchViewSet(
|
||||
):
|
||||
queryset = Scratch.objects.all()
|
||||
pagination_class = ScratchPagination
|
||||
filter_fields = ['platform', 'compiler']
|
||||
filter_backends = [django_filters.rest_framework.DjangoFilterBackend, filters.SearchFilter]
|
||||
search_fields = ['name', 'diff_label']
|
||||
filter_fields = ["platform", "compiler"]
|
||||
filter_backends = [
|
||||
django_filters.rest_framework.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
]
|
||||
search_fields = ["name", "diff_label"]
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "list":
|
||||
@@ -235,7 +284,7 @@ class ScratchViewSet(
|
||||
scratch = create_scratch(request.data)
|
||||
|
||||
return Response(
|
||||
ScratchSerializer(scratch, context={ 'request': request }).data,
|
||||
ScratchSerializer(scratch, context={"request": request}).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
@@ -253,12 +302,14 @@ class ScratchViewSet(
|
||||
if update_needs_recompile(request.data):
|
||||
scratch = self.get_object()
|
||||
compile_scratch_update_score(scratch)
|
||||
return Response(ScratchSerializer(scratch, context={ 'request': request }).data)
|
||||
return Response(
|
||||
ScratchSerializer(scratch, context={"request": request}).data
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
# POST on compile takes a partial and does not update the scratch's compilation status
|
||||
@action(detail=True, methods=['GET', 'POST'])
|
||||
@action(detail=True, methods=["GET", "POST"])
|
||||
def compile(self, request, pk):
|
||||
scratch: Scratch = self.get_object()
|
||||
|
||||
@@ -280,29 +331,35 @@ class ScratchViewSet(
|
||||
if request.method == "GET":
|
||||
update_scratch_score(scratch, diff)
|
||||
|
||||
return Response({
|
||||
"diff_output": diff,
|
||||
"errors": compilation.errors,
|
||||
})
|
||||
return Response(
|
||||
{
|
||||
"diff_output": diff,
|
||||
"errors": compilation.errors,
|
||||
}
|
||||
)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
@action(detail=True, methods=["POST"])
|
||||
def decompile(self, request, pk):
|
||||
scratch: Scratch = self.get_object()
|
||||
context = request.data.get("context", "")
|
||||
compiler = request.data.get("compiler", scratch.compiler)
|
||||
|
||||
decompilation = DecompilerWrapper.decompile("", scratch.platform, scratch.target_assembly.source_asm.data, context, compiler)
|
||||
decompilation = DecompilerWrapper.decompile(
|
||||
"",
|
||||
scratch.platform,
|
||||
scratch.target_assembly.source_asm.data,
|
||||
context,
|
||||
compiler,
|
||||
)
|
||||
|
||||
return Response({
|
||||
"decompilation": decompilation
|
||||
})
|
||||
return Response({"decompilation": decompilation})
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
@action(detail=True, methods=["POST"])
|
||||
def claim(self, request, pk):
|
||||
scratch: Scratch = self.get_object()
|
||||
|
||||
if not scratch.is_claimable():
|
||||
return Response({ "success": False })
|
||||
return Response({"success": False})
|
||||
|
||||
profile = request.profile
|
||||
|
||||
@@ -311,17 +368,21 @@ class ScratchViewSet(
|
||||
scratch.owner = profile
|
||||
scratch.save()
|
||||
|
||||
return Response({ "success": True })
|
||||
return Response({"success": True})
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
@action(detail=True, methods=["POST"])
|
||||
def fork(self, request, pk):
|
||||
parent_scratch: Scratch = self.get_object()
|
||||
|
||||
request_data = request.data.dict() if isinstance(request.data, QueryDict) else request.data
|
||||
parent_data = ScratchSerializer(parent_scratch, context={ "request": request }).data
|
||||
fork_data = { **parent_data, **request_data }
|
||||
request_data = (
|
||||
request.data.dict() if isinstance(request.data, QueryDict) else request.data
|
||||
)
|
||||
parent_data = ScratchSerializer(
|
||||
parent_scratch, context={"request": request}
|
||||
).data
|
||||
fork_data = {**parent_data, **request_data}
|
||||
|
||||
ser = ScratchSerializer(data=fork_data, context={ "request": request })
|
||||
ser = ScratchSerializer(data=fork_data, context={"request": request})
|
||||
ser.is_valid(raise_exception=True)
|
||||
new_scratch = ser.save(
|
||||
parent=parent_scratch,
|
||||
@@ -332,7 +393,7 @@ class ScratchViewSet(
|
||||
compile_scratch_update_score(new_scratch)
|
||||
|
||||
return Response(
|
||||
ScratchSerializer(new_scratch, context={ "request": request }).data,
|
||||
ScratchSerializer(new_scratch, context={"request": request}).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
@@ -341,12 +402,14 @@ class ScratchViewSet(
|
||||
def export(self, request: Request, pk):
|
||||
scratch: Scratch = self.get_object()
|
||||
|
||||
metadata = ScratchSerializer(scratch, context={ "request": request }).data
|
||||
metadata = ScratchSerializer(scratch, context={"request": request}).data
|
||||
metadata.pop("source_code")
|
||||
metadata.pop("context")
|
||||
|
||||
zip_bytes = io.BytesIO()
|
||||
with zipfile.ZipFile(zip_bytes, mode='w', compression=zipfile.ZIP_DEFLATED) as zip_f:
|
||||
with zipfile.ZipFile(
|
||||
zip_bytes, mode="w", compression=zipfile.ZIP_DEFLATED
|
||||
) as zip_f:
|
||||
zip_f.writestr("metadata.json", json.dumps(metadata, indent=4))
|
||||
zip_f.writestr("target.s", scratch.target_assembly.source_asm.data)
|
||||
zip_f.writestr("target.o", scratch.target_assembly.elf_object)
|
||||
@@ -359,10 +422,10 @@ class ScratchViewSet(
|
||||
|
||||
return HttpResponse(
|
||||
zip_bytes.getvalue(),
|
||||
headers = {
|
||||
headers={
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Disposition": f"attachment; filename={safe_name}.zip",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@action(detail=True)
|
||||
@@ -375,7 +438,10 @@ class ScratchViewSet(
|
||||
compiler=scratch.compiler,
|
||||
).order_by("creation_time")
|
||||
|
||||
return Response(TerseScratchSerializer(family, many=True, context={ 'request': request }).data)
|
||||
return Response(
|
||||
TerseScratchSerializer(family, many=True, context={"request": request}).data
|
||||
)
|
||||
|
||||
|
||||
router = DefaultRouter(trailing_slash=False)
|
||||
router.register(r'scratch', ScratchViewSet)
|
||||
router.register(r"scratch", ScratchViewSet)
|
||||
|
||||
@@ -12,6 +12,7 @@ from ..models.scratch import Scratch
|
||||
from ..models.github import GitHubUser
|
||||
from ..serializers import serialize_profile, TerseScratchSerializer
|
||||
|
||||
|
||||
class CurrentUser(APIView):
|
||||
"""
|
||||
View to access the current user profile.
|
||||
@@ -71,4 +72,6 @@ def user(request, username):
|
||||
Gets a user's basic data
|
||||
"""
|
||||
|
||||
return Response(serialize_profile(request, get_object_or_404(Profile, user__username=username)))
|
||||
return Response(
|
||||
serialize_profile(request, get_object_or_404(Profile, user__username=username))
|
||||
)
|
||||
|
||||
@@ -11,6 +11,6 @@ import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'decompme.settings')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "decompme.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
|
||||
@@ -21,8 +21,8 @@ env = environ.Env(
|
||||
SECURE_HSTS_SECONDS=(int, 0),
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS=(bool, False),
|
||||
SECURE_HSTS_PRELOAD=(bool, False),
|
||||
STATIC_URL=(str, '/static/'),
|
||||
STATIC_ROOT=(str, BASE_DIR / 'static'),
|
||||
STATIC_URL=(str, "/static/"),
|
||||
STATIC_ROOT=(str, BASE_DIR / "static"),
|
||||
LOCAL_FILE_DIR=(str, BASE_DIR / "local_files"),
|
||||
USE_SANDBOX_JAIL=(bool, False),
|
||||
SESSION_COOKIE_SECURE=(bool, True),
|
||||
@@ -39,140 +39,135 @@ for stem in [".env.local", ".env"]:
|
||||
with open(env_file) as f:
|
||||
environ.Env.read_env(f)
|
||||
|
||||
SECRET_KEY = env('SECRET_KEY')
|
||||
DEBUG = env('DEBUG')
|
||||
DJANGO_LOG_LEVEL = env('DJANGO_LOG_LEVEL')
|
||||
DUMMY_COMPILER = env('DUMMY_COMPILER')
|
||||
ALLOWED_HOSTS = env('ALLOWED_HOSTS')
|
||||
LOCAL_FILE_DIR = env('LOCAL_FILE_DIR')
|
||||
SECRET_KEY = env("SECRET_KEY")
|
||||
DEBUG = env("DEBUG")
|
||||
DJANGO_LOG_LEVEL = env("DJANGO_LOG_LEVEL")
|
||||
DUMMY_COMPILER = env("DUMMY_COMPILER")
|
||||
ALLOWED_HOSTS = env("ALLOWED_HOSTS")
|
||||
LOCAL_FILE_DIR = env("LOCAL_FILE_DIR")
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'rest_framework',
|
||||
'corsheaders',
|
||||
'coreapp.apps.CoreappConfig',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_filters',
|
||||
"rest_framework",
|
||||
"corsheaders",
|
||||
"coreapp.apps.CoreappConfig",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django_filters",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
#'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'coreapp.middleware.disable_csrf',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'coreapp.middleware.set_user_profile',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
"coreapp.middleware.disable_csrf",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"coreapp.middleware.set_user_profile",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'EXCEPTION_HANDLER': 'coreapp.error.custom_exception_handler',
|
||||
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
|
||||
"EXCEPTION_HANDLER": "coreapp.error.custom_exception_handler",
|
||||
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
|
||||
}
|
||||
|
||||
ROOT_URLCONF = 'decompme.urls'
|
||||
ROOT_URLCONF = "decompme.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'decompme.wsgi.application'
|
||||
WSGI_APPLICATION = "decompme.wsgi.application"
|
||||
|
||||
DATABASES = {
|
||||
'default': env.db()
|
||||
}
|
||||
DATABASES = {"default": env.db()}
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = 'Japan'
|
||||
LANGUAGE_CODE = "en-us"
|
||||
TIME_ZONE = "Japan"
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
STATIC_URL = env('STATIC_URL')
|
||||
STATIC_ROOT = env('STATIC_ROOT')
|
||||
STATIC_URL = env("STATIC_URL")
|
||||
STATIC_ROOT = env("STATIC_ROOT")
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple'
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"handlers": {
|
||||
"console": {"class": "logging.StreamHandler", "formatter": "simple"},
|
||||
},
|
||||
"formatters": {
|
||||
"simple": {
|
||||
"format": "{asctime} {levelname} {message}",
|
||||
"style": "{",
|
||||
"datefmt": "%H:%M:%S",
|
||||
},
|
||||
},
|
||||
'formatters': {
|
||||
'simple': {
|
||||
'format': '{asctime} {levelname} {message}',
|
||||
'style': '{',
|
||||
'datefmt': '%H:%M:%S',
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["console"],
|
||||
"level": "DEBUG",
|
||||
},
|
||||
'root': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['console'],
|
||||
'level': DJANGO_LOG_LEVEL,
|
||||
'propagate': False,
|
||||
"loggers": {
|
||||
"django": {
|
||||
"handlers": ["console"],
|
||||
"level": DJANGO_LOG_LEVEL,
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SECURE_SSL_REDIRECT = env('SECURE_SSL_REDIRECT')
|
||||
SECURE_HSTS_SECONDS = env('SECURE_HSTS_SECONDS')
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = env('SECURE_HSTS_INCLUDE_SUBDOMAINS')
|
||||
SECURE_HSTS_PRELOAD=env('SECURE_HSTS_PRELOAD')
|
||||
SECURE_SSL_REDIRECT = env("SECURE_SSL_REDIRECT")
|
||||
SECURE_HSTS_SECONDS = env("SECURE_HSTS_SECONDS")
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = env("SECURE_HSTS_INCLUDE_SUBDOMAINS")
|
||||
SECURE_HSTS_PRELOAD = env("SECURE_HSTS_PRELOAD")
|
||||
|
||||
SESSION_COOKIE_SECURE = env("SESSION_COOKIE_SECURE")
|
||||
if DEBUG:
|
||||
@@ -192,4 +187,4 @@ GITHUB_CLIENT_SECRET = env("GITHUB_CLIENT_SECRET", str)
|
||||
|
||||
COMPILATION_CACHE_SIZE = env("COMPILATION_CACHE_SIZE", int)
|
||||
|
||||
WINEPREFIX=Path(env("WINEPREFIX"))
|
||||
WINEPREFIX = Path(env("WINEPREFIX"))
|
||||
|
||||
@@ -2,6 +2,6 @@ from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
path('api/', include('coreapp.urls')),
|
||||
path('admin/', admin.site.urls),
|
||||
path("api/", include("coreapp.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
|
||||
@@ -11,6 +11,6 @@ import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'decompme.settings')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "decompme.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
||||
@@ -6,7 +6,7 @@ import sys
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'decompme.settings')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "decompme.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
@@ -18,5 +18,5 @@ def main():
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[subrepo]
|
||||
remote = https://github.com/matt-kempster/mips_to_c
|
||||
branch = master
|
||||
commit = 06ada559d7a32fdab49fa2d619cdfa027615bcda
|
||||
parent = fbc1649ac94d394506a352f22bae43cc6416e313
|
||||
commit = 6704d61fdd67333a7872981db913f1027d8f253e
|
||||
parent = 2841a4a99a6a94d9116bed7ebe4e35c1a30675d9
|
||||
method = merge
|
||||
cmdver = 0.4.3
|
||||
|
||||
@@ -33,4 +33,7 @@ typedef s64 MIPS2C_UNK64;
|
||||
#define MIPS2C_BREAK() (0)
|
||||
#define MIPS2C_SYNC() (0)
|
||||
|
||||
/* Carry bit from partially-implemented instructions */
|
||||
#define MIPS2C_CARRY 0
|
||||
|
||||
#endif
|
||||
|
||||
290
backend/mips_to_c/src/arch_mips.py
generated
290
backend/mips_to_c/src/arch_mips.py
generated
@@ -5,18 +5,22 @@ from typing import (
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from .error import DecompFailure
|
||||
from .options import Target
|
||||
from .parse_instruction import (
|
||||
Argument,
|
||||
AsmAddressMode,
|
||||
AsmGlobalSymbol,
|
||||
AsmInstruction,
|
||||
AsmLiteral,
|
||||
Instruction,
|
||||
InstructionMeta,
|
||||
JumpTarget,
|
||||
Register,
|
||||
get_jump_target,
|
||||
)
|
||||
from .asm_pattern import (
|
||||
AsmMatch,
|
||||
@@ -198,9 +202,7 @@ class ModP2Pattern1(SimpleAsmPattern):
|
||||
val = (m.literals["N"] & 0xFFFF) + 1
|
||||
if val & (val - 1):
|
||||
return None # not a power of two
|
||||
mod = m.derived_instr(
|
||||
"mod.fictive", [m.regs["o"], m.regs["i"], AsmLiteral(val)]
|
||||
)
|
||||
mod = AsmInstruction("mod.fictive", [m.regs["o"], m.regs["i"], AsmLiteral(val)])
|
||||
return Replacement([mod], len(m.body) - 1)
|
||||
|
||||
|
||||
@@ -224,8 +226,9 @@ class ModP2Pattern2(SimpleAsmPattern):
|
||||
val += ((m.literals["LO"] + 0x8000) & 0xFFFF) - 0x8000
|
||||
if not val or val & (val - 1):
|
||||
return None # not a power of two
|
||||
mod = m.derived_instr(
|
||||
"mod.fictive", [m.regs["o"], m.regs["i"], AsmLiteral(val)]
|
||||
mod = AsmInstruction(
|
||||
"mod.fictive",
|
||||
[m.regs["o"], m.regs["i"], AsmLiteral(val)],
|
||||
)
|
||||
return Replacement([mod], len(m.body) - 1)
|
||||
|
||||
@@ -243,7 +246,7 @@ class DivP2Pattern1(SimpleAsmPattern):
|
||||
|
||||
def replace(self, m: AsmMatch) -> Replacement:
|
||||
shift = m.literals["N"] & 0x1F
|
||||
div = m.derived_instr(
|
||||
div = AsmInstruction(
|
||||
"div.fictive", [m.regs["o"], m.regs["i"], AsmLiteral(2 ** shift)]
|
||||
)
|
||||
return Replacement([div], len(m.body) - 1)
|
||||
@@ -262,7 +265,7 @@ class DivP2Pattern2(SimpleAsmPattern):
|
||||
|
||||
def replace(self, m: AsmMatch) -> Replacement:
|
||||
shift = m.literals["N"] & 0x1F
|
||||
div = m.derived_instr(
|
||||
div = AsmInstruction(
|
||||
"div.fictive", [m.regs["x"], m.regs["x"], AsmLiteral(2 ** shift)]
|
||||
)
|
||||
return Replacement([div], len(m.body))
|
||||
@@ -279,8 +282,8 @@ class Div2S16Pattern(SimpleAsmPattern):
|
||||
|
||||
def replace(self, m: AsmMatch) -> Replacement:
|
||||
# Keep 32->16 conversion from $i to $o, just add a division
|
||||
div = m.derived_instr("div.fictive", [m.regs["o"], m.regs["o"], AsmLiteral(2)])
|
||||
return Replacement(m.body[:2] + [div], len(m.body))
|
||||
div = AsmInstruction("div.fictive", [m.regs["o"], m.regs["o"], AsmLiteral(2)])
|
||||
return Replacement([m.body[0], m.body[1], div], len(m.body))
|
||||
|
||||
|
||||
class Div2S32Pattern(SimpleAsmPattern):
|
||||
@@ -291,7 +294,7 @@ class Div2S32Pattern(SimpleAsmPattern):
|
||||
)
|
||||
|
||||
def replace(self, m: AsmMatch) -> Replacement:
|
||||
div = m.derived_instr("div.fictive", [m.regs["o"], m.regs["i"], AsmLiteral(2)])
|
||||
div = AsmInstruction("div.fictive", [m.regs["o"], m.regs["i"], AsmLiteral(2)])
|
||||
return Replacement([div], len(m.body))
|
||||
|
||||
|
||||
@@ -307,7 +310,7 @@ class UtfPattern(SimpleAsmPattern):
|
||||
)
|
||||
|
||||
def replace(self, m: AsmMatch) -> Replacement:
|
||||
new_instr = m.derived_instr("cvt.s.u.fictive", [m.regs["o"], m.regs["i"]])
|
||||
new_instr = AsmInstruction("cvt.s.u.fictive", [m.regs["o"], m.regs["i"]])
|
||||
return Replacement([new_instr], len(m.body) - 1)
|
||||
|
||||
|
||||
@@ -356,9 +359,9 @@ class FtuPattern(SimpleAsmPattern):
|
||||
fmt = sub.mnemonic.split(".")[-1]
|
||||
args = [m.regs["o"], sub.args[1]]
|
||||
if fmt == "s":
|
||||
new_instr = m.derived_instr("cvt.u.s.fictive", args)
|
||||
new_instr = AsmInstruction("cvt.u.s.fictive", args)
|
||||
else:
|
||||
new_instr = m.derived_instr("cvt.u.d.fictive", args)
|
||||
new_instr = AsmInstruction("cvt.u.d.fictive", args)
|
||||
return Replacement([new_instr], len(m.body))
|
||||
|
||||
|
||||
@@ -395,7 +398,7 @@ class Mips1DoubleLoadStorePattern(AsmPattern):
|
||||
# Store the even-numbered register (ra) into the low address (mb).
|
||||
new_args = [ra, mb]
|
||||
new_mn = "ldc1" if a.mnemonic == "lwc1" else "sdc1"
|
||||
new_instr = m.derived_instr(new_mn, new_args)
|
||||
new_instr = AsmInstruction(new_mn, new_args)
|
||||
return Replacement([new_instr], len(m.body))
|
||||
|
||||
|
||||
@@ -429,7 +432,7 @@ class TrapuvPattern(SimpleAsmPattern):
|
||||
)
|
||||
|
||||
def replace(self, m: AsmMatch) -> Replacement:
|
||||
new_instr = m.derived_instr("trapuv.fictive", [])
|
||||
new_instr = AsmInstruction("trapuv.fictive", [])
|
||||
return Replacement([m.body[2], new_instr], len(m.body))
|
||||
|
||||
|
||||
@@ -527,31 +530,114 @@ class MipsArch(Arch):
|
||||
"r0": Register("zero"),
|
||||
}
|
||||
|
||||
uses_delay_slots = True
|
||||
@classmethod
|
||||
def missing_return(cls) -> List[Instruction]:
|
||||
meta = InstructionMeta.missing()
|
||||
return [
|
||||
cls.parse("jr", [Register("ra")], meta),
|
||||
cls.parse("nop", [], meta),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def is_branch_instruction(instr: Instruction) -> bool:
|
||||
return (
|
||||
instr.mnemonic
|
||||
in [
|
||||
"beq",
|
||||
"bne",
|
||||
"beqz",
|
||||
"bnez",
|
||||
"bgez",
|
||||
"bgtz",
|
||||
"blez",
|
||||
"bltz",
|
||||
"bc1t",
|
||||
"bc1f",
|
||||
]
|
||||
or MipsArch.is_branch_likely_instruction(instr)
|
||||
or MipsArch.is_constant_branch_instruction(instr)
|
||||
)
|
||||
@classmethod
|
||||
def normalize_instruction(cls, instr: AsmInstruction) -> AsmInstruction:
|
||||
args = instr.args
|
||||
if len(args) == 3:
|
||||
if instr.mnemonic == "sll" and args[0] == args[1] == Register("zero"):
|
||||
return AsmInstruction("nop", [])
|
||||
if instr.mnemonic == "or" and args[2] == Register("zero"):
|
||||
return AsmInstruction("move", args[:2])
|
||||
if instr.mnemonic == "addu" and args[2] == Register("zero"):
|
||||
return AsmInstruction("move", args[:2])
|
||||
if instr.mnemonic == "daddu" and args[2] == Register("zero"):
|
||||
return AsmInstruction("move", args[:2])
|
||||
if instr.mnemonic == "nor" and args[1] == Register("zero"):
|
||||
return AsmInstruction("not", [args[0], args[2]])
|
||||
if instr.mnemonic == "nor" and args[2] == Register("zero"):
|
||||
return AsmInstruction("not", [args[0], args[1]])
|
||||
if instr.mnemonic == "addiu" and args[2] == AsmLiteral(0):
|
||||
return AsmInstruction("move", args[:2])
|
||||
if instr.mnemonic in DIV_MULT_INSTRUCTIONS:
|
||||
if args[0] != Register("zero"):
|
||||
raise DecompFailure("first argument to div/mult must be $zero")
|
||||
return AsmInstruction(instr.mnemonic, args[1:])
|
||||
if (
|
||||
instr.mnemonic == "ori"
|
||||
and args[1] == Register("zero")
|
||||
and isinstance(args[2], AsmLiteral)
|
||||
):
|
||||
lit = AsmLiteral(args[2].value & 0xFFFF)
|
||||
return AsmInstruction("li", [args[0], lit])
|
||||
if (
|
||||
instr.mnemonic == "addiu"
|
||||
and args[1] == Register("zero")
|
||||
and isinstance(args[2], AsmLiteral)
|
||||
):
|
||||
lit = AsmLiteral(((args[2].value + 0x8000) & 0xFFFF) - 0x8000)
|
||||
return AsmInstruction("li", [args[0], lit])
|
||||
if instr.mnemonic == "beq" and args[0] == args[1] == Register("zero"):
|
||||
return AsmInstruction("b", [args[2]])
|
||||
if instr.mnemonic in ["bne", "beq", "beql", "bnel"] and args[1] == Register(
|
||||
"zero"
|
||||
):
|
||||
mn = instr.mnemonic[:3] + "z" + instr.mnemonic[3:]
|
||||
return AsmInstruction(mn, [args[0], args[2]])
|
||||
if len(args) == 2:
|
||||
if instr.mnemonic == "beqz" and args[0] == Register("zero"):
|
||||
return AsmInstruction("b", [args[1]])
|
||||
if instr.mnemonic == "lui" and isinstance(args[1], AsmLiteral):
|
||||
lit = AsmLiteral((args[1].value & 0xFFFF) << 16)
|
||||
return AsmInstruction("li", [args[0], lit])
|
||||
if instr.mnemonic == "jalr" and args[0] != Register("ra"):
|
||||
raise DecompFailure("Two-argument form of jalr is not supported.")
|
||||
if instr.mnemonic in LENGTH_THREE:
|
||||
return cls.normalize_instruction(
|
||||
AsmInstruction(instr.mnemonic, [args[0]] + args)
|
||||
)
|
||||
if len(args) == 1:
|
||||
if instr.mnemonic == "jalr":
|
||||
return AsmInstruction("jalr", [Register("ra"), args[0]])
|
||||
if instr.mnemonic in LENGTH_TWO:
|
||||
return cls.normalize_instruction(
|
||||
AsmInstruction(instr.mnemonic, [args[0]] + args)
|
||||
)
|
||||
return instr
|
||||
|
||||
@staticmethod
|
||||
def is_branch_likely_instruction(instr: Instruction) -> bool:
|
||||
return instr.mnemonic in [
|
||||
@classmethod
|
||||
def parse(
|
||||
cls, mnemonic: str, args: List[Argument], meta: InstructionMeta
|
||||
) -> Instruction:
|
||||
jump_target: Optional[Union[JumpTarget, Register]] = None
|
||||
function_target: Optional[Union[AsmGlobalSymbol, Register]] = None
|
||||
has_delay_slot = False
|
||||
is_branch_likely = False
|
||||
is_conditional = False
|
||||
is_return = False
|
||||
|
||||
if mnemonic == "jr" and args[0] == Register("ra"):
|
||||
# Return
|
||||
is_return = True
|
||||
has_delay_slot = True
|
||||
elif mnemonic == "jr":
|
||||
# Jump table (switch)
|
||||
assert isinstance(args[0], Register)
|
||||
jump_target = args[0]
|
||||
is_conditional = True
|
||||
has_delay_slot = True
|
||||
elif mnemonic == "jal":
|
||||
# Function call to label
|
||||
assert isinstance(args[0], AsmGlobalSymbol)
|
||||
function_target = args[0]
|
||||
has_delay_slot = True
|
||||
elif mnemonic == "jalr":
|
||||
# Function call to pointer
|
||||
assert isinstance(args[0], Register)
|
||||
function_target = args[0]
|
||||
has_delay_slot = True
|
||||
elif mnemonic in ("b", "j"):
|
||||
# Unconditional jump
|
||||
jump_target = get_jump_target(args[0])
|
||||
has_delay_slot = True
|
||||
elif mnemonic in (
|
||||
"beql",
|
||||
"bnel",
|
||||
"beqzl",
|
||||
@@ -562,102 +648,40 @@ class MipsArch(Arch):
|
||||
"bltzl",
|
||||
"bc1tl",
|
||||
"bc1fl",
|
||||
]
|
||||
):
|
||||
# Branch-likely
|
||||
jump_target = get_jump_target(args[-1])
|
||||
has_delay_slot = True
|
||||
is_branch_likely = True
|
||||
is_conditional = True
|
||||
elif mnemonic in (
|
||||
"beq",
|
||||
"bne",
|
||||
"beqz",
|
||||
"bnez",
|
||||
"bgez",
|
||||
"bgtz",
|
||||
"blez",
|
||||
"bltz",
|
||||
"bc1t",
|
||||
"bc1f",
|
||||
):
|
||||
# Normal branch
|
||||
jump_target = get_jump_target(args[-1])
|
||||
has_delay_slot = True
|
||||
is_conditional = True
|
||||
|
||||
@staticmethod
|
||||
def is_constant_branch_instruction(instr: Instruction) -> bool:
|
||||
return instr.mnemonic in ("b", "j")
|
||||
|
||||
@staticmethod
|
||||
def is_jump_instruction(instr: Instruction) -> bool:
|
||||
# (we don't treat jal/jalr as jumps, since control flow will return
|
||||
# after the call)
|
||||
return MipsArch.is_branch_instruction(instr) or instr.mnemonic == "jr"
|
||||
|
||||
@staticmethod
|
||||
def is_delay_slot_instruction(instr: Instruction) -> bool:
|
||||
return MipsArch.is_branch_instruction(instr) or instr.mnemonic in [
|
||||
"jr",
|
||||
"jal",
|
||||
"jalr",
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def is_return_instruction(instr: Instruction) -> bool:
|
||||
return instr.mnemonic == "jr" and instr.args[0] == Register("ra")
|
||||
|
||||
@staticmethod
|
||||
def is_conditional_return_instruction(instr: Instruction) -> bool:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_jumptable_instruction(instr: Instruction) -> bool:
|
||||
return instr.mnemonic == "jr" and instr.args[0] != Register("ra")
|
||||
|
||||
@staticmethod
|
||||
def missing_return() -> List[Instruction]:
|
||||
meta = InstructionMeta.missing()
|
||||
return [Instruction("jr", [Register("ra")], meta), Instruction("nop", [], meta)]
|
||||
|
||||
@classmethod
|
||||
def normalize_instruction(cls, instr: Instruction) -> Instruction:
|
||||
args = instr.args
|
||||
if len(args) == 3:
|
||||
if instr.mnemonic == "sll" and args[0] == args[1] == Register("zero"):
|
||||
return Instruction("nop", [], instr.meta)
|
||||
if instr.mnemonic == "or" and args[2] == Register("zero"):
|
||||
return Instruction("move", args[:2], instr.meta)
|
||||
if instr.mnemonic == "addu" and args[2] == Register("zero"):
|
||||
return Instruction("move", args[:2], instr.meta)
|
||||
if instr.mnemonic == "daddu" and args[2] == Register("zero"):
|
||||
return Instruction("move", args[:2], instr.meta)
|
||||
if instr.mnemonic == "nor" and args[1] == Register("zero"):
|
||||
return Instruction("not", [args[0], args[2]], instr.meta)
|
||||
if instr.mnemonic == "nor" and args[2] == Register("zero"):
|
||||
return Instruction("not", [args[0], args[1]], instr.meta)
|
||||
if instr.mnemonic == "addiu" and args[2] == AsmLiteral(0):
|
||||
return Instruction("move", args[:2], instr.meta)
|
||||
if instr.mnemonic in DIV_MULT_INSTRUCTIONS:
|
||||
if args[0] != Register("zero"):
|
||||
raise DecompFailure("first argument to div/mult must be $zero")
|
||||
return Instruction(instr.mnemonic, args[1:], instr.meta)
|
||||
if (
|
||||
instr.mnemonic == "ori"
|
||||
and args[1] == Register("zero")
|
||||
and isinstance(args[2], AsmLiteral)
|
||||
):
|
||||
lit = AsmLiteral(args[2].value & 0xFFFF)
|
||||
return Instruction("li", [args[0], lit], instr.meta)
|
||||
if (
|
||||
instr.mnemonic == "addiu"
|
||||
and args[1] == Register("zero")
|
||||
and isinstance(args[2], AsmLiteral)
|
||||
):
|
||||
lit = AsmLiteral(((args[2].value + 0x8000) & 0xFFFF) - 0x8000)
|
||||
return Instruction("li", [args[0], lit], instr.meta)
|
||||
if instr.mnemonic == "beq" and args[0] == args[1] == Register("zero"):
|
||||
return Instruction("b", [args[2]], instr.meta)
|
||||
if instr.mnemonic in ["bne", "beq", "beql", "bnel"] and args[1] == Register(
|
||||
"zero"
|
||||
):
|
||||
mn = instr.mnemonic[:3] + "z" + instr.mnemonic[3:]
|
||||
return Instruction(mn, [args[0], args[2]], instr.meta)
|
||||
if len(args) == 2:
|
||||
if instr.mnemonic == "beqz" and args[0] == Register("zero"):
|
||||
return Instruction("b", [args[1]], instr.meta)
|
||||
if instr.mnemonic == "lui" and isinstance(args[1], AsmLiteral):
|
||||
lit = AsmLiteral((args[1].value & 0xFFFF) << 16)
|
||||
return Instruction("li", [args[0], lit], instr.meta)
|
||||
if instr.mnemonic in LENGTH_THREE:
|
||||
return cls.normalize_instruction(
|
||||
Instruction(instr.mnemonic, [args[0]] + args, instr.meta)
|
||||
)
|
||||
if len(args) == 1:
|
||||
if instr.mnemonic in LENGTH_TWO:
|
||||
return cls.normalize_instruction(
|
||||
Instruction(instr.mnemonic, [args[0]] + args, instr.meta)
|
||||
)
|
||||
return instr
|
||||
return Instruction(
|
||||
mnemonic=mnemonic,
|
||||
args=args,
|
||||
meta=meta,
|
||||
jump_target=jump_target,
|
||||
function_target=function_target,
|
||||
has_delay_slot=has_delay_slot,
|
||||
is_branch_likely=is_branch_likely,
|
||||
is_conditional=is_conditional,
|
||||
is_return=is_return,
|
||||
)
|
||||
|
||||
asm_patterns = [
|
||||
DivPattern(),
|
||||
|
||||
218
backend/mips_to_c/src/arch_ppc.py
generated
218
backend/mips_to_c/src/arch_ppc.py
generated
@@ -5,6 +5,7 @@ from typing import (
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from .error import DecompFailure
|
||||
from .options import Target
|
||||
@@ -12,12 +13,14 @@ from .parse_instruction import (
|
||||
Argument,
|
||||
AsmAddressMode,
|
||||
AsmGlobalSymbol,
|
||||
AsmInstruction,
|
||||
AsmLiteral,
|
||||
Instruction,
|
||||
InstructionMeta,
|
||||
JumpTarget,
|
||||
Macro,
|
||||
Register,
|
||||
get_jump_target,
|
||||
)
|
||||
from .asm_pattern import (
|
||||
AsmMatch,
|
||||
@@ -32,6 +35,7 @@ from .translate import (
|
||||
AbiArgSlot,
|
||||
Arch,
|
||||
BinaryOp,
|
||||
CarryBit,
|
||||
Cast,
|
||||
CmpInstrMap,
|
||||
CommentStmt,
|
||||
@@ -40,11 +44,13 @@ from .translate import (
|
||||
ImplicitInstrMap,
|
||||
InstrMap,
|
||||
InstrSet,
|
||||
Literal,
|
||||
PpcCmpInstrMap,
|
||||
PairInstrMap,
|
||||
SecondF64Half,
|
||||
StmtInstrMap,
|
||||
StoreInstrMap,
|
||||
TernaryOp,
|
||||
UnaryOp,
|
||||
as_f32,
|
||||
as_f64,
|
||||
@@ -68,6 +74,7 @@ from .translate import (
|
||||
handle_loadx,
|
||||
handle_or,
|
||||
handle_rlwinm,
|
||||
handle_rlwimi,
|
||||
handle_sra,
|
||||
load_upper,
|
||||
make_store,
|
||||
@@ -94,11 +101,11 @@ class FcmpoCrorPattern(SimpleAsmPattern):
|
||||
assert isinstance(fcmpo, Instruction)
|
||||
if m.literals["N"] == 0:
|
||||
return Replacement(
|
||||
[m.derived_instr("fcmpo.lte.fictive", fcmpo.args)], len(m.body)
|
||||
[AsmInstruction("fcmpo.lte.fictive", fcmpo.args)], len(m.body)
|
||||
)
|
||||
elif m.literals["N"] == 1:
|
||||
return Replacement(
|
||||
[m.derived_instr("fcmpo.gte.fictive", fcmpo.args)], len(m.body)
|
||||
[AsmInstruction("fcmpo.gte.fictive", fcmpo.args)], len(m.body)
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -120,8 +127,8 @@ class TailCallPattern(AsmPattern):
|
||||
):
|
||||
return Replacement(
|
||||
[
|
||||
Instruction.derived("bl", instr.args, instr),
|
||||
Instruction.derived("blr", [], instr),
|
||||
AsmInstruction("bl", instr.args),
|
||||
AsmInstruction("blr", []),
|
||||
],
|
||||
1,
|
||||
)
|
||||
@@ -139,7 +146,7 @@ class BoolCastPattern(SimpleAsmPattern):
|
||||
|
||||
def replace(self, m: AsmMatch) -> Optional[Replacement]:
|
||||
return Replacement(
|
||||
[m.derived_instr("boolcast.fictive", [Register("r0"), m.regs["x"]])],
|
||||
[AsmInstruction("boolcast.fictive", [Register("r0"), m.regs["x"]])],
|
||||
len(m.body),
|
||||
)
|
||||
|
||||
@@ -153,8 +160,8 @@ class BranchCtrPattern(AsmPattern):
|
||||
ctr = Register("ctr")
|
||||
return Replacement(
|
||||
[
|
||||
Instruction.derived("addi", [ctr, ctr, AsmLiteral(-1)], instr),
|
||||
Instruction.derived(instr.mnemonic + ".fictive", instr.args, instr),
|
||||
AsmInstruction("addi", [ctr, ctr, AsmLiteral(-1)]),
|
||||
AsmInstruction(instr.mnemonic + ".fictive", instr.args),
|
||||
],
|
||||
1,
|
||||
)
|
||||
@@ -283,75 +290,9 @@ class PpcArch(Arch):
|
||||
|
||||
aliased_regs: Dict[str, Register] = {}
|
||||
|
||||
uses_delay_slots = False
|
||||
|
||||
@staticmethod
|
||||
def is_branch_instruction(instr: Instruction) -> bool:
|
||||
return (
|
||||
instr.mnemonic
|
||||
in [
|
||||
"ble",
|
||||
"blt",
|
||||
"beq",
|
||||
"bge",
|
||||
"bgt",
|
||||
"bne",
|
||||
"bdnz",
|
||||
"bdz",
|
||||
"bdnz.fictive",
|
||||
"bdz.fictive",
|
||||
]
|
||||
or PpcArch.is_constant_branch_instruction(instr)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_branch_likely_instruction(instr: Instruction) -> bool:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_constant_branch_instruction(instr: Instruction) -> bool:
|
||||
return instr.mnemonic == "b"
|
||||
|
||||
@staticmethod
|
||||
def is_jump_instruction(instr: Instruction) -> bool:
|
||||
# (we don't treat jal/jalr as jumps, since control flow will return
|
||||
# after the call)
|
||||
return (
|
||||
PpcArch.is_conditional_return_instruction(instr)
|
||||
or PpcArch.is_branch_instruction(instr)
|
||||
or instr.mnemonic in ("blr", "bctr")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_delay_slot_instruction(instr: Instruction) -> bool:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def is_return_instruction(instr: Instruction) -> bool:
|
||||
return instr.mnemonic == "blr"
|
||||
|
||||
@staticmethod
|
||||
def is_conditional_return_instruction(instr: Instruction) -> bool:
|
||||
return instr.mnemonic in (
|
||||
"beqlr",
|
||||
"bgelr",
|
||||
"bgtlr",
|
||||
"blelr",
|
||||
"bltlr",
|
||||
"bnelr",
|
||||
"bnglr",
|
||||
"bnllr",
|
||||
"bnslr",
|
||||
"bsolr",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_jumptable_instruction(instr: Instruction) -> bool:
|
||||
return instr.mnemonic == "bctr"
|
||||
|
||||
@staticmethod
|
||||
def missing_return() -> List[Instruction]:
|
||||
return [Instruction("blr", [], InstructionMeta.missing())]
|
||||
@classmethod
|
||||
def missing_return(cls) -> List[Instruction]:
|
||||
return [cls.parse("blr", [], InstructionMeta.missing())]
|
||||
|
||||
# List of all instructions where `$r0` as certian args is interpreted as `0`
|
||||
# instead of the contents of `$r0`. The dict value represents the argument
|
||||
@@ -409,13 +350,13 @@ class PpcArch(Arch):
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def normalize_instruction(cls, instr: Instruction) -> Instruction:
|
||||
def normalize_instruction(cls, instr: AsmInstruction) -> AsmInstruction:
|
||||
# Remove +/- suffix, which indicates branch-(un)likely and can be ignored
|
||||
if instr.mnemonic.startswith("b") and (
|
||||
instr.mnemonic.endswith("+") or instr.mnemonic.endswith("-")
|
||||
):
|
||||
return PpcArch.normalize_instruction(
|
||||
Instruction(instr.mnemonic[:-1], instr.args, instr.meta)
|
||||
AsmInstruction(instr.mnemonic[:-1], instr.args)
|
||||
)
|
||||
|
||||
args = instr.args
|
||||
@@ -432,7 +373,7 @@ class PpcArch(Arch):
|
||||
new_args = args[:]
|
||||
new_args[r0_index] = r0_arg
|
||||
return PpcArch.normalize_instruction(
|
||||
Instruction(instr.mnemonic, new_args, instr.meta)
|
||||
AsmInstruction(instr.mnemonic, new_args)
|
||||
)
|
||||
if len(args) == 3:
|
||||
if (
|
||||
@@ -441,11 +382,11 @@ class PpcArch(Arch):
|
||||
and args[1] in (Register("r2"), Register("r13"))
|
||||
and args[2].macro_name in ("sda2", "sda21")
|
||||
):
|
||||
return Instruction("li", [args[0], args[2].argument], instr.meta)
|
||||
return AsmInstruction("li", [args[0], args[2].argument])
|
||||
if len(args) == 2:
|
||||
if instr.mnemonic == "lis" and isinstance(args[1], AsmLiteral):
|
||||
lit = AsmLiteral((args[1].value & 0xFFFF) << 16)
|
||||
return Instruction("li", [args[0], lit], instr.meta)
|
||||
return AsmInstruction("li", [args[0], lit])
|
||||
if (
|
||||
instr.mnemonic == "lis"
|
||||
and isinstance(args[1], Macro)
|
||||
@@ -457,13 +398,83 @@ class PpcArch(Arch):
|
||||
if value & 0x8000:
|
||||
value += 0x10000
|
||||
lit = AsmLiteral(value & 0xFFFF0000)
|
||||
return Instruction("li", [args[0], lit], instr.meta)
|
||||
return AsmInstruction("li", [args[0], lit])
|
||||
if instr.mnemonic.startswith("cmp"):
|
||||
# For the two-argument form of cmpw, the insert an implicit CR0 as the first arg
|
||||
cr0: Argument = Register("cr0")
|
||||
return Instruction(instr.mnemonic, [cr0] + instr.args, instr.meta)
|
||||
return AsmInstruction(instr.mnemonic, [cr0] + instr.args)
|
||||
return instr
|
||||
|
||||
@classmethod
|
||||
def parse(
|
||||
cls, mnemonic: str, args: List[Argument], meta: InstructionMeta
|
||||
) -> Instruction:
|
||||
jump_target: Optional[Union[JumpTarget, Register]] = None
|
||||
function_target: Optional[Union[AsmGlobalSymbol, Register]] = None
|
||||
is_conditional = False
|
||||
is_return = False
|
||||
|
||||
if mnemonic == "blr":
|
||||
# Return
|
||||
is_return = True
|
||||
elif mnemonic in (
|
||||
"beqlr",
|
||||
"bgelr",
|
||||
"bgtlr",
|
||||
"blelr",
|
||||
"bltlr",
|
||||
"bnelr",
|
||||
"bnglr",
|
||||
"bnllr",
|
||||
"bnslr",
|
||||
"bsolr",
|
||||
):
|
||||
# Conditional return
|
||||
is_return = True
|
||||
is_conditional = True
|
||||
elif mnemonic == "bctr":
|
||||
# Jump table (switch)
|
||||
jump_target = Register("ctr")
|
||||
is_conditional = True
|
||||
elif mnemonic == "bl":
|
||||
# Function call to label
|
||||
assert isinstance(args[0], AsmGlobalSymbol)
|
||||
function_target = args[0]
|
||||
elif mnemonic == "bctrl":
|
||||
# Function call to pointer in $ctr
|
||||
function_target = Register("ctr")
|
||||
elif mnemonic == "blrl":
|
||||
# Function call to pointer in $lr
|
||||
function_target = Register("lr")
|
||||
elif mnemonic == "b":
|
||||
# Unconditional jump
|
||||
jump_target = get_jump_target(args[0])
|
||||
elif mnemonic in (
|
||||
"ble",
|
||||
"blt",
|
||||
"beq",
|
||||
"bge",
|
||||
"bgt",
|
||||
"bne",
|
||||
"bdnz",
|
||||
"bdz",
|
||||
"bdnz.fictive",
|
||||
"bdz.fictive",
|
||||
):
|
||||
# Normal branch
|
||||
jump_target = get_jump_target(args[-1])
|
||||
is_conditional = True
|
||||
|
||||
return Instruction(
|
||||
mnemonic=mnemonic,
|
||||
args=args,
|
||||
meta=meta,
|
||||
jump_target=jump_target,
|
||||
function_target=function_target,
|
||||
is_conditional=is_conditional,
|
||||
is_return=is_return,
|
||||
)
|
||||
|
||||
asm_patterns = [
|
||||
FcmpoCrorPattern(),
|
||||
TailCallPattern(),
|
||||
@@ -543,14 +554,33 @@ class PpcArch(Arch):
|
||||
}
|
||||
instrs_destination_first: InstrMap = {
|
||||
# Integer arithmetic
|
||||
"addi": lambda a: handle_addi(a),
|
||||
# TODO: Read XER_CA in extended instrs, instead of using CarryBit
|
||||
"add": lambda a: handle_add(a),
|
||||
"addc": lambda a: handle_add(a),
|
||||
"adde": lambda a: CarryBit.add_to(handle_add(a)),
|
||||
"addze": lambda a: CarryBit.add_to(a.reg(1)),
|
||||
"addi": lambda a: handle_addi(a),
|
||||
"addic": lambda a: handle_addi(a),
|
||||
"addis": lambda a: handle_addis(a),
|
||||
"subf": lambda a: fold_divmod(
|
||||
BinaryOp.intptr(left=a.reg(2), op="-", right=a.reg(1))
|
||||
),
|
||||
"subfc": lambda a: fold_divmod(
|
||||
BinaryOp.intptr(left=a.reg(2), op="-", right=a.reg(1))
|
||||
),
|
||||
"subfe": lambda a: CarryBit.sub_from(
|
||||
fold_divmod(BinaryOp.intptr(left=a.reg(2), op="-", right=a.reg(1)))
|
||||
),
|
||||
"subfic": lambda a: fold_divmod(
|
||||
BinaryOp.intptr(left=a.imm(2), op="-", right=a.reg(1))
|
||||
),
|
||||
"subfze": lambda a: CarryBit.sub_from(
|
||||
fold_mul_chains(
|
||||
UnaryOp(op="-", expr=as_s32(a.reg(1), silent=True), type=Type.s32())
|
||||
)
|
||||
),
|
||||
"neg": lambda a: fold_mul_chains(
|
||||
UnaryOp(op="-", expr=as_s32(a.reg(1)), type=Type.s32())
|
||||
UnaryOp(op="-", expr=as_s32(a.reg(1), silent=True), type=Type.s32())
|
||||
),
|
||||
"divw": lambda a: BinaryOp.s32(a.reg(1), "/", a.reg(2)),
|
||||
"divwu": lambda a: BinaryOp.u32(a.reg(1), "/", a.reg(2)),
|
||||
@@ -563,11 +593,17 @@ class PpcArch(Arch):
|
||||
"ori": lambda a: handle_or(a.reg(1), a.unsigned_imm(2)),
|
||||
"oris": lambda a: handle_or(a.reg(1), a.shifted_imm(2)),
|
||||
"and": lambda a: BinaryOp.int(left=a.reg(1), op="&", right=a.reg(2)),
|
||||
"andc": lambda a: BinaryOp.int(
|
||||
left=a.reg(1), op="&", right=UnaryOp("~", a.reg(2), type=Type.intish())
|
||||
),
|
||||
"not": lambda a: UnaryOp("~", a.reg(1), type=Type.intish()),
|
||||
"nor": lambda a: UnaryOp(
|
||||
"~", BinaryOp.int(left=a.reg(1), op="|", right=a.reg(2)), type=Type.intish()
|
||||
),
|
||||
"xor": lambda a: BinaryOp.int(left=a.reg(1), op="^", right=a.reg(2)),
|
||||
"eqv": lambda a: UnaryOp(
|
||||
"~", BinaryOp.int(left=a.reg(1), op="^", right=a.reg(2)), type=Type.intish()
|
||||
),
|
||||
"andi": lambda a: BinaryOp.int(left=a.reg(1), op="&", right=a.unsigned_imm(2)),
|
||||
"andis": lambda a: BinaryOp.int(left=a.reg(1), op="&", right=a.shifted_imm(2)),
|
||||
"xori": lambda a: BinaryOp.int(left=a.reg(1), op="^", right=a.unsigned_imm(2)),
|
||||
@@ -575,6 +611,9 @@ class PpcArch(Arch):
|
||||
"boolcast.fictive": lambda a: UnaryOp(
|
||||
op="!!", expr=a.reg(1), type=Type.intish()
|
||||
),
|
||||
"rlwimi": lambda a: handle_rlwimi(
|
||||
a.reg(0), a.reg(1), a.imm_value(2), a.imm_value(3), a.imm_value(4)
|
||||
),
|
||||
"rlwinm": lambda a: handle_rlwinm(
|
||||
a.reg(1), a.imm_value(2), a.imm_value(3), a.imm_value(4)
|
||||
),
|
||||
@@ -622,6 +661,7 @@ class PpcArch(Arch):
|
||||
"srawi": lambda a: handle_sra(a),
|
||||
"extsb": lambda a: handle_convert(a.reg(1), Type.s8(), Type.intish()),
|
||||
"extsh": lambda a: handle_convert(a.reg(1), Type.s16(), Type.intish()),
|
||||
"cntlzw": lambda a: UnaryOp(op="CLZ", expr=a.reg(1), type=Type.intish()),
|
||||
# Integer Loads
|
||||
"lba": lambda a: handle_load(a, type=Type.s8()),
|
||||
"lbz": lambda a: handle_load(a, type=Type.u8()),
|
||||
@@ -697,6 +737,14 @@ class PpcArch(Arch):
|
||||
),
|
||||
# TODO: Detect if we should use fabs or fabsf
|
||||
"fabs": lambda a: fn_op("fabs", [a.reg(1)], Type.floatish()),
|
||||
"fres": lambda a: fn_op("__fres", [a.reg(1)], Type.floatish()),
|
||||
"frsqrte": lambda a: fn_op("__frsqrte", [a.reg(1)], Type.floatish()),
|
||||
"fsel": lambda a: TernaryOp(
|
||||
cond=BinaryOp.fcmp(a.reg(1), ">=", Literal(0)),
|
||||
left=a.reg(2),
|
||||
right=a.reg(3),
|
||||
type=Type.floatish(),
|
||||
),
|
||||
}
|
||||
instrs_load_update: InstrMap = {
|
||||
"lbau": lambda a: handle_load(a, type=Type.s8()),
|
||||
|
||||
39
backend/mips_to_c/src/asm_pattern.py
generated
39
backend/mips_to_c/src/asm_pattern.py
generated
@@ -5,8 +5,10 @@ from typing import Dict, List, Optional, Tuple, TypeVar, Union
|
||||
from .parse_file import Label
|
||||
from .parse_instruction import (
|
||||
Argument,
|
||||
ArchAsm,
|
||||
AsmAddressMode,
|
||||
AsmGlobalSymbol,
|
||||
AsmInstruction,
|
||||
AsmLiteral,
|
||||
BinOp,
|
||||
Instruction,
|
||||
@@ -14,12 +16,13 @@ from .parse_instruction import (
|
||||
JumpTarget,
|
||||
NaiveParsingArch,
|
||||
Register,
|
||||
parse_instruction,
|
||||
parse_asm_instruction,
|
||||
)
|
||||
|
||||
|
||||
BodyPart = Union[Instruction, Label]
|
||||
PatternPart = Union[Instruction, Label, None]
|
||||
ReplacementPart = Union[AsmInstruction, Instruction, Label]
|
||||
PatternPart = Union[AsmInstruction, Label, None]
|
||||
Pattern = List[Tuple[PatternPart, bool]]
|
||||
|
||||
|
||||
@@ -33,14 +36,14 @@ def make_pattern(*parts: str) -> Pattern:
|
||||
elif part.endswith(":"):
|
||||
ret.append((Label(part.strip(".:")), optional))
|
||||
else:
|
||||
ins = parse_instruction(part, InstructionMeta.missing(), NaiveParsingArch())
|
||||
ins = parse_asm_instruction(part, NaiveParsingArch())
|
||||
ret.append((ins, optional))
|
||||
return ret
|
||||
|
||||
|
||||
@dataclass
|
||||
class Replacement:
|
||||
new_body: List[BodyPart]
|
||||
new_body: List[ReplacementPart]
|
||||
num_consumed: int
|
||||
|
||||
|
||||
@@ -50,10 +53,6 @@ class AsmMatch:
|
||||
regs: Dict[str, Register]
|
||||
literals: Dict[str, int]
|
||||
|
||||
def derived_instr(self, mnemonic: str, args: List[Argument]) -> Instruction:
|
||||
old_instr = next(part for part in self.body if isinstance(part, Instruction))
|
||||
return Instruction.derived(mnemonic, args, old_instr)
|
||||
|
||||
|
||||
class AsmPattern(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
@@ -187,13 +186,27 @@ class AsmMatcher:
|
||||
state.symbolic_literals,
|
||||
)
|
||||
|
||||
def apply(self, repl: Replacement) -> None:
|
||||
self.output.extend(repl.new_body)
|
||||
def derived_meta(self) -> InstructionMeta:
|
||||
for part in self.input[self.index :]:
|
||||
if isinstance(part, Instruction):
|
||||
return part.meta.derived()
|
||||
return InstructionMeta.missing()
|
||||
|
||||
def apply(self, repl: Replacement, arch: ArchAsm) -> None:
|
||||
for part in repl.new_body:
|
||||
if isinstance(part, AsmInstruction):
|
||||
# Parse any AsmInstructions into Instructions before substituting
|
||||
instr = arch.parse(part.mnemonic, part.args, self.derived_meta())
|
||||
self.output.append(instr)
|
||||
else:
|
||||
self.output.append(part)
|
||||
self.index += repl.num_consumed
|
||||
|
||||
|
||||
def simplify_patterns(
|
||||
body: List[BodyPart], patterns: List[AsmPattern]
|
||||
body: List[BodyPart],
|
||||
patterns: List[AsmPattern],
|
||||
arch: ArchAsm,
|
||||
) -> List[BodyPart]:
|
||||
"""Detect and simplify asm standard patterns emitted by known compilers. This is
|
||||
especially useful for patterns that involve branches, which are hard to deal with
|
||||
@@ -203,9 +216,9 @@ def simplify_patterns(
|
||||
for pattern in patterns:
|
||||
m = pattern.match(matcher)
|
||||
if m:
|
||||
matcher.apply(m)
|
||||
matcher.apply(m, arch)
|
||||
break
|
||||
else:
|
||||
matcher.apply(Replacement([matcher.input[matcher.index]], 1))
|
||||
matcher.apply(Replacement([matcher.input[matcher.index]], 1), arch)
|
||||
|
||||
return matcher.output
|
||||
|
||||
100
backend/mips_to_c/src/flow_graph.py
generated
100
backend/mips_to_c/src/flow_graph.py
generated
@@ -114,12 +114,12 @@ class BlockBuilder:
|
||||
return self.blocks
|
||||
|
||||
|
||||
def verify_no_trailing_delay_slot(function: Function, arch: ArchFlowGraph) -> None:
|
||||
def verify_no_trailing_delay_slot(function: Function) -> None:
|
||||
last_ins: Optional[Instruction] = None
|
||||
for item in function.body:
|
||||
if isinstance(item, Instruction):
|
||||
last_ins = item
|
||||
if last_ins and arch.is_delay_slot_instruction(last_ins):
|
||||
if last_ins and last_ins.has_delay_slot:
|
||||
raise DecompFailure(f"Last instruction is missing a delay slot:\n{last_ins}")
|
||||
|
||||
|
||||
@@ -192,9 +192,10 @@ def normalize_likely_branches(function: Function, arch: ArchFlowGraph) -> Functi
|
||||
for item in body_iter:
|
||||
orig_item = item
|
||||
if isinstance(item, Instruction) and (
|
||||
arch.is_branch_likely_instruction(item) or item.mnemonic == "b"
|
||||
item.is_branch_likely or item.mnemonic == "b"
|
||||
):
|
||||
old_label = arch.get_branch_target(item).target
|
||||
assert isinstance(item.jump_target, JumpTarget)
|
||||
old_label = item.jump_target.target
|
||||
if old_label not in label_prev_instr:
|
||||
raise DecompFailure(
|
||||
f"Unable to parse branch: label {old_label} does not exist in function {function.name}"
|
||||
@@ -210,7 +211,7 @@ def normalize_likely_branches(function: Function, arch: ArchFlowGraph) -> Functi
|
||||
if (
|
||||
item.mnemonic == "b"
|
||||
and before_before_target is not None
|
||||
and arch.is_delay_slot_instruction(before_before_target)
|
||||
and before_before_target.has_delay_slot
|
||||
):
|
||||
# Don't treat 'b' instructions as branch likelies if doing so would
|
||||
# introduce a label in a delay slot.
|
||||
@@ -222,8 +223,8 @@ def normalize_likely_branches(function: Function, arch: ArchFlowGraph) -> Functi
|
||||
and item.mnemonic != "b"
|
||||
):
|
||||
mn_inverted = invert_branch_mnemonic(item.mnemonic[:-1])
|
||||
item = Instruction.derived(mn_inverted, item.args, item)
|
||||
new_nop = Instruction.derived("nop", [], item)
|
||||
item = arch.parse(mn_inverted, item.args, item.meta.derived())
|
||||
new_nop = arch.parse("nop", [], item.meta.derived())
|
||||
new_body.append((orig_item, item))
|
||||
new_body.append((new_nop, new_nop))
|
||||
new_body.append((orig_next_item, next_item))
|
||||
@@ -240,10 +241,10 @@ def normalize_likely_branches(function: Function, arch: ArchFlowGraph) -> Functi
|
||||
insert_label_before[id(before_target)] = new_label
|
||||
new_target = JumpTarget(label_before_instr[id(before_target)])
|
||||
mn_unlikely = item.mnemonic[:-1] or "b"
|
||||
item = Instruction.derived(
|
||||
mn_unlikely, item.args[:-1] + [new_target], item
|
||||
item = arch.parse(
|
||||
mn_unlikely, item.args[:-1] + [new_target], item.meta.derived()
|
||||
)
|
||||
next_item = Instruction.derived("nop", [], item)
|
||||
next_item = arch.parse("nop", [], item.meta.derived())
|
||||
new_body.append((orig_item, item))
|
||||
new_body.append((orig_next_item, next_item))
|
||||
else:
|
||||
@@ -261,17 +262,15 @@ def normalize_likely_branches(function: Function, arch: ArchFlowGraph) -> Functi
|
||||
return new_function
|
||||
|
||||
|
||||
def prune_unreferenced_labels(
|
||||
function: Function, asm_data: AsmData, arch: ArchFlowGraph
|
||||
) -> Function:
|
||||
def prune_unreferenced_labels(function: Function, asm_data: AsmData) -> Function:
|
||||
labels_used: Set[str] = {
|
||||
label.name
|
||||
for label in function.body
|
||||
if isinstance(label, Label) and label.name in asm_data.mentioned_labels
|
||||
}
|
||||
for item in function.body:
|
||||
if isinstance(item, Instruction) and arch.is_branch_instruction(item):
|
||||
labels_used.add(arch.get_branch_target(item).target)
|
||||
if isinstance(item, Instruction) and isinstance(item.jump_target, JumpTarget):
|
||||
labels_used.add(item.jump_target.target)
|
||||
|
||||
new_function = function.bodyless_copy()
|
||||
for item in function.body:
|
||||
@@ -282,7 +281,7 @@ def prune_unreferenced_labels(
|
||||
|
||||
|
||||
def simplify_standard_patterns(function: Function, arch: ArchFlowGraph) -> Function:
|
||||
new_body = simplify_patterns(function.body, arch.asm_patterns)
|
||||
new_body = simplify_patterns(function.body, arch.asm_patterns, arch)
|
||||
new_function = function.bodyless_copy()
|
||||
new_function.body.extend(new_body)
|
||||
return new_function
|
||||
@@ -292,12 +291,12 @@ def build_blocks(
|
||||
function: Function, asm_data: AsmData, arch: ArchFlowGraph
|
||||
) -> List[Block]:
|
||||
if arch.arch == Target.ArchEnum.MIPS:
|
||||
verify_no_trailing_delay_slot(function, arch)
|
||||
verify_no_trailing_delay_slot(function)
|
||||
function = normalize_likely_branches(function, arch)
|
||||
|
||||
function = prune_unreferenced_labels(function, asm_data, arch)
|
||||
function = prune_unreferenced_labels(function, asm_data)
|
||||
function = simplify_standard_patterns(function, arch)
|
||||
function = prune_unreferenced_labels(function, asm_data, arch)
|
||||
function = prune_unreferenced_labels(function, asm_data)
|
||||
|
||||
block_builder = BlockBuilder()
|
||||
|
||||
@@ -305,18 +304,16 @@ def build_blocks(
|
||||
branch_likely_counts: Counter[str] = Counter()
|
||||
cond_return_target: Optional[JumpTarget] = None
|
||||
|
||||
def process_with_delay_slots(item: Union[Instruction, Label]) -> None:
|
||||
def process_mips(item: Union[Instruction, Label]) -> None:
|
||||
if isinstance(item, Label):
|
||||
# Split blocks at labels.
|
||||
block_builder.new_block()
|
||||
block_builder.set_label(item)
|
||||
return
|
||||
|
||||
if not arch.is_delay_slot_instruction(item):
|
||||
if not item.has_delay_slot:
|
||||
block_builder.add_instruction(item)
|
||||
# Split blocks at jumps, at the next instruction.
|
||||
if arch.is_jump_instruction(item):
|
||||
block_builder.new_block()
|
||||
assert not item.is_jump(), "all MIPS jumps have a delay slot"
|
||||
return
|
||||
|
||||
process_after: List[Union[Instruction, Label]] = []
|
||||
@@ -332,8 +329,8 @@ def build_blocks(
|
||||
|
||||
assert isinstance(next_item, Instruction), "Cannot have two labels in a row"
|
||||
|
||||
# (Best-effort check for whether the instruction can be
|
||||
# executed twice in a row.)
|
||||
# Best-effort check for whether the instruction can be executed twice in a row.
|
||||
# TODO: This may be able to be improved to use the other fields in Instruction?
|
||||
r = next_item.args[0] if next_item.args else None
|
||||
if all(a != r for a in next_item.args[1:]):
|
||||
process_after.append(label)
|
||||
@@ -354,32 +351,35 @@ def build_blocks(
|
||||
]
|
||||
raise DecompFailure("\n".join(msg))
|
||||
|
||||
if arch.is_delay_slot_instruction(next_item):
|
||||
if next_item.has_delay_slot:
|
||||
raise DecompFailure(
|
||||
f"Two delay slot instructions in a row is not supported:\n{item}\n{next_item}"
|
||||
)
|
||||
|
||||
if arch.is_branch_likely_instruction(item):
|
||||
target = arch.get_branch_target(item)
|
||||
if item.is_branch_likely:
|
||||
assert isinstance(item.jump_target, JumpTarget)
|
||||
target = item.jump_target
|
||||
branch_likely_counts[target.target] += 1
|
||||
index = branch_likely_counts[target.target]
|
||||
mn_inverted = invert_branch_mnemonic(item.mnemonic[:-1])
|
||||
temp_label = JumpTarget(f"{target.target}_branchlikelyskip_{index}")
|
||||
branch_not = Instruction.derived(
|
||||
mn_inverted, item.args[:-1] + [temp_label], item
|
||||
branch_not = arch.parse(
|
||||
mn_inverted, item.args[:-1] + [temp_label], item.meta.derived()
|
||||
)
|
||||
nop = Instruction.derived("nop", [], item)
|
||||
nop = arch.parse("nop", [], item.meta.derived())
|
||||
block_builder.add_instruction(branch_not)
|
||||
block_builder.add_instruction(nop)
|
||||
block_builder.new_block()
|
||||
block_builder.add_instruction(next_item)
|
||||
block_builder.add_instruction(Instruction.derived("b", [target], item))
|
||||
block_builder.add_instruction(
|
||||
arch.parse("b", [target], item.meta.derived())
|
||||
)
|
||||
block_builder.add_instruction(nop)
|
||||
block_builder.new_block()
|
||||
block_builder.set_label(Label(temp_label.target))
|
||||
block_builder.add_instruction(nop)
|
||||
|
||||
elif item.mnemonic in ["jal", "jalr"]:
|
||||
elif item.function_target is not None:
|
||||
# Move the delay slot instruction to before the call so it
|
||||
# passes correct arguments.
|
||||
if next_item.args and next_item.args[0] == item.args[0]:
|
||||
@@ -394,12 +394,12 @@ def build_blocks(
|
||||
block_builder.add_instruction(item)
|
||||
block_builder.add_instruction(next_item)
|
||||
|
||||
if arch.is_jump_instruction(item):
|
||||
if item.is_jump():
|
||||
# Split blocks at jumps, after the next instruction.
|
||||
block_builder.new_block()
|
||||
|
||||
for item in process_after:
|
||||
process_with_delay_slots(item)
|
||||
process_mips(item)
|
||||
|
||||
def process_no_delay_slots(item: Union[Instruction, Label]) -> None:
|
||||
nonlocal cond_return_target
|
||||
@@ -410,13 +410,13 @@ def build_blocks(
|
||||
block_builder.set_label(item)
|
||||
return
|
||||
|
||||
if arch.is_conditional_return_instruction(item):
|
||||
if item.is_conditional and item.is_return:
|
||||
if cond_return_target is None:
|
||||
cond_return_target = JumpTarget(f"_conditionalreturn_")
|
||||
# Strip the "lr" off of the instruction
|
||||
assert item.mnemonic[-2:] == "lr"
|
||||
branch_instr = Instruction.derived(
|
||||
item.mnemonic[:-2], [cond_return_target], item
|
||||
branch_instr = arch.parse(
|
||||
item.mnemonic[:-2], [cond_return_target], item.meta.derived()
|
||||
)
|
||||
block_builder.add_instruction(branch_instr)
|
||||
block_builder.new_block()
|
||||
@@ -425,12 +425,12 @@ def build_blocks(
|
||||
block_builder.add_instruction(item)
|
||||
|
||||
# Split blocks at jumps, at the next instruction.
|
||||
if arch.is_jump_instruction(item):
|
||||
if item.is_jump():
|
||||
block_builder.new_block()
|
||||
|
||||
for item in body_iter:
|
||||
if arch.uses_delay_slots:
|
||||
process_with_delay_slots(item)
|
||||
if arch.arch == Target.ArchEnum.MIPS:
|
||||
process_mips(item)
|
||||
else:
|
||||
process_no_delay_slots(item)
|
||||
|
||||
@@ -647,9 +647,7 @@ def build_graph_from_block(
|
||||
return None
|
||||
|
||||
# Extract branching instructions from this block.
|
||||
jumps: List[Instruction] = [
|
||||
inst for inst in block.instructions if arch.is_jump_instruction(inst)
|
||||
]
|
||||
jumps: List[Instruction] = [inst for inst in block.instructions if inst.is_jump()]
|
||||
assert len(jumps) in [0, 1], "too many jump instructions in one block"
|
||||
|
||||
if len(jumps) == 0:
|
||||
@@ -670,12 +668,12 @@ def build_graph_from_block(
|
||||
# - a ConditionalNode.
|
||||
jump = jumps[0]
|
||||
|
||||
if arch.is_return_instruction(jump):
|
||||
if jump.is_return:
|
||||
new_node = ReturnNode(block, False, index=0, terminal=terminal_node)
|
||||
nodes.append(new_node)
|
||||
return new_node
|
||||
|
||||
if arch.is_jumptable_instruction(jump):
|
||||
if isinstance(jump.jump_target, Register):
|
||||
new_node = SwitchNode(block, False, [])
|
||||
nodes.append(new_node)
|
||||
|
||||
@@ -727,17 +725,17 @@ def build_graph_from_block(
|
||||
new_node.cases.append(case_node)
|
||||
return new_node
|
||||
|
||||
assert arch.is_branch_instruction(jump)
|
||||
|
||||
# Get the block associated with the jump target.
|
||||
branch_label = arch.get_branch_target(jump)
|
||||
branch_label = jump.jump_target
|
||||
assert isinstance(branch_label, JumpTarget)
|
||||
|
||||
branch_block = find_block_by_label(branch_label.target)
|
||||
if branch_block is None:
|
||||
target = branch_label.target
|
||||
raise DecompFailure(f"Cannot find branch target {target}")
|
||||
|
||||
emit_goto = jump.meta.emit_goto
|
||||
if arch.is_constant_branch_instruction(jump):
|
||||
if not jump.is_conditional:
|
||||
# A constant branch becomes a basic edge to our branch target.
|
||||
new_node = BasicNode(block, emit_goto, dummy_node)
|
||||
nodes.append(new_node)
|
||||
|
||||
111
backend/mips_to_c/src/parse_instruction.py
generated
111
backend/mips_to_c/src/parse_instruction.py
generated
@@ -110,6 +110,12 @@ Argument = Union[
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AsmInstruction:
|
||||
mnemonic: str
|
||||
args: List[Argument]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InstructionMeta:
|
||||
emit_goto: bool
|
||||
@@ -123,6 +129,9 @@ class InstructionMeta:
|
||||
emit_goto=False, filename="<unknown>", lineno=0, synthetic=True
|
||||
)
|
||||
|
||||
def derived(self) -> "InstructionMeta":
|
||||
return replace(self, synthetic=True)
|
||||
|
||||
def loc_str(self) -> str:
|
||||
adj = "near" if self.synthetic else "at"
|
||||
return f"{adj} {self.filename} line {self.lineno}"
|
||||
@@ -134,11 +143,19 @@ class Instruction:
|
||||
args: List[Argument]
|
||||
meta: InstructionMeta
|
||||
|
||||
@staticmethod
|
||||
def derived(
|
||||
mnemonic: str, args: List[Argument], old: "Instruction"
|
||||
) -> "Instruction":
|
||||
return Instruction(mnemonic, args, replace(old.meta, synthetic=True))
|
||||
jump_target: Optional[Union[JumpTarget, Register]] = None
|
||||
function_target: Optional[Union[AsmGlobalSymbol, Register]] = None
|
||||
is_conditional: bool = False
|
||||
is_return: bool = False
|
||||
|
||||
# These are for MIPS. `is_branch_likely` refers to branch instructions which
|
||||
# execute their delay slot only if the branch *is* taken. (Maybe these two
|
||||
# bools should be merged into a 3-valued enum?)
|
||||
has_delay_slot: bool = False
|
||||
is_branch_likely: bool = False
|
||||
|
||||
def is_jump(self) -> bool:
|
||||
return self.jump_target is not None or self.is_return
|
||||
|
||||
def __str__(self) -> str:
|
||||
if not self.args:
|
||||
@@ -158,7 +175,7 @@ class ArchAsmParsing(abc.ABC):
|
||||
aliased_regs: Dict[str, Register]
|
||||
|
||||
@abc.abstractmethod
|
||||
def normalize_instruction(self, instr: Instruction) -> Instruction:
|
||||
def normalize_instruction(self, instr: AsmInstruction) -> AsmInstruction:
|
||||
...
|
||||
|
||||
|
||||
@@ -181,55 +198,15 @@ class ArchAsm(ArchAsmParsing):
|
||||
|
||||
aliased_regs: Dict[str, Register]
|
||||
|
||||
uses_delay_slots: bool
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_branch_instruction(self, instr: Instruction) -> bool:
|
||||
"""Instructions with a label as a jump target (may be conditional)"""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_branch_likely_instruction(self, instr: Instruction) -> bool:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_constant_branch_instruction(self, instr: Instruction) -> bool:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_conditional_return_instruction(self, instr: Instruction) -> bool:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_jump_instruction(self, instr: Instruction) -> bool:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_delay_slot_instruction(self, instr: Instruction) -> bool:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_return_instruction(self, instr: Instruction) -> bool:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_jumptable_instruction(self, instr: Instruction) -> bool:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def missing_return(self) -> List[Instruction]:
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def get_branch_target(instr: Instruction) -> JumpTarget:
|
||||
label = instr.args[-1]
|
||||
if isinstance(label, AsmGlobalSymbol):
|
||||
return JumpTarget(label.symbol_name)
|
||||
if not isinstance(label, JumpTarget):
|
||||
raise DecompFailure(
|
||||
f'Couldn\'t parse instruction "{instr}": invalid branch target'
|
||||
)
|
||||
return label
|
||||
@abc.abstractmethod
|
||||
def parse(
|
||||
self, mnemonic: str, args: List[Argument], meta: InstructionMeta
|
||||
) -> Instruction:
|
||||
...
|
||||
|
||||
|
||||
class NaiveParsingArch(ArchAsmParsing):
|
||||
@@ -239,7 +216,7 @@ class NaiveParsingArch(ArchAsmParsing):
|
||||
all_regs: List[Register] = []
|
||||
aliased_regs: Dict[str, Register] = {}
|
||||
|
||||
def normalize_instruction(self, instr: Instruction) -> Instruction:
|
||||
def normalize_instruction(self, instr: AsmInstruction) -> AsmInstruction:
|
||||
return instr
|
||||
|
||||
|
||||
@@ -283,6 +260,13 @@ def constant_fold(arg: Argument) -> Argument:
|
||||
return arg
|
||||
|
||||
|
||||
def get_jump_target(label: Argument) -> JumpTarget:
|
||||
if isinstance(label, AsmGlobalSymbol):
|
||||
return JumpTarget(label.symbol_name)
|
||||
assert isinstance(label, JumpTarget), "invalid branch target"
|
||||
return label
|
||||
|
||||
|
||||
# Main parser.
|
||||
def parse_arg_elems(arg_elems: List[str], arch: ArchAsmParsing) -> Optional[Argument]:
|
||||
value: Optional[Argument] = None
|
||||
@@ -442,16 +426,19 @@ def split_arg_list(args: str) -> List[str]:
|
||||
)
|
||||
|
||||
|
||||
def parse_instruction(
|
||||
line: str, meta: InstructionMeta, arch: ArchAsmParsing
|
||||
) -> Instruction:
|
||||
def parse_asm_instruction(line: str, arch: ArchAsmParsing) -> AsmInstruction:
|
||||
# First token is instruction name, rest is args.
|
||||
line = line.strip()
|
||||
mnemonic, _, args_str = line.partition(" ")
|
||||
# Parse arguments.
|
||||
args = [parse_arg(arg_str, arch) for arg_str in split_arg_list(args_str)]
|
||||
instr = AsmInstruction(mnemonic, args)
|
||||
return arch.normalize_instruction(instr)
|
||||
|
||||
|
||||
def parse_instruction(line: str, meta: InstructionMeta, arch: ArchAsm) -> Instruction:
|
||||
try:
|
||||
# First token is instruction name, rest is args.
|
||||
line = line.strip()
|
||||
mnemonic, _, args_str = line.partition(" ")
|
||||
# Parse arguments.
|
||||
args = [parse_arg(arg_str, arch) for arg_str in split_arg_list(args_str)]
|
||||
instr = Instruction(mnemonic, args, meta)
|
||||
return arch.normalize_instruction(instr)
|
||||
base = parse_asm_instruction(line, arch)
|
||||
return arch.parse(base.mnemonic, base.args, meta)
|
||||
except Exception:
|
||||
raise DecompFailure(f"Failed to parse instruction {meta.loc_str()}: {line}")
|
||||
|
||||
224
backend/mips_to_c/src/translate.py
generated
224
backend/mips_to_c/src/translate.py
generated
@@ -117,7 +117,7 @@ class Arch(ArchFlowGraph):
|
||||
|
||||
ASSOCIATIVE_OPS: Set[str] = {"+", "&&", "||", "&", "|", "^", "*"}
|
||||
COMPOUND_ASSIGNMENT_OPS: Set[str] = {"+", "-", "*", "/", "%", "&", "|", "^", "<<", ">>"}
|
||||
PSEUDO_FUNCTION_OPS: Set[str] = {"MULT_HI", "MULTU_HI", "DMULT_HI", "DMULTU_HI"}
|
||||
PSEUDO_FUNCTION_OPS: Set[str] = {"MULT_HI", "MULTU_HI", "DMULT_HI", "DMULTU_HI", "CLZ"}
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -408,7 +408,7 @@ class StackInfo:
|
||||
|
||||
if store:
|
||||
# If there's already been a store to `location`, then return a fresh type
|
||||
field_type = Type.any_reg()
|
||||
field_type = Type.any_field()
|
||||
else:
|
||||
# Use the type of the last store instead of the one from `get_deref_field()`
|
||||
field_type = previous_stored_type
|
||||
@@ -780,6 +780,25 @@ class SecondF64Half(Expression):
|
||||
return "(second half of f64)"
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=False)
|
||||
class CarryBit(Expression):
|
||||
type: Type = field(default_factory=Type.intish)
|
||||
|
||||
def dependencies(self) -> List[Expression]:
|
||||
return []
|
||||
|
||||
def format(self, fmt: Formatter) -> str:
|
||||
return "MIPS2C_CARRY"
|
||||
|
||||
@staticmethod
|
||||
def add_to(expr: Expression) -> "BinaryOp":
|
||||
return fold_divmod(BinaryOp.intptr(expr, "+", CarryBit()))
|
||||
|
||||
@staticmethod
|
||||
def sub_from(expr: Expression) -> "BinaryOp":
|
||||
return BinaryOp.intptr(expr, "-", UnaryOp("!", CarryBit(), type=Type.intish()))
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=False)
|
||||
class BinaryOp(Condition):
|
||||
left: Expression
|
||||
@@ -861,8 +880,15 @@ class BinaryOp(Condition):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def s32(left: Expression, op: str, right: Expression) -> "BinaryOp":
|
||||
return BinaryOp(left=as_s32(left), op=op, right=as_s32(right), type=Type.s32())
|
||||
def s32(
|
||||
left: Expression, op: str, right: Expression, silent: bool = False
|
||||
) -> "BinaryOp":
|
||||
return BinaryOp(
|
||||
left=as_s32(left, silent=silent),
|
||||
op=op,
|
||||
right=as_s32(right, silent=silent),
|
||||
type=Type.s32(),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def u32(left: Expression, op: str, right: Expression) -> "BinaryOp":
|
||||
@@ -1015,6 +1041,10 @@ class UnaryOp(Condition):
|
||||
return UnaryOp("!", self, type=Type.bool())
|
||||
|
||||
def format(self, fmt: Formatter) -> str:
|
||||
# These aren't real operators (or functions); format them as a fn call
|
||||
if self.op in PSEUDO_FUNCTION_OPS:
|
||||
return f"{self.op}({self.expr.format(fmt)})"
|
||||
|
||||
return f"{self.op}{self.expr.format(fmt)}"
|
||||
|
||||
|
||||
@@ -1894,8 +1924,8 @@ class RegMeta:
|
||||
# True if the value derives solely from function call return values
|
||||
function_return: bool = False
|
||||
|
||||
# True if the value derives solely from regdata's with is_read = True or
|
||||
# function_return = True
|
||||
# True if the value derives solely from regdata's with is_read = True,
|
||||
# function_return = True, or is a passed in argument
|
||||
uninteresting: bool = False
|
||||
|
||||
# True if the regdata must be replaced by variable if it is ever read
|
||||
@@ -2921,6 +2951,22 @@ def fold_divmod(original_expr: BinaryOp) -> BinaryOp:
|
||||
right_expr = early_unwrap_ints(expr.right)
|
||||
divisor_shift = 0
|
||||
|
||||
# Detect signed power-of-two division: (x >> N) + MIPS2C_CARRY --> x / (1 << N)
|
||||
if (
|
||||
isinstance(left_expr, BinaryOp)
|
||||
and left_expr.op == ">>"
|
||||
and isinstance(left_expr.right, Literal)
|
||||
and expr.op == "+"
|
||||
and isinstance(right_expr, CarryBit)
|
||||
):
|
||||
new_denom = 1 << left_expr.right.value
|
||||
return BinaryOp.s32(
|
||||
left=left_expr.left,
|
||||
op="/",
|
||||
right=Literal(new_denom),
|
||||
silent=True,
|
||||
)
|
||||
|
||||
# Fold `/` with `>>`: ((x / N) >> M) --> x / (N << M)
|
||||
# NB: If x is signed, this is only correct if there is a sign-correcting subtraction term
|
||||
if (
|
||||
@@ -2945,11 +2991,16 @@ def fold_divmod(original_expr: BinaryOp) -> BinaryOp:
|
||||
if (
|
||||
isinstance(div_expr, BinaryOp)
|
||||
and early_unwrap_ints(div_expr.left) == left_expr
|
||||
and div_expr.op == "/"
|
||||
and early_unwrap_ints(div_expr.right) == mod_base
|
||||
and isinstance(mod_base, Literal)
|
||||
):
|
||||
return BinaryOp.int(left=left_expr, op="%", right=right_expr.right)
|
||||
# Accept either `(x / N) * N` or `(x >> N) * M` (where `1 << N == M`)
|
||||
divisor = early_unwrap_ints(div_expr.right)
|
||||
if (div_expr.op == "/" and divisor == mod_base) or (
|
||||
div_expr.op == ">>"
|
||||
and isinstance(divisor, Literal)
|
||||
and (1 << divisor.value) == mod_base.value
|
||||
):
|
||||
return BinaryOp.int(left=left_expr, op="%", right=right_expr.right)
|
||||
|
||||
# Detect dividing by a negative: ((x >> 31) - (x / N)) --> x / -N
|
||||
if (
|
||||
@@ -2968,11 +3019,13 @@ def fold_divmod(original_expr: BinaryOp) -> BinaryOp:
|
||||
)
|
||||
|
||||
# Remove outer error term: ((x / N) + ((x / N) >> 31)) --> x / N
|
||||
# As N gets close to (1 << 30), this is no longer a negligible error term
|
||||
if (
|
||||
expr.op == "+"
|
||||
and isinstance(left_expr, BinaryOp)
|
||||
and left_expr.op == "/"
|
||||
and isinstance(left_expr.right, Literal)
|
||||
and left_expr.right.value <= (1 << 29)
|
||||
and isinstance(right_expr, BinaryOp)
|
||||
and early_unwrap_ints(right_expr.left) == left_expr
|
||||
and right_expr.op == ">>"
|
||||
@@ -3074,6 +3127,48 @@ def fold_divmod(original_expr: BinaryOp) -> BinaryOp:
|
||||
return original_expr
|
||||
|
||||
|
||||
def replace_clz_shift(expr: BinaryOp) -> BinaryOp:
|
||||
"""
|
||||
Simplify an expression matching `CLZ(x) >> 5` into `x == 0`,
|
||||
and further simplify `(a - b) == 0` into `a == b`.
|
||||
"""
|
||||
# Check that the outer expression is `>>`
|
||||
if expr.is_floating() or expr.op != ">>":
|
||||
return expr
|
||||
|
||||
# Match `CLZ(x) >> 5`, or return the original expr
|
||||
left_expr = early_unwrap_ints(expr.left)
|
||||
right_expr = early_unwrap_ints(expr.right)
|
||||
if not (
|
||||
isinstance(left_expr, UnaryOp)
|
||||
and left_expr.op == "CLZ"
|
||||
and isinstance(right_expr, Literal)
|
||||
and right_expr.value == 5
|
||||
):
|
||||
return expr
|
||||
|
||||
# If the inner `x` is `(a - b)`, return `a == b`
|
||||
sub_expr = early_unwrap(left_expr.expr)
|
||||
if (
|
||||
isinstance(sub_expr, BinaryOp)
|
||||
and not sub_expr.is_floating()
|
||||
and sub_expr.op == "-"
|
||||
):
|
||||
return BinaryOp.icmp(sub_expr.left, "==", sub_expr.right)
|
||||
|
||||
return BinaryOp.icmp(left_expr.expr, "==", Literal(0, type=left_expr.expr.type))
|
||||
|
||||
|
||||
def replace_bitand(expr: BinaryOp) -> Expression:
|
||||
"""Detect expressions using `&` for truncating integer casts"""
|
||||
if not expr.is_floating() and expr.op == "&":
|
||||
if expr.right == Literal(0xFF):
|
||||
return as_type(expr.left, Type.int_of_size(8), silent=False)
|
||||
if expr.right == Literal(0xFFFF):
|
||||
return as_type(expr.left, Type.int_of_size(16), silent=False)
|
||||
return expr
|
||||
|
||||
|
||||
def fold_mul_chains(expr: Expression) -> Expression:
|
||||
"""Simplify an expression involving +, -, * and << to a single multiplication,
|
||||
e.g. 4*x - x -> 3*x, or x<<2 -> x*4. This includes some logic for preventing
|
||||
@@ -3322,35 +3417,49 @@ def handle_bgez(args: InstrArgs) -> Condition:
|
||||
return BinaryOp.scmp(expr, ">=", Literal(0))
|
||||
|
||||
|
||||
def handle_rlwinm(
|
||||
source: Expression, shift: int, mask_begin: int, mask_end: int
|
||||
) -> Expression:
|
||||
# Special case truncating casts
|
||||
# TODO: Detect shift + truncate, like `(x << 2) & 0xFFF3` or `(x >> 2) & 0x3FFF`
|
||||
if (shift, mask_begin, mask_end) == (0, 24, 31):
|
||||
return as_type(source, Type.u8(), silent=False)
|
||||
elif (shift, mask_begin, mask_end) == (0, 16, 31):
|
||||
return as_type(source, Type.u16(), silent=False)
|
||||
|
||||
# The output of the rlwinm instruction is `ROTL(source, shift) & mask`. We write this as
|
||||
# ((source << shift) & mask) | ((source >> (32 - shift)) & mask)
|
||||
# and compute both OR operands (upper_bits and lower_bits respectively).
|
||||
|
||||
def rlwi_mask(mask_begin: int, mask_end: int) -> int:
|
||||
# Compute the mask constant used by the rlwi* family of PPC instructions,
|
||||
# referred to as the `MASK(MB, ME)` function in the processor manual.
|
||||
# Bit 0 is the MSB, Bit 31 is the LSB
|
||||
bits_upto: Callable[[int], int] = lambda m: (1 << (32 - m)) - 1
|
||||
all_ones = bits_upto(0)
|
||||
all_ones = 0xFFFFFFFF
|
||||
if mask_begin <= mask_end:
|
||||
# Set bits inside the range, fully inclusive
|
||||
mask = bits_upto(mask_begin) - bits_upto(mask_end + 1)
|
||||
else:
|
||||
# Set bits from [31, mask_end] and [mask_begin, 0]
|
||||
mask = (bits_upto(mask_end + 1) - bits_upto(mask_begin)) ^ all_ones
|
||||
return mask
|
||||
|
||||
|
||||
def handle_rlwinm(
|
||||
source: Expression,
|
||||
shift: int,
|
||||
mask_begin: int,
|
||||
mask_end: int,
|
||||
simplify: bool = True,
|
||||
) -> Expression:
|
||||
# TODO: Detect shift + truncate, like `(x << 2) & 0xFFF3` or `(x >> 2) & 0x3FFF`
|
||||
|
||||
# The output of the rlwinm instruction is `ROTL(source, shift) & mask`. We write this as
|
||||
# ((source << shift) & mask) | ((source >> (32 - shift)) & mask)
|
||||
# and compute both OR operands (upper_bits and lower_bits respectively).
|
||||
all_ones = 0xFFFFFFFF
|
||||
mask = rlwi_mask(mask_begin, mask_end)
|
||||
left_shift = shift
|
||||
right_shift = 32 - shift
|
||||
left_mask = (all_ones << left_shift) & mask
|
||||
right_mask = (all_ones >> right_shift) & mask
|
||||
|
||||
# We only simplify if the `simplify` argument is True, and there will be no `|` in the
|
||||
# resulting expression. If there is an `|`, the expression is best left as bitwise math
|
||||
simplify = simplify and not (left_mask and right_mask)
|
||||
|
||||
if isinstance(source, Literal):
|
||||
upper_value = (source.value << left_shift) & mask
|
||||
lower_value = (source.value >> right_shift) & mask
|
||||
return Literal(upper_value | lower_value)
|
||||
|
||||
upper_bits: Optional[Expression]
|
||||
if left_mask == 0:
|
||||
upper_bits = None
|
||||
@@ -3360,30 +3469,66 @@ def handle_rlwinm(
|
||||
upper_bits = BinaryOp.int(
|
||||
left=upper_bits, op="<<", right=Literal(left_shift)
|
||||
)
|
||||
|
||||
if simplify:
|
||||
upper_bits = fold_mul_chains(upper_bits)
|
||||
|
||||
if left_mask != (all_ones << left_shift) & all_ones:
|
||||
upper_bits = BinaryOp.int(left=upper_bits, op="&", right=Literal(left_mask))
|
||||
if simplify:
|
||||
upper_bits = replace_bitand(upper_bits)
|
||||
|
||||
lower_bits: Optional[BinaryOp]
|
||||
lower_bits: Optional[Expression]
|
||||
if right_mask == 0:
|
||||
lower_bits = None
|
||||
else:
|
||||
lower_bits = BinaryOp.u32(left=source, op=">>", right=Literal(right_shift))
|
||||
|
||||
if simplify:
|
||||
lower_bits = replace_clz_shift(fold_divmod(lower_bits))
|
||||
|
||||
if right_mask != (all_ones >> right_shift) & all_ones:
|
||||
lower_bits = BinaryOp.int(
|
||||
left=lower_bits, op="&", right=Literal(right_mask)
|
||||
)
|
||||
if simplify:
|
||||
lower_bits = replace_bitand(lower_bits)
|
||||
|
||||
if upper_bits is None and lower_bits is None:
|
||||
return Literal(0)
|
||||
elif upper_bits is None:
|
||||
assert lower_bits is not None
|
||||
return fold_divmod(lower_bits)
|
||||
return lower_bits
|
||||
elif lower_bits is None:
|
||||
return fold_mul_chains(upper_bits)
|
||||
return upper_bits
|
||||
else:
|
||||
return BinaryOp.int(left=upper_bits, op="|", right=lower_bits)
|
||||
|
||||
|
||||
def handle_rlwimi(
|
||||
base: Expression, source: Expression, shift: int, mask_begin: int, mask_end: int
|
||||
) -> Expression:
|
||||
# This instruction reads from `base`, replaces some bits with values from `source`, then
|
||||
# writes the result back into the first register. This can be used to copy any contiguous
|
||||
# bitfield from `source` into `base`, and is commonly used when manipulating flags, such
|
||||
# as in `x |= 0x10` or `x &= ~0x10`.
|
||||
|
||||
# It's generally more readable to write the mask with `~` (instead of computing the inverse here)
|
||||
mask_literal = Literal(rlwi_mask(mask_begin, mask_end))
|
||||
mask = UnaryOp("~", mask_literal, type=Type.u32())
|
||||
masked_base = BinaryOp.int(left=base, op="&", right=mask)
|
||||
if source == Literal(0):
|
||||
# If the source is 0, there are no bits inserted. (This may look like `x &= ~0x10`)
|
||||
return masked_base
|
||||
# Set `simplify=False` to keep the `inserted` expression as bitwise math instead of `*` or `/`
|
||||
inserted = handle_rlwinm(source, shift, mask_begin, mask_end, simplify=False)
|
||||
if inserted == mask_literal:
|
||||
# If this instruction will set all the bits in the mask, we can OR the values
|
||||
# together without masking the base. (`x |= 0xF0` instead of `x = (x & ~0xF0) | 0xF0`)
|
||||
return BinaryOp.int(left=base, op="|", right=inserted)
|
||||
return BinaryOp.int(left=masked_base, op="|", right=inserted)
|
||||
|
||||
|
||||
def handle_loadx(args: InstrArgs, type: Type) -> Expression:
|
||||
# "indexed loads" like `lwzx rD, rA, rB` read `(rA + rB)` into `rD`
|
||||
size = type.get_size_bytes()
|
||||
@@ -3702,7 +3847,7 @@ def propagate_register_meta(nodes: List[Node], reg: Register) -> None:
|
||||
meta.uninteresting = True
|
||||
meta.function_return = True
|
||||
else:
|
||||
meta.uninteresting = meta.is_read or meta.function_return
|
||||
meta.uninteresting |= meta.is_read or meta.function_return
|
||||
|
||||
todo = non_terminal[:]
|
||||
while todo:
|
||||
@@ -3730,8 +3875,8 @@ def propagate_register_meta(nodes: List[Node], reg: Register) -> None:
|
||||
def determine_return_register(
|
||||
return_blocks: List[BlockInfo], fn_decl_provided: bool, arch: Arch
|
||||
) -> Optional[Register]:
|
||||
"""Determine which of v0 and f0 is the most likely to contain the return
|
||||
value, or if the function is likely void."""
|
||||
"""Determine which of the arch's base_return_regs (i.e. v0, f0) is the most
|
||||
likely to contain the return value, or if the function is likely void."""
|
||||
|
||||
def priority(block_info: BlockInfo, reg: Register) -> int:
|
||||
meta = block_info.final_register_states.get_meta(reg)
|
||||
@@ -4046,16 +4191,7 @@ def translate_node_body(node: Node, regs: RegInfo, stack_info: StackInfo) -> Blo
|
||||
elif arch_mnemonic == "ppc:bctrl":
|
||||
fn_target = args.regs[Register("ctr")]
|
||||
elif arch_mnemonic == "mips:jalr":
|
||||
if args.count() == 1:
|
||||
fn_target = args.reg(0)
|
||||
elif args.count() == 2:
|
||||
if args.reg_ref(0) != arch.return_address_reg:
|
||||
raise DecompFailure(
|
||||
"Two-argument form of jalr is not supported."
|
||||
)
|
||||
fn_target = args.reg(1)
|
||||
else:
|
||||
raise DecompFailure(f"jalr takes 2 arguments, {args.count()} given")
|
||||
fn_target = args.reg(1)
|
||||
else:
|
||||
assert False, f"Unhandled fn call mnemonic {arch_mnemonic}"
|
||||
|
||||
@@ -4780,10 +4916,14 @@ def translate_to_ast(
|
||||
for slot in abi.arg_slots:
|
||||
stack_info.add_known_param(slot.offset, slot.name, slot.type)
|
||||
if slot.reg is not None:
|
||||
start_regs[slot.reg] = make_arg(slot.offset, slot.type)
|
||||
start_regs.set_with_meta(
|
||||
slot.reg, make_arg(slot.offset, slot.type), RegMeta(uninteresting=True)
|
||||
)
|
||||
for slot in abi.possible_slots:
|
||||
if slot.reg is not None:
|
||||
start_regs[slot.reg] = make_arg(slot.offset, slot.type)
|
||||
start_regs.set_with_meta(
|
||||
slot.reg, make_arg(slot.offset, slot.type), RegMeta(uninteresting=True)
|
||||
)
|
||||
|
||||
if options.reg_vars == ["saved"]:
|
||||
reg_vars = arch.saved_regs
|
||||
|
||||
6
backend/mips_to_c/src/types.py
generated
6
backend/mips_to_c/src/types.py
generated
@@ -474,11 +474,7 @@ class Type:
|
||||
return zero_offset_results[0]
|
||||
elif exact:
|
||||
# Try to insert a new field into the struct at the given offset
|
||||
# TODO Loosen this to Type.any_field(), even for stack structs
|
||||
if data.struct.is_stack:
|
||||
field_type = Type.any_reg()
|
||||
else:
|
||||
field_type = Type.any_field()
|
||||
field_type = Type.any_field()
|
||||
field_name = f"{data.struct.new_field_prefix}{offset:X}"
|
||||
new_field = data.struct.try_add_field(
|
||||
field_type, offset, field_name, size=target_size
|
||||
|
||||
4
backend/mips_to_c/tests/add_test.py
generated
4
backend/mips_to_c/tests/add_test.py
generated
@@ -110,7 +110,7 @@ def get_compilers(paths: PathsToBinaries) -> List[Tuple[str, Compiler]]:
|
||||
if paths.MWCC_CC.suffix == ".exe" and sys.platform.startswith("linux"):
|
||||
cc_command.insert(0, "/usr/bin/wine")
|
||||
mwcc = Compiler(
|
||||
name="ppc",
|
||||
name="mwcc",
|
||||
cc_command=cc_command,
|
||||
)
|
||||
compilers.append(("mwcc-o4p", mwcc.with_cc_flags(["-O4,p"])))
|
||||
@@ -313,7 +313,7 @@ def main() -> int:
|
||||
continue
|
||||
expected_file = (
|
||||
Path(__file__).parent / "end_to_end" / orig_file.parent.name / "orig.c"
|
||||
)
|
||||
).resolve()
|
||||
if orig_file != expected_file:
|
||||
logger.error(
|
||||
f"`{orig_file}` does not have a path of the form `{expected_file}`! Skipping."
|
||||
|
||||
3
backend/mips_to_c/tests/elf_file.py
generated
3
backend/mips_to_c/tests/elf_file.py
generated
@@ -164,10 +164,11 @@ class ElfFile:
|
||||
elf.symbols[sym_name] = symbol
|
||||
symbols_by_index.append(symbol)
|
||||
# Do not overwrite existing symbols at this address, and skip
|
||||
# symbols starting with "..." (such as `...bss@0`).
|
||||
# empty names or names starting with "..." (such as `...bss@0`).
|
||||
if (
|
||||
section is not None
|
||||
and st_value not in section.symbols
|
||||
and sym_name
|
||||
and not sym_name.startswith("...")
|
||||
):
|
||||
section.symbols[st_value] = symbol
|
||||
|
||||
@@ -8,19 +8,19 @@
|
||||
if (((arg0 + arg1) != 0) || (((s32) (arg1 + arg2) != 0) && ((arg0 * arg1) != 0)) || ((arg3 != 0) && (arg0 != 0))) {
|
||||
phi_r8 = 1;
|
||||
}
|
||||
if ((arg0 != 0) && ((arg1 != 0) || (arg2 != 0)) && ((arg3 != 0) || (MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1) == 0)))) {
|
||||
if ((arg0 != 0) && ((arg1 != 0) || (arg2 != 0)) && ((arg3 != 0) || ((arg0 + 1) != 0))) {
|
||||
phi_r8 = 2;
|
||||
}
|
||||
if (((arg0 != 0) && (arg3 != 0)) || (((arg1 != 0) || (arg2 != 0)) && (MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1) == 0)))) {
|
||||
if (((arg0 != 0) && (arg3 != 0)) || (((arg1 != 0) || (arg2 != 0)) && ((arg0 + 1) != 0))) {
|
||||
phi_r8 = 3;
|
||||
}
|
||||
if ((arg0 != 0) && (arg1 != 0) && ((arg2 != 0) || (arg3 != 0)) && ((MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1) == 0)) || (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1) == 0)))) {
|
||||
if ((arg0 != 0) && (arg1 != 0) && ((arg2 != 0) || (arg3 != 0)) && (((arg0 + 1) != 0) || ((arg1 + 1) != 0))) {
|
||||
phi_r8 = 4;
|
||||
}
|
||||
if ((((arg0 != 0) || (arg1 != 0)) && (arg2 != 0)) || ((arg3 != 0) && (MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1) == 0))) || (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1) == 0)) || (MIPS2C_ERROR(unknown instruction: addic. $r0, $r5, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r5, 0x1) == 0))) {
|
||||
if ((((arg0 != 0) || (arg1 != 0)) && (arg2 != 0)) || ((arg3 != 0) && ((arg0 + 1) != 0)) || ((arg1 + 1) != 0) || ((arg2 + 1) != 0)) {
|
||||
phi_r8 = 5;
|
||||
}
|
||||
if ((((arg0 != 0) && (arg1 != 0)) || ((arg2 != 0) && (arg3 != 0))) && ((MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r3, 0x1) == 0)) || (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1) == 0)))) {
|
||||
if ((((arg0 != 0) && (arg1 != 0)) || ((arg2 != 0) && (arg3 != 0))) && (((arg0 + 1) != 0) || ((arg1 + 1) != 0))) {
|
||||
phi_r8 = 6;
|
||||
}
|
||||
if (arg0 != 0) {
|
||||
@@ -29,11 +29,8 @@
|
||||
} else {
|
||||
phi_r7 = arg3;
|
||||
}
|
||||
if ((s32) (arg0 + 1) == phi_r7) {
|
||||
MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1);
|
||||
if (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1) == 0) {
|
||||
phi_r8 = 7;
|
||||
}
|
||||
if (((s32) (arg0 + 1) == phi_r7) && ((arg1 + 1) != 0)) {
|
||||
phi_r8 = 7;
|
||||
}
|
||||
}
|
||||
if (arg0 == 0) {
|
||||
@@ -42,7 +39,7 @@
|
||||
} else {
|
||||
phi_r5 = arg3;
|
||||
}
|
||||
if (((s32) (arg0 + 1) == phi_r5) || (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1), (MIPS2C_ERROR(unknown instruction: addic. $r0, $r4, 0x1) == 0))) {
|
||||
if (((s32) (arg0 + 1) == phi_r5) || ((arg1 + 1) != 0)) {
|
||||
goto block_53;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
static ? globals;
|
||||
|
||||
f32 test(s32 arg0, f32 arg8) {
|
||||
void test(s32 arg0) {
|
||||
s32 temp_ctr;
|
||||
s32 phi_ctr;
|
||||
|
||||
@@ -11,7 +11,7 @@ loop_1:
|
||||
if ((s32) globals.unk4 == 2) {
|
||||
globals.unk8 = 3;
|
||||
globals.unk10 = 5;
|
||||
return arg8;
|
||||
return;
|
||||
}
|
||||
if ((s32) globals.unk8 == 2) {
|
||||
globals.unkC = 3;
|
||||
@@ -20,7 +20,7 @@ loop_1:
|
||||
if ((s32) globals.unk10 == 2) {
|
||||
globals.unk14 = 3;
|
||||
globals.unk10 = 5;
|
||||
return arg8;
|
||||
return;
|
||||
}
|
||||
if ((s32) globals.unk14 == 2) {
|
||||
globals.unk18 = 3;
|
||||
@@ -33,10 +33,9 @@ block_10:
|
||||
if (temp_ctr == 0) {
|
||||
/* Duplicate return node #11. Try simplifying control flow for better match */
|
||||
globals.unk10 = 5;
|
||||
return arg8;
|
||||
return;
|
||||
}
|
||||
goto loop_1;
|
||||
}
|
||||
globals.unk10 = 5;
|
||||
return arg8;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
extern u32 global;
|
||||
extern s32 global;
|
||||
|
||||
s32 test(s32 arg0, s32 arg1, s32 arg2) {
|
||||
MIPS2C_ERROR(unknown instruction: addic $r0, $r5, -0x1);
|
||||
global = (u32) MIPS2C_ERROR(unknown instruction: cntlzw $r0, $r0) >> 5U;
|
||||
MIPS2C_ERROR(unknown instruction: eqv $r0, $r4, $r3);
|
||||
global = MIPS2C_ERROR(unknown instruction: subfe $r5, $r0, $r5);
|
||||
MIPS2C_ERROR(unknown instruction: subfc $r5, $r4, $r3);
|
||||
global = MIPS2C_ERROR(unknown instruction: addze $r0, $r0) & 1;
|
||||
MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r4);
|
||||
global = MIPS2C_ERROR(unknown instruction: adde $r5, $r5, $r6);
|
||||
MIPS2C_ERROR(unknown instruction: addic $r0, $r3, -0x1);
|
||||
global = (u32) MIPS2C_ERROR(unknown instruction: cntlzw $r0, $r0) >> 5U;
|
||||
global = MIPS2C_ERROR(unknown instruction: subfe $r0, $r0, $r3);
|
||||
return -arg1;
|
||||
void test(s32 arg0, s32 arg1, s32 arg2) {
|
||||
s32 temp_r3;
|
||||
s32 temp_r5;
|
||||
|
||||
temp_r5 = arg2 - arg0;
|
||||
global = arg1 == arg0;
|
||||
global = (temp_r5 - (temp_r5 - 1)) - !MIPS2C_CARRY;
|
||||
global = ((s32) (u32) ~(arg1 ^ arg0) / 2147483648) & 1;
|
||||
temp_r3 = -arg1;
|
||||
global = (arg1 >> 0x1F) + ((u32) arg0 >> 0x1FU) + MIPS2C_CARRY;
|
||||
global = -arg0 == 0;
|
||||
global = (temp_r3 - (temp_r3 - 1)) - !MIPS2C_CARRY;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
u16 foo(?); /* static */
|
||||
s16 foo(?); /* static */
|
||||
extern s32 glob;
|
||||
|
||||
u16 test(void) {
|
||||
u16 temp_r3;
|
||||
s16 test(void) {
|
||||
s16 temp_r3;
|
||||
|
||||
temp_r3 = foo(1);
|
||||
if (temp_r3 != 0U) {
|
||||
if (temp_r3 != 0) {
|
||||
return temp_r3;
|
||||
}
|
||||
if ((s32) glob != 0x7B) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
f32 test(s32 *arg0, f32 arg8) {
|
||||
*arg0 = MIPS2C_ERROR(unknown instruction: addze $r0, $r0);
|
||||
return arg8;
|
||||
void test(s32 *arg0) {
|
||||
*arg0 = (s32) *arg0 / 2;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extern f32 x;
|
||||
|
||||
s32 test(s32 arg0) {
|
||||
void test(void) {
|
||||
s32 temp_cr0_lt;
|
||||
s32 temp_cr0_lt_2;
|
||||
|
||||
@@ -13,7 +13,5 @@ s32 test(s32 arg0) {
|
||||
x = 3.0f;
|
||||
if (temp_cr0_lt_2 == 0) {
|
||||
x = 7.0f;
|
||||
return arg0;
|
||||
}
|
||||
return arg0;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ void test_s8(s8 c) {
|
||||
s8 temp_r4_3;
|
||||
|
||||
sp8 = c;
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
func_00400090((u32) ((s32) (s8) (u8) sp8 / 2));
|
||||
func_00400090(MULT_HI(0x55555556, (s8) (u8) sp8) + ((s8) (u8) sp8 / 6442450941));
|
||||
func_00400090((s8) (u8) sp8 / 5);
|
||||
temp_r0 = (s8) (u8) sp8;
|
||||
@@ -31,8 +31,7 @@ void test_s8(s8 c) {
|
||||
temp_r0_3 = (s8) (u8) sp8;
|
||||
temp_r0_4 = (s32) (MULT_HI(0x80808081, temp_r0_3) + temp_r0_3) >> 7;
|
||||
func_00400090(temp_r0_4 + ((u32) temp_r0_4 >> 0x1FU));
|
||||
MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r0));
|
||||
func_00400090((s8) (u8) sp8 % 2);
|
||||
temp_r4 = (s8) (u8) sp8;
|
||||
func_00400090(temp_r4 - ((MULT_HI(0x55555556, temp_r4) + ((s8) (u8) sp8 / 6442450941)) * 3));
|
||||
func_00400090((s8) (u8) sp8 % 5);
|
||||
@@ -58,7 +57,7 @@ void test_s16(s16 h) {
|
||||
s32 temp_r3;
|
||||
|
||||
sp8 = h;
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
func_00400090((u32) ((s32) sp8 / 2));
|
||||
func_00400090(MULT_HI(0x55555556, sp8) + (sp8 / 6442450941));
|
||||
func_00400090(sp8 / 5);
|
||||
temp_r0 = (s32) (MULT_HI(0x92492493, sp8) + sp8) >> 2;
|
||||
@@ -71,8 +70,7 @@ void test_s16(s16 h) {
|
||||
func_00400090(temp_r0_3 + ((u32) temp_r0_3 >> 0x1FU));
|
||||
temp_r0_4 = (s32) (MULT_HI(0x80010003, sp8) + sp8) >> 0xF;
|
||||
func_00400090(temp_r0_4 + ((u32) temp_r0_4 >> 0x1FU));
|
||||
MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r0));
|
||||
func_00400090(sp8 % 2);
|
||||
func_00400090(sp8 - ((MULT_HI(0x55555556, sp8) + (sp8 / 6442450941)) * 3));
|
||||
func_00400090(sp8 % 5);
|
||||
temp_r0_5 = (s32) (MULT_HI(0x92492493, sp8) + sp8) >> 2;
|
||||
@@ -93,12 +91,12 @@ void test_s32_div(s32 d) {
|
||||
s32 temp_r0_10;
|
||||
s32 temp_r0_11;
|
||||
s32 temp_r0_12;
|
||||
s32 temp_r0_13;
|
||||
s32 temp_r0_14;
|
||||
s32 temp_r0_15;
|
||||
s32 temp_r0_16;
|
||||
s32 temp_r0_17;
|
||||
s32 temp_r0_18;
|
||||
s32 temp_r0_19;
|
||||
s32 temp_r0_20;
|
||||
s32 temp_r0_21;
|
||||
s32 temp_r0_22;
|
||||
s32 temp_r0_2;
|
||||
s32 temp_r0_3;
|
||||
s32 temp_r0_4;
|
||||
@@ -107,17 +105,21 @@ void test_s32_div(s32 d) {
|
||||
s32 temp_r0_7;
|
||||
s32 temp_r0_8;
|
||||
s32 temp_r0_9;
|
||||
u32 temp_r0_13;
|
||||
u32 temp_r0_14;
|
||||
u32 temp_r0_16;
|
||||
u32 temp_r0_17;
|
||||
|
||||
sp8 = d;
|
||||
func_00400090((u32) d);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
func_00400090((u32) (sp8 / 2));
|
||||
func_00400090(MULT_HI(0x55555556, sp8) + (sp8 / 6442450941));
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
func_00400090((u32) (sp8 / 4));
|
||||
func_00400090(sp8 / 5);
|
||||
func_00400090(MULT_HI(0x2AAAAAAB, sp8) + (sp8 / 12884901882));
|
||||
temp_r0 = (s32) (MULT_HI(0x92492493, sp8) + sp8) >> 2;
|
||||
func_00400090(temp_r0 + ((u32) temp_r0 >> 0x1FU));
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
func_00400090((u32) (sp8 / 8));
|
||||
func_00400090(sp8 / 9);
|
||||
func_00400090(sp8 / 10);
|
||||
func_00400090(sp8 / 11);
|
||||
@@ -127,7 +129,7 @@ void test_s32_div(s32 d) {
|
||||
func_00400090(temp_r0_2 + ((u32) temp_r0_2 >> 0x1FU));
|
||||
temp_r0_3 = (s32) (MULT_HI(0x88888889, sp8) + sp8) >> 3;
|
||||
func_00400090(temp_r0_3 + ((u32) temp_r0_3 >> 0x1FU));
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
func_00400090((u32) (sp8 / 16));
|
||||
func_00400090(sp8 / 17);
|
||||
func_00400090(sp8 / 18);
|
||||
func_00400090(sp8 / 19);
|
||||
@@ -148,7 +150,7 @@ void test_s32_div(s32 d) {
|
||||
func_00400090(temp_r0_7 + ((u32) temp_r0_7 >> 0x1FU));
|
||||
temp_r0_8 = (s32) (MULT_HI(0x84210843, sp8) + sp8) >> 4;
|
||||
func_00400090(temp_r0_8 + ((u32) temp_r0_8 >> 0x1FU));
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
func_00400090((u32) (sp8 / 32));
|
||||
func_00400090(sp8 / 33);
|
||||
func_00400090(sp8 / 100);
|
||||
temp_r0_9 = (s32) (MULT_HI(0x80808081, sp8) + sp8) >> 7;
|
||||
@@ -165,26 +167,30 @@ void test_s32_div(s32 d) {
|
||||
func_00400090(temp_r0_11 + ((u32) temp_r0_11 >> 0x1FU));
|
||||
temp_r0_12 = (s32) (MULT_HI(0x80000003, sp8) + sp8) >> 0x1D;
|
||||
func_00400090(temp_r0_12 + ((u32) temp_r0_12 >> 0x1FU));
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
func_00400090(sp8 / 1073741824);
|
||||
func_00400090(sp8 / 2147483644);
|
||||
temp_r0_13 = (s32) (MULT_HI(0x80000003, sp8) + sp8) >> 0x1E;
|
||||
func_00400090(temp_r0_13 + ((u32) temp_r0_13 >> 0x1FU));
|
||||
func_00400090(sp8 / 2147483646);
|
||||
func_00400090(MULTU_HI(2, sp8));
|
||||
func_00400090(sp8 / 715827883);
|
||||
temp_r0_14 = (s32) (MULT_HI(0x7FFFFFFD, sp8) - sp8) >> 0x1E;
|
||||
func_00400090(temp_r0_14 + ((u32) temp_r0_14 >> 0x1FU));
|
||||
temp_r0_15 = (s32) MULT_HI(0x99999999, sp8) >> 2;
|
||||
func_00400090((u32) (sp8 / 1073741824));
|
||||
temp_r0_13 = sp8 / 1073741824;
|
||||
func_00400090(temp_r0_13 + (temp_r0_13 >> 0x1FU));
|
||||
temp_r0_14 = sp8 / 2147483644;
|
||||
func_00400090(temp_r0_14 + (temp_r0_14 >> 0x1FU));
|
||||
temp_r0_15 = (s32) (MULT_HI(0x80000003, sp8) + sp8) >> 0x1E;
|
||||
func_00400090(temp_r0_15 + ((u32) temp_r0_15 >> 0x1FU));
|
||||
temp_r0_16 = (s32) (MULT_HI(0x6DB6DB6D, sp8) - sp8) >> 2;
|
||||
func_00400090(temp_r0_16 + ((u32) temp_r0_16 >> 0x1FU));
|
||||
temp_r0_17 = (s32) MULT_HI(0x99999999, sp8) >> 1;
|
||||
func_00400090(temp_r0_17 + ((u32) temp_r0_17 >> 0x1FU));
|
||||
func_00400090((u32) -(s32) MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
temp_r0_18 = (s32) (MULT_HI(0x55555555, sp8) - sp8) >> 1;
|
||||
temp_r0_16 = sp8 / 2147483646;
|
||||
func_00400090(temp_r0_16 + (temp_r0_16 >> 0x1FU));
|
||||
func_00400090(MULTU_HI(2, sp8));
|
||||
temp_r0_17 = sp8 / 715827883;
|
||||
func_00400090(temp_r0_17 + (temp_r0_17 >> 0x1FU));
|
||||
temp_r0_18 = (s32) (MULT_HI(0x7FFFFFFD, sp8) - sp8) >> 0x1E;
|
||||
func_00400090(temp_r0_18 + ((u32) temp_r0_18 >> 0x1FU));
|
||||
func_00400090((u32) -(s32) MIPS2C_ERROR(unknown instruction: addze $r3, $r3));
|
||||
temp_r0_19 = (s32) MULT_HI(0x99999999, sp8) >> 2;
|
||||
func_00400090(temp_r0_19 + ((u32) temp_r0_19 >> 0x1FU));
|
||||
temp_r0_20 = (s32) (MULT_HI(0x6DB6DB6D, sp8) - sp8) >> 2;
|
||||
func_00400090(temp_r0_20 + ((u32) temp_r0_20 >> 0x1FU));
|
||||
temp_r0_21 = (s32) MULT_HI(0x99999999, sp8) >> 1;
|
||||
func_00400090(temp_r0_21 + ((u32) temp_r0_21 >> 0x1FU));
|
||||
func_00400090((u32) -(sp8 / 4));
|
||||
temp_r0_22 = (s32) (MULT_HI(0x55555555, sp8) - sp8) >> 1;
|
||||
func_00400090(temp_r0_22 + ((u32) temp_r0_22 >> 0x1FU));
|
||||
func_00400090((u32) -(sp8 / 2));
|
||||
func_00400090((u32) (d / -1));
|
||||
}
|
||||
|
||||
@@ -193,11 +199,11 @@ void test_s32_mod(s32 d) {
|
||||
s32 temp_r0;
|
||||
s32 temp_r0_10;
|
||||
s32 temp_r0_11;
|
||||
s32 temp_r0_12;
|
||||
s32 temp_r0_13;
|
||||
s32 temp_r0_14;
|
||||
s32 temp_r0_15;
|
||||
s32 temp_r0_16;
|
||||
s32 temp_r0_17;
|
||||
s32 temp_r0_18;
|
||||
s32 temp_r0_19;
|
||||
s32 temp_r0_20;
|
||||
s32 temp_r0_2;
|
||||
s32 temp_r0_3;
|
||||
s32 temp_r0_4;
|
||||
@@ -209,21 +215,22 @@ void test_s32_mod(s32 d) {
|
||||
s32 temp_r3;
|
||||
s32 temp_r3_2;
|
||||
s32 temp_r3_3;
|
||||
u32 temp_r0_17;
|
||||
u32 temp_r0_12;
|
||||
u32 temp_r0_13;
|
||||
u32 temp_r0_14;
|
||||
u32 temp_r0_15;
|
||||
u32 temp_r0_21;
|
||||
|
||||
sp8 = d;
|
||||
func_00400090(0U);
|
||||
MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r0));
|
||||
func_00400090(sp8 % 2);
|
||||
func_00400090(sp8 - ((MULT_HI(0x55555556, sp8) + (sp8 / 6442450941)) * 3));
|
||||
MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r0));
|
||||
func_00400090(sp8 % 4);
|
||||
func_00400090(sp8 % 5);
|
||||
func_00400090(sp8 - ((MULT_HI(0x2AAAAAAB, sp8) + (sp8 / 12884901882)) * 6));
|
||||
temp_r0 = (s32) (MULT_HI(0x92492493, sp8) + sp8) >> 2;
|
||||
func_00400090(sp8 - ((temp_r0 + ((u32) temp_r0 >> 0x1FU)) * 7));
|
||||
MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r0));
|
||||
func_00400090(sp8 % 8);
|
||||
func_00400090(sp8 % 9);
|
||||
func_00400090(sp8 % 10);
|
||||
func_00400090(sp8 % 11);
|
||||
@@ -233,8 +240,7 @@ void test_s32_mod(s32 d) {
|
||||
func_00400090(sp8 - ((temp_r0_2 + ((u32) temp_r0_2 >> 0x1FU)) * 0xE));
|
||||
temp_r0_3 = (s32) (MULT_HI(0x88888889, sp8) + sp8) >> 3;
|
||||
func_00400090(sp8 - ((temp_r0_3 + ((u32) temp_r0_3 >> 0x1FU)) * 0xF));
|
||||
MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r0));
|
||||
func_00400090(sp8 % 16);
|
||||
func_00400090(sp8 % 17);
|
||||
func_00400090(sp8 % 18);
|
||||
func_00400090(sp8 % 19);
|
||||
@@ -255,8 +261,7 @@ void test_s32_mod(s32 d) {
|
||||
func_00400090(sp8 - ((temp_r0_7 + ((u32) temp_r0_7 >> 0x1FU)) * 0x1E));
|
||||
temp_r0_8 = (s32) (MULT_HI(0x84210843, sp8) + sp8) >> 4;
|
||||
func_00400090(sp8 - ((temp_r0_8 + ((u32) temp_r0_8 >> 0x1FU)) * 0x1F));
|
||||
MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r0));
|
||||
func_00400090(sp8 - ((sp8 / 32) << 5));
|
||||
func_00400090(sp8 % 33);
|
||||
func_00400090(sp8 % 100);
|
||||
temp_r0_9 = (s32) (MULT_HI(0x80808081, sp8) + sp8) >> 7;
|
||||
@@ -273,29 +278,32 @@ void test_s32_mod(s32 d) {
|
||||
func_00400090(sp8 - ((temp_r0_11 + ((u32) temp_r0_11 >> 0x1FU)) * 0x3FFFFFFE));
|
||||
temp_r3 = (s32) (MULT_HI(0x80000003, sp8) + sp8) >> 0x1D;
|
||||
func_00400090(sp8 - ((temp_r3 + ((u32) temp_r3 >> 0x1FU)) * 0x3FFFFFFF));
|
||||
MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
func_00400090(MIPS2C_ERROR(unknown instruction: subfc $r3, $r3, $r0));
|
||||
func_00400090(sp8 - ((sp8 / 1073741824) * 0x40000001));
|
||||
func_00400090(sp8 - ((sp8 / 2147483644) * 0x7FFFFFFD));
|
||||
func_00400090(sp8 - ((sp8 / 1073741824) << 0x1E));
|
||||
temp_r0_12 = sp8 / 1073741824;
|
||||
func_00400090(sp8 - ((temp_r0_12 + (temp_r0_12 >> 0x1FU)) * 0x40000001));
|
||||
temp_r0_13 = sp8 / 2147483644;
|
||||
func_00400090(sp8 - ((temp_r0_13 + (temp_r0_13 >> 0x1FU)) * 0x7FFFFFFD));
|
||||
temp_r3_2 = (s32) (MULT_HI(0x80000003, sp8) + sp8) >> 0x1E;
|
||||
func_00400090(sp8 - ((temp_r3_2 + ((u32) temp_r3_2 >> 0x1FU)) * 0x7FFFFFFE));
|
||||
func_00400090(sp8 - ((sp8 / 2147483646) * 0x7FFFFFFF));
|
||||
temp_r0_14 = sp8 / 2147483646;
|
||||
func_00400090(sp8 - ((temp_r0_14 + (temp_r0_14 >> 0x1FU)) * 0x7FFFFFFF));
|
||||
func_00400090(d - (MULTU_HI(2, d) * 0x80000000));
|
||||
func_00400090(sp8 - ((sp8 / 715827883) * 0x80000001));
|
||||
temp_r0_15 = sp8 / 715827883;
|
||||
func_00400090(sp8 - ((temp_r0_15 + (temp_r0_15 >> 0x1FU)) * 0x80000001));
|
||||
temp_r3_3 = (s32) (MULT_HI(0x7FFFFFFD, sp8) - sp8) >> 0x1E;
|
||||
func_00400090(sp8 - ((temp_r3_3 + ((u32) temp_r3_3 >> 0x1FU)) * 0x80000002));
|
||||
temp_r0_12 = (s32) MULT_HI(0x99999999, sp8) >> 2;
|
||||
func_00400090(sp8 - ((temp_r0_12 + ((u32) temp_r0_12 >> 0x1FU)) * -0xA));
|
||||
temp_r0_13 = (s32) (MULT_HI(0x6DB6DB6D, sp8) - sp8) >> 2;
|
||||
func_00400090(sp8 - ((temp_r0_13 + ((u32) temp_r0_13 >> 0x1FU)) * -7));
|
||||
temp_r0_14 = (s32) MULT_HI(0x99999999, sp8) >> 1;
|
||||
func_00400090(sp8 - ((temp_r0_14 + ((u32) temp_r0_14 >> 0x1FU)) * -5));
|
||||
temp_r0_15 = (s32) (MULT_HI(0x7FFFFFFF, sp8) - sp8) >> 1;
|
||||
func_00400090(sp8 - ((temp_r0_15 + ((u32) temp_r0_15 >> 0x1FU)) * -4));
|
||||
temp_r0_16 = (s32) (MULT_HI(0x55555555, sp8) - sp8) >> 1;
|
||||
func_00400090(sp8 - ((temp_r0_16 + ((u32) temp_r0_16 >> 0x1FU)) * -3));
|
||||
temp_r0_17 = MULT_HI(0x7FFFFFFF, sp8) - sp8;
|
||||
func_00400090(sp8 - ((temp_r0_17 + (temp_r0_17 >> 0x1FU)) * -2));
|
||||
temp_r0_16 = (s32) MULT_HI(0x99999999, sp8) >> 2;
|
||||
func_00400090(sp8 - ((temp_r0_16 + ((u32) temp_r0_16 >> 0x1FU)) * -0xA));
|
||||
temp_r0_17 = (s32) (MULT_HI(0x6DB6DB6D, sp8) - sp8) >> 2;
|
||||
func_00400090(sp8 - ((temp_r0_17 + ((u32) temp_r0_17 >> 0x1FU)) * -7));
|
||||
temp_r0_18 = (s32) MULT_HI(0x99999999, sp8) >> 1;
|
||||
func_00400090(sp8 - ((temp_r0_18 + ((u32) temp_r0_18 >> 0x1FU)) * -5));
|
||||
temp_r0_19 = (s32) (MULT_HI(0x7FFFFFFF, sp8) - sp8) >> 1;
|
||||
func_00400090(sp8 - ((temp_r0_19 + ((u32) temp_r0_19 >> 0x1FU)) * -4));
|
||||
temp_r0_20 = (s32) (MULT_HI(0x55555555, sp8) - sp8) >> 1;
|
||||
func_00400090(sp8 - ((temp_r0_20 + ((u32) temp_r0_20 >> 0x1FU)) * -3));
|
||||
temp_r0_21 = MULT_HI(0x7FFFFFFF, sp8) - sp8;
|
||||
func_00400090(sp8 - ((temp_r0_21 + (temp_r0_21 >> 0x1FU)) * -2));
|
||||
func_00400090(sp8 % -1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extern s32 glob;
|
||||
|
||||
s32 test(s32 arg0) {
|
||||
void test(void) {
|
||||
if ((glob & 1) != 0) {
|
||||
glob = 0;
|
||||
}
|
||||
@@ -18,7 +18,5 @@ s32 test(s32 arg0) {
|
||||
}
|
||||
if ((glob & 0x80000000) != 0) {
|
||||
glob = 0;
|
||||
return arg0;
|
||||
}
|
||||
return arg0;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,11 @@ extern s32 e;
|
||||
extern s32 f;
|
||||
static ? ar;
|
||||
|
||||
f32 test(f32 arg8) {
|
||||
void test(void) {
|
||||
ar.unk0 = (s32) (s8) a;
|
||||
ar.unk4 = (s32) b;
|
||||
ar.unk8 = (s32) c;
|
||||
ar.unkC = (s32) d;
|
||||
ar.unk10 = (s32) e;
|
||||
ar.unk14 = (s32) f;
|
||||
return arg8;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
f32 test(s32 arg0, s32 arg1, f32 arg8) {
|
||||
void test(s32 arg0, s32 arg1) {
|
||||
s32 temp_ctr_2;
|
||||
s32 temp_r5;
|
||||
s32 temp_r6;
|
||||
@@ -44,10 +44,6 @@ f32 test(s32 arg0, s32 arg1, f32 arg8) {
|
||||
phi_r3 += 1;
|
||||
phi_ctr_2 = temp_ctr_2;
|
||||
} while (temp_ctr_2 != 0);
|
||||
return arg8;
|
||||
}
|
||||
/* Duplicate return node #7. Try simplifying control flow for better match */
|
||||
return arg8;
|
||||
}
|
||||
return arg8;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
f32 test(s32 *arg0, f32 arg8) {
|
||||
MIPS2C_ERROR(unknown instruction: addze $r0, $r0);
|
||||
*arg0 = MIPS2C_ERROR(unknown instruction: subfc $r0, $r0, $r4);
|
||||
return arg8;
|
||||
void test(s32 *arg0) {
|
||||
*arg0 %= 2;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extern s32 y;
|
||||
|
||||
f32 test(s32 arg0, f32 arg8) {
|
||||
void test(s32 arg0) {
|
||||
y = arg0;
|
||||
y = arg0 * 2;
|
||||
y = arg0 * 3;
|
||||
@@ -23,5 +23,4 @@ f32 test(s32 arg0, f32 arg8) {
|
||||
y = arg0 * 0x14;
|
||||
y = arg0 * 0x15;
|
||||
y = arg0 * 0x16;
|
||||
return arg8;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
extern f32 x;
|
||||
extern f64 y;
|
||||
|
||||
s32 test(s32 arg0) {
|
||||
void test(void) {
|
||||
y *= 2.0;
|
||||
x *= 2.0f;
|
||||
return arg0;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@ extern s32 glob;
|
||||
s32 test(s32 arg0) {
|
||||
s32 phi_r3;
|
||||
s32 phi_r3_2;
|
||||
s32 phi_r3_3;
|
||||
s32 phi_r3_4;
|
||||
s32 phi_r3_5;
|
||||
|
||||
phi_r3_2 = arg0;
|
||||
phi_r3 = arg0;
|
||||
phi_r3_3 = arg0;
|
||||
phi_r3_4 = arg0;
|
||||
phi_r3_5 = arg0;
|
||||
if (arg0 != 0x32) {
|
||||
if (arg0 < 0x32) {
|
||||
switch (arg0) { // switch 1; irregular
|
||||
@@ -19,7 +24,7 @@ s32 test(s32 arg0) {
|
||||
glob = arg0 - 1;
|
||||
return 2;
|
||||
default: // switch 1
|
||||
phi_r3 = arg0 * 2;
|
||||
phi_r3_3 = arg0 * 2;
|
||||
goto block_28;
|
||||
}
|
||||
} else {
|
||||
@@ -33,8 +38,11 @@ s32 test(s32 arg0) {
|
||||
return 2;
|
||||
case 0x66:
|
||||
block_28:
|
||||
phi_r3 = phi_r3_3;
|
||||
phi_r3_5 = phi_r3_3;
|
||||
if ((s32) glob == 0) {
|
||||
phi_r3 = MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
phi_r3_4 = phi_r3_5 - 1;
|
||||
phi_r3 = phi_r3_4 / 2;
|
||||
}
|
||||
glob = phi_r3;
|
||||
return 2;
|
||||
|
||||
1
backend/mips_to_c/tests/end_to_end/return-detection/ppc-void-flags.txt
generated
Normal file
1
backend/mips_to_c/tests/end_to_end/return-detection/ppc-void-flags.txt
generated
Normal file
@@ -0,0 +1 @@
|
||||
--target ppc-mwcc-c
|
||||
7
backend/mips_to_c/tests/end_to_end/return-detection/ppc-void-out.c
generated
Normal file
7
backend/mips_to_c/tests/end_to_end/return-detection/ppc-void-out.c
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
? foo(); /* extern */
|
||||
|
||||
void test(s32 arg0) {
|
||||
if (arg0 == 0) {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
14
backend/mips_to_c/tests/end_to_end/return-detection/ppc-void.s
generated
Normal file
14
backend/mips_to_c/tests/end_to_end/return-detection/ppc-void.s
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
# Manual regression test for PPC, where the same registers are used for
|
||||
# function arguments & return values. This test calls a function `foo`
|
||||
# in a branch, and we expect that the deduced signature of `test` is
|
||||
# still `void foo(s32)` even though it would be possible for for foo
|
||||
# to return a float value from $f1 (i.e. `f32 foo(s32, f32)`)
|
||||
|
||||
glabel test
|
||||
mflr r0
|
||||
cmpwi r3, 0
|
||||
bne .end
|
||||
bl foo
|
||||
.end:
|
||||
mtlr r0
|
||||
blr
|
||||
@@ -1,6 +1,6 @@
|
||||
s32 test(s32 arg0, f32 arg8) {
|
||||
f32 test(f32 arg8) {
|
||||
if (arg8 != 0.0f) {
|
||||
return arg0;
|
||||
return 15.0f;
|
||||
}
|
||||
return arg0;
|
||||
return arg8;
|
||||
}
|
||||
|
||||
1
backend/mips_to_c/tests/end_to_end/rlwimi/mwcc-o4p-flags.txt
generated
Normal file
1
backend/mips_to_c/tests/end_to_end/rlwimi/mwcc-o4p-flags.txt
generated
Normal file
@@ -0,0 +1 @@
|
||||
--target ppc-mwcc-c
|
||||
11
backend/mips_to_c/tests/end_to_end/rlwimi/mwcc-o4p-out.c
generated
Normal file
11
backend/mips_to_c/tests/end_to_end/rlwimi/mwcc-o4p-out.c
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
extern u8 x;
|
||||
extern u8 y;
|
||||
|
||||
void test(void *arg0) {
|
||||
x = (x & ~0x80) | ((arg0->unk0 << 7) & 0x80);
|
||||
x = (x & ~0x40) | ((arg0->unk1 << 6) & 0x40);
|
||||
x = (x & ~0x3E) | ((arg0->unk2 << 1) & 0x3E);
|
||||
y |= 0x80;
|
||||
x = (s16) (((u16) x & ~0x1F0) | (((s8) arg0->unk3 << 4) & 0x1F0));
|
||||
y &= ~0x40;
|
||||
}
|
||||
43
backend/mips_to_c/tests/end_to_end/rlwimi/mwcc-o4p.s
generated
Normal file
43
backend/mips_to_c/tests/end_to_end/rlwimi/mwcc-o4p.s
generated
Normal file
@@ -0,0 +1,43 @@
|
||||
.include "macros.inc"
|
||||
|
||||
.section .text # 0x0 - 0x68
|
||||
|
||||
.global test
|
||||
test:
|
||||
/* 00000000 00000000 88 C3 00 00 */ lbz r6, 0(r3)
|
||||
/* 00000004 00000004 38 A0 00 01 */ li r5, 1
|
||||
/* 00000008 00000008 88 00 00 00 */ lbz r0, x@sda21(r2)
|
||||
/* 0000000C 0000000C 38 80 00 00 */ li r4, 0
|
||||
/* 00000010 00000010 50 C0 3E 30 */ rlwimi r0, r6, 7, 0x18, 0x18
|
||||
/* 00000014 00000014 98 00 00 00 */ stb r0, x@sda21(r2)
|
||||
/* 00000018 00000018 88 00 00 00 */ lbz r0, y@sda21(r2)
|
||||
/* 0000001C 0000001C 50 A0 3E 30 */ rlwimi r0, r5, 7, 0x18, 0x18
|
||||
/* 00000020 00000020 88 C3 00 01 */ lbz r6, 1(r3)
|
||||
/* 00000024 00000024 88 A0 00 00 */ lbz r5, x@sda21(r2)
|
||||
/* 00000028 00000028 50 C5 36 72 */ rlwimi r5, r6, 6, 0x19, 0x19
|
||||
/* 0000002C 0000002C 98 A0 00 00 */ stb r5, x@sda21(r2)
|
||||
/* 00000030 00000030 88 C3 00 02 */ lbz r6, 2(r3)
|
||||
/* 00000034 00000034 88 A0 00 00 */ lbz r5, x@sda21(r2)
|
||||
/* 00000038 00000038 50 C5 0E BC */ rlwimi r5, r6, 1, 0x1a, 0x1e
|
||||
/* 0000003C 0000003C 98 A0 00 00 */ stb r5, x@sda21(r2)
|
||||
/* 00000040 00000040 88 C3 00 03 */ lbz r6, 3(r3)
|
||||
/* 00000044 00000044 A0 A0 00 00 */ lhz r5, x@sda21(r2)
|
||||
/* 00000048 00000048 98 00 00 00 */ stb r0, y@sda21(r2)
|
||||
/* 0000004C 0000004C 7C C0 07 74 */ extsb r0, r6
|
||||
/* 00000050 00000050 50 05 25 F6 */ rlwimi r5, r0, 4, 0x17, 0x1b
|
||||
/* 00000054 00000054 88 00 00 00 */ lbz r0, y@sda21(r2)
|
||||
/* 00000058 00000058 50 80 36 72 */ rlwimi r0, r4, 6, 0x19, 0x19
|
||||
/* 0000005C 0000005C B0 A0 00 00 */ sth r5, x@sda21(r2)
|
||||
/* 00000060 00000060 98 00 00 00 */ stb r0, y@sda21(r2)
|
||||
/* 00000064 00000064 4E 80 00 20 */ blr
|
||||
|
||||
.section .sbss # 0x0 - 0x8
|
||||
|
||||
.global y
|
||||
y:
|
||||
.word 0x00000000
|
||||
|
||||
.global x
|
||||
x:
|
||||
.word 0x00000000
|
||||
|
||||
21
backend/mips_to_c/tests/end_to_end/rlwimi/orig.c
generated
Normal file
21
backend/mips_to_c/tests/end_to_end/rlwimi/orig.c
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
struct foo {
|
||||
int a: 1;
|
||||
unsigned int b: 1;
|
||||
int c: 5;
|
||||
unsigned int d: 5;
|
||||
};
|
||||
|
||||
struct foo x;
|
||||
struct foo y;
|
||||
|
||||
int test(char * ptr) {
|
||||
// Bitfield assignment
|
||||
x.a = ptr[0];
|
||||
x.b = ptr[1];
|
||||
x.c = ptr[2];
|
||||
x.d = ptr[3];
|
||||
|
||||
// Flag setting/clearing
|
||||
y.a = 1;
|
||||
y.b = 0;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
extern s32 glob;
|
||||
|
||||
f32 test(s8 arg0, f32 arg8) {
|
||||
void test(s8 arg0) {
|
||||
s8 temp_r5;
|
||||
s8 temp_r6;
|
||||
|
||||
@@ -12,5 +12,4 @@ f32 test(s8 arg0, f32 arg8) {
|
||||
glob = (s32) (s16) arg0;
|
||||
glob = (s32) (s16) temp_r5;
|
||||
glob = (s32) (s16) temp_r6;
|
||||
return arg8;
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ s32 test(s32 arg0, s32 arg1) {
|
||||
sp8 = arg0;
|
||||
temp_r0 = &spC - temp_r5;
|
||||
spC = arg1;
|
||||
return MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
return temp_r0 / 4;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
f32 test(void *arg0, void *arg1, f32 arg8) {
|
||||
void test(void *arg0, void *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;
|
||||
return arg8;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ s32 test(s32 arg0) {
|
||||
return 2;
|
||||
}
|
||||
block_14:
|
||||
glob = MIPS2C_ERROR(unknown instruction: addze $r3, $r3);
|
||||
glob = arg0 / 2;
|
||||
return 2;
|
||||
}
|
||||
glob = arg0 + 1;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
extern s32 x;
|
||||
|
||||
f32 test(f32 arg8) {
|
||||
void test(void) {
|
||||
loop_1:
|
||||
if ((s32) x != 2) {
|
||||
x = 1;
|
||||
goto loop_1;
|
||||
}
|
||||
return arg8;
|
||||
}
|
||||
|
||||
12
backend/poetry.lock
generated
12
backend/poetry.lock
generated
@@ -74,7 +74,7 @@ unicode_backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.0.3"
|
||||
version = "8.0.4"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -363,7 +363,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.0"
|
||||
version = "2.5.1"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -707,8 +707,8 @@ charset-normalizer = [
|
||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
|
||||
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
|
||||
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
|
||||
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
@@ -876,8 +876,8 @@ pathspec = [
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"},
|
||||
{file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"},
|
||||
{file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
|
||||
{file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
|
||||
]
|
||||
psycopg2-binary = [
|
||||
{file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"},
|
||||
|
||||
Reference in New Issue
Block a user