* 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:
Ethan Roseman
2022-02-20 09:21:38 -05:00
committed by GitHub
parent 36d1f1d706
commit eb7534be0d
85 changed files with 2141 additions and 1186 deletions

View File

@@ -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"

View File

@@ -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",
}

View File

@@ -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

View File

@@ -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(

View File

@@ -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)

View File

@@ -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"

View File

@@ -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 = ""

View File

@@ -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())}"
)

View File

@@ -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")

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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",
),
),
],
),
]

View File

@@ -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",
),
]

View File

@@ -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,
),
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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",
),
]

View File

@@ -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),
)
),
]

View File

@@ -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
),
),
]

View File

@@ -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",
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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,

View File

@@ -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"
),
),
]

View File

@@ -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):

View File

@@ -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)

View File

@@ -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):

View File

@@ -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):

View File

@@ -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()

View File

@@ -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)

View File

@@ -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(

View File

@@ -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",
),
]

View File

@@ -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()

View File

@@ -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(),
}
)

View File

@@ -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"],
)
)

View File

@@ -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)

View File

@@ -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))
)

View File

@@ -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()

View File

@@ -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"))

View File

@@ -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),
]

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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(),

View File

@@ -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()),

View File

@@ -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

View File

@@ -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)

View File

@@ -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}")

View File

@@ -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

View File

@@ -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

View File

@@ -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."

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -0,0 +1 @@
--target ppc-mwcc-c

View File

@@ -0,0 +1,7 @@
? foo(); /* extern */
void test(s32 arg0) {
if (arg0 == 0) {
foo();
}
}

View 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

View File

@@ -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;
}

View File

@@ -0,0 +1 @@
--target ppc-mwcc-c

View 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;
}

View 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

View 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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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
View File

@@ -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"},