/src/xz/src/liblzma/simple/arm64.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: 0BSD |
2 | | |
3 | | /////////////////////////////////////////////////////////////////////////////// |
4 | | // |
5 | | /// \file arm64.c |
6 | | /// \brief Filter for ARM64 binaries |
7 | | /// |
8 | | /// This converts ARM64 relative addresses in the BL and ADRP immediates |
9 | | /// to absolute values to increase redundancy of ARM64 code. |
10 | | /// |
11 | | /// Converting B or ADR instructions was also tested but it's not useful. |
12 | | /// A majority of the jumps for the B instruction are very small (+/- 0xFF). |
13 | | /// These are typical for loops and if-statements. Encoding them to their |
14 | | /// absolute address reduces redundancy since many of the small relative |
15 | | /// jump values are repeated, but very few of the absolute addresses are. |
16 | | // |
17 | | // Authors: Lasse Collin |
18 | | // Jia Tan |
19 | | // Igor Pavlov |
20 | | // |
21 | | /////////////////////////////////////////////////////////////////////////////// |
22 | | |
23 | | #include "simple_private.h" |
24 | | |
25 | | |
26 | | static size_t |
27 | | arm64_code(void *simple lzma_attribute((__unused__)), |
28 | | uint32_t now_pos, bool is_encoder, |
29 | | uint8_t *buffer, size_t size) |
30 | 8.68k | { |
31 | 8.68k | size &= ~(size_t)3; |
32 | | |
33 | 8.68k | size_t i; |
34 | | |
35 | | // Clang 14.0.6 on x86-64 makes this four times bigger and 40 % slower |
36 | | // with auto-vectorization that is enabled by default with -O2. |
37 | | // Such vectorization bloat happens with -O2 when targeting ARM64 too |
38 | | // but performance hasn't been tested. |
39 | 8.68k | #ifdef __clang__ |
40 | 8.68k | # pragma clang loop vectorize(disable) |
41 | 8.68k | #endif |
42 | 1.46M | for (i = 0; i < size; i += 4) { |
43 | 1.45M | uint32_t pc = (uint32_t)(now_pos + i); |
44 | 1.45M | uint32_t instr = read32le(buffer + i); |
45 | | |
46 | 1.45M | if ((instr >> 26) == 0x25) { |
47 | | // BL instruction: |
48 | | // The full 26-bit immediate is converted. |
49 | | // The range is +/-128 MiB. |
50 | | // |
51 | | // Using the full range helps quite a lot with |
52 | | // big executables. Smaller range would reduce false |
53 | | // positives in non-code sections of the input though |
54 | | // so this is a compromise that slightly favors big |
55 | | // files. With the full range, only six bits of the 32 |
56 | | // need to match to trigger a conversion. |
57 | 10.1k | const uint32_t src = instr; |
58 | 10.1k | instr = 0x94000000; |
59 | | |
60 | 10.1k | pc >>= 2; |
61 | 10.1k | if (!is_encoder) |
62 | 10.1k | pc = 0U - pc; |
63 | | |
64 | 10.1k | instr |= (src + pc) & 0x03FFFFFF; |
65 | 10.1k | write32le(buffer + i, instr); |
66 | | |
67 | 1.44M | } else if ((instr & 0x9F000000) == 0x90000000) { |
68 | | // ADRP instruction: |
69 | | // Only values in the range +/-512 MiB are converted. |
70 | | // |
71 | | // Using less than the full +/-4 GiB range reduces |
72 | | // false positives on non-code sections of the input |
73 | | // while being excellent for executables up to 512 MiB. |
74 | | // The positive effect of ADRP conversion is smaller |
75 | | // than that of BL but it also doesn't hurt so much in |
76 | | // non-code sections of input because, with +/-512 MiB |
77 | | // range, nine bits of 32 need to match to trigger a |
78 | | // conversion (two 10-bit match choices = 9 bits). |
79 | 8.24k | const uint32_t src = ((instr >> 29) & 3) |
80 | 8.24k | | ((instr >> 3) & 0x001FFFFC); |
81 | | |
82 | | // With the addition only one branch is needed to |
83 | | // check the +/- range. This is usually false when |
84 | | // processing ARM64 code so branch prediction will |
85 | | // handle it well in terms of performance. |
86 | | // |
87 | | //if ((src & 0x001E0000) != 0 |
88 | | // && (src & 0x001E0000) != 0x001E0000) |
89 | 8.24k | if ((src + 0x00020000) & 0x001C0000) |
90 | 4.69k | continue; |
91 | | |
92 | 3.54k | instr &= 0x9000001F; |
93 | | |
94 | 3.54k | pc >>= 12; |
95 | 3.54k | if (!is_encoder) |
96 | 3.54k | pc = 0U - pc; |
97 | | |
98 | 3.54k | const uint32_t dest = src + pc; |
99 | 3.54k | instr |= (dest & 3) << 29; |
100 | 3.54k | instr |= (dest & 0x0003FFFC) << 3; |
101 | 3.54k | instr |= (0U - (dest & 0x00020000)) & 0x00E00000; |
102 | 3.54k | write32le(buffer + i, instr); |
103 | 3.54k | } |
104 | 1.45M | } |
105 | | |
106 | 8.68k | return i; |
107 | 8.68k | } |
108 | | |
109 | | |
110 | | static lzma_ret |
111 | | arm64_coder_init(lzma_next_coder *next, const lzma_allocator *allocator, |
112 | | const lzma_filter_info *filters, bool is_encoder) |
113 | 727 | { |
114 | 727 | return lzma_simple_coder_init(next, allocator, filters, |
115 | 727 | &arm64_code, 0, 4, 4, is_encoder); |
116 | 727 | } |
117 | | |
118 | | |
119 | | #ifdef HAVE_ENCODER_ARM64 |
120 | | extern lzma_ret |
121 | | lzma_simple_arm64_encoder_init(lzma_next_coder *next, |
122 | | const lzma_allocator *allocator, |
123 | | const lzma_filter_info *filters) |
124 | 0 | { |
125 | 0 | return arm64_coder_init(next, allocator, filters, true); |
126 | 0 | } |
127 | | |
128 | | |
129 | | extern LZMA_API(size_t) |
130 | | lzma_bcj_arm64_encode(uint32_t start_offset, uint8_t *buf, size_t size) |
131 | 0 | { |
132 | | // start_offset must be a multiple of four. |
133 | 0 | start_offset &= ~UINT32_C(3); |
134 | 0 | return arm64_code(NULL, start_offset, true, buf, size); |
135 | 0 | } |
136 | | #endif |
137 | | |
138 | | |
139 | | #ifdef HAVE_DECODER_ARM64 |
140 | | extern lzma_ret |
141 | | lzma_simple_arm64_decoder_init(lzma_next_coder *next, |
142 | | const lzma_allocator *allocator, |
143 | | const lzma_filter_info *filters) |
144 | 727 | { |
145 | 727 | return arm64_coder_init(next, allocator, filters, false); |
146 | 727 | } |
147 | | |
148 | | |
149 | | extern LZMA_API(size_t) |
150 | | lzma_bcj_arm64_decode(uint32_t start_offset, uint8_t *buf, size_t size) |
151 | 0 | { |
152 | | // start_offset must be a multiple of four. |
153 | 0 | start_offset &= ~UINT32_C(3); |
154 | 0 | return arm64_code(NULL, start_offset, false, buf, size); |
155 | 0 | } |
156 | | #endif |