asmdiff 0.4.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- asmdiff-0.4.0/README.md → asmdiff-0.4.1/PKG-INFO +42 -5
- asmdiff-0.4.0/PKG-INFO → asmdiff-0.4.1/README.md +24 -23
- {asmdiff-0.4.0 → asmdiff-0.4.1}/asmdiff.example.toml +9 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/asmdiff.py +140 -24
- {asmdiff-0.4.0 → asmdiff-0.4.1}/test_asmdiff.py +389 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/.gitignore +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/LICENSE +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/asmdiff_example.c +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/across-two-files.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/elf-span-stats.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/elf.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/inspect-span-stats.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/inspect-two-cc.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/inspect.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/pairs-cost.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/pairs-s.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/pairs-two-cc.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/pairs.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/golden/summary-two-files.txt +0 -0
- {asmdiff-0.4.0 → asmdiff-0.4.1}/pyproject.toml +0 -0
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: asmdiff
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Per-function assembly inspection and comparison, across a compiler matrix or from a shipped ELF
|
|
5
|
+
Project-URL: Homepage, https://github.com/rt-rtos/asmdiff
|
|
6
|
+
Project-URL: Repository, https://github.com/rt-rtos/asmdiff
|
|
7
|
+
Author: Rasmus Tikkanen
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: arm,assembly,cfg,clang,cli,codegen,compiler,devtools,diff,disassembly,dsp,embedded,firmware,gcc,loop-analysis,objdump,riscv,static-analysis,xtensa
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
15
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
1
19
|
# [asmdiff](https://pypi.org/project/asmdiff/)
|
|
2
20
|
## per-function assembly inspection and comparison, across a compiler matrix or from a shipped ELF
|
|
3
21
|
|
|
@@ -321,11 +339,11 @@ everywhere else; two things are ELF-specific:
|
|
|
321
339
|
targets, not labels, so in-function targets are rewritten to
|
|
322
340
|
`.L<hex-offset>` labels (the offset from the function start - stable
|
|
323
341
|
across rebuilds of an unchanged function, unlike link addresses). An
|
|
324
|
-
Xtensa zero-overhead loop's end address
|
|
325
|
-
hardware loop is distinguishable from a
|
|
326
|
-
spans` column at a glance: `render_lut`
|
|
327
|
-
body as a ZOL, `render_lut_cub` fell back
|
|
328
|
-
branch loop.
|
|
342
|
+
Xtensa zero-overhead loop's end address (a pi32v2 `rep` block's too)
|
|
343
|
+
becomes `.L<off>_LEND`, so a hardware loop is distinguishable from a
|
|
344
|
+
branch loop in the `loop spans` column at a glance: `render_lut`
|
|
345
|
+
shipped its 32-instruction body as a ZOL, `render_lut_cub` fell back
|
|
346
|
+
to a 74-instruction branch loop.
|
|
329
347
|
- **`-mlongcalls` calls are resolved.** An out-of-range call survives
|
|
330
348
|
linking as `l32r a8, <lit>` + `callx8 a8`, which has no callee name
|
|
331
349
|
of its own. When the disassembly annotates the literal with its
|
|
@@ -397,6 +415,22 @@ cue to pass a matching target.
|
|
|
397
415
|
A whole firmware has over a thousand functions, so a bare ELF with no
|
|
398
416
|
function names and no `--filter` is an error, not a thousand-row table.
|
|
399
417
|
|
|
418
|
+
**Other toolchains.** Besides GNU objdump output, the JieLi pi32v2
|
|
419
|
+
objdump's dialect is read (the AC79 family, e.g. the M-VAVE FM-1 and
|
|
420
|
+
its open firmwares): bare `name:` headers, the space-padded byte column,
|
|
421
|
+
`<sym+0x96 : addr >` targets and the decimal displacement before
|
|
422
|
+
`call`/`goto` are normalised into the GNU shape, `.rodata` dumps are
|
|
423
|
+
skipped, and the algebraic instruction form (`r5_r4 = r4 * r14 (s)`,
|
|
424
|
+
`[r6+0] += r4`) is bucketed by shape for `--span-stats`. The
|
|
425
|
+
toolchain's objdump sits beside its clang, so the bundled `pi32v2`
|
|
426
|
+
profile (or any `--cc` naming that clang by path) derives it; otherwise
|
|
427
|
+
pass `--objdump`. Compile mode works with
|
|
428
|
+
`--cc '$JIELI_TOOLCHAIN/pi32v2/bin/clang -target pi32v2 -Os'`. The
|
|
429
|
+
compiler does not emit the `rep` hardware loop from C, but vendor inline
|
|
430
|
+
asm uses it (`rep n rN { ... }`); a `rep` block gets a span over its
|
|
431
|
+
body, named `.L<off>_LEND` in ELF input like an Xtensa zero-overhead
|
|
432
|
+
loop.
|
|
433
|
+
|
|
400
434
|
If the question behind the sweep is "did my hot loops ship as
|
|
401
435
|
zero-overhead loops", the empirical rules (verified on esp-gcc 15.2,
|
|
402
436
|
`-O2`) are worth knowing before blaming the compiler:
|
|
@@ -1400,6 +1434,9 @@ names its *end* label, forward:
|
|
|
1400
1434
|
retw.n
|
|
1401
1435
|
```
|
|
1402
1436
|
|
|
1437
|
+
pi32v2's `rep` hardware loop (`rep .Ltmp8 r7` in `-S` output, `rep 6 r7 {`
|
|
1438
|
+
in objdump) is the same case, with its body ending at the named label.
|
|
1439
|
+
|
|
1403
1440
|
That is the entire mechanism. There is no CFG, no trip count, and no
|
|
1404
1441
|
notion of "the" loop: a backward `goto` produces a span exactly like a
|
|
1405
1442
|
`for` loop, and an unrolled loop's span is the unrolled body. The column
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: asmdiff
|
|
3
|
-
Version: 0.4.0
|
|
4
|
-
Summary: Per-function assembly inspection and comparison, across a compiler matrix or from a shipped ELF
|
|
5
|
-
Project-URL: Homepage, https://github.com/rt-rtos/asmdiff
|
|
6
|
-
Project-URL: Repository, https://github.com/rt-rtos/asmdiff
|
|
7
|
-
Author: Rasmus Tikkanen
|
|
8
|
-
License-Expression: MIT
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Keywords: arm,assembly,cfg,clang,cli,codegen,compiler,devtools,diff,disassembly,dsp,embedded,firmware,gcc,loop-analysis,objdump,riscv,static-analysis,xtensa
|
|
11
|
-
Classifier: Environment :: Console
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Topic :: Software Development :: Compilers
|
|
15
|
-
Classifier: Topic :: Software Development :: Debuggers
|
|
16
|
-
Requires-Python: >=3.8
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
|
|
19
1
|
# [asmdiff](https://pypi.org/project/asmdiff/)
|
|
20
2
|
## per-function assembly inspection and comparison, across a compiler matrix or from a shipped ELF
|
|
21
3
|
|
|
@@ -339,11 +321,11 @@ everywhere else; two things are ELF-specific:
|
|
|
339
321
|
targets, not labels, so in-function targets are rewritten to
|
|
340
322
|
`.L<hex-offset>` labels (the offset from the function start - stable
|
|
341
323
|
across rebuilds of an unchanged function, unlike link addresses). An
|
|
342
|
-
Xtensa zero-overhead loop's end address
|
|
343
|
-
hardware loop is distinguishable from a
|
|
344
|
-
spans` column at a glance: `render_lut`
|
|
345
|
-
body as a ZOL, `render_lut_cub` fell back
|
|
346
|
-
branch loop.
|
|
324
|
+
Xtensa zero-overhead loop's end address (a pi32v2 `rep` block's too)
|
|
325
|
+
becomes `.L<off>_LEND`, so a hardware loop is distinguishable from a
|
|
326
|
+
branch loop in the `loop spans` column at a glance: `render_lut`
|
|
327
|
+
shipped its 32-instruction body as a ZOL, `render_lut_cub` fell back
|
|
328
|
+
to a 74-instruction branch loop.
|
|
347
329
|
- **`-mlongcalls` calls are resolved.** An out-of-range call survives
|
|
348
330
|
linking as `l32r a8, <lit>` + `callx8 a8`, which has no callee name
|
|
349
331
|
of its own. When the disassembly annotates the literal with its
|
|
@@ -415,6 +397,22 @@ cue to pass a matching target.
|
|
|
415
397
|
A whole firmware has over a thousand functions, so a bare ELF with no
|
|
416
398
|
function names and no `--filter` is an error, not a thousand-row table.
|
|
417
399
|
|
|
400
|
+
**Other toolchains.** Besides GNU objdump output, the JieLi pi32v2
|
|
401
|
+
objdump's dialect is read (the AC79 family, e.g. the M-VAVE FM-1 and
|
|
402
|
+
its open firmwares): bare `name:` headers, the space-padded byte column,
|
|
403
|
+
`<sym+0x96 : addr >` targets and the decimal displacement before
|
|
404
|
+
`call`/`goto` are normalised into the GNU shape, `.rodata` dumps are
|
|
405
|
+
skipped, and the algebraic instruction form (`r5_r4 = r4 * r14 (s)`,
|
|
406
|
+
`[r6+0] += r4`) is bucketed by shape for `--span-stats`. The
|
|
407
|
+
toolchain's objdump sits beside its clang, so the bundled `pi32v2`
|
|
408
|
+
profile (or any `--cc` naming that clang by path) derives it; otherwise
|
|
409
|
+
pass `--objdump`. Compile mode works with
|
|
410
|
+
`--cc '$JIELI_TOOLCHAIN/pi32v2/bin/clang -target pi32v2 -Os'`. The
|
|
411
|
+
compiler does not emit the `rep` hardware loop from C, but vendor inline
|
|
412
|
+
asm uses it (`rep n rN { ... }`); a `rep` block gets a span over its
|
|
413
|
+
body, named `.L<off>_LEND` in ELF input like an Xtensa zero-overhead
|
|
414
|
+
loop.
|
|
415
|
+
|
|
418
416
|
If the question behind the sweep is "did my hot loops ship as
|
|
419
417
|
zero-overhead loops", the empirical rules (verified on esp-gcc 15.2,
|
|
420
418
|
`-O2`) are worth knowing before blaming the compiler:
|
|
@@ -1418,6 +1416,9 @@ names its *end* label, forward:
|
|
|
1418
1416
|
retw.n
|
|
1419
1417
|
```
|
|
1420
1418
|
|
|
1419
|
+
pi32v2's `rep` hardware loop (`rep .Ltmp8 r7` in `-S` output, `rep 6 r7 {`
|
|
1420
|
+
in objdump) is the same case, with its body ending at the named label.
|
|
1421
|
+
|
|
1421
1422
|
That is the entire mechanism. There is no CFG, no trip count, and no
|
|
1422
1423
|
notion of "the" loop: a backward `goto` produces a span exactly like a
|
|
1423
1424
|
`for` loop, and an unrolled loop's span is the unrolled body. The column
|
|
@@ -143,3 +143,12 @@ flags = ["-O2", "-mcpu=cortex-m4", "-mthumb"]
|
|
|
143
143
|
[rp2350]
|
|
144
144
|
cc = "riscv64-unknown-elf-gcc"
|
|
145
145
|
flags = ["-O2", "-march=rv32imac_zicsr_zifencei", "-mabi=ilp32"]
|
|
146
|
+
|
|
147
|
+
# JieLi pi32v2 (the AC79 family: M-VAVE FM-1 and other JieLi-based
|
|
148
|
+
# devices). The vendor's clang 4.0.1 fork comes as one tarball from
|
|
149
|
+
# pkgman.jieliapp.com/s/linux-toolchain; unpack it anywhere and point
|
|
150
|
+
# JIELI_TOOLCHAIN at the directory holding pi32v2/ and common/. Its
|
|
151
|
+
# objdump sits beside clang, so ELF input needs no --objdump.
|
|
152
|
+
[pi32v2]
|
|
153
|
+
cc = "$JIELI_TOOLCHAIN/pi32v2/bin/clang"
|
|
154
|
+
flags = ["-target", "pi32v2", "-Os"]
|
|
@@ -46,8 +46,10 @@ Four modes, plus the no-flag whole-file summary:
|
|
|
46
46
|
listed in full; --filter matches are summarized
|
|
47
47
|
only, unless -l list asks for their listings too.
|
|
48
48
|
objdump comes from
|
|
49
|
-
--objdump PATH
|
|
50
|
-
(trailing gcc swapped for objdump)
|
|
49
|
+
--objdump PATH, the first gcc in the matrix
|
|
50
|
+
(trailing gcc swapped for objdump), or an objdump
|
|
51
|
+
beside a clang given by path. GNU and JieLi
|
|
52
|
+
pi32v2 objdump output are both read; Xtensa
|
|
51
53
|
-mlongcalls call sequences are resolved to their
|
|
52
54
|
real callees when the disassembly names them,
|
|
53
55
|
and report as indirect(<reg>) when it does not.
|
|
@@ -173,7 +175,7 @@ except ModuleNotFoundError:
|
|
|
173
175
|
|
|
174
176
|
# Single source of truth for the package version: pyproject.toml reads
|
|
175
177
|
# it from here (hatchling dynamic version).
|
|
176
|
-
__version__ = "0.4.
|
|
178
|
+
__version__ = "0.4.1"
|
|
177
179
|
|
|
178
180
|
DEFAULT_COMPILERS = ["gcc", "clang"]
|
|
179
181
|
FALLBACK_FLAGS = "-O3"
|
|
@@ -330,6 +332,15 @@ flags = ["-O2", "-mcpu=cortex-m4", "-mthumb"]
|
|
|
330
332
|
[rp2350]
|
|
331
333
|
cc = "riscv64-unknown-elf-gcc"
|
|
332
334
|
flags = ["-O2", "-march=rv32imac_zicsr_zifencei", "-mabi=ilp32"]
|
|
335
|
+
|
|
336
|
+
# JieLi pi32v2 (the AC79 family: M-VAVE FM-1 and other JieLi-based
|
|
337
|
+
# devices). The vendor's clang 4.0.1 fork comes as one tarball from
|
|
338
|
+
# pkgman.jieliapp.com/s/linux-toolchain; unpack it anywhere and point
|
|
339
|
+
# JIELI_TOOLCHAIN at the directory holding pi32v2/ and common/. Its
|
|
340
|
+
# objdump sits beside clang, so ELF input needs no --objdump.
|
|
341
|
+
[pi32v2]
|
|
342
|
+
cc = "$JIELI_TOOLCHAIN/pi32v2/bin/clang"
|
|
343
|
+
flags = ["-target", "pi32v2", "-Os"]
|
|
333
344
|
"""
|
|
334
345
|
|
|
335
346
|
# Preprocessor flags lifted from a compile_commands.json entry so a project
|
|
@@ -669,6 +680,10 @@ NOISE = re.compile(
|
|
|
669
680
|
)
|
|
670
681
|
# Compiler-generated bracketing labels that add nothing (.LFB0:, .Lfunc_end0:).
|
|
671
682
|
NOISE_LABEL = re.compile(r"^\.(LFB|LFE|Lfunc_begin|Lfunc_end)\d*:")
|
|
683
|
+
# clang annotates local labels in the target's comment syntax
|
|
684
|
+
# (".LBB2_1: ; =>This Inner Loop Header: Depth=1" - '#' on x86, '@' on
|
|
685
|
+
# ARM, ';' on pi32v2); the loop-span scan keys on lines ending in ':'.
|
|
686
|
+
LABEL_COMMENT = re.compile(r"^(\.?[\w$.]+:)\s*(?:;|#|@|//).*$")
|
|
672
687
|
# Data emitted *inside* a function body: switch jump tables (.long/.word
|
|
673
688
|
# entries), inline constants, strings. These are not instructions, so they
|
|
674
689
|
# must not be counted; and a self-relative table entry (".long .L5-.L4")
|
|
@@ -714,29 +729,59 @@ def extract_functions(asm_text):
|
|
|
714
729
|
current = None
|
|
715
730
|
continue
|
|
716
731
|
line = raw.strip()
|
|
717
|
-
if not line or line.startswith(("#", "//")):
|
|
732
|
+
if not line or line.startswith(("#", "//", ";", "@")):
|
|
718
733
|
continue
|
|
719
734
|
if NOISE.match(line) or NOISE_LABEL.match(line) or DATA.match(line):
|
|
720
735
|
continue
|
|
736
|
+
line = LABEL_COMMENT.sub(r"\1", line)
|
|
737
|
+
if line == "}": # pi32v2 predicated-block close
|
|
738
|
+
continue
|
|
739
|
+
if line == "dummy": # pi32v2 rep-end filler, no bytes
|
|
740
|
+
continue
|
|
721
741
|
funcs[current].append(line)
|
|
722
742
|
return funcs
|
|
723
743
|
|
|
724
744
|
|
|
725
745
|
# `objdump -d` function header: "40370400 <render_lut>:".
|
|
726
746
|
OBJDUMP_HEADER = re.compile(r"^([0-9a-f]+) <([^>]+)>:\s*$")
|
|
747
|
+
# JieLi's pi32v2 objdump writes the header as a bare label ("rev_hall:");
|
|
748
|
+
# the function's address is its first instruction's.
|
|
749
|
+
OBJDUMP_BARE_HEADER = re.compile(r"^([A-Za-z_][\w$.]*):\s*$")
|
|
727
750
|
# `objdump -d` instruction line: address, one byte-dump field (hex digits
|
|
728
|
-
# and spaces; "004136" is the encoding, not the mnemonic), then
|
|
729
|
-
# mnemonic and operands.
|
|
751
|
+
# and spaces; "004136" is the encoding, not the mnemonic), a tab, then
|
|
752
|
+
# the mnemonic and operands. GNU separates the fields with tabs; JieLi's
|
|
753
|
+
# pads the byte field with spaces before the tab. A data line (JieLi's
|
|
754
|
+
# objdump dumps .rodata too) ends in an ASCII column with no tab after
|
|
755
|
+
# the bytes, so it does not match.
|
|
730
756
|
OBJDUMP_INSN = re.compile(
|
|
731
|
-
r"^\s*([0-9a-f]+):\
|
|
757
|
+
r"^\s*([0-9a-f]+):\s*[0-9a-f][0-9a-f ]*\t\s*(\S+)[ \t]*(.*?)\s*$")
|
|
758
|
+
# Section banner; data sections are skipped (GNU -d never prints them,
|
|
759
|
+
# JieLi's does, as bare-label symbols over byte dumps).
|
|
760
|
+
OBJDUMP_SECTION = re.compile(r"^Disassembly of section (\S+):")
|
|
732
761
|
# An "addr <annotation>" operand: branch, loop or call target.
|
|
733
762
|
OBJDUMP_TARGET = re.compile(r"\b([0-9a-f]+) <([^>]+)>")
|
|
763
|
+
# pi32v2 hardware loop as objdump prints it: "rep 6 r7 {" or
|
|
764
|
+
# "rep 20 16 {" - the body length in bytes (decimal), then the repeat
|
|
765
|
+
# count (register or immediate). The body starts at the next
|
|
766
|
+
# instruction; the closing "}" line has no address and is ignored.
|
|
767
|
+
OBJDUMP_REP = re.compile(r"^(\d+)\s+(\S+)\s*\{?$")
|
|
768
|
+
# JieLi's objdump annotates a target as "<sym+0x96 : 2039318 >" (GNU:
|
|
769
|
+
# "2039318 <sym+0x96>") and puts the decimal displacement before it
|
|
770
|
+
# ("call 762 <fm1_fatal_common : 2000458 >", "goto -26 <...>"). Both are
|
|
771
|
+
# rewritten to the GNU shape so the relabel and call detection apply.
|
|
772
|
+
JIELI_TARGET = re.compile(r"<\s*([^<>:]+?)\s*:\s*([0-9a-f]+)\s*>")
|
|
773
|
+
JIELI_DISP = re.compile(r"(^|goto |call )(-?\d+)\s+(?=[0-9a-f]+ <)")
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def _normalize_targets(ops):
|
|
777
|
+
ops = JIELI_TARGET.sub(r"\2 <\1>", ops)
|
|
778
|
+
return JIELI_DISP.sub(r"\1", ops)
|
|
734
779
|
# A nearest-symbol offset annotation ("<_etext+0x100>"): objdump's name
|
|
735
780
|
# for an address that is not a symbol start. For addresses outside the
|
|
736
781
|
# current function the base symbol is usually unrelated (a literal-pool
|
|
737
782
|
# word or data landing after some other object), so the annotation is
|
|
738
783
|
# misleading, not informative.
|
|
739
|
-
OFFSET_ANNOT = re.compile(r"[+-]0x[0-9a-f]+$")
|
|
784
|
+
OFFSET_ANNOT = re.compile(r"[+-]0x[0-9a-f]+$", re.IGNORECASE)
|
|
740
785
|
|
|
741
786
|
|
|
742
787
|
def extract_functions_objdump(dump_text):
|
|
@@ -749,8 +794,11 @@ def extract_functions_objdump(dump_text):
|
|
|
749
794
|
synthetic local labels (.L<hex-offset>) inserted at the target
|
|
750
795
|
instruction; an Xtensa zero-overhead loop's end address becomes
|
|
751
796
|
.L<off>_LEND, keeping the ZOL-vs-branch naming convention -S output
|
|
752
|
-
has.
|
|
753
|
-
|
|
797
|
+
has. A pi32v2 "rep <bytes> <count> {" block is rewritten to the
|
|
798
|
+
shape its -S output has ("rep .L<off>_LEND <count>"), with the
|
|
799
|
+
label at the address the body ends on. Targets outside the
|
|
800
|
+
function keep a bare-symbol annotation, so calls still name their
|
|
801
|
+
callee; offset-form annotations of other
|
|
754
802
|
symbols are nearest-symbol noise and become raw hex addresses (see
|
|
755
803
|
_relabel_objdump). Offset-form headers
|
|
756
804
|
(``<sym+0x..>``/``<sym-0x..>``: literal pools and other symbol-less
|
|
@@ -758,23 +806,43 @@ def extract_functions_objdump(dump_text):
|
|
|
758
806
|
"""
|
|
759
807
|
funcs = {}
|
|
760
808
|
current = None
|
|
809
|
+
in_code = True
|
|
761
810
|
for raw in dump_text.splitlines():
|
|
811
|
+
s = OBJDUMP_SECTION.match(raw)
|
|
812
|
+
if s:
|
|
813
|
+
in_code = not s.group(1).startswith((".rodata", ".data", ".bss"))
|
|
814
|
+
current = None
|
|
815
|
+
continue
|
|
816
|
+
if not in_code:
|
|
817
|
+
continue
|
|
762
818
|
h = OBJDUMP_HEADER.match(raw)
|
|
763
819
|
if h:
|
|
764
|
-
name = h.group(2)
|
|
820
|
+
name, start = h.group(2), int(h.group(1), 16)
|
|
821
|
+
else:
|
|
822
|
+
h = OBJDUMP_BARE_HEADER.match(raw)
|
|
823
|
+
if h:
|
|
824
|
+
name, start = h.group(1), None
|
|
825
|
+
if h:
|
|
765
826
|
if OFFSET_ANNOT.search(name):
|
|
766
827
|
current = None # symbol-less gap, not a function
|
|
767
828
|
else:
|
|
768
829
|
current = []
|
|
769
|
-
funcs[name] = (
|
|
830
|
+
funcs[name] = (start, current)
|
|
770
831
|
continue
|
|
771
832
|
if current is None:
|
|
772
833
|
continue
|
|
773
834
|
m = OBJDUMP_INSN.match(raw)
|
|
774
835
|
if m:
|
|
775
|
-
current.append((int(m.group(1), 16), m.group(2),
|
|
776
|
-
|
|
777
|
-
|
|
836
|
+
current.append((int(m.group(1), 16), m.group(2),
|
|
837
|
+
_normalize_targets(m.group(3))))
|
|
838
|
+
out = {}
|
|
839
|
+
for name, (start, insns) in funcs.items():
|
|
840
|
+
if start is None:
|
|
841
|
+
if not insns:
|
|
842
|
+
continue # bare-label data symbol in a code section
|
|
843
|
+
start = insns[0][0]
|
|
844
|
+
out[name] = _relabel_objdump(name, start, _resolve_longcalls(insns))
|
|
845
|
+
return out
|
|
778
846
|
|
|
779
847
|
|
|
780
848
|
# The value annotation objdump appends to an l32r whose literal resolves
|
|
@@ -833,7 +901,14 @@ def _relabel_objdump(name, start, insns):
|
|
|
833
901
|
rendered as the raw hex address instead."""
|
|
834
902
|
addrs = {addr for addr, _, _ in insns}
|
|
835
903
|
labels = {}
|
|
836
|
-
|
|
904
|
+
reps = {} # rep addr -> (end addr, count)
|
|
905
|
+
for k, (addr, mnem, ops) in enumerate(insns):
|
|
906
|
+
r = OBJDUMP_REP.match(ops) if mnem == "rep" else None
|
|
907
|
+
if r and k + 1 < len(insns):
|
|
908
|
+
end = insns[k + 1][0] + int(r.group(1))
|
|
909
|
+
if end in addrs:
|
|
910
|
+
reps[addr] = (end, r.group(2))
|
|
911
|
+
labels[end] = ".L%x_LEND" % (end - start)
|
|
837
912
|
for m in OBJDUMP_TARGET.finditer(ops):
|
|
838
913
|
taddr = int(m.group(1), 16)
|
|
839
914
|
if taddr not in addrs:
|
|
@@ -856,7 +931,10 @@ def _relabel_objdump(name, start, insns):
|
|
|
856
931
|
for addr, mnem, ops in insns:
|
|
857
932
|
if addr in labels:
|
|
858
933
|
lines.append(labels[addr] + ":")
|
|
859
|
-
|
|
934
|
+
if addr in reps:
|
|
935
|
+
ops = "%s %s" % (labels[reps[addr][0]], reps[addr][1])
|
|
936
|
+
else:
|
|
937
|
+
ops = OBJDUMP_TARGET.sub(target_name, ops)
|
|
860
938
|
lines.append(mnem + "\t" + ops if ops else mnem)
|
|
861
939
|
return lines
|
|
862
940
|
|
|
@@ -944,8 +1022,9 @@ def loop_span_ranges(lines):
|
|
|
944
1022
|
A span is the run of instructions from a local label to the last
|
|
945
1023
|
instruction that references it from below — a backward branch, which
|
|
946
1024
|
is what a compiled loop looks like on every target the tool parses.
|
|
947
|
-
Xtensa zero-overhead loops (loop/loopnez/loopgt)
|
|
948
|
-
label instead; there the span is the
|
|
1025
|
+
Xtensa zero-overhead loops (loop/loopnez/loopgt) and pi32v2's rep
|
|
1026
|
+
reference their END label instead; there the span is the
|
|
1027
|
+
instructions the loop encloses.
|
|
949
1028
|
Spans are reported in order of appearance, one per label; nested
|
|
950
1029
|
labels yield nested spans.
|
|
951
1030
|
"""
|
|
@@ -962,8 +1041,8 @@ def loop_span_ranges(lines):
|
|
|
962
1041
|
j = label_at[ref]
|
|
963
1042
|
if j < i: # label above: backward branch
|
|
964
1043
|
lo, hi = j, i
|
|
965
|
-
elif mnem.startswith("loop")
|
|
966
|
-
lo, hi = i + 1, j - 1
|
|
1044
|
+
elif mnem.startswith("loop") or mnem == "rep":
|
|
1045
|
+
lo, hi = i + 1, j - 1 # Xtensa/pi32v2: end label below
|
|
967
1046
|
else:
|
|
968
1047
|
continue
|
|
969
1048
|
if ref in spans: # several edges to one label
|
|
@@ -1051,6 +1130,33 @@ _BRANCH_MNEMONICS = frozenset("""
|
|
|
1051
1130
|
""".split())
|
|
1052
1131
|
|
|
1053
1132
|
|
|
1133
|
+
# pi32v2 (JieLi) assembly is algebraic: "r4 = h[r3++=2] (s)", "[r6+0] += r4",
|
|
1134
|
+
# "r5_r4 = r4 * r14 (s)", "if (--r2 != 0) goto .L1". No mnemonic table
|
|
1135
|
+
# fits, so the shape of the line picks the bucket: memory on the left of
|
|
1136
|
+
# the assignment is a store (push and read-modify-write included), on
|
|
1137
|
+
# the right a load (pop included), a product or quotient on the right is
|
|
1138
|
+
# mul or div, and the if/goto/return forms are branches.
|
|
1139
|
+
_ALGEBRAIC = re.compile(
|
|
1140
|
+
r"^(?:[hbwd]?\[|\{|(?:r\d+(?:_r\d+)?(?:\.[lh])?|sp|psr|icfg)\s*[-+*/|&^<>]*="
|
|
1141
|
+
r"|(?:ifs?|goto|rts|ret[iex]|rets|rep)\b)")
|
|
1142
|
+
_ALGEBRAIC_BRANCH = frozenset("if ifs goto rts reti rete retx rets rep".split())
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
def _classify_algebraic(line, mnem):
|
|
1146
|
+
if mnem in _ALGEBRAIC_BRANCH:
|
|
1147
|
+
return "branch"
|
|
1148
|
+
lhs, _, rhs = line.partition("=")
|
|
1149
|
+
if "[" in lhs:
|
|
1150
|
+
return "store"
|
|
1151
|
+
if "[" in rhs:
|
|
1152
|
+
return "load"
|
|
1153
|
+
if "*" in rhs:
|
|
1154
|
+
return "mul"
|
|
1155
|
+
if "/" in rhs:
|
|
1156
|
+
return "div"
|
|
1157
|
+
return "other"
|
|
1158
|
+
|
|
1159
|
+
|
|
1054
1160
|
def classify_insn(line):
|
|
1055
1161
|
"""Bucket one cleaned instruction: load/store/mul/div/branch/other.
|
|
1056
1162
|
|
|
@@ -1059,11 +1165,14 @@ def classify_insn(line):
|
|
|
1059
1165
|
mnemonics no table knows, an AT&T-style memory operand decides
|
|
1060
1166
|
load (source side) vs store (destination, the last operand);
|
|
1061
1167
|
lea and alignment nops are excluded from that heuristic first.
|
|
1168
|
+
pi32v2's algebraic lines are bucketed by shape (_classify_algebraic).
|
|
1062
1169
|
"""
|
|
1063
1170
|
parts = line.split(None, 1)
|
|
1064
1171
|
mnem = parts[0].lower()
|
|
1065
1172
|
rest = parts[1] if len(parts) > 1 else ""
|
|
1066
1173
|
base = mnem.split(".", 1)[0]
|
|
1174
|
+
if _ALGEBRAIC.match(line):
|
|
1175
|
+
return _classify_algebraic(line, mnem)
|
|
1067
1176
|
if mnem in _LOAD_MNEMONICS or base in _LOAD_MNEMONICS:
|
|
1068
1177
|
return "load"
|
|
1069
1178
|
if mnem in _STORE_MNEMONICS or base in _STORE_MNEMONICS:
|
|
@@ -2945,12 +3054,18 @@ def run_inspect(source, matrix, fn_names, layout, extra_flags, tmp,
|
|
|
2945
3054
|
def derive_objdump(matrix):
|
|
2946
3055
|
"""Objdump belonging to the first gcc in the matrix: the toolchain
|
|
2947
3056
|
prefix stays, the tool name swaps (xtensa-esp32s3-elf-gcc ->
|
|
2948
|
-
xtensa-esp32s3-elf-objdump).
|
|
2949
|
-
|
|
3057
|
+
xtensa-esp32s3-elf-objdump). A clang given by path counts when an
|
|
3058
|
+
objdump sits beside it in the same bin directory (JieLi's pi32v2
|
|
3059
|
+
toolchain ships one there). None otherwise: a bare clang has no
|
|
3060
|
+
paired cross-objdump to guess at."""
|
|
2950
3061
|
for cc_cmd in matrix:
|
|
2951
3062
|
cc = shlex.split(cc_cmd)[0]
|
|
2952
3063
|
if cc == "gcc" or cc.endswith(("-gcc", "/gcc")):
|
|
2953
3064
|
return cc[:-3] + "objdump"
|
|
3065
|
+
if cc.endswith("/clang"):
|
|
3066
|
+
sibling = cc[:-5] + "objdump"
|
|
3067
|
+
if os.path.exists(sibling):
|
|
3068
|
+
return sibling
|
|
2954
3069
|
return None
|
|
2955
3070
|
|
|
2956
3071
|
|
|
@@ -3481,7 +3596,8 @@ def main(argv=None):
|
|
|
3481
3596
|
parser.add_argument("--objdump", metavar="PATH",
|
|
3482
3597
|
help="disassembler for ELF input; default: derived "
|
|
3483
3598
|
"from the first gcc in the matrix by swapping "
|
|
3484
|
-
"the trailing gcc for objdump"
|
|
3599
|
+
"the trailing gcc for objdump, or an objdump "
|
|
3600
|
+
"beside a clang given by path")
|
|
3485
3601
|
parser.add_argument("-t", "--target", action="append", default=[],
|
|
3486
3602
|
metavar="NAME",
|
|
3487
3603
|
help="named [table], [groups] name, comma-list, or "
|
|
@@ -1006,6 +1006,10 @@ class TestTargetGroups(unittest.TestCase):
|
|
|
1006
1006
|
def test_expansion_errors(self):
|
|
1007
1007
|
for case, config, tokens, present, absent in self.EXPANSION_ERRORS:
|
|
1008
1008
|
with self.subTest(case=case):
|
|
1009
|
+
# The example-config pointer needs tomllib to read the
|
|
1010
|
+
# example; without it the pointer is omitted (_example_names).
|
|
1011
|
+
if case.startswith("example ") and asmdiff.tomllib is None:
|
|
1012
|
+
self.skipTest("tomllib requires Python >= 3.11")
|
|
1009
1013
|
with self.assertRaises(SystemExit) as ctx:
|
|
1010
1014
|
self.expand(*tokens, config=config)
|
|
1011
1015
|
msg = str(ctx.exception)
|
|
@@ -3780,5 +3784,390 @@ class TestGolden(unittest.TestCase):
|
|
|
3780
3784
|
+ diff)
|
|
3781
3785
|
|
|
3782
3786
|
|
|
3787
|
+
|
|
3788
|
+
# JieLi pi32v2 clang -S output: algebraic assembly, ';' comments, labels
|
|
3789
|
+
# annotated with a trailing comment, a predicated block closed by '}'.
|
|
3790
|
+
PI32V2_S = """\
|
|
3791
|
+
\t.section\t.text.dot16,"ax",@progbits
|
|
3792
|
+
\t.globl\tdot16
|
|
3793
|
+
\t.type\tdot16,@function
|
|
3794
|
+
dot16: ; @dot16
|
|
3795
|
+
; BB#0:
|
|
3796
|
+
\t[--sp] = {rets, r5, r4}
|
|
3797
|
+
\tr3 = r0
|
|
3798
|
+
\tr0 = 0
|
|
3799
|
+
\tifs (r2 < 1) goto .LBB2_2
|
|
3800
|
+
.LBB2_1: ; =>This Inner Loop Header: Depth=1
|
|
3801
|
+
\tr4 = h[r3++=2] (s)
|
|
3802
|
+
\tr5 = h[r1++=2] (s)
|
|
3803
|
+
\tr4 *= r5
|
|
3804
|
+
\tr0 += r4
|
|
3805
|
+
\tif (--r2 != 0) goto .LBB2_1
|
|
3806
|
+
.LBB2_2:
|
|
3807
|
+
\t{pc, r5, r4} = [sp++]
|
|
3808
|
+
.Lfunc_end2:
|
|
3809
|
+
\t.size\tdot16, .Lfunc_end2-dot16
|
|
3810
|
+
\t.section\t.text.add16_sat,"ax",@progbits
|
|
3811
|
+
\t.globl\tadd16_sat
|
|
3812
|
+
\t.type\tadd16_sat,@function
|
|
3813
|
+
add16_sat: ; @add16_sat
|
|
3814
|
+
; BB#0:
|
|
3815
|
+
\tr3 = -32768
|
|
3816
|
+
.LBB0_2: ; =>This Inner Loop Header: Depth=1
|
|
3817
|
+
\tr5 += r4
|
|
3818
|
+
\tr4 = 32767
|
|
3819
|
+
\tifs (r5 < 32768) {
|
|
3820
|
+
\t\tr4 = smax(r5, r3)
|
|
3821
|
+
\t}
|
|
3822
|
+
\th[r0++=2] = r4
|
|
3823
|
+
\tif (--r2 != 0) goto .LBB0_2
|
|
3824
|
+
\t{pc, r5, r4} = [sp++]
|
|
3825
|
+
.Lfunc_end0:
|
|
3826
|
+
\t.size\tadd16_sat, .Lfunc_end0-add16_sat
|
|
3827
|
+
"""
|
|
3828
|
+
|
|
3829
|
+
# JieLi pi32v2 `objdump -d`: bare-label headers, space-padded byte column,
|
|
3830
|
+
# "<sym : addr >" targets with a decimal displacement before them, and a
|
|
3831
|
+
# .rodata section dumped as a symbol over byte lines.
|
|
3832
|
+
PI32V2_OBJDUMP = """\
|
|
3833
|
+
|
|
3834
|
+
build/felucca.elf:\tfile format ELF32-pi32v2
|
|
3835
|
+
|
|
3836
|
+
Disassembly of section .text:
|
|
3837
|
+
_start:
|
|
3838
|
+
2000120: 04 81 \tgoto 2 <_start+0x4 : 2000124 >
|
|
3839
|
+
2000122: 80 00 \trts
|
|
3840
|
+
2000124: 81 ea 12 77 \tcall 192036 <fm1_cstart : 202ef7a >
|
|
3841
|
+
2000128: 49 e0 fe 07 \tr9 = 2046 <build/felucca_size.ll+0x7FE : 7fe >
|
|
3842
|
+
200012c: 80 00 \trts
|
|
3843
|
+
fm6_op_run:
|
|
3844
|
+
203916e: 05 90 \tr5 = [r1+r7<<2]
|
|
3845
|
+
2039170: 44 d1 0d 00 \tr4 = r12 >> 13
|
|
3846
|
+
2039174: 45 1c \tr5_r4 = r4 * r14 (s)
|
|
3847
|
+
2039176: b0 20 \t[r6+0] += r4
|
|
3848
|
+
2039178: 87 f8 e5 1f \tif (r7 != 32) goto -10 <fm6_op_run : 203916e >
|
|
3849
|
+
203917c: 7f 05 \t{pc, r14-r4} = [sp++]
|
|
3850
|
+
Disassembly of section .rodata:
|
|
3851
|
+
SINE:
|
|
3852
|
+
203b5f4:\t 00 00 24 03 48 06 6c 09 ..$.H.l.
|
|
3853
|
+
203b5fc:\t 90 0c b3 0f d6 12 f8 15 ........
|
|
3854
|
+
"""
|
|
3855
|
+
|
|
3856
|
+
|
|
3857
|
+
class TestPi32v2(unittest.TestCase):
|
|
3858
|
+
"""JieLi pi32v2 dialect: clang -S with ';' comments and annotated
|
|
3859
|
+
labels, the vendor objdump's header, byte-column and target syntax,
|
|
3860
|
+
and shape-based span-stats buckets for algebraic assembly."""
|
|
3861
|
+
|
|
3862
|
+
def test_s_label_comment_stripped_so_span_is_found(self):
|
|
3863
|
+
funcs = asmdiff.extract_functions(PI32V2_S)
|
|
3864
|
+
self.assertEqual(sorted(funcs), ["add16_sat", "dot16"])
|
|
3865
|
+
self.assertIn(".LBB2_1:", funcs["dot16"])
|
|
3866
|
+
self.assertEqual(asmdiff.loop_spans(funcs["dot16"]), [(".LBB2_1", 5)])
|
|
3867
|
+
|
|
3868
|
+
def test_s_comment_lines_and_block_close_not_counted(self):
|
|
3869
|
+
body = asmdiff.extract_functions(PI32V2_S)["add16_sat"]
|
|
3870
|
+
self.assertNotIn("}", body)
|
|
3871
|
+
self.assertFalse(any(ln.startswith(";") for ln in body))
|
|
3872
|
+
self.assertEqual(asmdiff.loop_spans(body), [(".LBB0_2", 6)])
|
|
3873
|
+
|
|
3874
|
+
def test_objdump_bare_headers_and_data_section(self):
|
|
3875
|
+
funcs = asmdiff.extract_functions_objdump(PI32V2_OBJDUMP)
|
|
3876
|
+
self.assertEqual(sorted(funcs), ["_start", "fm6_op_run"])
|
|
3877
|
+
|
|
3878
|
+
def test_objdump_targets_normalized(self):
|
|
3879
|
+
funcs = asmdiff.extract_functions_objdump(PI32V2_OBJDUMP)
|
|
3880
|
+
self.assertIn("goto\t.L4", funcs["_start"])
|
|
3881
|
+
self.assertIn("call\tfm1_cstart", funcs["_start"])
|
|
3882
|
+
# uppercase nearest-symbol offset: noise, rendered as the raw address
|
|
3883
|
+
self.assertIn("r9\t= 2046 0x7fe", funcs["_start"])
|
|
3884
|
+
self.assertEqual(asmdiff.call_symbol("call\tfm1_cstart"), "fm1_cstart")
|
|
3885
|
+
self.assertEqual(asmdiff.loop_spans(funcs["fm6_op_run"]), [(".L0", 5)])
|
|
3886
|
+
|
|
3887
|
+
def test_objdump_insn_lines_cleaned(self):
|
|
3888
|
+
body = asmdiff.extract_functions_objdump(PI32V2_OBJDUMP)["fm6_op_run"]
|
|
3889
|
+
self.assertEqual(body[1], "r5\t= [r1+r7<<2]")
|
|
3890
|
+
for ln in body:
|
|
3891
|
+
self.assertNotRegex(ln, r"^[0-9a-f]+\s")
|
|
3892
|
+
|
|
3893
|
+
def test_algebraic_buckets(self):
|
|
3894
|
+
cases = {"r5\t= [r1+r7<<2]": "load",
|
|
3895
|
+
"{pc,\tr14-r4} = [sp++]": "load",
|
|
3896
|
+
"[r6+0]\t+= r4": "store",
|
|
3897
|
+
"[--sp]\t= {rets, r5, r4}": "store",
|
|
3898
|
+
"h[r0++=2]\t= r4": "store",
|
|
3899
|
+
"r5_r4\t= r4 * r14 (s)": "mul",
|
|
3900
|
+
"r1_r0\t+= r6 * r5 (s)": "mul",
|
|
3901
|
+
"r2\t= r2 / r3 (s)": "div",
|
|
3902
|
+
"if\t(r7 != 32) goto .L0": "branch",
|
|
3903
|
+
"ifs\t(r5 < 32768) {": "branch",
|
|
3904
|
+
"goto\t.L4": "branch",
|
|
3905
|
+
"rts": "branch",
|
|
3906
|
+
"call\t__mulsf3": "branch",
|
|
3907
|
+
"r4\t= r12 >> 13": "other",
|
|
3908
|
+
"r4\t= smax(r5, r3)": "other",
|
|
3909
|
+
"r12\t= r7 #": "other"}
|
|
3910
|
+
for line, want in cases.items():
|
|
3911
|
+
self.assertEqual(asmdiff.classify_insn(line), want, line)
|
|
3912
|
+
|
|
3913
|
+
def test_gnu_buckets_untouched_by_algebraic_rule(self):
|
|
3914
|
+
self.assertEqual(asmdiff.classify_insn("l32i\ta0, a2, 0"), "load")
|
|
3915
|
+
self.assertEqual(asmdiff.classify_insn("movl\t%eax, (%rdi)"), "store")
|
|
3916
|
+
|
|
3917
|
+
def test_objdump_beside_clang_is_derived(self):
|
|
3918
|
+
with tempfile.TemporaryDirectory() as d:
|
|
3919
|
+
binary = Path(d) / "pi32v2" / "bin"
|
|
3920
|
+
binary.mkdir(parents=True)
|
|
3921
|
+
(binary / "clang").write_text("")
|
|
3922
|
+
self.assertIsNone(asmdiff.derive_objdump([str(binary / "clang")]))
|
|
3923
|
+
(binary / "objdump").write_text("")
|
|
3924
|
+
self.assertEqual(asmdiff.derive_objdump([str(binary / "clang") + " -target pi32v2"]),
|
|
3925
|
+
str(binary / "objdump"))
|
|
3926
|
+
|
|
3927
|
+
|
|
3928
|
+
# JieLi pi32v2 `rep` hardware loops (clang 4.0.1 fork, -Os), assembled from
|
|
3929
|
+
# inline asm after fast_i2f in JieLi's jl_fft.c: "rep %1 { ... }" with a
|
|
3930
|
+
# register count. rep_two holds two blocks after a plain C loop,
|
|
3931
|
+
# rep_nested one inside a C loop. In -S the block's "}" is an end label
|
|
3932
|
+
# followed by a "dummy" placeholder; objdump prints "rep <body bytes> rN {".
|
|
3933
|
+
PI32V2_REP_S = """\
|
|
3934
|
+
\t.globl\trep_two
|
|
3935
|
+
\t.p2align\t1
|
|
3936
|
+
\t.type\trep_two,@function
|
|
3937
|
+
rep_two: ; @rep_two
|
|
3938
|
+
; BB#0:
|
|
3939
|
+
\t[--sp] = {rets, r8-r4}
|
|
3940
|
+
\tr8 = [sp+24]
|
|
3941
|
+
\tifs (r3 < 1) goto .LBB0_3
|
|
3942
|
+
; BB#1:
|
|
3943
|
+
\tr5 = r3
|
|
3944
|
+
\tr6 = r2
|
|
3945
|
+
\tr7 = r0
|
|
3946
|
+
.LBB0_2: ; =>This Inner Loop Header: Depth=1
|
|
3947
|
+
\tr4 = [r7++=4]
|
|
3948
|
+
\tr4 += 1
|
|
3949
|
+
\t[r6++=4] = r4
|
|
3950
|
+
\tif (--r5 != 0) goto .LBB0_2
|
|
3951
|
+
.LBB0_3:
|
|
3952
|
+
\tr5 = r1
|
|
3953
|
+
\tr6 = r8
|
|
3954
|
+
\tr7 = r3
|
|
3955
|
+
\t;APP
|
|
3956
|
+
.Ltmp7:
|
|
3957
|
+
\trep .Ltmp8 r7
|
|
3958
|
+
|
|
3959
|
+
\tr4 = [r0++=4]
|
|
3960
|
+
|
|
3961
|
+
\tr4 = r4 + r6
|
|
3962
|
+
|
|
3963
|
+
\t[r5++=4] = r4
|
|
3964
|
+
|
|
3965
|
+
.Ltmp8:
|
|
3966
|
+
\tdummy
|
|
3967
|
+
|
|
3968
|
+
\tif (r7 > 0) goto .Ltmp7
|
|
3969
|
+
|
|
3970
|
+
|
|
3971
|
+
\t;NO_APP
|
|
3972
|
+
\t;APP
|
|
3973
|
+
.Ltmp9:
|
|
3974
|
+
\trep .Ltmp10 r3
|
|
3975
|
+
|
|
3976
|
+
\tr0 = [r1++=4]
|
|
3977
|
+
|
|
3978
|
+
\tr0 += r8
|
|
3979
|
+
|
|
3980
|
+
\tr0 = r0 << 1
|
|
3981
|
+
|
|
3982
|
+
\t[r2++=4] = r0
|
|
3983
|
+
|
|
3984
|
+
.Ltmp10:
|
|
3985
|
+
\tdummy
|
|
3986
|
+
|
|
3987
|
+
\tif (r3 > 0) goto .Ltmp9
|
|
3988
|
+
|
|
3989
|
+
|
|
3990
|
+
\t;NO_APP
|
|
3991
|
+
\t{pc, r8-r4} = [sp++]
|
|
3992
|
+
.Lfunc_end0:
|
|
3993
|
+
\t.size\trep_two, .Lfunc_end0-rep_two
|
|
3994
|
+
|
|
3995
|
+
\t.globl\trep_nested
|
|
3996
|
+
\t.p2align\t1
|
|
3997
|
+
\t.type\trep_nested,@function
|
|
3998
|
+
rep_nested: ; @rep_nested
|
|
3999
|
+
; BB#0:
|
|
4000
|
+
\t[--sp] = {rets, r10-r4}
|
|
4001
|
+
\tifs (r3 < 1) goto .LBB1_3
|
|
4002
|
+
; BB#1:
|
|
4003
|
+
\tr8 = [sp+32]
|
|
4004
|
+
\tr9 = r2 << 2
|
|
4005
|
+
.LBB1_2: ; =>This Inner Loop Header: Depth=1
|
|
4006
|
+
\tr10 = r0
|
|
4007
|
+
\tr7 = r8
|
|
4008
|
+
\tr4 = r2
|
|
4009
|
+
\tr5 = r1
|
|
4010
|
+
\t;APP
|
|
4011
|
+
.Ltmp20:
|
|
4012
|
+
\trep .Ltmp21 r4
|
|
4013
|
+
|
|
4014
|
+
\tr6 = [r10++=4]
|
|
4015
|
+
|
|
4016
|
+
\tr6 = r6 + r7
|
|
4017
|
+
|
|
4018
|
+
\t[r5++=4] = r6
|
|
4019
|
+
|
|
4020
|
+
.Ltmp21:
|
|
4021
|
+
\tdummy
|
|
4022
|
+
|
|
4023
|
+
\tif (r4 > 0) goto .Ltmp20
|
|
4024
|
+
|
|
4025
|
+
|
|
4026
|
+
\t;NO_APP
|
|
4027
|
+
\tr1 += r9
|
|
4028
|
+
\tif (--r3 != 0) goto .LBB1_2
|
|
4029
|
+
.LBB1_3:
|
|
4030
|
+
\t{pc, r10-r4} = [sp++]
|
|
4031
|
+
.Lfunc_end1:
|
|
4032
|
+
\t.size\trep_nested, .Lfunc_end1-rep_nested
|
|
4033
|
+
|
|
4034
|
+
|
|
4035
|
+
"""
|
|
4036
|
+
|
|
4037
|
+
PI32V2_REP_OBJDUMP = """\
|
|
4038
|
+
|
|
4039
|
+
rep_demo.o:\tfile format ELF32-pi32v2
|
|
4040
|
+
|
|
4041
|
+
Disassembly of section .text:
|
|
4042
|
+
rep_two:
|
|
4043
|
+
0: 78 04 \t[--sp] = {rets, r8-r4}
|
|
4044
|
+
2: d4 e9 18 80 \tr8 = [sp+24]
|
|
4045
|
+
6: 83 fd 08 02 \tifs (r3 < 1) goto 16 <rep_two+0x1A : 1a >
|
|
4046
|
+
a: 35 16 \tr5 = r3
|
|
4047
|
+
c: 26 16 \tr6 = r2
|
|
4048
|
+
e: 07 16 \tr7 = r0
|
|
4049
|
+
10: 74 05 \tr4 = [r7++=4]
|
|
4050
|
+
12: c4 21 \tr4 += 1
|
|
4051
|
+
14: e4 05 \t[r6++=4] = r4
|
|
4052
|
+
16: 05 ea fb ff \tif (--r5 != 0) goto -10 <rep_two+0x10 : 10 >
|
|
4053
|
+
1a: 15 16 \tr5 = r1
|
|
4054
|
+
1c: 86 16 \tr6 = r8
|
|
4055
|
+
1e: 37 16 \tr7 = r3
|
|
4056
|
+
20: 27 03 \trep 6 r7 {
|
|
4057
|
+
22: 04 05 \t\tr4 = [r0++=4]
|
|
4058
|
+
24: c4 1d \t\tr4 = r4 + r6
|
|
4059
|
+
26: d4 05 \t\t[r5++=4] = r4
|
|
4060
|
+
\t}
|
|
4061
|
+
28: 07 fc fa 01 \tif (r7 > 0) goto -12 <rep_two+0x20 : 20 >
|
|
4062
|
+
2c: 33 03 \trep 8 r3 {
|
|
4063
|
+
2e: 10 05 \t\tr0 = [r1++=4]
|
|
4064
|
+
30: 80 18 \t\tr0 += r8
|
|
4065
|
+
32: 00 a1 \t\tr0 = r0 << 1
|
|
4066
|
+
34: a0 05 \t\t[r2++=4] = r0
|
|
4067
|
+
\t}
|
|
4068
|
+
36: 03 fc f9 01 \tif (r3 > 0) goto -14 <rep_two+0x2C : 2c >
|
|
4069
|
+
3a: 58 04 \t{pc, r8-r4} = [sp++]
|
|
4070
|
+
|
|
4071
|
+
|
|
4072
|
+
rep_nested:
|
|
4073
|
+
3c: 7a 04 \t[--sp] = {rets, r10-r4}
|
|
4074
|
+
3e: 83 fd 12 02 \tifs (r3 < 1) goto 36 <rep_nested+0x2A : 66 >
|
|
4075
|
+
42: d4 e9 20 80 \tr8 = [sp+32]
|
|
4076
|
+
46: c0 e1 22 90 \tr9 = r2 << 2
|
|
4077
|
+
4a: 0a 16 \tr10 = r0
|
|
4078
|
+
4c: 87 16 \tr7 = r8
|
|
4079
|
+
4e: 24 16 \tr4 = r2
|
|
4080
|
+
50: 15 16 \tr5 = r1
|
|
4081
|
+
52: 34 03 \trep 8 r4 {
|
|
4082
|
+
54: d8 ec a4 60 \t\tr6 = [r10++=4]
|
|
4083
|
+
58: ee 1d \t\tr6 = r6 + r7
|
|
4084
|
+
5a: d6 05 \t\t[r5++=4] = r6
|
|
4085
|
+
\t}
|
|
4086
|
+
5c: 04 fc f9 01 \tif (r4 > 0) goto -14 <rep_nested+0x16 : 52 >
|
|
4087
|
+
60: 91 18 \tr1 += r9
|
|
4088
|
+
62: 03 ea f2 ff \tif (--r3 != 0) goto -28 <rep_nested+0xE : 4a >
|
|
4089
|
+
66: 5a 04 \t{pc, r10-r4} = [sp++]
|
|
4090
|
+
|
|
4091
|
+
"""
|
|
4092
|
+
|
|
4093
|
+
# An immediate count ("rep 20 16 {"), from a second probe object.
|
|
4094
|
+
PI32V2_REP_IMM_OBJDUMP = """\
|
|
4095
|
+
|
|
4096
|
+
imm.o:\tfile format ELF32-pi32v2
|
|
4097
|
+
|
|
4098
|
+
Disassembly of section .text:
|
|
4099
|
+
rep_imm:
|
|
4100
|
+
0: 90 8f \trep 20 16 {
|
|
4101
|
+
2: 02 05 \t\tr2 = [r0++=4]
|
|
4102
|
+
4: 2a 1c \t\tr2 = r2 + r1
|
|
4103
|
+
6: 22 a1 \t\tr2 = r2 << 1
|
|
4104
|
+
8: 2a 1c \t\tr2 = r2 + r1
|
|
4105
|
+
a: 2a 1c \t\tr2 = r2 + r1
|
|
4106
|
+
c: 2a 1c \t\tr2 = r2 + r1
|
|
4107
|
+
e: 2a 1c \t\tr2 = r2 + r1
|
|
4108
|
+
10: 2a 1c \t\tr2 = r2 + r1
|
|
4109
|
+
12: 2a 1c \t\tr2 = r2 + r1
|
|
4110
|
+
14: 82 05 \t\t[r0++=4] = r2
|
|
4111
|
+
\t}
|
|
4112
|
+
16: 80 00 \trts
|
|
4113
|
+
|
|
4114
|
+
"""
|
|
4115
|
+
|
|
4116
|
+
|
|
4117
|
+
class TestPi32v2Rep(unittest.TestCase):
|
|
4118
|
+
"""pi32v2 `rep` hardware loops: the span covers the repeated body,
|
|
4119
|
+
in -S output (rep names its end label) and in objdump output (the
|
|
4120
|
+
body length in bytes becomes a .L<off>_LEND label, as for Xtensa)."""
|
|
4121
|
+
|
|
4122
|
+
spans = staticmethod(asmdiff.loop_spans)
|
|
4123
|
+
|
|
4124
|
+
def test_s_rep_span_is_the_body(self):
|
|
4125
|
+
# Same counts as the ELF of this object: the zero-byte "dummy"
|
|
4126
|
+
# clang puts after a rep end label is not an instruction.
|
|
4127
|
+
body = asmdiff.extract_functions(PI32V2_REP_S)["rep_two"]
|
|
4128
|
+
self.assertNotIn("dummy", body)
|
|
4129
|
+
self.assertEqual(self.spans(body),
|
|
4130
|
+
[(".LBB0_2", 4), (".Ltmp7", 5), (".Ltmp8", 3),
|
|
4131
|
+
(".Ltmp9", 6), (".Ltmp10", 4)])
|
|
4132
|
+
|
|
4133
|
+
def test_s_rep_inside_c_loop_nests(self):
|
|
4134
|
+
body = asmdiff.extract_functions(PI32V2_REP_S)["rep_nested"]
|
|
4135
|
+
ranges = asmdiff.loop_span_ranges(body)
|
|
4136
|
+
self.assertEqual(self.spans(body),
|
|
4137
|
+
[(".LBB1_2", 11), (".Ltmp20", 5), (".Ltmp21", 3)])
|
|
4138
|
+
self.assertEqual(asmdiff.span_depths(ranges),
|
|
4139
|
+
{".LBB1_2": 0, ".Ltmp20": 1, ".Ltmp21": 2})
|
|
4140
|
+
|
|
4141
|
+
def test_objdump_rep_gets_end_label(self):
|
|
4142
|
+
body = asmdiff.extract_functions_objdump(PI32V2_REP_OBJDUMP)["rep_two"]
|
|
4143
|
+
self.assertIn("rep\t.L28_LEND r7", body)
|
|
4144
|
+
self.assertIn("rep\t.L36_LEND r3", body)
|
|
4145
|
+
self.assertNotIn("}", body)
|
|
4146
|
+
self.assertEqual(body[body.index(".L28_LEND:") + 1],
|
|
4147
|
+
"if\t(r7 > 0) goto .L20")
|
|
4148
|
+
|
|
4149
|
+
def test_objdump_rep_spans(self):
|
|
4150
|
+
funcs = asmdiff.extract_functions_objdump(PI32V2_REP_OBJDUMP)
|
|
4151
|
+
self.assertEqual(self.spans(funcs["rep_two"]),
|
|
4152
|
+
[(".L10", 4), (".L20", 5), (".L28_LEND", 3),
|
|
4153
|
+
(".L2c", 6), (".L36_LEND", 4)])
|
|
4154
|
+
self.assertEqual(self.spans(funcs["rep_nested"]),
|
|
4155
|
+
[(".Le", 11), (".L16", 5), (".L20_LEND", 3)])
|
|
4156
|
+
|
|
4157
|
+
def test_objdump_rep_immediate_count(self):
|
|
4158
|
+
body = asmdiff.extract_functions_objdump(
|
|
4159
|
+
PI32V2_REP_IMM_OBJDUMP)["rep_imm"]
|
|
4160
|
+
self.assertEqual(body[0], "rep\t.L16_LEND 16")
|
|
4161
|
+
self.assertEqual(self.spans(body), [(".L16_LEND", 10)])
|
|
4162
|
+
|
|
4163
|
+
def test_rep_stays_a_branch(self):
|
|
4164
|
+
self.assertEqual(asmdiff.classify_insn("rep\t.L28_LEND r7"), "branch")
|
|
4165
|
+
self.assertEqual(asmdiff.classify_insn("rep\t.Ltmp8 r7"), "branch")
|
|
4166
|
+
|
|
4167
|
+
def test_x86_rep_prefix_makes_no_span(self):
|
|
4168
|
+
self.assertEqual(asmdiff.loop_span_ranges(
|
|
4169
|
+
["rep movsb", ".L2:", "addl\t$1, %eax"]), [])
|
|
4170
|
+
|
|
4171
|
+
|
|
3783
4172
|
if __name__ == "__main__":
|
|
3784
4173
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|