Revert the reversion of the updates (#459)

* Revert "Revert "Dep updates (#457)" (#458)"

This reverts commit eb43c51586.

* ci changes

* Update almost everything, fix issues

* add jest-environment-jsdom

* fix to mwcc debug info option

* remove frontend tests

* black

* remove eslint-plugin-testing-library

* Do db migration

Co-authored-by: Alex Bates <alex@nanaian.town>
This commit is contained in:
Ethan Roseman
2022-06-01 23:06:51 -04:00
committed by GitHub
parent 6e9cb4f6cf
commit db03a48ef9
830 changed files with 6273 additions and 6803 deletions

14
.gitattributes vendored
View File

@@ -1,10 +1,10 @@
# asm_differ
backend/asm_differ/diff.py linguist-generated=true
# mips_to_c
backend/mips_to_c/src/** linguist-generated=true
backend/mips_to_c/stubs/** linguist-generated=true
backend/mips_to_c/tests/** linguist-generated=true
backend/mips_to_c/mips_to_c.py linguist-generated=true
backend/mips_to_c/run_tests.py linguist-generated=true
backend/mips_to_c/website.py linguist-generated=true
# m2c
backend/m2c/src/** linguist-generated=true
backend/m2c/stubs/** linguist-generated=true
backend/m2c/tests/** linguist-generated=true
backend/m2c/m2c.py linguist-generated=true
backend/m2c/run_tests.py linguist-generated=true
backend/m2c/website.py linguist-generated=true

View File

@@ -5,8 +5,8 @@ on:
- main
pull_request:
jobs:
backend_test:
name: backend tests
full_test_and_build:
name: full test and build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -58,7 +58,7 @@ jobs:
devkitpro/devkitarm:20210726 \
-c "cp /opt/devkitpro/devkitARM/bin/arm* /tmp/bin"
sudo mv bin/arm* /usr/bin/
- name: Run tests
- name: Run backend tests
run: |-
mkdir -p "${WINEPREFIX}"
wineboot --init
@@ -68,6 +68,18 @@ jobs:
SYSTEM_ENV: GITHUB_WORKFLOW
WINEPREFIX: /tmp/wine
- name: Migrate backend
run: cd backend && poetry run python3 ./manage.py migrate
- name: Start backend
run: cd backend && poetry run python3 ./manage.py runserver > /dev/null 2>&1 &
- name: Install frontend dependencies
run: cd frontend && yarn
- name: Build frontend
run: cd frontend && yarn build
backend_test_windows:
name: backend tests (windows)
runs-on: windows-latest
@@ -117,21 +129,6 @@ jobs:
poetry install && \
poetry run python manage.py test'
frontend_test:
name: frontend tests
runs-on: ${{ matrix.config.os }}
strategy:
matrix:
config:
- os: ubuntu-latest
#- os: windows-latest
steps:
- uses: actions/checkout@v2
- run: cd frontend && yarn
name: Install dependencies
- run: cd frontend && yarn test:ci
name: Run tests
reviewdog:
name: reviewdog
runs-on: ubuntu-latest

View File

@@ -6,7 +6,7 @@
[subrepo]
remote = https://github.com/simonlindholm/asm-differ
branch = main
commit = a1c893ed0d31caf25e76b8b7ae42a9af78c9af8a
parent = 10fa88a2f198af9b7cd7daac6c286fe01ae211c5
commit = 9b2b6ad5e5eee364a7010b908a2d70ded0df0dc4
parent = 3a5205ed5e6c0c8949b1d2bfa904fb33c2667157
method = merge
cmdver = 0.4.3

View File

@@ -1095,7 +1095,7 @@ def search_map_file(
ram = int(tokens[1], 0)
rom = int(tokens[5], 0)
ram_to_rom = rom - ram
if line.endswith(" " + fn_name):
if line.endswith(" " + fn_name) or f" {fn_name} = 0x" in line:
ram = int(line.split()[0], 0)
if cur_objfile is not None and ram_to_rom is not None:
cands.append((cur_objfile, ram + ram_to_rom))

View File

@@ -267,7 +267,7 @@ GCC272SN = GCCCompiler(
)
# MACOS9
MWCPPC_CC = 'printf "%s" "${COMPILER_FLAGS}" | xargs -x -- ${WINE} "${COMPILER_DIR}/MWCPPC.exe" -o object.o "${INPUT}" && printf "%s" "-dis -h -module ".${FUNCTION}" -nonames -nodata" | xargs -x -- ${WINE} "${COMPILER_DIR}/MWLinkPPC.exe" "${OUTPUT}" > "${OUTPUT}.s" && python3 ${COMPILER_DIR}/convert_gas_syntax.py "${OUTPUT}.s" ".${FUNCTION}" > "${OUTPUT}_new.s" && powerpc-linux-gnu-as "${OUTPUT}_new.s" -o "${OUTPUT}" && rm "${OUTPUT}.s" && rm "${OUTPUT}_new.s"'
MWCPPC_CC = 'printf "%s" "${COMPILER_FLAGS}" | xargs -x -- ${WINE} "${COMPILER_DIR}/MWCPPC.exe" -o object.o "${INPUT}" && printf "%s" "-dis -h -module ".${FUNCTION}" -nonames -nodata" | xargs -x -- ${WINE} "${COMPILER_DIR}/MWLinkPPC.exe" "${OUTPUT}" > "${OUTPUT}.s" && python3 ${COMPILER_DIR}/convert_gas_syntax.py "${OUTPUT}.s" ".${FUNCTION}" > "${OUTPUT}_new.s" && powerpc-linux-gnu-as "${OUTPUT}_new.s" -o "${OUTPUT}"'
MWCPPC_23 = MWCCCompiler(
id="mwcppc_23",

View File

@@ -29,8 +29,8 @@ class DecompilerWrapper:
except M2CError as e:
ret = f"{e}\n{default_source_code}"
except Exception:
logger.exception("Error running mips_to_c")
ret = f"/* Internal error while running mips_to_c */\n{default_source_code}"
logger.exception("Error running m2c")
ret = f"/* Internal error while running m2c */\n{default_source_code}"
else:
ret = f"/* No decompiler yet implemented for {platform.arch} */\n{default_source_code}"

View File

@@ -126,7 +126,7 @@ COMMON_MWCC_FLAGS: Flags = [
Checkbox(id="mwcc_fp_contract_on", flag="-fp_contract on"),
Checkbox(id="mwcc_nodefaults", flag="-nodefaults"),
Checkbox(id="mwcc_use_lmw_stmw_on", flag="-use_lmw_stmw on"),
Checkbox(id="mwcc_debug_on", flag="-g"),
Checkbox(id="mwcc_line_numbers_on", flag="-sym on"),
]
COMMON_GCC_PS1_FLAGS: Flags = [

View File

@@ -2,7 +2,7 @@ import contextlib
import io
import logging
from mips_to_c.src.main import parse_flags, run
from m2c.src.main import parse_flags, run
from coreapp.compilers import Compiler

View File

@@ -19,10 +19,13 @@ jobs:
python-version: 3.6
- name: Install dependencies
run: |
python -m pip install --upgrade mypy dataclasses types-dataclasses pycparser coverage
python -m pip install --upgrade mypy dataclasses types-dataclasses pycparser coverage black==22.3.0
- name: Run tests
run: |
./run_tests.py
- name: Type-check
run: |
mypy
- name: Formatting check
run: |
black --check .

View File

@@ -6,7 +6,7 @@
[subrepo]
remote = https://github.com/matt-kempster/mips_to_c
branch = master
commit = 8146cbb7ef9f70c23fb987bd29b738085d5dc9a2
parent = ee3b613a92a62a17fa50b008a70882c2e6de26c9
commit = 10184592c4cc3a1e71840851ab82c85068e927d2
parent = 66af20d9ab50868d86eb6d09950dc9be7dde1ca5
method = merge
cmdver = 0.4.3

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 20.8b1
rev: 22.3.0
hooks:
- id: black

View File

@@ -1,20 +1,17 @@
# `mips_to_c`
Given some MIPS or PPC assembly, this program will attempt to convert it to C.
# `m2c` Decompiler
`m2c` ("*Machine code to C*") is a decompiler for MIPS and PowerPC assembly that produces C code, with partial support for C++.
The goal of this project is to support decompilation projects, which aim to write C code that yields byte-identical output when compiled with a particular build system.
It primarily focuses on supporting popular compilers of the late 1990's.
However, it may also work with other compilers or hand-written assembly.
This project, initially named `mips_to_c`, has the goal to support decompilation projects, which aim to write source code that yields byte-identical output when compiled with a particular build system.
It originally targeted popular compilers of the late 1990's, but it also works well with newer compilers or hand-written assembly.
The focus of `mips_to_c` is to aid in the process of producing "matching" C source files.
This differentiates it from other decompilation suites, such as IDA or Ghidra.
`m2c` is often used in decompilation workflows with [`splat`](https://github.com/ethteck/splat), [`asm-differ`](https://github.com/simonlindholm/asm-differ), and [`decomp-permuter`](https://github.com/simonlindholm/decomp-permuter).
Its focus on finding "matching" C source differentiates it from other decompilation suites, such as IDA or Ghidra.
Right now the decompiler is fairly functional, though it sometimes generates suboptimal code (especially for loops).
The input is expected to match a particular assembly format, such as that produced by tools like [`mipsdisasm`](https://github.com/queueRAM/sm64tools).
See the `tests/` directory for some example input and output.
Despite the project's name, `mips_to_c` also has **experimental support** for PowerPC (PPC) as well.
[An online version is also available](https://simonsoftware.se/other/mips_to_c.py).
[An online version is also available](https://simonsoftware.se/other/m2c.html).
## Install
@@ -35,7 +32,7 @@ sudo apt install python3-pip
## Usage
```bash
python3 mips_to_c.py [options] [-t <target>] [--context <context file>] [-f <function name>] <asmfile>...
python3 m2c.py [options] [-t <target>] [--context <context file>] [-f <function name>] <asmfile>...
```
Run with `--help` to see which options are available.
@@ -44,7 +41,7 @@ Context files provided with `--context` are parsed and cached, so subsequent run
### Target Architecture / Compiler / Language
Despite the name, `mips_to_c` has support for both MIPS and PPC assembly.
`m2c` has support for both MIPS and PowerPC assembly.
It also has some compiler-specific heuristics and language-specific behavior.
For example, it can demangle C++ symbol names as used by CodeWarrior.
@@ -58,19 +55,19 @@ The following target triples are supported:
### Multiple functions
By default, `mips_to_c` decompiles all functions in the text sections from the input assembly files.
`mips_to_c` is able to perform a small amount of cross-function type inference, if the functions call each other.
By default, `m2c` decompiles all functions in the text sections from the input assembly files.
`m2c` is able to perform a small amount of cross-function type inference, if the functions call each other.
You can limit the function(s) that decompiled by providing the `-f <function name>` flags (or the "Function" dropdown on the website).
### Global Declarations & Initializers
When provided input files with `data`, `rodata`, and/or `bss` sections, `mips_to_c` can generate the initializers for variables it knows the types of.
When provided input files with `data`, `rodata`, and/or `bss` sections, `m2c` can generate the initializers for variables it knows the types of.
Qualifier hints such as `const`, `static`, and `extern` are based on which sections the symbols appear in, or if they aren't provided at all.
The output also includes prototypes for functions not declared in the context.
`mips_to_c` cannot generate initializers for structs with bitfields (e.g. `unsigned foo: 3;`) or for symbols that it cannot infer the type of.
`m2c` cannot generate initializers for structs with bitfields (e.g. `unsigned foo: 3;`) or for symbols that it cannot infer the type of.
For the latter, you can provide a type for the symbol the context.
This feature is controlled with the `--globals` option (or "Global declarations" on the website):
@@ -81,7 +78,7 @@ This feature is controlled with the `--globals` option (or "Global declarations"
### Struct Field Inference
By default, `mips_to_c` can use type information from decompiled functions to help fill in unknown struct fields.
By default, `m2c` can use type information from decompiled functions to help fill in unknown struct fields.
This behavior can be disabled with `--no-unk-inference` ("Disable unknown struct/type inference" on the website).
For structs in the context, the following fields treated as "unknown" space that can be inferred:
@@ -95,24 +92,24 @@ The output will include declarations for any struct with at least one inferred f
### Specifying stack variables
By default, `mips_to_c` infers the types of stack (local) variables, and names them with the `sp` prefix based on their offset.
By default, `m2c` infers the types of stack (local) variables, and names them with the `sp` prefix based on their offset.
Internally, the stack is represented as a struct, so it is possible to manually specify the names & types of stack variables by providing a struct declaration in the context. `mips_to_c` looks in the context for a struct with the tag name `_mips2c_stack_<function name>` (e.g. `struct _mips2c_stack_test` for a function `test()`).
Internally, the stack is represented as a struct, so it is possible to manually specify the names & types of stack variables by providing a struct declaration in the context. `m2c` looks in the context for a struct with the tag name `_m2c_stack_<function name>` (e.g. `struct _m2c_stack_test` for a function `test()`).
The size of the stack must exactly match the detected frame size, or `mips_to_c` will return an error.
If you run `mips_to_c` with the `--stack-structs` option ("Stack struct templates" on the website), the output will include the inferred stack declaration, which can then be edited and provided as context by re-running `mips_to_c`.
The size of the stack must exactly match the detected frame size, or `m2c` will return an error.
If you run `m2c` with the `--stack-structs` option ("Stack struct templates" on the website), the output will include the inferred stack declaration, which can then be edited and provided as context by re-running `m2c`.
#### Example
Here is an example for specifying the stack for the `custom_stack` end-to-end test.
First, run `mips_to_c` with the `--stack-structs` option to get the inferred struct for the `test()` function:
First, run `m2c` with the `--stack-structs` option to get the inferred struct for the `test()` function:
<details>
<summary><code>python3 mips_to_c.py tests/end_to_end/custom_stack/irix-o2.s -f test --stack-structs</code></summary>
<summary><code>python3 m2c.py tests/end_to_end/custom_stack/irix-o2.s -f test --stack-structs</code></summary>
```c
struct _mips2c_stack_test {
struct _m2c_stack_test {
/* 0x00 */ char pad0[0x20];
/* 0x20 */ s8 sp20; /* inferred */
/* 0x21 */ char pad21[0x3]; /* maybe part of sp20[4]? */
@@ -170,7 +167,7 @@ struct Vec {
s32 x, y, z;
};
struct _mips2c_stack_test {
struct _m2c_stack_test {
char pad0[0x20];
struct Vec vec;
struct Vec *vec_ptr;
@@ -183,10 +180,10 @@ struct _mips2c_stack_test {
int test(struct Vec *vec_arg);
```
Finally, re-run `mips_to_c` with our custom stack as part of the `--context`. The `--context` option can be specified multiple times to combine files.
Finally, re-run `m2c` with our custom stack as part of the `--context`. The `--context` option can be specified multiple times to combine files.
<details>
<summary><code>python3 mips_to_c.py tests/end_to_end/custom_stack/irix-o2.s -f test --context test_context.c</code></summary>
<summary><code>python3 m2c.py tests/end_to_end/custom_stack/irix-o2.s -f test --context test_context.c</code></summary>
```c
? func_00400090(s8 *); /* static */
@@ -223,7 +220,7 @@ s32 test(struct Vec *vec_arg) {
### Formatting
The following options control the formatting details of the output, such as braces style or numeric format. See `./mips_to_c.py --help` for more details.
The following options control the formatting details of the output, such as braces style or numeric format. See `./m2c.py --help` for more details.
(The option name on the website, if available, is in parentheses.)
@@ -236,12 +233,13 @@ The following options control the formatting details of the output, such as brac
- `--comment-column N` ("Comment style")
- `--no-casts`
- `--zfill-constants` ("0-fill constants")
- `--deterministic-vars`
Note: `--valid-syntax` is used to produce output that is less human-readable, but is likely to directly compile without edits. This can be used to go directly from assembly to the permuter without human intervention.
### Debugging poor results (Advanced)
There are several options to `mips_to_c` which can be used to troubleshoot poor results. Many of these options produce more "primitive" output or debugging information.
There are several options to `m2c` which can be used to troubleshoot poor results. Many of these options produce more "primitive" output or debugging information.
- `--no-andor` ("Disable &&/||"): Disable complex conditional detection, such as `if (a && b)`. Instead, emit each part of the conditional as a separate `if` statement. Ands, ors, nots, etc. are usually represented with `goto`s.
- `--no-switches` ("Disable irregular switch detection"): Disable "irregular" `switch` statements, where the compiler emits a single `switch` as a series of branches and/or jump tables. By default, these are coalesced into a single `switch` and marked with an `/* irregular */` comment.
@@ -253,16 +251,29 @@ There are several options to `mips_to_c` which can be used to troubleshoot poor
#### Visualization
`mips_to_c` can generate an SVG representation of the control flow of a function, which can sometimes be helpful to untangle complex loops or early returns.
`m2c` can generate an SVG representation of the control flow of a function, which can sometimes be helpful to untangle complex loops or early returns.
Pass `--visualize` on the command line, or use the "Visualize" button on the website. The output will be an SVG file.
Example to produce `my_fn.svg` of `my_fn()`:
Example to produce C & assembly visualizations of `my_fn()`:
```sh
python3 ./mips_to_c.py --visualize --context ctx.c -f my_fn my_asm.s > my_fn.svg
python3 ./m2c.py --visualize=c --context ctx.c -f my_fn my_asm.s > my_fn_c.svg
python3 ./m2c.py --visualize=asm --context ctx.c -f my_fn my_asm.s > my_fn_asm.svg
```
### Migrating from `mips_to_c.py`
This tool was originally known as `mips_to_c`. As part of the rename, deprecated command line arguments were removed.
When migrating to `m2c`, note the following changes to the CLI:
- Entrypoint rename: `./mips_to_c.py` becomes `./m2c.py`
- To limit decompilation to a [single function](#multiple-function): use `-f FN, --function FN`
- `--rodata` is [no longer needed](#multiple-functions): `my_text.s --rodata my_data.s` becomes `my_text.s my_data.s`
- `--compiler` has been replaced by [`--target`](#target-architecture--compiler--language): `--compiler gcc` becomes `--target mips-gcc-c`
- `--structs` is now the [default behavior](#struct-field-inference): remove `--structs` from the arguments
## Contributing
There is much low-hanging fruit still. Take a look at the issues if you want to help out.
@@ -283,7 +294,7 @@ To get pretty graph visualizations, install `graphviz` using `pip` and globally
There is a small test suite, which works as follows:
- As you develop your commit, occasionally run `./run_tests.py` to see if any tests have changed output.
These tests run the decompiler on a small corpus of IDO 5.3-compiled MIPS assembly.
These tests run the decompiler on a small corpus of assembly.
- Before pushing your commit, run `./run_tests.py --overwrite` to write changed tests to disk, and commit resultant changes.
### Running Decompilation Project Tests
@@ -293,7 +304,7 @@ It's possible to use the entire corpus of assembly files from decompilation proj
For now, the output of these tests are not tracked in version control.
You need to run `./run_tests.py --overwrite ...` **before** making any code changes to create the baseline output.
As an example, if you have the `oot` project cloned locally in the parent directory containing `mips_to_c`, the following will decompile all of its assembly files.
As an example, if you have the `oot` project cloned locally in the parent directory containing `m2c`, the following will decompile all of its assembly files.
```bash
./run_tests.py --project ../oot --project-with-context ../oot

39
backend/m2c/m2c_macros.h Normal file
View File

@@ -0,0 +1,39 @@
/*
* This header contains macros emitted by m2c in "valid syntax" mode,
* which can be enabled by passing `--valid-syntax` on the command line.
*
* In this mode, unhandled types and expressions are emitted as macros so
* that the output is compilable without human intervention.
*/
#ifndef M2C_MACROS_H
#define M2C_MACROS_H
/* Unknown types */
typedef s32 M2C_UNK;
typedef s8 M2C_UNK8;
typedef s16 M2C_UNK16;
typedef s32 M2C_UNK32;
typedef s64 M2C_UNK64;
/* Unknown field access, like `*(type_ptr) &expr->unk_offset` */
#define M2C_FIELD(expr, type_ptr, offset) (*(type_ptr)((s8 *)(expr) + (offset)))
/* Bitwise (reinterpret) cast */
#define M2C_BITWISE(type, expr) ((type)(expr))
/* Unaligned reads */
#define M2C_LWL(expr) (expr)
#define M2C_FIRST3BYTES(expr) (expr)
#define M2C_UNALIGNED32(expr) (expr)
/* Unhandled instructions */
#define M2C_ERROR(desc) (0)
#define M2C_TRAP_IF(cond) (0)
#define M2C_BREAK() (0)
#define M2C_SYNC() (0)
/* Carry bit from partially-implemented instructions */
#define M2C_CARRY 0
#endif

View File

@@ -11,7 +11,7 @@ warn_redundant_casts = True
warn_return_any = True
warn_unused_ignores = True
mypy_path = stubs
files = mips_to_c.py, run_tests.py, tests/add_test.py
files = m2c.py, run_tests.py, tests/add_test.py
[mypy-graphviz]
ignore_missing_imports = True

View File

@@ -220,14 +220,14 @@ def create_project_tests(
asm_dir = base_dir / "asm"
if "oot" in base_dir.parts:
file_iter = find_tests_oot(asm_dir)
base_flags = ["--compiler=ido", "--stack-structs", "--unk-underscore"]
base_flags = ["--target=mips-ido-c", "--stack-structs", "--unk-underscore"]
elif "mm" in base_dir.parts:
file_iter = find_tests_mm(asm_dir)
base_flags = ["--compiler=ido", "--stack-structs", "--unk-underscore"]
base_flags = ["--target=mips-ido-c", "--stack-structs", "--unk-underscore"]
elif "papermario" in base_dir.parts:
file_iter = find_tests_splat(asm_dir)
base_flags = [
"--compiler=gcc",
"--target=mips-gcc-c",
"--stack-structs",
"--unk-underscore",
"--pointer-style=left",
@@ -245,7 +245,7 @@ def create_project_tests(
if not file_list:
continue
flags = base_flags[:]
flags = base_flags + ["--deterministic-vars"]
if context_file is not None:
flags.extend(["--context", str(context_file)])
@@ -411,7 +411,7 @@ if __name__ == "__main__":
parser.add_argument(
"extra_flags",
nargs=argparse.REMAINDER,
help="Additional arguments to pass to mips_to_c. Use `--` to separate them from run_tests's flags.",
help="Additional arguments to pass to m2c. Use `--` to separate them from run_tests's flags.",
)
parser.add_argument(
"--project-with-context",

View File

@@ -1,6 +1,7 @@
from dataclasses import replace
import typing
from typing import (
Callable,
Dict,
List,
Optional,
@@ -11,19 +12,21 @@ from typing import (
from .error import DecompFailure
from .options import Target
from .parse_instruction import (
from .asm_instruction import (
Argument,
AsmAddressMode,
AsmGlobalSymbol,
AsmInstruction,
AsmLiteral,
JumpTarget,
Register,
get_jump_target,
)
from .instruction import (
Instruction,
InstructionMeta,
JumpTarget,
Location,
Register,
StackLocation,
get_jump_target,
)
from .asm_pattern import (
AsmMatch,
@@ -42,11 +45,13 @@ from .translate import (
CmpInstrMap,
CommentStmt,
ErrorExpr,
ExprCondition,
ExprStmt,
Expression,
InstrArgs,
InstrMap,
InstrSet,
Literal,
PairInstrMap,
NodeState,
SecondF64Half,
StmtInstrMap,
StoreInstrMap,
@@ -63,6 +68,7 @@ from .translate import (
as_u32,
as_u64,
as_uintish,
condition_from_expr,
error_stmt,
fn_op,
fold_divmod,
@@ -109,9 +115,11 @@ LENGTH_THREE: Set[str] = {
"slti",
"sltu",
"sltiu",
"add",
"addi",
"addiu",
"addu",
"sub",
"subu",
"daddi",
"daddiu",
@@ -252,7 +260,7 @@ class DivP2Pattern1(SimpleAsmPattern):
def replace(self, m: AsmMatch) -> Replacement:
shift = m.literals["N"] & 0x1F
div = AsmInstruction(
"div.fictive", [m.regs["o"], m.regs["i"], AsmLiteral(2 ** shift)]
"div.fictive", [m.regs["o"], m.regs["i"], AsmLiteral(2**shift)]
)
return Replacement([div], len(m.body) - 1)
@@ -271,7 +279,7 @@ class DivP2Pattern2(SimpleAsmPattern):
def replace(self, m: AsmMatch) -> Replacement:
shift = m.literals["N"] & 0x1F
div = AsmInstruction(
"div.fictive", [m.regs["x"], m.regs["x"], AsmLiteral(2 ** shift)]
"div.fictive", [m.regs["x"], m.regs["x"], AsmLiteral(2**shift)]
)
return Replacement([div], len(m.body))
@@ -438,7 +446,7 @@ class GccSqrtPattern(SimpleAsmPattern):
)
def replace(self, m: AsmMatch) -> Replacement:
return Replacement([m.body[0]], len(m.body))
return Replacement([m.body[0], m.body[4]], len(m.body))
class TrapuvPattern(SimpleAsmPattern):
@@ -675,6 +683,10 @@ class MipsArch(Arch):
is_branch_likely = False
is_conditional = False
is_return = False
is_store = False
eval_fn: Optional[Callable[[NodeState, InstrArgs], object]] = None
instr_str = str(AsmInstruction(mnemonic, args))
memory_sizes = {
"b": 1,
@@ -697,6 +709,11 @@ class MipsArch(Arch):
return [loc]
return []
def unreachable_eval(s: NodeState, a: InstrArgs) -> None:
raise DecompFailure(
f"Instruction {instr_str} should be replaced before eval"
)
if mnemonic == "jr" and args[0] == Register("ra"):
# Return
assert len(args) == 1
@@ -710,7 +727,8 @@ class MipsArch(Arch):
jump_target = args[0]
is_conditional = True
has_delay_slot = True
elif mnemonic == "jal":
eval_fn = lambda s, a: s.set_switch_expr(a.reg(0))
elif mnemonic == "jal" or mnemonic == "bal":
# Function call to label
assert len(args) == 1 and isinstance(args[0], AsmGlobalSymbol)
inputs = list(cls.argument_regs)
@@ -718,6 +736,7 @@ class MipsArch(Arch):
clobbers = list(cls.temp_regs)
function_target = args[0]
has_delay_slot = True
eval_fn = lambda s, a: s.make_function_call(a.sym_imm(0), outputs)
elif mnemonic == "jalr":
# Function call to pointer
assert (
@@ -731,6 +750,7 @@ class MipsArch(Arch):
clobbers = list(cls.temp_regs)
function_target = args[1]
has_delay_slot = True
eval_fn = lambda s, a: s.make_function_call(a.reg(1), outputs)
elif mnemonic in ("b", "j"):
# Unconditional jump
assert len(args) == 1
@@ -767,6 +787,9 @@ class MipsArch(Arch):
has_delay_slot = True
is_branch_likely = True
is_conditional = True
# Branch-likely instructions should be rewritten by flow_graph.py
eval_fn = unreachable_eval
elif mnemonic in (
"beq",
"bne",
@@ -796,24 +819,49 @@ class MipsArch(Arch):
jump_target = get_jump_target(args[-1])
has_delay_slot = True
is_conditional = True
def eval_fn(s: NodeState, a: InstrArgs) -> None:
if mnemonic in ("bc1t", "bc1f"):
cond = condition_from_expr(a.regs[Register("condition_bit")])
if mnemonic == "bc1f":
cond = cond.negated()
else:
cond = cls.instrs_branches[mnemonic](a)
s.set_branch_condition(cond)
elif mnemonic == "mfc0":
assert len(args) == 2 and isinstance(args[0], Register)
outputs = [args[0]]
eval_fn = lambda s, a: s.set_reg(
a.reg_ref(0), ErrorExpr(f"mfc0 {a.raw_arg(1)}")
)
elif mnemonic == "mtc0":
assert len(args) == 2 and isinstance(args[0], Register)
inputs = [args[0]]
eval_fn = lambda s, a: s.write_statement(error_stmt(instr_str))
elif mnemonic in cls.instrs_no_dest:
assert not any(isinstance(a, AsmAddressMode) for a in args)
inputs = [r for r in args if isinstance(r, Register)]
eval_fn = lambda s, a: s.write_statement(cls.instrs_no_dest[mnemonic](a))
elif mnemonic in cls.instrs_store:
assert isinstance(args[0], Register)
inputs = [args[0]]
outputs = make_memory_access(args[1])
is_store = True
if isinstance(args[1], AsmAddressMode):
inputs.append(args[1].rhs)
if mnemonic == "sdc1":
inputs.append(args[0].other_f64_reg())
elif mnemonic in cls.instrs_source_first:
def eval_fn(s: NodeState, a: InstrArgs) -> None:
store = cls.instrs_store[mnemonic](a)
if store is not None:
s.store_memory(
source=store.source, dest=store.dest, reg=a.reg_ref(0)
)
elif mnemonic == "mtc1":
# Floating point moving instruction, source first
assert (
len(args) == 2
and isinstance(args[0], Register)
@@ -821,6 +869,7 @@ class MipsArch(Arch):
)
inputs = [args[0]]
outputs = [args[1]]
eval_fn = lambda s, a: s.set_reg(a.reg_ref(1), a.reg(0))
elif mnemonic in cls.instrs_destination_first:
assert isinstance(args[0], Register)
outputs = [args[0]]
@@ -880,6 +929,13 @@ class MipsArch(Arch):
else:
assert not any(isinstance(a, AsmAddressMode) for a in args)
inputs = [r for r in args[1:] if isinstance(r, Register)]
def eval_fn(s: NodeState, a: InstrArgs) -> None:
target = a.reg_ref(0)
s.set_reg(target, cls.instrs_destination_first[mnemonic](a))
if len(outputs) == 2:
s.set_reg(target.other_f64_reg(), SecondF64Half())
elif mnemonic in cls.instrs_float_comp:
assert (
len(args) == 2
@@ -896,6 +952,9 @@ class MipsArch(Arch):
else:
inputs = [args[0], args[1]]
outputs = [Register("condition_bit")]
eval_fn = lambda s, a: s.set_reg(
Register("condition_bit"), cls.instrs_float_comp[mnemonic](a)
)
elif mnemonic in cls.instrs_hi_lo:
assert (
len(args) == 2
@@ -904,14 +963,29 @@ class MipsArch(Arch):
)
inputs = [args[0], args[1]]
outputs = [Register("hi"), Register("lo")]
def eval_fn(s: NodeState, a: InstrArgs) -> None:
hi, lo = cls.instrs_hi_lo[mnemonic](a)
s.set_reg(Register("hi"), hi)
s.set_reg(Register("lo"), lo)
elif mnemonic in cls.instrs_ignore:
# TODO: There might be some instrs to handle here
pass
elif args and isinstance(args[0], Register):
# If the mnemonic is unsupported, guess
assert not any(isinstance(a, AsmAddressMode) for a in args)
inputs = [r for r in args[1:] if isinstance(r, Register)]
outputs = [args[0]]
else:
# If the mnemonic is unsupported, guess if it is destination-first
if args and isinstance(args[0], Register):
inputs = [r for r in args[1:] if isinstance(r, Register)]
outputs = [args[0]]
maybe_dest_first = True
else:
maybe_dest_first = False
def eval_fn(s: NodeState, a: InstrArgs) -> None:
error = ErrorExpr(f"unknown instruction: {instr_str}")
if maybe_dest_first:
s.set_reg_real(a.reg_ref(0), error, emit_exactly_once=True)
else:
s.write_statement(ExprStmt(error))
return Instruction(
mnemonic=mnemonic,
@@ -926,6 +1000,8 @@ class MipsArch(Arch):
is_branch_likely=is_branch_likely,
is_conditional=is_conditional,
is_return=is_return,
is_store=is_store,
eval_fn=eval_fn,
)
asm_patterns = [
@@ -945,9 +1021,9 @@ class MipsArch(Arch):
TrapuvPattern(),
]
instrs_ignore: InstrSet = {
instrs_ignore: Set[str] = {
# Ignore FCSR sets; they are leftovers from float->unsigned conversions.
# FCSR gets are as well, but it's fine to read MIPS2C_ERROR for those.
# FCSR gets are as well, but it's fine to read M2C_ERROR for those.
"ctc1",
"nop",
"b",
@@ -977,67 +1053,52 @@ class MipsArch(Arch):
"bltz": lambda a: BinaryOp.scmp(a.reg(0), "<", Literal(0)),
"bgez": lambda a: handle_bgez(a),
}
instrs_float_branches: InstrSet = {
# Floating-point branch instructions
"bc1t",
"bc1f",
}
instrs_jumps: InstrSet = {
# Unconditional jump
"jr"
}
instrs_fn_call: InstrSet = {
# Function call
"jal",
"jalr",
}
instrs_no_dest: StmtInstrMap = {
# Conditional traps (happen with Pascal code sometimes, might as well give a nicer
# output than MIPS2C_ERROR(...))
# output than M2C_ERROR(...))
"teq": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.icmp(a.reg(0), "==", a.reg(1))]
"M2C_TRAP_IF", [BinaryOp.icmp(a.reg(0), "==", a.reg(1))]
),
"tne": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.icmp(a.reg(0), "!=", a.reg(1))]
"M2C_TRAP_IF", [BinaryOp.icmp(a.reg(0), "!=", a.reg(1))]
),
"tlt": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.scmp(a.reg(0), "<", a.reg(1))]
"M2C_TRAP_IF", [BinaryOp.scmp(a.reg(0), "<", a.reg(1))]
),
"tltu": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.ucmp(a.reg(0), "<", a.reg(1))]
"M2C_TRAP_IF", [BinaryOp.ucmp(a.reg(0), "<", a.reg(1))]
),
"tge": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.scmp(a.reg(0), ">=", a.reg(1))]
"M2C_TRAP_IF", [BinaryOp.scmp(a.reg(0), ">=", a.reg(1))]
),
"tgeu": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.ucmp(a.reg(0), ">=", a.reg(1))]
"M2C_TRAP_IF", [BinaryOp.ucmp(a.reg(0), ">=", a.reg(1))]
),
"teqi": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.icmp(a.reg(0), "==", a.imm(1))]
"M2C_TRAP_IF", [BinaryOp.icmp(a.reg(0), "==", a.imm(1))]
),
"tnei": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.icmp(a.reg(0), "!=", a.imm(1))]
"M2C_TRAP_IF", [BinaryOp.icmp(a.reg(0), "!=", a.imm(1))]
),
"tlti": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.scmp(a.reg(0), "<", a.imm(1))]
"M2C_TRAP_IF", [BinaryOp.scmp(a.reg(0), "<", a.imm(1))]
),
"tltiu": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.ucmp(a.reg(0), "<", a.imm(1))]
"M2C_TRAP_IF", [BinaryOp.ucmp(a.reg(0), "<", a.imm(1))]
),
"tgei": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.scmp(a.reg(0), ">=", a.imm(1))]
"M2C_TRAP_IF", [BinaryOp.scmp(a.reg(0), ">=", a.imm(1))]
),
"tgeiu": lambda a: void_fn_op(
"MIPS2C_TRAP_IF", [BinaryOp.ucmp(a.reg(0), ">=", a.imm(1))]
"M2C_TRAP_IF", [BinaryOp.ucmp(a.reg(0), ">=", a.imm(1))]
),
"break": lambda a: void_fn_op(
"MIPS2C_BREAK", [a.imm(0)] if a.count() >= 1 else []
"M2C_BREAK", [a.imm(0)] if a.count() >= 1 else []
),
"sync": lambda a: void_fn_op("MIPS2C_SYNC", []),
"mtc0": lambda a: error_stmt(f"mtc0 {a.raw_arg(0)}, {a.raw_arg(1)}"),
"sync": lambda a: void_fn_op("M2C_SYNC", []),
"trapuv.fictive": lambda a: CommentStmt("code compiled with -trapuv"),
}
instrs_float_comp: CmpInstrMap = {
instrs_float_comp: InstrMap = {
# Float comparisons that don't raise exception on nan
"c.eq.s": lambda a: BinaryOp.fcmp(a.reg(0), "==", a.reg(1)),
"c.olt.s": lambda a: BinaryOp.fcmp(a.reg(0), "<", a.reg(1)),
@@ -1083,7 +1144,7 @@ class MipsArch(Arch):
"c.nge.d": lambda a: BinaryOp.dcmp(a.dreg(0), ">=", a.dreg(1)).negated(),
"c.ngt.d": lambda a: BinaryOp.dcmp(a.dreg(0), ">", a.dreg(1)).negated(),
}
instrs_hi_lo: PairInstrMap = {
instrs_hi_lo: Dict[str, Callable[[InstrArgs], Tuple[Expression, Expression]]] = {
# Div and mul output two results, to LO/HI registers. (Format: (hi, lo))
"div": lambda a: (
BinaryOp.sint(a.reg(0), "%", a.reg(1)),
@@ -1118,10 +1179,6 @@ class MipsArch(Arch):
BinaryOp.int64(a.reg(0), "*", a.reg(1)),
),
}
instrs_source_first: InstrMap = {
# Floating point moving instruction
"mtc1": lambda a: a.reg(0)
}
instrs_destination_first: InstrMap = {
# Flag-setting instructions
"slt": lambda a: BinaryOp.scmp(a.reg(1), "<", a.reg(2)),
@@ -1131,7 +1188,11 @@ class MipsArch(Arch):
# Integer arithmetic
"addi": lambda a: handle_addi(a),
"addiu": lambda a: handle_addi(a),
"add": lambda a: handle_add(a),
"addu": lambda a: handle_add(a),
"sub": lambda a: (
fold_mul_chains(fold_divmod(BinaryOp.intptr(a.reg(1), "-", a.reg(2))))
),
"subu": lambda a: (
fold_mul_chains(fold_divmod(BinaryOp.intptr(a.reg(1), "-", a.reg(2))))
),
@@ -1268,8 +1329,6 @@ class MipsArch(Arch):
"movz": lambda a: handle_conditional_move(a, False),
# FCSR get
"cfc1": lambda a: ErrorExpr("cfc1"),
# Read from coprocessor 0
"mfc0": lambda a: ErrorExpr(f"mfc0 {a.raw_arg(1)}"),
# Immediates
"li": lambda a: a.full_imm(1),
"lui": lambda a: load_upper(a),

View File

@@ -1,6 +1,7 @@
from dataclasses import replace
import typing
from typing import (
Callable,
ClassVar,
Dict,
List,
@@ -14,21 +15,23 @@ from .error import DecompFailure
from .flow_graph import FlowGraph
from .ir_pattern import IrMatch, IrPattern
from .options import Target
from .parse_instruction import (
from .asm_instruction import (
Argument,
AsmAddressMode,
AsmGlobalSymbol,
AsmInstruction,
AsmLiteral,
Instruction,
InstructionMeta,
JumpTarget,
Location,
Macro,
Register,
StackLocation,
get_jump_target,
)
from .instruction import (
Instruction,
InstructionMeta,
Location,
StackLocation,
)
from .asm_pattern import (
AsmMatch,
AsmMatcher,
@@ -40,6 +43,7 @@ from .asm_pattern import (
from .translate import (
Abi,
AbiArgSlot,
AddressMode,
Arch,
BinaryOp,
CarryBit,
@@ -47,18 +51,18 @@ from .translate import (
CmpInstrMap,
CommentStmt,
ErrorExpr,
ExprStmt,
Expression,
ImplicitInstrMap,
InstrArgs,
InstrMap,
InstrSet,
Literal,
PpcCmpInstrMap,
PairInstrMap,
NodeState,
SecondF64Half,
StmtInstrMap,
StoreInstrMap,
TernaryOp,
UnaryOp,
add_imm,
as_f32,
as_f64,
as_int64,
@@ -69,6 +73,7 @@ from .translate import (
as_type,
as_u32,
as_uintish,
error_stmt,
fn_op,
fold_divmod,
fold_mul_chains,
@@ -81,8 +86,8 @@ from .translate import (
handle_load,
handle_loadx,
handle_or,
handle_rlwinm,
handle_rlwimi,
handle_rlwinm,
handle_sra,
load_upper,
make_store,
@@ -510,6 +515,10 @@ class PpcArch(Arch):
function_target: Optional[Union[AsmGlobalSymbol, Register]] = None
is_conditional = False
is_return = False
is_store = False
eval_fn: Optional[Callable[[NodeState, InstrArgs], object]] = None
instr_str = str(AsmInstruction(mnemonic, args))
cr0_bits: List[Location] = [
Register("cr0_lt"),
@@ -544,6 +553,11 @@ class PpcArch(Arch):
return [loc]
return []
def unreachable_eval(s: NodeState, a: InstrArgs) -> None:
raise DecompFailure(
f"Instruction {instr_str} should be replaced before eval"
)
if mnemonic == "blr":
# Return
assert len(args) == 0
@@ -567,12 +581,15 @@ class PpcArch(Arch):
inputs = cr0_bits + [Register("lr")]
is_return = True
is_conditional = True
# NB: These are rewritten to mnemonic[:-2] by build_blocks in flow_graph.py
eval_fn = unreachable_eval
elif mnemonic == "bctr":
# Jump table (switch)
assert len(args) == 0
inputs = [Register("ctr")]
jump_target = Register("ctr")
is_conditional = True
eval_fn = lambda s, a: s.set_switch_expr(a.regs[Register("ctr")])
elif mnemonic == "bl":
# Function call to label
assert len(args) == 1 and isinstance(args[0], AsmGlobalSymbol)
@@ -580,52 +597,43 @@ class PpcArch(Arch):
outputs = list(cls.all_return_regs)
clobbers = list(cls.temp_regs)
function_target = args[0]
elif mnemonic == "bctrl":
# Function call to pointer in $ctr
eval_fn = lambda s, a: s.make_function_call(a.sym_imm(0), outputs)
elif mnemonic in ("bctrl", "blrl"):
# Function call to pointer in special reg ($ctr or $lr)
assert len(args) == 0
reg = Register(mnemonic[1:-1])
inputs = list(cls.argument_regs)
inputs.append(Register("ctr"))
inputs.append(reg)
outputs = list(cls.all_return_regs)
clobbers = list(cls.temp_regs)
function_target = Register("ctr")
elif mnemonic == "blrl":
# Function call to pointer in $lr
assert len(args) == 0
inputs = list(cls.argument_regs)
inputs.append(Register("lr"))
outputs = list(cls.all_return_regs)
clobbers = list(cls.temp_regs)
function_target = Register("lr")
function_target = reg
eval_fn = lambda s, a: s.make_function_call(a.regs[reg], outputs)
elif mnemonic == "b":
# Unconditional jump
assert len(args) == 1
jump_target = get_jump_target(args[0])
elif mnemonic in cls.instrs_branches or mnemonic in ("bdnz", "bdz"):
elif mnemonic in cls.instrs_branches:
# Normal branch
# TODO: Support crN argument
assert 1 <= len(args) <= 2
inputs = [
Register(
{
"beq": "cr0_eq",
"bge": "cr0_lt",
"bgt": "cr0_gt",
"ble": "cr0_gt",
"blt": "cr0_lt",
"bne": "cr0_eq",
"bns": "cr0_so",
"bso": "cr0_so",
"bdnz": "ctr",
"bdz": "ctr",
"bdnz.fictive": "ctr",
"bdz.fictive": "ctr",
}[mnemonic]
)
]
# If the name starts with "!", negate the condition
raw_name = cls.instrs_branches[mnemonic]
negated = raw_name.startswith("!")
reg_name = raw_name.lstrip("!")
inputs = [Register(reg_name)]
jump_target = get_jump_target(args[-1])
is_conditional = True
def eval_fn(s: NodeState, a: InstrArgs) -> None:
cond = a.cmp_reg(reg_name)
if negated:
cond = cond.negated()
s.set_branch_condition(cond)
elif mnemonic in cls.instrs_store:
assert isinstance(args[0], Register) and size is not None
is_store = True
if mnemonic.endswith("x"):
assert (
len(args) == 3 + psq_imms
@@ -637,8 +645,17 @@ class PpcArch(Arch):
assert len(args) == 2 + psq_imms and isinstance(args[1], AsmAddressMode)
inputs = [args[0], args[1].rhs]
outputs = make_memory_access(args[1], size)
def eval_fn(s: NodeState, a: InstrArgs) -> None:
store = cls.instrs_store[mnemonic](a)
if store is not None:
s.store_memory(
source=store.source, dest=store.dest, reg=a.reg_ref(0)
)
elif mnemonic in cls.instrs_store_update:
assert isinstance(args[0], Register) and size is not None
is_store = True
if mnemonic.endswith("x"):
assert (
len(args) == 3 + psq_imms
@@ -651,6 +668,26 @@ class PpcArch(Arch):
assert len(args) == 2 + psq_imms and isinstance(args[1], AsmAddressMode)
inputs = [args[0], args[1].rhs]
outputs = make_memory_access(args[1], size) + [args[1].rhs]
def eval_fn(s: NodeState, a: InstrArgs) -> None:
store = cls.instrs_store_update[mnemonic](a)
# Update the register in the second argument
update = a.memory_ref(1)
if not isinstance(update, AddressMode):
raise DecompFailure(
f"Unhandled store-and-update arg in {instr_str}: {update!r}"
)
s.set_reg(
update.rhs,
add_imm(update.rhs, a.regs[update.rhs], Literal(update.offset), a),
)
if store is not None:
s.store_memory(
source=store.source, dest=store.dest, reg=a.reg_ref(0)
)
elif mnemonic in cls.instrs_load:
assert isinstance(args[0], Register) and size is not None
if mnemonic.endswith("x"):
@@ -661,9 +698,11 @@ class PpcArch(Arch):
)
inputs = [args[1], args[2]]
else:
assert len(args) == 2 + psq_imms and isinstance(args[1], AsmAddressMode)
inputs = make_memory_access(args[1], size) + [args[1].rhs]
assert len(args) == 2 + psq_imms
if isinstance(args[1], AsmAddressMode):
inputs = make_memory_access(args[1], size) + [args[1].rhs]
outputs = [args[0]]
eval_fn = lambda s, a: s.set_reg(a.reg_ref(0), cls.instrs_load[mnemonic](a))
elif mnemonic in cls.instrs_load_update:
assert isinstance(args[0], Register) and size is not None
if mnemonic.endswith("x"):
@@ -674,10 +713,47 @@ class PpcArch(Arch):
)
inputs = [args[1], args[2]]
outputs = [args[0], args[1]]
def eval_fn(s: NodeState, a: InstrArgs) -> None:
target = a.reg_ref(0)
val = cls.instrs_load_update[mnemonic](a)
s.set_reg(target, val)
# In `rD, rA, rB`, update `rA = rA + rB`
update_reg = a.reg_ref(1)
offset = a.reg(2)
if update_reg == target:
raise DecompFailure(
f"Invalid instruction, rA and rD must be different in {instr_str}"
)
s.set_reg(
update_reg, add_imm(update_reg, a.regs[update_reg], offset, a)
)
else:
assert len(args) == 2 + psq_imms and isinstance(args[1], AsmAddressMode)
inputs = make_memory_access(args[1], size) + [args[1].rhs]
outputs = [args[0], args[1].rhs]
def eval_fn(s: NodeState, a: InstrArgs) -> None:
target = a.reg_ref(0)
val = cls.instrs_load_update[mnemonic](a)
s.set_reg(target, val)
# In `rD, rA(N)`, update `rA = rA + N`
update = a.memory_ref(1)
if not isinstance(update, AddressMode):
raise DecompFailure(
f"Unhandled load-and-update arg in {instr_str}: {update!r}"
)
update_reg = update.rhs
offset = Literal(update.offset)
if update_reg == target:
raise DecompFailure(
f"Invalid instruction, rA and rD must be different in {instr_str}"
)
s.set_reg(
update_reg, add_imm(update_reg, a.regs[update_reg], offset, a)
)
elif mnemonic in ("stmw", "lmw"):
assert (
len(args) == 2
@@ -685,6 +761,7 @@ class PpcArch(Arch):
and isinstance(args[1], AsmAddressMode)
and args[0].register_name[0] == "r"
)
is_store = mnemonic == "stmw"
index = int(args[0].register_name[1:])
offset = args[1].lhs_as_literal()
while index <= 31:
@@ -701,8 +778,11 @@ class PpcArch(Arch):
index += 1
offset += 4
inputs.append(args[1].rhs)
# TODO: These are only supported in function prologues/epilogues
eval_fn = None
elif mnemonic in cls.instrs_no_dest:
assert not any(isinstance(a, (Register, AsmAddressMode)) for a in args)
eval_fn = lambda s, a: s.write_statement(cls.instrs_no_dest[mnemonic](a))
elif mnemonic.rstrip(".") in cls.instrs_destination_first:
assert isinstance(args[0], Register)
outputs = [args[0]]
@@ -732,24 +812,90 @@ class PpcArch(Arch):
else:
assert not any(isinstance(a, AsmAddressMode) for a in args)
inputs = [r for r in args[1:] if isinstance(r, Register)]
elif mnemonic in cls.instrs_implicit_destination:
if mnemonic.endswith("."):
# Instructions ending in `.` update the condition reg
outputs.extend(cr0_bits)
def eval_fn(s: NodeState, a: InstrArgs) -> None:
target = a.reg_ref(0)
val = cls.instrs_destination_first[mnemonic.rstrip(".")](a)
target_val = s.set_reg(target, val)
if target_val is None:
target_val = val
s.set_reg(
Register("cr0_eq"),
BinaryOp.icmp(
target_val, "==", Literal(0, type=target_val.type)
),
)
# Use manual casts for cr0_gt/cr0_lt so that the type of target_val is not modified
# until the resulting bit is .use()'d.
target_s32 = Cast(
target_val, reinterpret=True, silent=True, type=Type.s32()
)
s.set_reg(
Register("cr0_gt"),
BinaryOp(target_s32, ">", Literal(0), type=Type.s32()),
)
s.set_reg(
Register("cr0_lt"),
BinaryOp(target_s32, "<", Literal(0), type=Type.s32()),
)
s.set_reg(
Register("cr0_so"),
fn_op("MIPS2C_OVERFLOW", [target_val], type=Type.s32()),
)
else:
eval_fn = lambda s, a: s.set_reg(
a.reg_ref(0), cls.instrs_destination_first[mnemonic](a)
)
elif mnemonic in ("mtctr", "mtlr"):
assert len(args) == 1 and isinstance(args[0], Register)
dest_reg = Register(mnemonic[2:])
inputs = [args[0]]
outputs = [cls.instrs_implicit_destination[mnemonic][0]]
outputs = [dest_reg]
eval_fn = lambda s, a: s.set_reg(dest_reg, a.reg(0))
elif mnemonic in cls.instrs_ppc_compare:
assert len(args) == 3 and isinstance(args[1], Register)
inputs = [r for r in args[1:] if isinstance(r, Register)]
outputs = list(cr0_bits)
def eval_fn(s: NodeState, a: InstrArgs) -> None:
base_reg = a.reg_ref(0)
if base_reg != Register("cr0"):
error = f'"{instr_str}" is not supported, the first arg is not $cr0'
s.write_statement(error_stmt(error))
return
s.set_reg(Register("cr0_eq"), cls.instrs_ppc_compare[mnemonic](a, "=="))
s.set_reg(Register("cr0_gt"), cls.instrs_ppc_compare[mnemonic](a, ">"))
s.set_reg(Register("cr0_lt"), cls.instrs_ppc_compare[mnemonic](a, "<"))
s.set_reg(Register("cr0_so"), Literal(0))
elif mnemonic in cls.instrs_ignore:
pass
elif args and isinstance(args[0], Register):
# If the mnemonic is unsupported, guess it is destination-first
inputs = [r for r in args[1:] if isinstance(r, Register)]
outputs = [args[0]]
else:
# If the mnemonic is unsupported, guess if it is destination-first
if args and isinstance(args[0], Register):
inputs = [r for r in args[1:] if isinstance(r, Register)]
outputs = [args[0]]
maybe_dest_first = True
else:
maybe_dest_first = False
if mnemonic.endswith("."):
# PPC instructions ending in `.` update the condition reg
outputs.extend(cr0_bits)
def eval_fn(s: NodeState, a: InstrArgs) -> None:
error = ErrorExpr(f"unknown instruction: {instr_str}")
if mnemonic.endswith("."):
# Unimplemented instructions that modify CR0
s.set_reg(Register("cr0_eq"), error)
s.set_reg(Register("cr0_gt"), error)
s.set_reg(Register("cr0_lt"), error)
s.set_reg(Register("cr0_so"), error)
if maybe_dest_first:
s.set_reg_real(a.reg_ref(0), error, emit_exactly_once=True)
else:
s.write_statement(ExprStmt(error))
return Instruction(
mnemonic=mnemonic,
@@ -762,6 +908,8 @@ class PpcArch(Arch):
function_target=function_target,
is_conditional=is_conditional,
is_return=is_return,
is_store=is_store,
eval_fn=eval_fn,
)
ir_patterns = [
@@ -780,7 +928,7 @@ class PpcArch(Arch):
FloatishToUintPattern(),
]
instrs_ignore: InstrSet = {
instrs_ignore: Set[str] = {
"nop",
"b",
# Assume stmw/lmw are only used for saving/restoring saved regs
@@ -853,38 +1001,28 @@ class PpcArch(Arch):
"lfdux": lambda a: handle_loadx(a, type=Type.f64()),
}
instrs_branches: CmpInstrMap = {
instrs_branches: Dict[str, str] = {
# Branch instructions/pseudoinstructions
# Technically `bge` is defined as `cr0_gt || cr0_eq`; not as `!cr0_lt`
# This assumption may not hold if the bits are modified with instructions like
# `crand` which modify individual bits in CR.
"beq": lambda a: a.cmp_reg("cr0_eq"),
"bge": lambda a: a.cmp_reg("cr0_lt").negated(),
"bgt": lambda a: a.cmp_reg("cr0_gt"),
"ble": lambda a: a.cmp_reg("cr0_gt").negated(),
"blt": lambda a: a.cmp_reg("cr0_lt"),
"bne": lambda a: a.cmp_reg("cr0_eq").negated(),
"bns": lambda a: a.cmp_reg("cr0_so").negated(),
"bso": lambda a: a.cmp_reg("cr0_so"),
"bdnz.fictive": lambda a: a.cmp_reg("ctr"),
"bdz.fictive": lambda a: a.cmp_reg("ctr").negated(),
}
instrs_float_branches: InstrSet = {}
instrs_jumps: InstrSet = {
# Unconditional jumps
"b",
"blr",
"bctr",
}
instrs_fn_call: InstrSet = {
# Function call
"bl",
"blrl",
"bctrl",
# The `!` indicates that the condition in the register is negated
"beq": "cr0_eq",
"bge": "!cr0_lt",
"bgt": "cr0_gt",
"ble": "!cr0_gt",
"blt": "cr0_lt",
"bne": "!cr0_eq",
"bns": "!cr0_so",
"bso": "cr0_so",
"bdnz": "ctr",
"bdz": "!ctr",
"bdnz.fictive": "ctr",
"bdz.fictive": "!ctr",
}
instrs_no_dest: StmtInstrMap = {
"sync": lambda a: void_fn_op("MIPS2C_SYNC", []),
"isync": lambda a: void_fn_op("MIPS2C_SYNC", []),
"sync": lambda a: void_fn_op("M2C_SYNC", []),
"isync": lambda a: void_fn_op("M2C_SYNC", []),
}
instrs_dest_first_non_load: InstrMap = {
@@ -1082,12 +1220,7 @@ class PpcArch(Arch):
**instrs_load,
}
instrs_implicit_destination: ImplicitInstrMap = {
"mtlr": (Register("lr"), lambda a: a.reg(0)),
"mtctr": (Register("ctr"), lambda a: a.reg(0)),
}
instrs_ppc_compare: PpcCmpInstrMap = {
instrs_ppc_compare: Dict[str, Callable[[InstrArgs, str], Expression]] = {
# Integer (signed/unsigned)
"cmpw": lambda a, op: BinaryOp.sintptr_cmp(a.reg(1), op, a.reg(2)),
"cmpwi": lambda a, op: BinaryOp.sintptr_cmp(a.reg(1), op, a.imm(2)),

View File

@@ -8,11 +8,11 @@ from typing import Callable, Dict, List, Match, Optional, Set, Tuple, TypeVar, U
from .error import DecompFailure
from .options import Options
from .parse_instruction import (
from .asm_instruction import RegFormatter
from .instruction import (
ArchAsm,
Instruction,
InstructionMeta,
RegFormatter,
parse_instruction,
)
@@ -100,7 +100,7 @@ class AsmData:
@dataclass
class MIPSFile:
class AsmFile:
filename: str
functions: List[Function] = field(default_factory=list)
asm_data: AsmData = field(default_factory=AsmData)
@@ -276,14 +276,18 @@ def parse_incbin(
return None
def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> MIPSFile:
def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> AsmFile:
filename = Path(f.name).name
mips_file: MIPSFile = MIPSFile(filename)
defines: Dict[str, int] = options.preproc_defines
asm_file: AsmFile = AsmFile(filename)
ifdef_level: int = 0
ifdef_levels: List[int] = []
curr_section = ".text"
warnings: List[str] = []
defines: Dict[str, Optional[int]] = {
# NULL is a non-standard but common asm macro that expands to 0
"NULL": 0,
**options.preproc_defines,
}
# https://stackoverflow.com/a/241506
def re_comment_replacer(match: Match[str]) -> str:
@@ -301,7 +305,7 @@ def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> MIPSFile:
T = TypeVar("T")
def try_parse(parser: Callable[[], T], directive: str) -> T:
def try_parse(parser: Callable[[], T]) -> T:
try:
return parser()
except ValueError:
@@ -309,6 +313,12 @@ def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> MIPSFile:
f"Could not parse asm_data {directive} in {curr_section}: {line}"
)
def parse_int(w: str) -> int:
var_value = defines.get(w)
if var_value is not None:
return var_value
return int(w, 0)
for lineno, line in enumerate(f, 1):
# Check for goto markers before stripping comments
emit_goto = any(pattern in line for pattern in options.goto_patterns)
@@ -320,18 +330,18 @@ def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> MIPSFile:
def process_label(label: str, *, glabel: bool) -> None:
if curr_section == ".rodata":
mips_file.new_data_label(label, is_readonly=True, is_bss=False)
asm_file.new_data_label(label, is_readonly=True, is_bss=False)
elif curr_section == ".data":
mips_file.new_data_label(label, is_readonly=False, is_bss=False)
asm_file.new_data_label(label, is_readonly=False, is_bss=False)
elif curr_section == ".bss":
mips_file.new_data_label(label, is_readonly=False, is_bss=True)
asm_file.new_data_label(label, is_readonly=False, is_bss=True)
elif curr_section == ".text":
re_local = re_local_glabel if glabel else re_local_label
if label.startswith("."):
if mips_file.current_function is None:
if asm_file.current_function is None:
raise DecompFailure(f"Label {label} is not within a function!")
mips_file.new_label(label.lstrip("."))
elif re_local.match(label) and mips_file.current_function is not None:
asm_file.new_label(label.lstrip("."))
elif re_local.match(label) and asm_file.current_function is not None:
# Don't treat labels as new functions if they follow a
# specific naming pattern. This is used for jump table
# targets in both IDA and old n64split output.
@@ -339,9 +349,9 @@ def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> MIPSFile:
# file though, to avoid crashes due to unidentified
# functions. (Should possibly be generalized to cover any
# glabel that has a branch that goes across?)
mips_file.new_label(label)
asm_file.new_label(label)
else:
mips_file.new_function(label)
asm_file.new_function(label)
# Check for labels
while True:
@@ -358,23 +368,30 @@ def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> MIPSFile:
if not line:
continue
if line.startswith("."):
if "=" in line:
key, value = line.split("=", 1)
key = key.strip()
if " " not in key:
line = f".set {key}, {value}"
directive = line.split()[0]
if directive.startswith("."):
# Assembler directive.
if line.startswith(".ifdef") or line.startswith(".ifndef"):
if directive == ".ifdef" or directive == ".ifndef":
macro_name = line.split()[1]
if macro_name not in defines:
defines[macro_name] = 0
defines[macro_name] = None
add_warning(
warnings,
f"Note: assuming {macro_name} is unset for .ifdef, "
f"pass -D{macro_name}/-U{macro_name} to set/unset explicitly.",
)
level = defines[macro_name]
if line.startswith(".ifdef"):
level = 1 if defines[macro_name] is not None else 0
if directive == ".ifdef":
level = 1 - level
ifdef_level += level
ifdef_levels.append(level)
elif line.startswith(".if"):
elif directive.startswith(".if"):
macro_name = line.split()[1]
if macro_name == "0":
level = 1
@@ -385,93 +402,97 @@ def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> MIPSFile:
add_warning(warnings, f"Note: ignoring .if {macro_name} directive")
ifdef_level += level
ifdef_levels.append(level)
elif line.startswith(".else"):
elif directive == ".else":
level = ifdef_levels.pop()
ifdef_level -= level
level = 1 - level
ifdef_level += level
ifdef_levels.append(level)
elif line.startswith(".endif"):
elif directive == ".endif":
ifdef_level -= ifdef_levels.pop()
elif line.startswith(".macro"):
elif directive == ".macro":
ifdef_level += 1
elif line.startswith(".endm"):
elif directive == ".endm":
ifdef_level -= 1
elif ifdef_level == 0:
if line.startswith(".section"):
curr_section = line.split(" ")[1].split(",")[0]
if directive == ".section":
curr_section = line.split()[1].split(",")[0]
if curr_section in (".rdata", ".late_rodata", ".sdata2"):
curr_section = ".rodata"
if curr_section.startswith(".text"):
curr_section = ".text"
elif (
line.startswith(".rdata")
or line.startswith(".rodata")
or line.startswith(".late_rodata")
directive == ".rdata"
or directive == ".rodata"
or directive == ".late_rodata"
):
curr_section = ".rodata"
elif line.startswith(".data"):
elif directive == ".data":
curr_section = ".data"
elif line.startswith(".bss"):
elif directive == ".bss":
curr_section = ".bss"
elif line.startswith(".text"):
elif directive == ".text":
curr_section = ".text"
elif directive == ".set":
_, _, args_str = line.partition(" ")
args = split_arg_list(args_str)
if len(args) == 1:
# ".set noreorder" or similar, just ignore
pass
elif len(args) == 2:
defines[args[0]] = try_parse(lambda: parse_int(args[1]))
else:
raise DecompFailure(f"Could not parse {directive}: {line}")
elif curr_section in (".rodata", ".data", ".bss"):
directive, _, args_str = line.partition(" ")
_, _, args_str = line.partition(" ")
args = split_arg_list(args_str)
if directive in (".word", ".4byte"):
for w in args:
if not w or w[0].isdigit() or w[0] == "-":
ival = (
try_parse(lambda: int(w, 0), directive) & 0xFFFFFFFF
)
mips_file.new_data_bytes(struct.pack(">I", ival))
elif w == "NULL":
# NULL is a non-standard but common asm macro
# that expands to 0
mips_file.new_data_bytes(b"\0\0\0\0")
if not w or w[0].isdigit() or w[0] == "-" or w in defines:
ival = try_parse(lambda: parse_int(w)) & 0xFFFFFFFF
asm_file.new_data_bytes(struct.pack(">I", ival))
else:
mips_file.new_data_sym(w)
asm_file.new_data_sym(w)
elif directive in (".short", ".half", ".2byte"):
for w in args:
ival = try_parse(lambda: int(w, 0), directive) & 0xFFFF
mips_file.new_data_bytes(struct.pack(">H", ival))
ival = try_parse(lambda: parse_int(w)) & 0xFFFF
asm_file.new_data_bytes(struct.pack(">H", ival))
elif directive == ".byte":
for w in args:
ival = try_parse(lambda: int(w, 0), directive) & 0xFF
mips_file.new_data_bytes(bytes([ival]))
ival = try_parse(lambda: parse_int(w)) & 0xFF
asm_file.new_data_bytes(bytes([ival]))
elif directive == ".float":
for w in args:
fval = try_parse(lambda: float(w), directive)
mips_file.new_data_bytes(struct.pack(">f", fval))
fval = try_parse(lambda: float(w))
asm_file.new_data_bytes(struct.pack(">f", fval))
elif directive == ".double":
for w in args:
fval = try_parse(lambda: float(w), directive)
mips_file.new_data_bytes(struct.pack(">d", fval))
fval = try_parse(lambda: float(w))
asm_file.new_data_bytes(struct.pack(">d", fval))
elif directive in (".asci", ".asciz", ".ascii", ".asciiz"):
z = directive.endswith("z")
mips_file.new_data_bytes(
asm_file.new_data_bytes(
parse_ascii_directive(line, z), is_string=True
)
elif directive in (".space", ".skip"):
if len(args) == 2:
fill = try_parse(lambda: int(args[1], 0), directive) & 0xFF
fill = try_parse(lambda: parse_int(args[1])) & 0xFF
elif len(args) == 1:
fill = 0
else:
raise DecompFailure(
f"Could not parse asm_data {directive} in {curr_section}: {line}"
)
size = try_parse(lambda: int(args[0], 0), directive)
mips_file.new_data_bytes(bytes([fill] * size))
size = try_parse(lambda: parse_int(args[0]))
asm_file.new_data_bytes(bytes([fill] * size))
elif line.startswith(".incbin"):
data = parse_incbin(args, options, warnings)
if data is not None:
mips_file.new_data_bytes(data)
asm_file.new_data_bytes(data)
elif ifdef_level == 0:
parts = line.split()
if parts and parts[0] in ("glabel", "dlabel"):
if directive in ("glabel", "dlabel"):
parts = line.split()
if len(parts) >= 2:
process_label(parts[1], glabel=True)
@@ -482,16 +503,17 @@ def parse_file(f: typing.TextIO, arch: ArchAsm, options: Options) -> MIPSFile:
lineno=lineno,
synthetic=False,
)
if mips_file.current_function is not None:
reg_formatter = mips_file.current_function.reg_formatter
if asm_file.current_function is not None:
reg_formatter = asm_file.current_function.reg_formatter
else:
reg_formatter = RegFormatter()
instr = parse_instruction(line, meta, arch, reg_formatter)
mips_file.new_instruction(instr)
defined_vars = {k: v for k, v in defines.items() if v is not None}
instr = parse_instruction(line, meta, arch, reg_formatter, defined_vars)
asm_file.new_instruction(instr)
if warnings:
print("/*")
print("\n".join(warnings))
print("*/")
return mips_file
return asm_file

View File

@@ -1,13 +1,10 @@
"""Functions and classes useful for parsing an arbitrary MIPS instruction.
"""
"""Functions and classes useful for parsing an arbitrary assembly instruction."""
import abc
from contextlib import contextmanager
from dataclasses import dataclass, field, replace
from dataclasses import dataclass, field
import string
from typing import Dict, Iterator, List, Optional, Set, Union
from typing import Dict, List, Optional, Union
from .error import DecompFailure
from .options import Target
@dataclass(frozen=True)
@@ -110,66 +107,6 @@ Argument = Union[
]
@dataclass(frozen=True)
class StackLocation:
"""
Represents a word on the stack. Currently only used for pattern matching.
`symbolic_offset` represents a label offset that is only used in patterns,
to represent the "N" in arguments such as `(N+4)($sp)`.
"""
offset: int
symbolic_offset: Optional[str]
def __str__(self) -> str:
prefix = "" if self.symbolic_offset is None else f"{self.symbolic_offset}+"
return f"{prefix}{self.offset}($sp)"
def offset_as_arg(self) -> Argument:
if self.symbolic_offset is None:
return AsmLiteral(self.offset)
if self.offset == 0:
return AsmGlobalSymbol(self.symbolic_offset)
return BinOp(
lhs=AsmGlobalSymbol(self.symbolic_offset),
op="+",
rhs=AsmLiteral(self.offset),
)
@staticmethod
def from_offset(offset: Argument) -> Optional["StackLocation"]:
def align(x: int) -> int:
return x & ~3
if isinstance(offset, AsmLiteral):
return StackLocation(
offset=align(offset.value),
symbolic_offset=None,
)
if isinstance(offset, AsmGlobalSymbol):
return StackLocation(
offset=0,
symbolic_offset=offset.symbol_name,
)
if (
isinstance(offset, BinOp)
and offset.op in ("+", "-")
and isinstance(offset.lhs, AsmGlobalSymbol)
and isinstance(offset.rhs, AsmLiteral)
):
base = offset.rhs.value
if offset.op == "-":
base = -base
return StackLocation(
offset=align(base),
symbolic_offset=offset.lhs.symbol_name,
)
return None
Location = Union[Register, StackLocation]
@dataclass(frozen=True)
class AsmInstruction:
mnemonic: str
@@ -182,71 +119,6 @@ class AsmInstruction:
return f"{self.mnemonic} {args}"
@dataclass(frozen=True)
class InstructionMeta:
# True if the original asm line was marked with a goto pattern
emit_goto: bool
# Asm source filename & line number
filename: str
lineno: int
# True if the Instruction is not directly from the source asm
synthetic: bool
@staticmethod
def missing() -> "InstructionMeta":
return 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}"
@dataclass(frozen=True)
class Instruction:
mnemonic: str
args: List[Argument]
meta: InstructionMeta
# Track register and stack dependencies
# An Instruction evaluates by reading from `inputs`, invalidating `clobbers`,
# then writing to `outputs` (in that order)
inputs: List[Location]
clobbers: List[Location]
outputs: List[Location]
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
# True if the Instruction was part of a matched IR pattern, but not elided
in_pattern: 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:
return self.mnemonic
args = ", ".join(str(arg) for arg in self.args)
return f"{self.mnemonic} {args}"
def arch_mnemonic(self, arch: "ArchAsm") -> str:
"""Combine architecture name with mnemonic for pattern matching"""
return f"{arch.arch}:{self.mnemonic}"
class ArchAsmParsing(abc.ABC):
"""Arch-specific information needed to parse asm."""
@@ -258,34 +130,6 @@ class ArchAsmParsing(abc.ABC):
...
class ArchAsm(ArchAsmParsing):
"""Arch-specific information that relates to the asm level. Extends the above."""
arch: Target.ArchEnum
stack_pointer_reg: Register
frame_pointer_reg: Optional[Register]
return_address_reg: Register
base_return_regs: List[Register]
all_return_regs: List[Register]
argument_regs: List[Register]
simple_temp_regs: List[Register]
temp_regs: List[Register]
saved_regs: List[Register]
all_regs: List[Register]
@abc.abstractmethod
def missing_return(self) -> List[Instruction]:
...
@abc.abstractmethod
def parse(
self, mnemonic: str, args: List[Argument], meta: InstructionMeta
) -> Instruction:
...
class NaiveParsingArch(ArchAsmParsing):
"""A fake arch that can parse asm in a naive fashion. Used by the pattern matching
machinery to reduce arch dependence."""
@@ -354,11 +198,13 @@ def parse_number(elems: List[str]) -> int:
return ret
def constant_fold(arg: Argument) -> Argument:
def constant_fold(arg: Argument, defines: Dict[str, int]) -> Argument:
if isinstance(arg, AsmGlobalSymbol) and arg.symbol_name in defines:
return AsmLiteral(defines[arg.symbol_name])
if not isinstance(arg, BinOp):
return arg
lhs = constant_fold(arg.lhs)
rhs = constant_fold(arg.rhs)
lhs = constant_fold(arg.lhs, defines)
rhs = constant_fold(arg.rhs, defines)
if isinstance(lhs, AsmLiteral) and isinstance(rhs, AsmLiteral):
if arg.op == "+":
return AsmLiteral(lhs.value + rhs.value)
@@ -372,7 +218,7 @@ def constant_fold(arg: Argument) -> Argument:
return AsmLiteral(lhs.value << rhs.value)
if arg.op == "&":
return AsmLiteral(lhs.value & rhs.value)
return arg
return BinOp(arg.op, lhs, rhs)
def replace_bare_reg(
@@ -396,7 +242,12 @@ def get_jump_target(label: Argument) -> JumpTarget:
# Main parser.
def parse_arg_elems(
arg_elems: List[str], arch: ArchAsmParsing, reg_formatter: RegFormatter
arg_elems: List[str],
arch: ArchAsmParsing,
reg_formatter: RegFormatter,
defines: Dict[str, int],
*,
top_level: bool,
) -> Optional[Argument]:
value: Optional[Argument] = None
@@ -442,9 +293,11 @@ def parse_arg_elems(
assert macro_name in ("hi", "lo")
expect("(")
# Get the argument of the macro (which must exist).
m = parse_arg_elems(arg_elems, arch, reg_formatter)
m = parse_arg_elems(
arg_elems, arch, reg_formatter, defines, top_level=False
)
assert m is not None
m = constant_fold(m)
m = constant_fold(m, defines)
expect(")")
# A macro may be the lhs of an AsmAddressMode, so we don't return here.
value = Macro(macro_name, m)
@@ -456,23 +309,31 @@ def parse_arg_elems(
assert value is None
value = AsmLiteral(parse_number(arg_elems))
elif tok == "(":
if value is not None and not top_level:
# Only allow parsing AsmAddressMode at top level. This makes us parse
# a+b(c) as (a+b)(c) instead of a+(b(c)).
break
# Address mode or binary operation.
expect("(")
# Get what is being dereferenced.
rhs = parse_arg_elems(arg_elems, arch, reg_formatter)
rhs = parse_arg_elems(
arg_elems, arch, reg_formatter, defines, top_level=False
)
assert rhs is not None
expect(")")
if isinstance(rhs, BinOp):
# Binary operation.
assert value is None
value = constant_fold(rhs)
value = constant_fold(rhs, defines)
else:
# Address mode.
assert top_level
rhs = replace_bare_reg(rhs, arch, reg_formatter)
if rhs == AsmLiteral(0):
rhs = Register("zero")
assert isinstance(rhs, Register)
value = AsmAddressMode(value or AsmLiteral(0), rhs)
value = constant_fold(value or AsmLiteral(0), defines)
value = AsmAddressMode(value, rhs)
elif tok == '"':
# Quoted global symbol.
expect('"')
@@ -506,15 +367,17 @@ def parse_arg_elems(
)
value = Macro("sda21", value)
else:
rhs = parse_arg_elems(arg_elems, arch, reg_formatter)
rhs = parse_arg_elems(
arg_elems, arch, reg_formatter, defines, top_level=False
)
assert rhs is not None
if isinstance(rhs, BinOp) and rhs.op == "*":
rhs = constant_fold(rhs)
rhs = constant_fold(rhs, defines)
if isinstance(rhs, BinOp) and isinstance(
constant_fold(rhs), AsmLiteral
constant_fold(rhs, defines), AsmLiteral
):
raise DecompFailure(
"Math is too complicated for mips_to_c. Try adding parentheses."
"Math is too complicated for m2c. Try adding parentheses."
)
if isinstance(rhs, AsmLiteral) and isinstance(
value, AsmSectionGlobalSymbol
@@ -526,6 +389,9 @@ def parse_arg_elems(
value = BinOp(op, value, rhs)
elif tok == "@":
# A relocation (e.g. (...)@ha or (...)@l).
if not top_level:
# Parse a+b@l as (a+b)@l, not a+(b@l)
break
arg_elems.pop(0)
reloc_name = parse_word(arg_elems)
assert reloc_name in ("h", "ha", "l", "sda2", "sda21")
@@ -538,52 +404,32 @@ def parse_arg_elems(
def parse_args(
args: str, arch: ArchAsmParsing, reg_formatter: RegFormatter
args: str,
arch: ArchAsmParsing,
reg_formatter: RegFormatter,
defines: Dict[str, int],
) -> List[Argument]:
arg_elems: List[str] = list(args.strip())
output = []
while arg_elems:
ret = parse_arg_elems(arg_elems, arch, reg_formatter)
ret = parse_arg_elems(arg_elems, arch, reg_formatter, defines, top_level=True)
assert ret is not None
output.append(replace_bare_reg(constant_fold(ret), arch, reg_formatter))
output.append(
replace_bare_reg(constant_fold(ret, defines), arch, reg_formatter)
)
return output
def parse_asm_instruction(
line: str, arch: ArchAsmParsing, reg_formatter: RegFormatter
line: str,
arch: ArchAsmParsing,
reg_formatter: RegFormatter,
defines: Dict[str, int],
) -> AsmInstruction:
# First token is instruction name, rest is args.
line = line.strip()
mnemonic, _, args_str = line.partition(" ")
# Parse arguments.
args = parse_args(args_str, arch, reg_formatter)
args = parse_args(args_str, arch, reg_formatter, defines)
instr = AsmInstruction(mnemonic, args)
return arch.normalize_instruction(instr)
def parse_instruction(
line: str, meta: InstructionMeta, arch: ArchAsm, reg_formatter: RegFormatter
) -> Instruction:
try:
base = parse_asm_instruction(line, arch, reg_formatter)
return arch.parse(base.mnemonic, base.args, meta)
except Exception:
raise DecompFailure(f"Failed to parse instruction {meta.loc_str()}: {line}")
@dataclass
class InstrProcessingFailure(Exception):
instr: Instruction
def __str__(self) -> str:
return f"Error while processing instruction:\n{self.instr}"
@contextmanager
def current_instr(instr: Instruction) -> Iterator[None]:
"""Mark an instruction as being the one currently processed, for the
purposes of error messages. Use like |with current_instr(instr): ...|"""
try:
yield
except Exception as e:
raise InstrProcessingFailure(instr) from e

View File

@@ -2,23 +2,25 @@ import abc
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple, TypeVar, Union
from .parse_file import Label
from .parse_instruction import (
from .asm_file import Label
from .asm_instruction import (
Argument,
ArchAsm,
AsmAddressMode,
AsmGlobalSymbol,
AsmInstruction,
AsmLiteral,
BinOp,
Instruction,
InstructionMeta,
JumpTarget,
NaiveParsingArch,
Register,
RegFormatter,
parse_asm_instruction,
)
from .instruction import (
ArchAsm,
Instruction,
InstructionMeta,
)
BodyPart = Union[Instruction, Label]
@@ -37,7 +39,7 @@ def make_pattern(*parts: str) -> Pattern:
elif part.endswith(":"):
ret.append((Label(part.strip(".:")), optional))
else:
ins = parse_asm_instruction(part, NaiveParsingArch(), RegFormatter())
ins = parse_asm_instruction(part, NaiveParsingArch(), RegFormatter(), {})
ret.append((ins, optional))
return ret

View File

@@ -213,7 +213,7 @@ def is_unk_type(type: CType, typemap: TypeMap) -> bool:
if declname.startswith("unk_") or declname.startswith("pad"):
return True
# Check for types which are typedefs starting with "UNK_" or "MIPS2C_UNK",
# Check for types which are typedefs starting with "UNK_" or "M2C_UNK",
# or are arrays/pointers to one of these types.
while True:
if (
@@ -223,7 +223,7 @@ def is_unk_type(type: CType, typemap: TypeMap) -> bool:
and type.type.names[0] in typemap.typedefs
):
type_name = type.type.names[0]
if type_name.startswith("UNK_") or type_name.startswith("MIPS2C_UNK"):
if type_name.startswith("UNK_") or type_name.startswith("M2C_UNK"):
return True
type = typemap.typedefs[type_name]
elif isinstance(type, (PtrDecl, ArrayDecl)):
@@ -543,7 +543,7 @@ def do_parse_struct(struct: Union[ca.Struct, ca.Union], typemap: TypeMap) -> Str
def add_builtin_typedefs(source: str) -> str:
"""Add built-in typedefs to the source code (mips_to_c emits those, so it makes
"""Add built-in typedefs to the source code (m2c emits those, so it makes
sense to pre-define them to simplify hand-written C contexts)."""
typedefs = {
"u8": "unsigned char",
@@ -578,7 +578,7 @@ def strip_comments(text: str) -> str:
def strip_macro_defs(text: str) -> str:
"""Strip macro definitions from C source. mips_to_c does not run the preprocessor,
"""Strip macro definitions from C source. m2c does not run the preprocessor,
for a bunch of reasons:
- we don't know what include directories to use

View File

@@ -17,23 +17,25 @@ from typing import (
Union,
)
from .error import DecompFailure
from .options import Formatter, Target
from .parse_file import AsmData, Function, Label
from .parse_instruction import (
ArchAsm,
from .error import DecompFailure, static_assert_unreachable
from .options import Formatter, Options, Target
from .asm_file import AsmData, Function, Label
from .asm_instruction import (
AsmAddressMode,
AsmGlobalSymbol,
AsmInstruction,
AsmLiteral,
BinOp,
Instruction,
InstructionMeta,
JumpTarget,
Location,
Macro,
Register,
)
from .instruction import (
ArchAsm,
Instruction,
InstructionMeta,
Location,
)
from .asm_pattern import simplify_patterns, AsmPattern
@@ -105,6 +107,7 @@ class Block:
label: Optional[Label]
approx_label_name: str
instruction_refs: List[InstrRef] = field(default_factory=list)
is_safeguard: bool = False
# block_info is actually an Optional[BlockInfo], set by translate.py for
# non-TerminalNode's, but due to circular dependencies we cannot type it
@@ -130,54 +133,57 @@ class Block:
@dataclass(eq=False)
class BlockBuilder:
curr_index: int = 0
curr_label: Optional[Label] = None
last_label_name: str = "initial"
label_counter: int = 0
curr_instructions: List[Instruction] = field(default_factory=list)
blocks: List[Block] = field(default_factory=list)
_curr_index: int = 0
_curr_label: Optional[Label] = None
_last_label_name: str = "initial"
_label_counter: int = 0
_curr_instructions: List[Instruction] = field(default_factory=list)
_blocks: List[Block] = field(default_factory=list)
def new_block(self) -> Optional[Block]:
if len(self.curr_instructions) == 0:
if len(self._curr_instructions) == 0:
return None
label_name = self.last_label_name
if self.label_counter > 0:
label_name += f".{self.label_counter}"
block = Block(self.curr_index, self.curr_label, label_name)
label_name = self._last_label_name
if self._label_counter > 0:
label_name += f".{self._label_counter}"
block = Block(self._curr_index, self._curr_label, label_name)
block.instruction_refs.extend(
InstrRef(i, block) for i in self.curr_instructions
InstrRef(i, block) for i in self._curr_instructions
)
self.blocks.append(block)
self._blocks.append(block)
self.curr_index += 1
self.curr_label = None
self.label_counter += 1
self.curr_instructions = []
self._curr_index += 1
self._curr_label = None
self._label_counter += 1
self._curr_instructions = []
return block
def add_instruction(self, instruction: Instruction) -> None:
self.curr_instructions.append(instruction)
self._curr_instructions.append(instruction)
def set_label(self, label: Label) -> None:
if label == self.curr_label:
if label == self._curr_label:
# It's okay to repeat a label (e.g. once with glabel, once as a
# standard label -- this often occurs for switches).
return
# We could support multiple labels at the same position, and output
# empty blocks. For now we don't, however.
if self.curr_label:
if self._curr_label:
raise DecompFailure(
"A block is currently not allowed to have more than one label,\n"
f"but {self.curr_label.name}/{label.name} is given two."
f"but {self._curr_label.name}/{label.name} is given two."
)
self.curr_label = label
self.last_label_name = label.name
self.label_counter = 0
self._curr_label = label
self._last_label_name = label.name
self._label_counter = 0
def is_empty(self) -> bool:
return not self._blocks and not self._curr_instructions
def get_blocks(self) -> List[Block]:
return self.blocks
return self._blocks
def verify_no_trailing_delay_slot(function: Function) -> None:
@@ -500,22 +506,24 @@ def build_blocks(
else:
process_no_delay_slots(item)
if block_builder.is_empty():
raise DecompFailure(
f"Function {function.name} contains no instructions. Maybe it is rodata?"
)
if fragment:
# If we're parsing an asm fragment instead of a full function,
# then it does not need to end in a return or jump
block_builder.new_block()
return block_builder.get_blocks()
if block_builder.curr_label:
# As an easy-to-implement safeguard, check that the current block is
# anonymous (jump instructions create new anonymous blocks, so if it's
# not we must be missing a return instruction).
label = block_builder.curr_label.name
return_instrs = arch.missing_return()
print(f'Warning: missing "{return_instrs[0]}" in last block (.{label}).\n')
for instr in return_instrs:
block_builder.add_instruction(instr)
block_builder.new_block()
# Add an extra return statement as a safeguard. We emit a warning later on if
# this is reachable.
for instr in arch.missing_return():
block_builder.add_instruction(instr)
emitted_block = block_builder.new_block()
assert emitted_block is not None
emitted_block.is_safeguard = True
if cond_return_target is not None:
# Add an empty return block at the end of the function
@@ -524,7 +532,6 @@ def build_blocks(
block_builder.add_instruction(instr)
block_builder.new_block()
# Throw away whatever is past the last return instruction and return what we have.
return block_builder.get_blocks()
@@ -831,15 +838,13 @@ def build_graph_from_block(
return new_node
def is_trivial_return_block(block: Block) -> bool:
def is_trivial_return_block(block: Block, arch: ArchFlowGraph) -> bool:
# A heuristic for when a block is a simple "early-return" block.
# This could be improved.
stores = ["sb", "sh", "sw", "swc1", "sdc1", "swr", "swl", "jal"]
return_regs = [Register("v0"), Register("f0")]
for instr in block.instructions:
if instr.mnemonic in stores:
if instr.is_store or instr.function_target is not None:
return False
if any(reg in instr.args for reg in return_regs):
if any(reg in arch.all_return_regs for reg in instr.inputs + instr.outputs):
return False
return True
@@ -870,11 +875,6 @@ def build_nodes(
terminal_node = TerminalNode.terminal()
graph: List[Node] = [terminal_node]
if not blocks:
raise DecompFailure(
f"Function {function.name} contains no instructions. Maybe it is rodata?"
)
# Fragments do not need a ReturnNode, they can directly fall through to the TerminalNode
if fragment:
blocks.append(terminal_node.block)
@@ -892,7 +892,17 @@ def build_nodes(
return graph
def is_premature_return(node: Node, edge: Node, nodes: List[Node]) -> bool:
def warn_on_safeguard_use(nodes: List[Node], arch: ArchFlowGraph) -> None:
node = next((node for node in nodes if node.block.is_safeguard), None)
if node:
label = node.block.approx_label_name
return_instrs = arch.missing_return()
print(f'Warning: missing "{return_instrs[0]}" in last block (.{label}).\n')
def is_premature_return(
node: Node, edge: Node, nodes: List[Node], arch: ArchFlowGraph
) -> bool:
"""Check whether a given edge in the flow graph is an early return."""
if len(nodes) < 3:
return False
@@ -900,7 +910,7 @@ def is_premature_return(node: Node, edge: Node, nodes: List[Node]) -> bool:
assert isinstance(terminal_node, TerminalNode)
if not isinstance(edge, ReturnNode) or edge != final_return_node:
return False
if not is_trivial_return_block(edge.block):
if not is_trivial_return_block(edge.block, arch):
# Only trivial return blocks can be used for premature returns,
# hopefully.
return False
@@ -917,7 +927,7 @@ def is_premature_return(node: Node, edge: Node, nodes: List[Node]) -> bool:
return node != antepenultimate_node
def duplicate_premature_returns(nodes: List[Node]) -> List[Node]:
def duplicate_premature_returns(nodes: List[Node], arch: ArchFlowGraph) -> List[Node]:
"""For each jump to an early return node, create a duplicate return node
for it to jump to. This ensures nice nesting for the if_statements code,
and avoids a phi node for the return value."""
@@ -927,7 +937,7 @@ def duplicate_premature_returns(nodes: List[Node]) -> List[Node]:
if (
isinstance(node, BasicNode)
and not node.emit_goto
and is_premature_return(node, node.successor, nodes)
and is_premature_return(node, node.successor, nodes, arch)
):
assert isinstance(node.successor, ReturnNode)
index += 1
@@ -1468,9 +1478,11 @@ def build_flowgraph(
for analyzing IR patterns which do not need to be normalized in the same way.
"""
blocks = build_blocks(function, asm_data, arch, fragment=fragment)
nodes = build_nodes(function, blocks, asm_data, arch, fragment=fragment)
warn_on_safeguard_use(nodes, arch)
if not fragment:
nodes = duplicate_premature_returns(nodes)
nodes = duplicate_premature_returns(nodes, arch)
compute_relations(nodes)
if not fragment:
@@ -1485,7 +1497,9 @@ def build_flowgraph(
return flow_graph
def visualize_flowgraph(flow_graph: FlowGraph) -> str:
def visualize_flowgraph(
flow_graph: FlowGraph, viz_type: Options.VisualizeTypeEnum
) -> str:
import graphviz as g
fmt = Formatter(debug=True)
@@ -1498,32 +1512,40 @@ def visualize_flowgraph(flow_graph: FlowGraph) -> str:
"fontname": "Monospace",
},
)
for node in flow_graph.nodes:
block_info: Optional[Any] = node.block.block_info
asm_label = ""
c_label = ""
# In Graphviz, "\l" makes the preceeding text left-aligned, and inserts a newline
label = f"// Node {node.name()}\l"
if block_info:
label += "".join(
asm_label = "".join(
f"{'*' if i.meta.synthetic else '&nbsp;'} {i}\l"
for i in node.block.instructions
)
c_label = "".join(
w.format(fmt) + "\l" for w in block_info.to_write if w.should_write()
)
dot.node(node.name())
if isinstance(node, BasicNode):
dot.edge(node.name(), node.successor.name(), color="black")
elif isinstance(node, ConditionalNode):
if block_info:
label += f"if ({block_info.branch_condition.format(fmt)})\l"
c_label += f"if ({block_info.branch_condition.format(fmt)})\l"
dot.edge(node.name(), node.fallthrough_edge.name(), label="F", color="blue")
dot.edge(node.name(), node.conditional_edge.name(), label="T", color="red")
elif isinstance(node, ReturnNode):
if block_info and block_info.return_value:
label += f"return ({block_info.return_value.format(fmt)});\l"
c_label += f"return ({block_info.return_value.format(fmt)});\l"
else:
label += "return;\l"
c_label += "return;\l"
dot.edge(node.name(), node.terminal.name())
elif isinstance(node, SwitchNode):
assert block_info is not None
switch_control = block_info.switch_control
label += f"switch ({switch_control.control_expr.format(fmt)})\l"
c_label += f"switch ({switch_control.control_expr.format(fmt)})\l"
for i, case in enumerate(node.cases):
dot.edge(
node.name(),
@@ -1533,7 +1555,22 @@ def visualize_flowgraph(flow_graph: FlowGraph) -> str:
)
else:
assert isinstance(node, TerminalNode)
label += "// exit\l"
asm_label += "// exit\l"
c_label += "// exit\l"
line_label = ""
first_instr = next(node.block.instructions, None)
if first_instr is not None and first_instr.meta.lineno > 0:
line_label = f" (line {first_instr.meta.lineno})"
label = f"// Node {node.name()}{line_label}\l"
if viz_type == Options.VisualizeTypeEnum.ASM:
label += asm_label
elif viz_type == Options.VisualizeTypeEnum.C:
label += c_label
else:
raise ValueError("Invalid viz_type: {viz_type!r}")
dot.node(node.name(), label=label)
svg_bytes: bytes = dot.pipe("svg")
return svg_bytes.decode("utf-8", "replace")

View File

@@ -1,6 +1,6 @@
from collections import defaultdict
from dataclasses import dataclass, field, replace
from typing import Dict, List, Optional, Set, Tuple, Union
from typing import DefaultDict, Dict, List, Optional, Set, Tuple, Union
from .flow_graph import (
BasicNode,
@@ -38,7 +38,7 @@ class Context:
options: Options
is_void: bool = True
switch_nodes: Dict[Node, int] = field(default_factory=dict)
case_nodes: Dict[Node, List[Tuple[int, str]]] = field(
case_nodes: DefaultDict[Node, List[Tuple[int, str]]] = field(
default_factory=lambda: defaultdict(list)
)
goto_nodes: Set[Node] = field(default_factory=set)
@@ -87,6 +87,12 @@ class IfElseStatement:
return if_str
def comments_for_switch(index: int) -> List[str]:
if index == 0:
return []
return [f"switch {index}"]
@dataclass
class SwitchStatement:
jump: SwitchControl
@@ -103,20 +109,22 @@ class SwitchStatement:
comments = []
body_is_empty = self.body.is_empty()
if self.index > 0:
comments.append(f"switch {self.index}")
comments.extend(comments_for_switch(self.index))
if self.jump.is_irregular:
comments.append("irregular")
elif not self.jump.jump_table:
comments.append("unable to parse jump table")
elif body_is_empty:
comments.append(f"jump table: {self.jump.jump_table.symbol_name}")
suffix = ";" if body_is_empty else " {"
lines.append(
fmt.with_comments(
f"switch ({format_expr(self.jump.control_expr, fmt)}){suffix}", comments
)
)
if not body_is_empty:
head = f"switch ({format_expr(self.jump.control_expr, fmt)})"
if body_is_empty:
lines.append(fmt.with_comments(f"{head};", comments))
else:
if fmt.coding_style.newline_after_if:
lines.append(fmt.with_comments(f"{head}", comments))
lines.append(fmt.indent("{"))
else:
lines.append(fmt.with_comments(f"{head} {{", comments))
with fmt.indented():
lines.append(self.body.format(fmt))
lines.append(fmt.indent("}"))
@@ -126,11 +134,12 @@ class SwitchStatement:
@dataclass
class SimpleStatement:
contents: Optional[Union[str, TrStatement]]
comment: Optional[str] = None
comments: List[str] = field(default_factory=list)
is_jump: bool = False
indent: int = 0
def should_write(self) -> bool:
return self.contents is not None or self.comment is not None
return self.contents is not None or bool(self.comments)
def format(self, fmt: Formatter) -> str:
if self.contents is None:
@@ -140,16 +149,11 @@ class SimpleStatement:
else:
content = self.contents.format(fmt)
if self.comment is not None:
comments = [self.comment]
else:
comments = []
return fmt.with_comments(content, comments)
return fmt.with_comments(content, self.comments, indent=self.indent)
def clear(self) -> None:
self.contents = None
self.comment = None
self.comments = []
@dataclass
@@ -158,15 +162,15 @@ class LabelStatement:
node: Node
def should_write(self) -> bool:
return (
self.node in self.context.goto_nodes or self.node in self.context.case_nodes
return self.node in self.context.goto_nodes or bool(
self.context.case_nodes.get(self.node)
)
def format(self, fmt: Formatter) -> str:
lines = []
if self.node in self.context.case_nodes:
for (switch, case_label) in self.context.case_nodes[self.node]:
comments = [f"switch {switch}"] if switch != 0 else []
comments = comments_for_switch(switch)
lines.append(fmt.with_comments(f"{case_label}:", comments, indent=-1))
if self.node in self.context.goto_nodes:
lines.append(f"{label_for_node(self.context, self.node)}:")
@@ -229,7 +233,7 @@ class Body:
self.statements.append(statement)
def add_comment(self, contents: str) -> None:
self.add_statement(SimpleStatement(None, comment=contents))
self.add_statement(SimpleStatement(None, comments=[contents]))
def add_if_else(self, if_else: IfElseStatement) -> None:
if if_else.else_body is None or if_else.if_body.ends_in_jump():
@@ -543,8 +547,8 @@ class Bounds:
conditional branches.
"""
lower: int = -(2 ** 31) # `INT32_MAX`
upper: int = (2 ** 32) - 1 # `UINT32_MAX`
lower: int = -(2**31) # `INT32_MAX`
upper: int = (2**32) - 1 # `UINT32_MAX`
holes: Set[int] = field(default_factory=set)
def __post_init__(self) -> None:
@@ -1056,6 +1060,22 @@ def build_switch_statement(
"""
switch_body = Body(print_node_comment=context.options.debug)
# If there are any case labels to jump to the `end` node immediately after the
# switch block, emit them as `case ...: break;` at the start of the switch block
# instead. This avoids having "dangling" `case ...:` labels outside of the block.
remaining_labels = []
for index, case_label in context.case_nodes[end]:
if index == switch_index:
comments = comments_for_switch(switch_index)
switch_body.add_statement(
SimpleStatement(f"{case_label}:", comments=comments, indent=-1)
)
else:
remaining_labels.append((index, case_label))
if len(remaining_labels) != len(context.case_nodes[end]):
switch_body.add_statement(SimpleStatement(f"break;", is_jump=True))
context.case_nodes[end] = remaining_labels
# Order case blocks by their position in the asm, not by their order in the jump table
# (but use the order in the jump table to break ties)
sorted_cases = sorted(
@@ -1271,7 +1291,7 @@ def build_flowgraph_between(
def build_naive(context: Context, nodes: List[Node]) -> Body:
"""Naive procedure for generating output with only gotos for control flow.
Used for --no-ifs, when the regular if_statements code fails."""
Used for --gotos-only, when the regular if_statements code fails."""
body = Body(print_node_comment=context.options.debug)
@@ -1424,44 +1444,34 @@ def get_function_text(function_info: FunctionInfo, options: Options) -> str:
local_vars = function_info.stack_info.local_vars
# GCC's stack is ordered low-to-high (e.g. `int sp10; int sp14;`)
# IDO's stack is ordered high-to-low (e.g. `int sp14; int sp10;`)
if options.target.compiler == Target.CompilerEnum.IDO:
# IDO's and MWCC's stack is ordered high-to-low (e.g. `int sp14; int sp10;`)
if options.target.compiler != Target.CompilerEnum.GCC:
local_vars = local_vars[::-1]
for local_var in local_vars:
type_decl = local_var.toplevel_decl(fmt)
if type_decl is not None:
comment = None
comments = []
if local_var.value in function_info.stack_info.weak_stack_var_locations:
comment = "compiler-managed"
comments = ["compiler-managed"]
function_lines.append(
SimpleStatement(f"{type_decl};", comment=comment).format(fmt)
SimpleStatement(f"{type_decl};", comments=comments).format(fmt)
)
any_decl = True
# With reused temps (no longer used), we can get duplicate declarations,
# hence the use of a set here.
temp_decls = set()
for temp_var in function_info.stack_info.temp_vars:
if temp_var.need_decl():
expr = temp_var.expr
type_decl = expr.type.to_decl(expr.var.format(fmt), fmt)
temp_decls.add(f"{type_decl};")
temp_decls = []
for var in function_info.stack_info.temp_vars:
if var.is_emitted:
type_decl = var.type.to_decl(var.format(fmt), fmt)
temp_decls.append(f"{type_decl};")
any_decl = True
for decl in sorted(temp_decls):
function_lines.append(SimpleStatement(decl).format(fmt))
for phi_var in function_info.stack_info.phi_vars:
for phi_var in function_info.stack_info.naive_phi_vars:
type_decl = phi_var.type.to_decl(phi_var.get_var_name(), fmt)
function_lines.append(SimpleStatement(f"{type_decl};").format(fmt))
any_decl = True
for reg_var in function_info.stack_info.reg_vars.values():
if reg_var.reg not in function_info.stack_info.used_reg_vars:
continue
type_decl = reg_var.type.to_decl(reg_var.format(fmt), fmt)
function_lines.append(SimpleStatement(f"{type_decl};").format(fmt))
any_decl = True
# Create a variable to cast the original first argument to the assumed type
if function_info.stack_info.replace_first_arg is not None:
assert len(function_info.stack_info.arguments) >= 1

214
backend/m2c/src/instruction.py generated Normal file
View File

@@ -0,0 +1,214 @@
import abc
from contextlib import contextmanager
from dataclasses import dataclass, replace
from typing import Callable, Dict, Iterator, List, Optional, Union
from .error import DecompFailure
from .options import Target
from .asm_instruction import (
ArchAsmParsing,
Argument,
AsmGlobalSymbol,
AsmLiteral,
BinOp,
JumpTarget,
RegFormatter,
Register,
parse_asm_instruction,
)
@dataclass(frozen=True)
class StackLocation:
"""
Represents a word on the stack. Currently only used for pattern matching.
`symbolic_offset` represents a label offset that is only used in patterns,
to represent the "N" in arguments such as `(N+4)($sp)`.
"""
offset: int
symbolic_offset: Optional[str]
def __str__(self) -> str:
prefix = "" if self.symbolic_offset is None else f"{self.symbolic_offset}+"
return f"{prefix}{self.offset}($sp)"
def offset_as_arg(self) -> Argument:
if self.symbolic_offset is None:
return AsmLiteral(self.offset)
if self.offset == 0:
return AsmGlobalSymbol(self.symbolic_offset)
return BinOp(
lhs=AsmGlobalSymbol(self.symbolic_offset),
op="+",
rhs=AsmLiteral(self.offset),
)
@staticmethod
def from_offset(offset: Argument) -> Optional["StackLocation"]:
def align(x: int) -> int:
return x & ~3
if isinstance(offset, AsmLiteral):
return StackLocation(
offset=align(offset.value),
symbolic_offset=None,
)
if isinstance(offset, AsmGlobalSymbol):
return StackLocation(
offset=0,
symbolic_offset=offset.symbol_name,
)
if (
isinstance(offset, BinOp)
and offset.op in ("+", "-")
and isinstance(offset.lhs, AsmGlobalSymbol)
and isinstance(offset.rhs, AsmLiteral)
):
base = offset.rhs.value
if offset.op == "-":
base = -base
return StackLocation(
offset=align(base),
symbolic_offset=offset.lhs.symbol_name,
)
return None
Location = Union[Register, StackLocation]
@dataclass(frozen=True)
class InstructionMeta:
# True if the original asm line was marked with a goto pattern
emit_goto: bool
# Asm source filename & line number
filename: str
lineno: int
# True if the Instruction is not directly from the source asm
synthetic: bool
@staticmethod
def missing() -> "InstructionMeta":
return 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}"
@dataclass(frozen=True, eq=False)
class Instruction:
mnemonic: str
args: List[Argument]
meta: InstructionMeta
# Track register and stack dependencies
# An Instruction evaluates by reading from `inputs`, invalidating `clobbers`,
# then writing to `outputs` (in that order)
inputs: List[Location]
clobbers: List[Location]
outputs: List[Location]
# This should be typed as `eval_fn: Optional[Callable[[NodeState, InstrArgs], object]]`
# but this use classes that are defined in translate.py. We're unable to use correct
# types here without creating circular dependencies.
# The return value is ignored, but is typed as `object` so lambdas are more ergonomic.
# This member should only be accessed by `evaluate_instruction`.
eval_fn: Optional[Callable[..., object]]
jump_target: Optional[Union[JumpTarget, Register]] = None
function_target: Optional[Union[AsmGlobalSymbol, Register]] = None
is_conditional: bool = False
is_return: bool = False
is_store: 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
# True if the Instruction was part of a matched IR pattern, but not elided
in_pattern: 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:
return self.mnemonic
args = ", ".join(str(arg) for arg in self.args)
return f"{self.mnemonic} {args}"
def arch_mnemonic(self, arch: "ArchAsm") -> str:
"""Combine architecture name with mnemonic for pattern matching"""
return f"{arch.arch}:{self.mnemonic}"
class ArchAsm(ArchAsmParsing):
"""Arch-specific information that relates to the asm level. Extends ArchAsmParsing."""
arch: Target.ArchEnum
stack_pointer_reg: Register
frame_pointer_reg: Optional[Register]
return_address_reg: Register
base_return_regs: List[Register]
all_return_regs: List[Register]
argument_regs: List[Register]
simple_temp_regs: List[Register]
temp_regs: List[Register]
saved_regs: List[Register]
all_regs: List[Register]
@abc.abstractmethod
def missing_return(self) -> List[Instruction]:
...
@abc.abstractmethod
def parse(
self, mnemonic: str, args: List[Argument], meta: InstructionMeta
) -> Instruction:
...
def parse_instruction(
line: str,
meta: InstructionMeta,
arch: ArchAsm,
reg_formatter: RegFormatter,
defines: Dict[str, int],
) -> Instruction:
try:
base = parse_asm_instruction(line, arch, reg_formatter, defines)
return arch.parse(base.mnemonic, base.args, meta)
except Exception as e:
msg = f"Failed to parse instruction {meta.loc_str()}: {line}"
if isinstance(e, DecompFailure):
msg += "\n\n" + e.message
raise DecompFailure(msg)
@dataclass
class InstrProcessingFailure(Exception):
instr: Instruction
def __str__(self) -> str:
return f"Error while processing instruction:\n{self.instr}"
@contextmanager
def current_instr(instr: Instruction) -> Iterator[None]:
"""Mark an instruction as being the one currently processed, for the
purposes of error messages. Use like |with current_instr(instr): ...|"""
try:
yield
except Exception as e:
raise InstrProcessingFailure(instr) from e

View File

@@ -16,20 +16,22 @@ from .flow_graph import (
TerminalNode,
build_flowgraph,
)
from .parse_file import AsmData, Function
from .parse_instruction import (
from .asm_file import AsmData, Function
from .asm_instruction import (
Argument,
AsmAddressMode,
AsmGlobalSymbol,
AsmInstruction,
AsmLiteral,
BinOp,
Instruction,
InstructionMeta,
JumpTarget,
Location,
RegFormatter,
Register,
)
from .instruction import (
Instruction,
InstructionMeta,
Location,
StackLocation,
parse_instruction,
)
@@ -61,7 +63,7 @@ class IrPattern(abc.ABC):
missing_meta = InstructionMeta.missing()
regf = RegFormatter()
replacement_instr = parse_instruction(
self.replacement, missing_meta, arch, regf
self.replacement, missing_meta, arch, regf, {}
)
name = f"__pattern_{self.__class__.__name__}"
@@ -81,10 +83,11 @@ class IrPattern(abc.ABC):
inputs=[],
clobbers=[],
outputs=[inp],
eval_fn=None,
)
)
for part in self.parts:
func.new_instruction(parse_instruction(part, missing_meta, arch, regf))
func.new_instruction(parse_instruction(part, missing_meta, arch, regf, {}))
asm_data = AsmData()
flow_graph = build_flowgraph(func, asm_data, arch, fragment=True)

View File

@@ -10,8 +10,8 @@ from .error import DecompFailure
from .flow_graph import FlowGraph, build_flowgraph, visualize_flowgraph
from .if_statements import get_function_text
from .options import CodingStyle, Options, Target
from .parse_file import AsmData, Function, parse_file
from .parse_instruction import InstrProcessingFailure
from .asm_file import AsmData, Function, parse_file
from .instruction import InstrProcessingFailure
from .translate import (
Arch,
FunctionInfo,
@@ -76,12 +76,12 @@ def run(options: Options) -> int:
try:
for filename in options.filenames:
if filename == "-":
mips_file = parse_file(sys.stdin, arch, options)
asm_file = parse_file(sys.stdin, arch, options)
else:
with open(filename, "r", encoding="utf-8-sig") as f:
mips_file = parse_file(f, arch, options)
all_functions.update((fn.name, fn) for fn in mips_file.functions)
mips_file.asm_data.merge_into(asm_data)
asm_file = parse_file(f, arch, options)
all_functions.update((fn.name, fn) for fn in asm_file.functions)
asm_file.asm_data.merge_into(asm_data)
typemap = build_typemap(options.c_contexts, use_cache=options.use_cache)
except Exception as e:
@@ -121,7 +121,13 @@ def run(options: Options) -> int:
unk_inference=options.unk_inference,
)
global_info = GlobalInfo(
asm_data, arch, options.target, function_names, typemap, typepool
asm_data,
arch,
options.target,
function_names,
typemap,
typepool,
deterministic_vars=options.deterministic_vars,
)
flow_graphs: List[Union[FlowGraph, Exception]] = []
@@ -150,16 +156,16 @@ def run(options: Options) -> int:
flow_graph.reset_block_info()
info = translate_to_ast(function, flow_graph, options, global_info)
preliminary_infos.append(info)
except:
except Exception:
pass
try:
global_info.global_decls(fmt, options.global_decls, [])
except:
except Exception:
pass
for info in preliminary_infos:
try:
get_function_text(info, options)
except:
except Exception:
pass
# This operation can change struct field paths, so it is only performed
@@ -180,11 +186,11 @@ def run(options: Options) -> int:
return_code = 0
try:
if options.visualize_flowgraph:
if options.visualize_flowgraph is not None:
fn_info = function_infos[0]
if isinstance(fn_info, Exception):
raise fn_info
print(visualize_flowgraph(fn_info.flow_graph))
print(visualize_flowgraph(fn_info.flow_graph, options.visualize_flowgraph))
return 0
type_decls = typepool.format_type_declarations(
@@ -242,17 +248,11 @@ def parse_flags(flags: List[str]) -> Options:
group = parser.add_argument_group("Input Options")
group.add_argument(
"filename",
metavar="filename",
nargs="+",
dest="filenames",
help="Input asm filename(s)",
)
group.add_argument(
"--rodata",
dest="rodata_filenames",
action="append",
default=[],
help=argparse.SUPPRESS, # For backwards compatibility
)
group.add_argument(
"--context",
metavar="C_FILE",
@@ -274,17 +274,19 @@ def parse_flags(flags: List[str]) -> Options:
)
group.add_argument(
"-D",
metavar="SYM[=VALUE]",
dest="defined",
action="append",
default=[],
help="Mark preprocessor constant as defined",
help="Mark preprocessor symbol as defined",
)
group.add_argument(
"-U",
metavar="SYM",
dest="undefined",
action="append",
default=[],
help="Mark preprocessor constant as undefined",
help="Mark preprocessor symbol as undefined",
)
group.add_argument(
"--incbin-dir",
@@ -322,7 +324,7 @@ def parse_flags(flags: List[str]) -> Options:
action="store_true",
help=(
"Include template structs for each function's stack. These can be modified and passed back "
"into mips_to_c with --context to set the types & names of stack vars."
"into m2c with --context to set the types & names of stack vars."
),
)
group.add_argument(
@@ -346,8 +348,12 @@ def parse_flags(flags: List[str]) -> Options:
)
group.add_argument(
"--visualize",
dest="visualize",
action="store_true",
dest="visualize_flowgraph",
nargs="?",
default=None,
const=Options.VisualizeTypeEnum.C,
type=Options.VisualizeTypeEnum,
choices=list(Options.VisualizeTypeEnum),
help="Print an SVG visualization of the control flow graph using graphviz",
)
group.add_argument(
@@ -363,7 +369,7 @@ def parse_flags(flags: List[str]) -> Options:
dest="valid_syntax",
action="store_true",
help="Emit valid C syntax, using macros to indicate unknown types or other "
"unusual statements. Macro definitions are in `mips2c_macros.h`.",
"unusual statements. Macro definitions are in `m2c_macros.h`.",
)
group.add_argument(
"--allman",
@@ -425,6 +431,13 @@ def parse_flags(flags: List[str]) -> Options:
action="store_true",
help="Pad hex constants with 0's to fill their type's width.",
)
group.add_argument(
"--deterministic-vars",
dest="deterministic_vars",
action="store_true",
help="Name temp and phi vars after their location in the source asm, "
"rather than using an incrementing suffix. Can help reduce diff size in tests.",
)
group = parser.add_argument_group("Analysis Options")
group.add_argument(
@@ -437,13 +450,6 @@ def parse_flags(flags: List[str]) -> Options:
"Supported triples: mips-ido-c, mips-gcc-c, ppc-mwcc-c++, ppc-mwcc-c. "
"Default is mips-ido-c, `ppc` is an alias for ppc-mwcc-c++. ",
)
group.add_argument(
"--compiler",
dest="target_compiler",
type=Target.CompilerEnum,
choices=list(Target.CompilerEnum),
help=argparse.SUPPRESS, # For backwards compatibility; now use `--target`
)
group.add_argument(
"--passes",
"-P",
@@ -525,19 +531,14 @@ def parse_flags(flags: List[str]) -> Options:
action="store_true",
help=argparse.SUPPRESS,
)
group.add_argument(
"--structs",
dest="structs_compat",
action="store_true",
help=argparse.SUPPRESS, # For backwards compatibility; now enabled by default
)
args = parser.parse_args(flags)
reg_vars = args.reg_vars.split(",") if args.reg_vars else []
preproc_defines = {
**{d: 0 for d in args.undefined},
**{d.split("=")[0]: 1 for d in args.defined},
}
preproc_defines: Dict[str, Optional[int]] = {d: None for d in args.undefined}
for d in args.defined:
parts = d.split("=", 1)
preproc_defines[parts[0]] = int(parts[1], 0) if len(parts) >= 2 else 1
coding_style = CodingStyle(
newline_after_function=args.allman,
newline_after_if=args.allman,
@@ -548,29 +549,6 @@ def parse_flags(flags: List[str]) -> Options:
comment_style=args.comment_style,
comment_column=args.comment_column,
)
filenames = args.filename + args.rodata_filenames
# Backwards compatibility: MIPS targets can be described with --compiler
if args.target_compiler == Target.CompilerEnum.IDO:
target = Target.parse("mips-ido-c")
elif args.target_compiler == Target.CompilerEnum.GCC:
target = Target.parse("mips-gcc-c")
elif args.target_compiler is None:
target = args.target
else:
parser.error(
"--compiler is partially supported for backwards compatibility, use --target instead"
)
# Backwards compatibility: giving a function index/name as a final argument, or "all"
assert filenames, "checked by argparse, nargs='+'"
if filenames[-1] == "all":
filenames.pop()
elif re.match(r"^[0-9a-zA-Z_]+$", filenames[-1]):
# The filename is a valid C identifier or a number
args.functions.append(filenames.pop())
if not filenames:
parser.error("the following arguments are required: filename")
functions: List[Union[int, str]] = []
for fn in args.functions:
@@ -580,11 +558,11 @@ def parse_flags(flags: List[str]) -> Options:
functions.append(fn)
# The debug output interferes with the visualize output
if args.visualize:
if args.visualize_flowgraph is not None:
args.debug = False
return Options(
filenames=filenames,
filenames=args.filenames,
function_indexes_or_names=functions,
debug=args.debug,
void=args.void,
@@ -597,7 +575,7 @@ def parse_flags(flags: List[str]) -> Options:
goto_patterns=args.goto_patterns,
stop_on_error=args.stop_on_error,
print_assembly=args.print_assembly,
visualize_flowgraph=args.visualize,
visualize_flowgraph=args.visualize_flowgraph,
c_contexts=args.c_contexts,
use_cache=args.use_cache,
dump_typemap=args.dump_typemap,
@@ -607,18 +585,19 @@ def parse_flags(flags: List[str]) -> Options:
sanitize_tracebacks=args.sanitize_tracebacks,
valid_syntax=args.valid_syntax,
global_decls=args.global_decls,
target=target,
target=args.target,
print_stack_structs=args.print_stack_structs,
unk_inference=args.unk_inference,
passes=args.passes,
incbin_dirs=args.incbin_dirs,
deterministic_vars=args.deterministic_vars,
)
def main() -> None:
# Large functions can sometimes require a higher recursion limit than the
# CPython default. Cap to INT_MAX to avoid an OverflowError, though.
sys.setrecursionlimit(min(2 ** 31 - 1, 10 * sys.getrecursionlimit()))
sys.setrecursionlimit(min(2**31 - 1, 10 * sys.getrecursionlimit()))
options = parse_flags(sys.argv[1:])
sys.exit(run(options))

View File

@@ -89,6 +89,10 @@ class Options:
USED = "used"
NONE = "none"
class VisualizeTypeEnum(ChoicesEnum):
ASM = "asm"
C = "c"
filenames: List[str]
function_indexes_or_names: List[Union[int, str]]
debug: bool
@@ -102,12 +106,12 @@ class Options:
goto_patterns: List[str]
stop_on_error: bool
print_assembly: bool
visualize_flowgraph: bool
visualize_flowgraph: Optional[VisualizeTypeEnum]
c_contexts: List[Path]
use_cache: bool
dump_typemap: bool
pdb_translate: bool
preproc_defines: Dict[str, int]
preproc_defines: Dict[str, Optional[int]] # None = explicitly unset
coding_style: CodingStyle
sanitize_tracebacks: bool
valid_syntax: bool
@@ -117,6 +121,7 @@ class Options:
unk_inference: bool
passes: int
incbin_dirs: List[Path]
deterministic_vars: bool
def formatter(self) -> "Formatter":
return Formatter(

File diff suppressed because it is too large Load Diff

View File

@@ -162,6 +162,7 @@ class TypeData:
array_dim: Optional[int] = None # K_ARRAY
struct: Optional["StructDeclaration"] = None # K_STRUCT
enum: Optional[Enum] = None # K_INT
new_field_within: Set["StructDeclaration"] = field(default_factory=set)
def __post_init__(self) -> None:
assert self.kind
@@ -268,6 +269,10 @@ class Type:
if x.enum is not None and y.enum is not None:
if x.enum != y.enum:
return False
if y.struct in x.new_field_within or x.struct in y.new_field_within:
# Disallow recursive structs: if y is a struct that has a newly added
# field of (yet unknown) type x, don't allow unifying x and y.
return False
x.kind = kind
x.likely_kind = likely_kind
x.size_bits = size_bits
@@ -277,6 +282,7 @@ class Type:
x.array_dim = array_dim
x.struct = struct
x.enum = enum
x.new_field_within |= y.new_field_within
y.uf_parent = x
return True
@@ -508,7 +514,7 @@ class Type:
return zero_offset_results[0]
elif exact:
# Try to insert a new field into the struct at the given offset
field_type = Type.any_field()
field_type = Type.any_field(new_within=data.struct)
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
@@ -651,7 +657,7 @@ class Type:
align=[],
)
unk_symbol = "MIPS2C_UNK" if fmt.valid_syntax else "?"
unk_symbol = "M2C_UNK" if fmt.valid_syntax else "?"
data = self.data()
if data in seen:
@@ -788,8 +794,13 @@ class Type:
return Type(TypeData(kind=TypeData.K_ANYREG))
@staticmethod
def any_field() -> "Type":
return Type(TypeData(kind=TypeData.K_ANY & ~TypeData.K_VOID))
def any_field(*, new_within: "Optional[StructDeclaration]" = None) -> "Type":
return Type(
TypeData(
kind=TypeData.K_ANY & ~TypeData.K_VOID,
new_field_within={new_within} if new_within else set(),
)
)
@staticmethod
def intish() -> "Type":

View File

@@ -1,20 +1,18 @@
f32 test(s32 arg0) {
f64 sp8;
s32 sp4;
f64 temp_ft3;
f64 var_ft3;
s32 temp_t9;
f64 phi_ft3;
sp8 = 1.0;
sp4 = arg0;
if (sp4 != 0) {
do {
temp_ft3 = (f64) sp4;
phi_ft3 = temp_ft3;
var_ft3 = (f64) sp4;
if (sp4 < 0) {
phi_ft3 = temp_ft3 + 4294967296.0;
var_ft3 += 4294967296.0;
}
sp8 *= phi_ft3;
sp8 *= var_ft3;
temp_t9 = sp4 - 1;
sp4 = temp_t9;
} while (temp_t9 != 0);

View File

@@ -0,0 +1,62 @@
f32 test(s32 arg0) {
f64 var_ft1;
f64 var_ft1_2;
f64 var_ft2;
f64 var_ft4;
f64 var_ft5;
f64 var_fv1;
s32 temp_a1;
s32 temp_ft0;
s32 temp_ft3;
s32 temp_t6;
s32 temp_t7;
s32 temp_t8;
s32 var_v0;
var_fv1 = 0.0;
var_v0 = arg0;
if (arg0 != 0) {
temp_a1 = -(arg0 & 3);
if (temp_a1 != 0) {
do {
var_ft1 = (f64) var_v0;
if (var_v0 < 0) {
var_ft1 += 4294967296.0;
}
var_fv1 *= var_ft1;
var_v0 -= 1;
} while ((temp_a1 + arg0) != var_v0);
if (var_v0 != 0) {
goto loop_6;
}
} else {
do {
loop_6:
temp_t6 = var_v0 - 1;
var_ft4 = (f64) var_v0;
if (var_v0 < 0) {
var_ft4 += 4294967296.0;
}
temp_t7 = var_v0 - 2;
temp_t8 = var_v0 - 3;
var_ft2 = (f64) temp_t6;
if (temp_t6 < 0) {
var_ft2 += 4294967296.0;
}
temp_ft3 = temp_t7;
temp_ft0 = temp_t8;
var_v0 -= 4;
var_ft5 = (f64) temp_ft3;
if (temp_t7 < 0) {
var_ft5 += 4294967296.0;
}
var_ft1_2 = (f64) temp_ft0;
if (temp_t8 < 0) {
var_ft1_2 += 4294967296.0;
}
var_fv1 = var_fv1 * var_ft4 * var_ft2 * var_ft5 * var_ft1_2;
} while (var_v0 != 0);
}
}
return (f32) var_fv1;
}

View File

@@ -0,0 +1 @@
--deterministic-vars

View File

@@ -0,0 +1,57 @@
s32 func_00400090(s32); /* static */
s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
s32 sp24;
s32 sp20;
s32 temp_t3_60;
s32 temp_t5_82;
s32 temp_t7_13;
s32 temp_v0_22;
s32 temp_v0_47;
s32 var_s0_12;
s32 var_t0_19;
s32 var_v1_34;
s32 var_v1_42;
s32 var_v1_88;
var_s0_12 = arg0 + arg1;
temp_t7_13 = arg1 + arg2;
sp20 = temp_t7_13;
var_t0_19 = temp_t7_13;
if ((var_s0_12 != 0) || (temp_t7_13 != 0) || (temp_v0_22 = func_00400090(temp_t7_13), var_t0_19 = temp_v0_22, (temp_v0_22 != 0)) || (var_s0_12 = 2, (arg3 != 0))) {
var_v1_34 = 1;
} else {
var_v1_34 = -2;
if (arg0 != 0) {
var_v1_34 = -1;
}
}
var_v1_42 = var_v1_34 + arg2;
if ((var_s0_12 != 0) && (var_t0_19 != 0)) {
var_s0_12 += var_t0_19;
sp24 = var_v1_42;
temp_v0_47 = func_00400090(var_s0_12);
var_t0_19 = temp_v0_47;
if ((temp_v0_47 != 0) && (arg3 != 0)) {
if (var_v1_42 < 5) {
do {
temp_t3_60 = (var_v1_42 + 1) * 2;
var_v1_42 = temp_t3_60;
} while (temp_t3_60 < 5);
}
var_v1_42 += 5;
}
}
if ((var_s0_12 != 0) && (var_t0_19 != 0) && (sp24 = var_v1_42, (func_00400090(var_s0_12 + var_t0_19) != 0)) && (arg3 != 0)) {
if (var_v1_42 < 5) {
do {
temp_t5_82 = (var_v1_42 + 1) * 2;
var_v1_42 = temp_t5_82;
} while (temp_t5_82 < 5);
}
var_v1_88 = var_v1_42 + 5;
} else {
var_v1_88 = var_v1_42 + 6;
}
return var_v1_88;
}

View File

@@ -13,39 +13,28 @@ s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
s32 temp_t9;
s32 temp_v0;
s32 temp_v0_2;
s32 temp_v1;
s32 phi_t0;
s32 phi_t1;
s32 phi_v1;
s32 phi_a2;
s32 phi_v1_2;
s32 phi_t1_2;
s32 phi_v1_3;
s32 phi_v1_4;
s32 phi_v1_5;
s32 phi_v1_6;
s32 phi_v1_7;
s32 var_t1;
s32 var_v1;
s32 var_v1_2;
s32 var_v1_3;
temp_t0 = arg0 + arg1;
temp_t7 = arg1 + arg2;
sp2C = temp_t0;
sp1C = temp_t7;
phi_t1 = temp_t7;
var_t1 = temp_t7;
if (temp_t0 == 0) {
if (temp_t7 == 0) {
sp20 = temp_t0;
temp_v0 = func_00400090(temp_t7);
phi_t0 = temp_t0;
phi_t1 = temp_v0;
phi_a2 = arg2;
phi_t1 = temp_v0;
var_t1 = temp_v0;
if (temp_v0 == 0) {
if (arg3 != 0) {
goto block_4;
}
phi_v1 = -2;
var_v1_2 = -2;
if (arg0 != 0) {
phi_v1 = -1;
var_v1_2 = -1;
}
} else {
goto block_4;
@@ -55,54 +44,43 @@ s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
}
} else {
block_4:
phi_t0 = arg0 + arg1;
phi_v1 = 1;
phi_a2 = arg2;
var_v1_2 = 1;
}
temp_v1 = phi_v1 + phi_a2;
phi_t1_2 = phi_t1;
phi_v1_3 = temp_v1;
if (phi_t0 != 0) {
temp_a0 = phi_t0 + phi_t1;
if (phi_t1 != 0) {
var_v1_3 = var_v1_2 + arg2;
if (temp_t0 != 0) {
temp_a0 = temp_t0 + var_t1;
if (var_t1 != 0) {
sp2C = temp_a0;
sp24 = temp_v1;
sp24 = var_v1_3;
temp_v0_2 = func_00400090(temp_a0);
phi_v1_2 = temp_v1;
phi_t1_2 = temp_v0_2;
phi_v1_3 = temp_v1;
phi_v1_6 = temp_v1;
var_t1 = temp_v0_2;
if (temp_v0_2 != 0) {
if (arg3 != 0) {
if (temp_v1 < 5) {
if (var_v1_3 < 5) {
do {
temp_t5 = (phi_v1_2 + 1) * 2;
phi_v1_2 = temp_t5;
phi_v1_6 = temp_t5;
temp_t5 = (var_v1_3 + 1) * 2;
var_v1_3 = temp_t5;
} while (temp_t5 < 5);
}
phi_v1_3 = phi_v1_6 + 5;
var_v1_3 += 5;
}
}
}
}
if (sp2C != 0) {
temp_a0_2 = sp2C + phi_t1_2;
if (phi_t1_2 != 0) {
temp_a0_2 = sp2C + var_t1;
if (var_t1 != 0) {
sp2C = temp_a0_2;
sp24 = phi_v1_3;
phi_v1_4 = phi_v1_3;
phi_v1_7 = phi_v1_3;
sp24 = var_v1_3;
if (func_00400090(temp_a0_2) != 0) {
if (arg3 != 0) {
if (phi_v1_3 < 5) {
if (var_v1_3 < 5) {
do {
temp_t9 = (phi_v1_4 + 1) * 2;
phi_v1_4 = temp_t9;
phi_v1_7 = temp_t9;
temp_t9 = (var_v1_3 + 1) * 2;
var_v1_3 = temp_t9;
} while (temp_t9 < 5);
}
phi_v1_5 = phi_v1_7 + 5;
var_v1 = var_v1_3 + 5;
} else {
goto block_21;
}
@@ -114,7 +92,7 @@ block_4:
}
} else {
block_21:
phi_v1_5 = phi_v1_3 + 6;
var_v1 = var_v1_3 + 6;
}
return phi_v1_5;
return var_v1;
}

View File

@@ -0,0 +1,57 @@
s32 func_00400090(s32); /* static */
s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
s32 sp24;
s32 sp20;
s32 temp_t3;
s32 temp_t5;
s32 temp_t7;
s32 temp_v0;
s32 temp_v0_2;
s32 var_s0;
s32 var_t0;
s32 var_v1;
s32 var_v1_2;
s32 var_v1_3;
var_s0 = arg0 + arg1;
temp_t7 = arg1 + arg2;
sp20 = temp_t7;
var_t0 = temp_t7;
if ((var_s0 != 0) || (temp_t7 != 0) || (temp_v0 = func_00400090(temp_t7), var_t0 = temp_v0, (temp_v0 != 0)) || (var_s0 = 2, (arg3 != 0))) {
var_v1_2 = 1;
} else {
var_v1_2 = -2;
if (arg0 != 0) {
var_v1_2 = -1;
}
}
var_v1_3 = var_v1_2 + arg2;
if ((var_s0 != 0) && (var_t0 != 0)) {
var_s0 += var_t0;
sp24 = var_v1_3;
temp_v0_2 = func_00400090(var_s0);
var_t0 = temp_v0_2;
if ((temp_v0_2 != 0) && (arg3 != 0)) {
if (var_v1_3 < 5) {
do {
temp_t3 = (var_v1_3 + 1) * 2;
var_v1_3 = temp_t3;
} while (temp_t3 < 5);
}
var_v1_3 += 5;
}
}
if ((var_s0 != 0) && (var_t0 != 0) && (sp24 = var_v1_3, (func_00400090(var_s0 + var_t0) != 0)) && (arg3 != 0)) {
if (var_v1_3 < 5) {
do {
temp_t5 = (var_v1_3 + 1) * 2;
var_v1_3 = temp_t5;
} while (temp_t5 < 5);
}
var_v1 = var_v1_3 + 5;
} else {
var_v1 = var_v1_3 + 6;
}
return var_v1;
}

View File

@@ -0,0 +1,96 @@
.set noat # allow manual use of $at
.set noreorder # don't insert nops after branches
glabel func_00400090
/* 000090 00400090 03E00008 */ jr $ra
/* 000094 00400094 24820001 */ addiu $v0, $a0, 1
glabel test
/* 000098 00400098 27BDFFD0 */ addiu $sp, $sp, -0x30
/* 00009C 0040009C AFB00018 */ sw $s0, 0x18($sp)
/* 0000A0 004000A0 00858021 */ addu $s0, $a0, $a1
/* 0000A4 004000A4 00A67821 */ addu $t7, $a1, $a2
/* 0000A8 004000A8 AFBF001C */ sw $ra, 0x1c($sp)
/* 0000AC 004000AC AFA40030 */ sw $a0, 0x30($sp)
/* 0000B0 004000B0 AFA7003C */ sw $a3, 0x3c($sp)
/* 0000B4 004000B4 AFAF0020 */ sw $t7, 0x20($sp)
/* 0000B8 004000B8 1600000D */ bnez $s0, .L004000F0
/* 0000BC 004000BC 01E04025 */ move $t0, $t7
/* 0000C0 004000C0 15E0000B */ bnez $t7, .L004000F0
/* 0000C4 004000C4 01E02025 */ move $a0, $t7
/* 0000C8 004000C8 0C100024 */ jal func_00400090
/* 0000CC 004000CC AFA60038 */ sw $a2, 0x38($sp)
/* 0000D0 004000D0 8FA60038 */ lw $a2, 0x38($sp)
/* 0000D4 004000D4 14400006 */ bnez $v0, .L004000F0
/* 0000D8 004000D8 00404025 */ move $t0, $v0
/* 0000DC 004000DC 8FB9003C */ lw $t9, 0x3c($sp)
/* 0000E0 004000E0 24100002 */ addiu $s0, $zero, 2
/* 0000E4 004000E4 8FA90030 */ lw $t1, 0x30($sp)
/* 0000E8 004000E8 13200003 */ beqz $t9, .L004000F8
/* 0000EC 004000EC 00000000 */ nop
.L004000F0:
/* 0000F0 004000F0 10000005 */ b .L00400108
/* 0000F4 004000F4 24030001 */ addiu $v1, $zero, 1
.L004000F8:
/* 0000F8 004000F8 11200003 */ beqz $t1, .L00400108
/* 0000FC 004000FC 2403FFFE */ addiu $v1, $zero, -2
/* 000100 00400100 10000001 */ b .L00400108
/* 000104 00400104 2403FFFF */ addiu $v1, $zero, -1
.L00400108:
/* 000108 00400108 12000016 */ beqz $s0, .L00400164
/* 00010C 0040010C 00661821 */ addu $v1, $v1, $a2
/* 000110 00400110 11000014 */ beqz $t0, .L00400164
/* 000114 00400114 00000000 */ nop
/* 000118 00400118 02088021 */ addu $s0, $s0, $t0
/* 00011C 0040011C 02002025 */ move $a0, $s0
/* 000120 00400120 0C100024 */ jal func_00400090
/* 000124 00400124 AFA30024 */ sw $v1, 0x24($sp)
/* 000128 00400128 8FA30024 */ lw $v1, 0x24($sp)
/* 00012C 0040012C 1040000D */ beqz $v0, .L00400164
/* 000130 00400130 00404025 */ move $t0, $v0
/* 000134 00400134 8FAA003C */ lw $t2, 0x3c($sp)
/* 000138 00400138 28610005 */ slti $at, $v1, 5
/* 00013C 0040013C 11400009 */ beqz $t2, .L00400164
/* 000140 00400140 00000000 */ nop
/* 000144 00400144 50200007 */ beql $at, $zero, .L00400164
/* 000148 00400148 24630005 */ addiu $v1, $v1, 5
.L0040014C:
/* 00014C 0040014C 24630001 */ addiu $v1, $v1, 1
/* 000150 00400150 00035840 */ sll $t3, $v1, 1
/* 000154 00400154 29610005 */ slti $at, $t3, 5
/* 000158 00400158 1420FFFC */ bnez $at, .L0040014C
/* 00015C 0040015C 01601825 */ move $v1, $t3
/* 000160 00400160 24630005 */ addiu $v1, $v1, 5
.L00400164:
/* 000164 00400164 52000015 */ beql $s0, $zero, .L004001BC
/* 000168 00400168 24630006 */ addiu $v1, $v1, 6
/* 00016C 0040016C 11000012 */ beqz $t0, .L004001B8
/* 000170 00400170 02082021 */ addu $a0, $s0, $t0
/* 000174 00400174 0C100024 */ jal func_00400090
/* 000178 00400178 AFA30024 */ sw $v1, 0x24($sp)
/* 00017C 0040017C 1040000E */ beqz $v0, .L004001B8
/* 000180 00400180 8FA30024 */ lw $v1, 0x24($sp)
/* 000184 00400184 8FAC003C */ lw $t4, 0x3c($sp)
/* 000188 00400188 28610005 */ slti $at, $v1, 5
/* 00018C 0040018C 5180000B */ beql $t4, $zero, .L004001BC
/* 000190 00400190 24630006 */ addiu $v1, $v1, 6
/* 000194 00400194 10200006 */ beqz $at, .L004001B0
/* 000198 00400198 00000000 */ nop
.L0040019C:
/* 00019C 0040019C 24630001 */ addiu $v1, $v1, 1
/* 0001A0 004001A0 00036840 */ sll $t5, $v1, 1
/* 0001A4 004001A4 29A10005 */ slti $at, $t5, 5
/* 0001A8 004001A8 1420FFFC */ bnez $at, .L0040019C
/* 0001AC 004001AC 01A01825 */ move $v1, $t5
.L004001B0:
/* 0001B0 004001B0 10000002 */ b .L004001BC
/* 0001B4 004001B4 24630005 */ addiu $v1, $v1, 5
.L004001B8:
/* 0001B8 004001B8 24630006 */ addiu $v1, $v1, 6
.L004001BC:
/* 0001BC 004001BC 8FBF001C */ lw $ra, 0x1c($sp)
/* 0001C0 004001C0 8FB00018 */ lw $s0, 0x18($sp)
/* 0001C4 004001C4 27BD0030 */ addiu $sp, $sp, 0x30
/* 0001C8 004001C8 03E00008 */ jr $ra
/* 0001CC 004001CC 00601025 */ move $v0, $v1

View File

@@ -0,0 +1,47 @@
s32 foo(s32); /* static */
s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
s32 sp14;
s32 temp_r0;
s32 var_r30;
s32 var_r30_2;
s32 var_r30_3;
s32 var_r31;
s32 var_r3;
temp_r0 = arg0 + arg1;
var_r3 = arg1 + arg2;
sp14 = arg3;
var_r31 = temp_r0;
if ((temp_r0 != 0) || (var_r3 != 0) || (var_r3 = foo(), ((var_r3 == 0) == 0)) || (var_r31 = 2, ((sp14 == 0) == 0))) {
var_r30_2 = 1;
} else if (arg0 != 0) {
var_r30_2 = -1;
} else {
var_r30_2 = -2;
}
var_r30_3 = var_r30_2 + arg2;
if ((var_r31 != 0) && (var_r3 != 0)) {
var_r31 += var_r3;
var_r3 = foo(var_r31);
if ((var_r3 != 0) && (sp14 != 0)) {
loop_14:
if (var_r30_3 < 5) {
var_r30_3 = (var_r30_3 + 1) * 2;
goto loop_14;
}
var_r30_3 += 5;
}
}
if ((var_r31 != 0) && (var_r3 != 0) && (foo(var_r31 + var_r3) != 0) && (sp14 != 0)) {
loop_22:
if (var_r30_3 < 5) {
var_r30_3 = (var_r30_3 + 1) * 2;
goto loop_22;
}
var_r30 = var_r30_3 + 5;
} else {
var_r30 = var_r30_3 + 6;
}
return var_r30;
}

View File

@@ -6,7 +6,6 @@ s32 func_00400090(s32, s32, s32, s32); /* static */
s32 sp1C;
s32 sp18;
s32 temp_a0;
s32 temp_a2;
s32 temp_a3;
s32 temp_v0;
s32 temp_v0_2;
@@ -21,12 +20,10 @@ s32 func_00400090(s32, s32, s32, s32); /* static */
sp18 = temp_v1;
sp1C = temp_a3;
temp_v0_2 = func_00400090(temp_v0 + arg0, temp_a3);
temp_a2 = arg2;
if (temp_v0_2 >= 0xB) {
temp_a0 = temp_v0_2 + arg1;
arg2 = temp_a2;
sp1C = temp_a3;
sp2C = func_00400090(temp_a0, arg1, temp_a2, temp_a3);
sp2C = func_00400090(temp_a0, arg1, arg2, temp_a3);
sp28 = func_00400090(temp_a3 + arg2);
if ((sp2C != 0) && (sp28 != 0) && (func_00400090(sp18 + arg3) != 0)) {
return 1;

View File

@@ -0,0 +1,56 @@
s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
s32 var_v0;
s32 var_v1;
var_v1 = 0;
if ((arg0 != 0) && ((arg1 != 0) || (arg2 != 0))) {
loop_3:
var_v1 += 1;
if (arg0 != 0) {
if ((arg1 != 0) || (arg2 != 0)) {
goto loop_3;
}
}
}
if ((arg0 != 0) || ((arg1 != 0) && (arg2 != 0))) {
loop_9:
var_v1 += 1;
if (arg0 != 0) {
goto loop_9;
}
if ((arg1 != 0) && (arg2 != 0)) {
goto loop_9;
}
}
if (arg0 != 0) {
loop_13:
var_v1 += 1;
if (((arg1 == 0) || ((arg2 == 0) && (arg3 == 0))) && (var_v1 += 1, (arg1 == 0)) && ((arg2 == 0) || (arg3 == 0))) {
var_v1 += 1;
if ((arg1 == 0) || ((arg2 == 0) && (arg3 == 0))) {
var_v1 += 1;
if ((arg1 == 0) && ((arg2 == 0) || (arg3 == 0))) {
var_v1 += 1;
goto block_26;
}
}
} else {
block_26:
if (arg0 != 0) {
goto loop_13;
}
}
}
var_v0 = 0;
if ((arg0 != 0) || (arg1 != 0)) {
loop_29:
var_v0 = var_v0 + arg2 + arg3;
var_v1 += 1;
if (var_v0 < 0xA) {
if ((arg0 != 0) || (arg1 != 0)) {
goto loop_29;
}
}
}
return var_v1;
}

View File

@@ -0,0 +1,52 @@
s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
s32 var_r5;
s32 var_r7;
var_r7 = 0;
loop_2:
if (arg0 != 0) {
if ((arg1 == 0) && (arg2 == 0)) {
} else {
var_r7 += 1;
goto loop_2;
}
}
loop_7:
if (arg0 != 0) {
block_6:
var_r7 += 1;
goto loop_7;
}
if (arg1 != 0) {
if (arg2 == 0) {
} else {
goto block_6;
}
}
loop_24:
if (arg0 != 0) {
var_r7 += 1;
if (((arg1 == 0) || ((arg2 == 0) && (arg3 == 0))) && (var_r7 += 1, ((arg1 == 0) != 0)) && ((arg2 == 0) || (arg3 == 0))) {
var_r7 += 1;
if ((arg1 == 0) || ((arg2 == 0) && (arg3 == 0))) {
var_r7 += 1;
if ((arg1 == 0) && ((arg2 == 0) || (arg3 == 0))) {
var_r7 += 1;
goto loop_24;
}
}
} else {
goto loop_24;
}
}
var_r5 = 0;
loop_27:
if ((var_r5 < 0xA) && ((arg0 != 0) || (arg1 != 0))) {
var_r5 += arg2 + arg3;
var_r7 += 1;
goto loop_27;
}
return var_r7;
}

View File

@@ -2,8 +2,8 @@ s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
s32 spC;
s32 sp8;
s32 sp4;
s32 phi_t0;
s32 phi_t0_2;
s32 var_t0;
s32 var_t0_2;
spC = arg0 + arg1;
sp8 = arg1 + arg2;
@@ -28,21 +28,21 @@ s32 test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
}
if (arg0 != 0) {
if (arg1 != 0) {
phi_t0 = arg2;
var_t0 = arg2;
} else {
phi_t0 = arg3;
var_t0 = arg3;
}
if ((phi_t0 == (arg0 + 1)) && ((arg1 + 1) != 0)) {
if ((var_t0 == (arg0 + 1)) && ((arg1 + 1) != 0)) {
sp4 = 7;
}
}
if (arg0 == 0) {
if (arg1 != 0) {
phi_t0_2 = arg2;
var_t0_2 = arg2;
} else {
phi_t0_2 = arg3;
var_t0_2 = arg3;
}
if ((phi_t0_2 == (arg0 + 1)) || ((arg1 + 1) != 0)) {
if ((var_t0_2 == (arg0 + 1)) || ((arg1 + 1) != 0)) {
goto block_53;
}
} else {

View File

@@ -1,49 +1,49 @@
? test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
s32 phi_t0;
s32 phi_t0_2;
? phi_v1;
? var_v1;
s32 var_t0;
s32 var_t0_2;
phi_v1 = 0;
var_v1 = 0;
if (((arg0 + arg1) != 0) || (((arg1 + arg2) != 0) && ((arg0 * arg1) != 0)) || ((arg3 != 0) && (arg0 != 0))) {
phi_v1 = 1;
var_v1 = 1;
}
if ((arg0 != 0) && ((arg1 != 0) || (arg2 != 0)) && ((arg3 != 0) || ((arg0 + 1) != 0))) {
phi_v1 = 2;
var_v1 = 2;
}
if (((arg0 != 0) && (arg3 != 0)) || (((arg1 != 0) || (arg2 != 0)) && ((arg0 + 1) != 0))) {
phi_v1 = 3;
var_v1 = 3;
}
if ((arg0 != 0) && (arg1 != 0) && ((arg2 != 0) || (arg3 != 0)) && (((arg0 + 1) != 0) || ((arg1 + 1) != 0))) {
phi_v1 = 4;
var_v1 = 4;
}
if ((((arg0 != 0) || (arg1 != 0)) && (arg2 != 0)) || ((arg3 != 0) && ((arg0 + 1) != 0)) || ((arg1 + 1) != 0) || ((arg2 + 1) != 0)) {
phi_v1 = 5;
var_v1 = 5;
}
if ((((arg0 != 0) && (arg1 != 0)) || ((arg2 != 0) && (arg3 != 0))) && (((arg0 + 1) != 0) || ((arg1 + 1) != 0))) {
phi_v1 = 6;
var_v1 = 6;
}
if (arg0 != 0) {
if (arg1 != 0) {
phi_t0 = arg2;
var_t0 = arg2;
} else {
phi_t0 = arg3;
var_t0 = arg3;
}
if ((phi_t0 == (arg0 + 1)) && ((arg1 + 1) != 0)) {
phi_v1 = 7;
if ((var_t0 == (arg0 + 1)) && ((arg1 + 1) != 0)) {
var_v1 = 7;
}
}
if (arg0 == 0) {
if (arg1 != 0) {
phi_t0_2 = arg2;
var_t0_2 = arg2;
} else {
phi_t0_2 = arg3;
var_t0_2 = arg3;
}
if ((phi_t0_2 == (arg0 + 1)) || ((arg1 + 1) != 0)) {
if ((var_t0_2 == (arg0 + 1)) || ((arg1 + 1) != 0)) {
goto block_53;
}
} else {
block_53:
phi_v1 = 8;
var_v1 = 8;
}
return phi_v1;
return var_v1;
}

View File

@@ -0,0 +1,50 @@
? test(s32 arg0, s32 arg1, s32 arg2, s32 arg3) {
? var_r8;
s32 var_r5;
s32 var_r7;
var_r5 = arg2;
var_r8 = 0;
if (((arg0 + arg1) != 0) || (((s32) (arg1 + var_r5) != 0) && ((arg0 * arg1) != 0)) || ((arg3 != 0) && (arg0 != 0))) {
var_r8 = 1;
}
if ((arg0 != 0) && ((arg1 != 0) || (var_r5 != 0)) && ((arg3 != 0) || ((arg0 + 1) != 0))) {
var_r8 = 2;
}
if (((arg0 != 0) && (arg3 != 0)) || (((arg1 != 0) || (var_r5 != 0)) && ((arg0 + 1) != 0))) {
var_r8 = 3;
}
if ((arg0 != 0) && (arg1 != 0) && ((var_r5 != 0) || (arg3 != 0)) && (((arg0 + 1) != 0) || ((arg1 + 1) != 0))) {
var_r8 = 4;
}
if ((((arg0 != 0) || (arg1 != 0)) && (var_r5 != 0)) || ((arg3 != 0) && ((arg0 + 1) != 0)) || ((arg1 + 1) != 0) || ((var_r5 + 1) != 0)) {
var_r8 = 5;
}
if ((((arg0 != 0) && (arg1 != 0)) || ((var_r5 != 0) && (arg3 != 0))) && (((arg0 + 1) != 0) || ((arg1 + 1) != 0))) {
var_r8 = 6;
}
if (arg0 != 0) {
if (arg1 != 0) {
var_r7 = var_r5;
} else {
var_r7 = arg3;
}
if (((s32) (arg0 + 1) == var_r7) && ((arg1 + 1) != 0)) {
var_r8 = 7;
}
}
if (arg0 == 0) {
if (arg1 != 0) {
} else {
var_r5 = arg3;
}
if (((s32) (arg0 + 1) == var_r5) || ((arg1 + 1) != 0)) {
goto block_53;
}
} else {
block_53:
var_r8 = 8;
}
return var_r8;
}

Some files were not shown because too many files have changed in this diff Show More