1//! Implementation of ARM specific builtins for Run-time ABI
2//! This file includes all ARM-only functions.
3const std = @import("std");
4const builtin = @import("builtin");
5const target = builtin.target;
6const arch = builtin.cpu.arch;
7const compiler_rt = @import("../compiler_rt.zig");
8const symbol = compiler_rt.symbol;
9
10comptime {
11 if (!builtin.is_test) {
12 if (arch.isArm()) {
13 symbol(&__aeabi_unwind_cpp_pr0, "__aeabi_unwind_cpp_pr0");
14 symbol(&__aeabi_unwind_cpp_pr1, "__aeabi_unwind_cpp_pr1");
15 symbol(&__aeabi_unwind_cpp_pr2, "__aeabi_unwind_cpp_pr2");
16
17 if (compiler_rt.want_windows_arm_abi) {
18 symbol(&__aeabi_ldivmod, "__rt_sdiv64");
19 symbol(&__aeabi_uldivmod, "__rt_udiv64");
20 symbol(&__aeabi_idivmod, "__rt_sdiv");
21 symbol(&__aeabi_uidivmod, "__rt_udiv");
22 }
23 symbol(&__aeabi_ldivmod, "__aeabi_ldivmod");
24 symbol(&__aeabi_uldivmod, "__aeabi_uldivmod");
25 symbol(&__aeabi_idivmod, "__aeabi_idivmod");
26 symbol(&__aeabi_uidivmod, "__aeabi_uidivmod");
27
28 symbol(&__aeabi_memcpy, "__aeabi_memcpy");
29 symbol(&__aeabi_memcpy4, "__aeabi_memcpy4");
30 symbol(&__aeabi_memcpy8, "__aeabi_memcpy8");
31
32 symbol(&__aeabi_memmove, "__aeabi_memmove");
33 symbol(&__aeabi_memmove4, "__aeabi_memmove4");
34 symbol(&__aeabi_memmove8, "__aeabi_memmove8");
35
36 symbol(&__aeabi_memset, "__aeabi_memset");
37 symbol(&__aeabi_memset4, "__aeabi_memset4");
38 symbol(&__aeabi_memset8, "__aeabi_memset8");
39
40 symbol(&__aeabi_memclr, "__aeabi_memclr");
41 symbol(&__aeabi_memclr4, "__aeabi_memclr4");
42 symbol(&__aeabi_memclr8, "__aeabi_memclr8");
43
44 if (builtin.os.tag == .linux or builtin.os.tag == .freebsd) {
45 symbol(&__aeabi_read_tp, "__aeabi_read_tp");
46 }
47
48 // floating-point helper functions (single+double-precision reverse subtraction, y – x), see subdf3.zig
49 symbol(&__aeabi_frsub, "__aeabi_frsub");
50 symbol(&__aeabi_drsub, "__aeabi_drsub");
51 }
52 }
53}
54
55const __divmodsi4 = @import("int.zig").__divmodsi4;
56const __udivmodsi4 = @import("int.zig").__udivmodsi4;
57const __divmoddi4 = @import("int.zig").__divmoddi4;
58const __udivmoddi4 = @import("int.zig").__udivmoddi4;
59
60extern fn memset(dest: ?*anyopaque, c: i32, n: usize) ?*anyopaque;
61extern fn memcpy(noalias dest: ?*anyopaque, noalias src: ?*const anyopaque, n: usize) ?*anyopaque;
62extern fn memmove(dest: ?*anyopaque, src: ?*const anyopaque, n: usize) ?*anyopaque;
63
64pub fn __aeabi_memcpy(dest: [*]u8, src: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
65 @setRuntimeSafety(false);
66 _ = memcpy(dest, src, n);
67}
68pub fn __aeabi_memcpy4(dest: [*]u8, src: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
69 @setRuntimeSafety(false);
70 _ = memcpy(dest, src, n);
71}
72pub fn __aeabi_memcpy8(dest: [*]u8, src: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
73 @setRuntimeSafety(false);
74 _ = memcpy(dest, src, n);
75}
76
77pub fn __aeabi_memmove(dest: [*]u8, src: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
78 @setRuntimeSafety(false);
79 _ = memmove(dest, src, n);
80}
81pub fn __aeabi_memmove4(dest: [*]u8, src: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
82 @setRuntimeSafety(false);
83 _ = memmove(dest, src, n);
84}
85pub fn __aeabi_memmove8(dest: [*]u8, src: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
86 @setRuntimeSafety(false);
87 _ = memmove(dest, src, n);
88}
89
90pub fn __aeabi_memset(dest: [*]u8, n: usize, c: i32) callconv(.{ .arm_aapcs = .{} }) void {
91 @setRuntimeSafety(false);
92 // This is dentical to the standard `memset` definition but with the last
93 // two arguments swapped
94 _ = memset(dest, c, n);
95}
96pub fn __aeabi_memset4(dest: [*]u8, n: usize, c: i32) callconv(.{ .arm_aapcs = .{} }) void {
97 @setRuntimeSafety(false);
98 _ = memset(dest, c, n);
99}
100pub fn __aeabi_memset8(dest: [*]u8, n: usize, c: i32) callconv(.{ .arm_aapcs = .{} }) void {
101 @setRuntimeSafety(false);
102 _ = memset(dest, c, n);
103}
104
105pub fn __aeabi_memclr(dest: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
106 @setRuntimeSafety(false);
107 _ = memset(dest, 0, n);
108}
109pub fn __aeabi_memclr4(dest: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
110 @setRuntimeSafety(false);
111 _ = memset(dest, 0, n);
112}
113pub fn __aeabi_memclr8(dest: [*]u8, n: usize) callconv(.{ .arm_aapcs = .{} }) void {
114 @setRuntimeSafety(false);
115 _ = memset(dest, 0, n);
116}
117
118// Dummy functions to avoid errors during the linking phase
119pub fn __aeabi_unwind_cpp_pr0() callconv(.{ .arm_aapcs = .{} }) void {}
120pub fn __aeabi_unwind_cpp_pr1() callconv(.{ .arm_aapcs = .{} }) void {}
121pub fn __aeabi_unwind_cpp_pr2() callconv(.{ .arm_aapcs = .{} }) void {}
122
123// This function can only clobber r0 according to the ABI
124pub fn __aeabi_read_tp() callconv(.naked) void {
125 @setRuntimeSafety(false);
126 asm volatile (
127 \\ mrc p15, 0, r0, c13, c0, 3
128 \\ bx lr
129 );
130 unreachable;
131}
132
133// The following functions are wrapped in an asm block to ensure the required
134// calling convention is always respected
135
136pub fn __aeabi_uidivmod() callconv(.naked) void {
137 @setRuntimeSafety(false);
138 // Divide r0 by r1; the quotient goes in r0, the remainder in r1
139 asm volatile (
140 \\ push {lr}
141 \\ sub sp, #4
142 \\ mov r2, sp
143 \\ bl %[__udivmodsi4]
144 \\ ldr r1, [sp]
145 \\ add sp, #4
146 \\ pop {pc}
147 :
148 : [__udivmodsi4] "X" (&__udivmodsi4),
149 : .{ .memory = true });
150 unreachable;
151}
152
153pub fn __aeabi_uldivmod() callconv(.naked) void {
154 @setRuntimeSafety(false);
155 // Divide r1:r0 by r3:r2; the quotient goes in r1:r0, the remainder in r3:r2
156 asm volatile (
157 \\ push {r4, lr}
158 \\ sub sp, #16
159 \\ add r4, sp, #8
160 \\ str r4, [sp]
161 \\ bl %[__udivmoddi4]
162 \\ ldr r2, [sp, #8]
163 \\ ldr r3, [sp, #12]
164 \\ add sp, #16
165 \\ pop {r4, pc}
166 :
167 : [__udivmoddi4] "X" (&__udivmoddi4),
168 : .{ .memory = true });
169 unreachable;
170}
171
172pub fn __aeabi_idivmod() callconv(.naked) void {
173 @setRuntimeSafety(false);
174 // Divide r0 by r1; the quotient goes in r0, the remainder in r1
175 asm volatile (
176 \\ push {lr}
177 \\ sub sp, #4
178 \\ mov r2, sp
179 \\ bl %[__divmodsi4]
180 \\ ldr r1, [sp]
181 \\ add sp, #4
182 \\ pop {pc}
183 :
184 : [__divmodsi4] "X" (&__divmodsi4),
185 : .{ .memory = true });
186 unreachable;
187}
188
189pub fn __aeabi_ldivmod() callconv(.naked) void {
190 @setRuntimeSafety(false);
191 // Divide r1:r0 by r3:r2; the quotient goes in r1:r0, the remainder in r3:r2
192 asm volatile (
193 \\ push {r4, lr}
194 \\ sub sp, #16
195 \\ add r4, sp, #8
196 \\ str r4, [sp]
197 \\ bl %[__divmoddi4]
198 \\ ldr r2, [sp, #8]
199 \\ ldr r3, [sp, #12]
200 \\ add sp, #16
201 \\ pop {r4, pc}
202 :
203 : [__divmoddi4] "X" (&__divmoddi4),
204 : .{ .memory = true });
205 unreachable;
206}
207
208// Float Arithmetic
209
210fn __aeabi_frsub(a: f32, b: f32) callconv(.{ .arm_aapcs = .{} }) f32 {
211 const neg_a: f32 = @bitCast(@as(u32, @bitCast(a)) ^ (@as(u32, 1) << 31));
212 return b + neg_a;
213}
214
215fn __aeabi_drsub(a: f64, b: f64) callconv(.{ .arm_aapcs = .{} }) f64 {
216 const neg_a: f64 = @bitCast(@as(u64, @bitCast(a)) ^ (@as(u64, 1) << 63));
217 return b + neg_a;
218}
219
220test "__aeabi_frsub" {
221 if (!builtin.cpu.arch.isArm() or builtin.cpu.arch.isThumb()) return error.SkipZigTest;
222 const inf32 = std.math.inf(f32);
223 const maxf32 = std.math.floatMax(f32);
224 const frsub_data = [_][3]f32{
225 [_]f32{ 0.0, 0.0, -0.0 },
226 [_]f32{ 0.0, -0.0, -0.0 },
227 [_]f32{ -0.0, 0.0, 0.0 },
228 [_]f32{ -0.0, -0.0, -0.0 },
229 [_]f32{ 0.0, 1.0, 1.0 },
230 [_]f32{ 1.0, 0.0, -1.0 },
231 [_]f32{ 1.0, 1.0, 0.0 },
232 [_]f32{ 1234.56789, 9876.54321, 8641.97532 },
233 [_]f32{ 9876.54321, 1234.56789, -8641.97532 },
234 [_]f32{ -8641.97532, 1234.56789, 9876.54321 },
235 [_]f32{ 8641.97532, 9876.54321, 1234.56789 },
236 [_]f32{ -maxf32, -maxf32, 0.0 },
237 [_]f32{ maxf32, maxf32, 0.0 },
238 [_]f32{ maxf32, -maxf32, -inf32 },
239 [_]f32{ -maxf32, maxf32, inf32 },
240 };
241 for (frsub_data) |data| {
242 try std.testing.expectApproxEqAbs(data[2], __aeabi_frsub(data[0], data[1]), 0.001);
243 }
244}
245
246test "__aeabi_drsub" {
247 if (!builtin.cpu.arch.isArm() or builtin.cpu.arch.isThumb()) return error.SkipZigTest;
248 if (builtin.cpu.arch == .armeb and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22061
249 const inf64 = std.math.inf(f64);
250 const maxf64 = std.math.floatMax(f64);
251 const frsub_data = [_][3]f64{
252 [_]f64{ 0.0, 0.0, -0.0 },
253 [_]f64{ 0.0, -0.0, -0.0 },
254 [_]f64{ -0.0, 0.0, 0.0 },
255 [_]f64{ -0.0, -0.0, -0.0 },
256 [_]f64{ 0.0, 1.0, 1.0 },
257 [_]f64{ 1.0, 0.0, -1.0 },
258 [_]f64{ 1.0, 1.0, 0.0 },
259 [_]f64{ 1234.56789, 9876.54321, 8641.97532 },
260 [_]f64{ 9876.54321, 1234.56789, -8641.97532 },
261 [_]f64{ -8641.97532, 1234.56789, 9876.54321 },
262 [_]f64{ 8641.97532, 9876.54321, 1234.56789 },
263 [_]f64{ -maxf64, -maxf64, 0.0 },
264 [_]f64{ maxf64, maxf64, 0.0 },
265 [_]f64{ maxf64, -maxf64, -inf64 },
266 [_]f64{ -maxf64, maxf64, inf64 },
267 };
268 for (frsub_data) |data| {
269 try std.testing.expectApproxEqAbs(data[2], __aeabi_drsub(data[0], data[1]), 0.000001);
270 }
271}