mirror of
https://github.com/decompme/decomp.me.git
synced 2025-12-21 04:49:53 -06:00
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:
14
.gitattributes
vendored
14
.gitattributes
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
2
backend/asm_differ/diff.py
generated
2
backend/asm_differ/diff.py
generated
@@ -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))
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 .
|
||||
@@ -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
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 20.8b1
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
@@ -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
39
backend/m2c/m2c_macros.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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",
|
||||
@@ -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),
|
||||
@@ -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)),
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 ' '} {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")
|
||||
@@ -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
214
backend/m2c/src/instruction.py
generated
Normal 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
|
||||
@@ -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)
|
||||
121
backend/mips_to_c/src/main.py → backend/m2c/src/main.py
generated
121
backend/mips_to_c/src/main.py → backend/m2c/src/main.py
generated
@@ -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))
|
||||
|
||||
@@ -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
@@ -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":
|
||||
@@ -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);
|
||||
62
backend/m2c/tests/end_to_end/abi-float-regs/irix-o2-out.c
generated
Normal file
62
backend/m2c/tests/end_to_end/abi-float-regs/irix-o2-out.c
generated
Normal 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;
|
||||
}
|
||||
1
backend/m2c/tests/end_to_end/andor_assignment/irix-o2-detvar-flags.txt
generated
Normal file
1
backend/m2c/tests/end_to_end/andor_assignment/irix-o2-detvar-flags.txt
generated
Normal file
@@ -0,0 +1 @@
|
||||
--deterministic-vars
|
||||
57
backend/m2c/tests/end_to_end/andor_assignment/irix-o2-detvar-out.c
generated
Normal file
57
backend/m2c/tests/end_to_end/andor_assignment/irix-o2-detvar-out.c
generated
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
57
backend/m2c/tests/end_to_end/andor_assignment/irix-o2-out.c
generated
Normal file
57
backend/m2c/tests/end_to_end/andor_assignment/irix-o2-out.c
generated
Normal 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;
|
||||
}
|
||||
96
backend/m2c/tests/end_to_end/andor_assignment/irix-o2.s
generated
Normal file
96
backend/m2c/tests/end_to_end/andor_assignment/irix-o2.s
generated
Normal 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
|
||||
47
backend/m2c/tests/end_to_end/andor_assignment/mwcc-o4p-out.c
generated
Normal file
47
backend/m2c/tests/end_to_end/andor_assignment/mwcc-o4p-out.c
generated
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
56
backend/m2c/tests/end_to_end/andor_loops/irix-o2-out.c
generated
Normal file
56
backend/m2c/tests/end_to_end/andor_loops/irix-o2-out.c
generated
Normal 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;
|
||||
}
|
||||
52
backend/m2c/tests/end_to_end/andor_loops/mwcc-o4p-out.c
generated
Normal file
52
backend/m2c/tests/end_to_end/andor_loops/mwcc-o4p-out.c
generated
Normal 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;
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
}
|
||||
50
backend/m2c/tests/end_to_end/andor_mixed/mwcc-o4p-out.c
generated
Normal file
50
backend/m2c/tests/end_to_end/andor_mixed/mwcc-o4p-out.c
generated
Normal 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
Reference in New Issue
Block a user