asmjit_test_emitters.cpp (10089B)
1 // This file is part of AsmJit project <https://asmjit.com> 2 // 3 // See <asmjit/core.h> or LICENSE.md for license and copyright information 4 // SPDX-License-Identifier: Zlib 5 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #include <asmjit/core.h> 11 #include "../commons/asmjitutils.h" 12 13 #if ASMJIT_ARCH_X86 != 0 14 #include <asmjit/x86.h> 15 #endif 16 17 #if ASMJIT_ARCH_ARM == 64 18 #include <asmjit/a64.h> 19 #endif 20 21 using namespace asmjit; 22 23 static void print_app_info() noexcept { 24 printf("AsmJit Emitters Test-Suite v%u.%u.%u [Arch=%s] [Mode=%s]\n\n", 25 unsigned((ASMJIT_LIBRARY_VERSION >> 16) ), 26 unsigned((ASMJIT_LIBRARY_VERSION >> 8) & 0xFF), 27 unsigned((ASMJIT_LIBRARY_VERSION ) & 0xFF), 28 asmjit_arch_as_string(Arch::kHost), 29 asmjit_build_type() 30 ); 31 } 32 33 #if !defined(ASMJIT_NO_JIT) && ((ASMJIT_ARCH_X86 != 0 && !defined(ASMJIT_NO_X86 )) || \ 34 (ASMJIT_ARCH_ARM == 64 && !defined(ASMJIT_NO_AARCH64)) ) 35 36 // Signature of the generated function. 37 using SumIntsFunc = void (*)(int* dst, const int* a, const int* b); 38 39 // X86 Backend 40 // ----------- 41 42 #if ASMJIT_ARCH_X86 != 0 43 // This function works with both x86::Assembler and x86::Builder. It shows how 44 // `x86::Emitter` can be used to make your code more generic. 45 static void generate_func_with_emitter(x86::Emitter* emitter) noexcept { 46 // Decide which registers will be mapped to function arguments. Try changing 47 // registers of `dst`, `src_a`, and `src_b` and see what happens in function's 48 // prolog and epilog. 49 x86::Gp dst = emitter->zax(); 50 x86::Gp src_a = emitter->zcx(); 51 x86::Gp src_b = emitter->zdx(); 52 53 // Decide which vector registers to use. We use these to keep the code generic, 54 // you can switch to any other registers when needed. 55 x86::Vec vec0 = x86::xmm0; 56 x86::Vec vec1 = x86::xmm1; 57 58 // Create and initialize `FuncDetail` and `FuncFrame`. 59 FuncDetail func; 60 func.init(FuncSignature::build<void, int*, const int*, const int*>(), emitter->environment()); 61 62 FuncFrame frame; 63 frame.init(func); 64 65 // Make or registers dirty. 66 frame.add_dirty_regs(vec0, vec1); 67 68 FuncArgsAssignment args(&func); // Create arguments assignment context. 69 args.assign_all(dst, src_a, src_b); // Assign our registers to arguments. 70 args.update_func_frame(frame); // Reflect our args in FuncFrame. 71 frame.finalize(); 72 73 // Emit prolog and allocate arguments to registers. 74 emitter->emit_prolog(frame); 75 emitter->emit_args_assignment(frame, args); 76 77 emitter->movdqu(vec0, x86::ptr(src_a)); // Load 4 ints from [src_a] to XMM0. 78 emitter->movdqu(vec1, x86::ptr(src_b)); // Load 4 ints from [src_b] to XMM1. 79 80 emitter->paddd(vec0, vec1); // Add 4 ints in XMM1 to XMM0. 81 emitter->movdqu(x86::ptr(dst), vec0); // Store the result to [dst]. 82 83 // Emit epilog and return. 84 emitter->emit_epilog(frame); 85 } 86 87 #ifndef ASMJIT_NO_COMPILER 88 // This function works with x86::Compiler, provided for comparison. 89 static void generate_func_with_compiler(x86::Compiler* cc) noexcept { 90 x86::Gp dst = cc->new_gp_ptr("dst"); 91 x86::Gp src_a = cc->new_gp_ptr("src_a"); 92 x86::Gp src_b = cc->new_gp_ptr("src_b"); 93 x86::Vec vec0 = cc->new_xmm("vec0"); 94 x86::Vec vec1 = cc->new_xmm("vec1"); 95 96 FuncNode* func_node = cc->add_func(FuncSignature::build<void, int*, const int*, const int*>()); 97 func_node->set_arg(0, dst); 98 func_node->set_arg(1, src_a); 99 func_node->set_arg(2, src_b); 100 101 cc->movdqu(vec0, x86::ptr(src_a)); 102 cc->movdqu(vec1, x86::ptr(src_b)); 103 cc->paddd(vec0, vec1); 104 cc->movdqu(x86::ptr(dst), vec0); 105 cc->end_func(); 106 } 107 #endif 108 109 static Error generate_func(CodeHolder& code, EmitterType emitter_type) noexcept { 110 switch (emitter_type) { 111 case EmitterType::kAssembler: { 112 printf("Using x86::Assembler:\n"); 113 x86::Assembler a(&code); 114 generate_func_with_emitter(a.as<x86::Emitter>()); 115 return Error::kOk; 116 } 117 118 #ifndef ASMJIT_NO_BUILDER 119 case EmitterType::kBuilder: { 120 printf("Using x86::Builder:\n"); 121 x86::Builder cb(&code); 122 generate_func_with_emitter(cb.as<x86::Emitter>()); 123 124 return cb.finalize(); 125 } 126 #endif 127 128 #ifndef ASMJIT_NO_COMPILER 129 case EmitterType::kCompiler: { 130 printf("Using x86::Compiler:\n"); 131 x86::Compiler cc(&code); 132 generate_func_with_compiler(&cc); 133 134 return cc.finalize(); 135 } 136 #endif 137 138 default: { 139 printf("** FAILURE: No emitter to use **\n"); 140 exit(1); 141 } 142 } 143 } 144 #endif 145 146 // AArch64 Backend 147 // --------------- 148 149 #if ASMJIT_ARCH_ARM == 64 150 // This function works with both a64::Assembler and a64::Builder. It shows how 151 // `a64::Emitter` can be used to make your code more generic. 152 static void generate_func_with_emitter(a64::Emitter* emitter) noexcept { 153 // Decide which registers will be mapped to function arguments. Try changing 154 // registers of `dst`, `src_a`, and `src_b` and see what happens in function's 155 // prolog and epilog. 156 a64::Gp dst = a64::x0; 157 a64::Gp src_a = a64::x1; 158 a64::Gp src_b = a64::x2; 159 160 // Decide which vector registers to use. We use these to keep the code generic, 161 // you can switch to any other registers when needed. 162 a64::Vec vec0 = a64::v0; 163 a64::Vec vec1 = a64::v1; 164 a64::Vec vec2 = a64::v2; 165 166 // Create and initialize `FuncDetail` and `FuncFrame`. 167 FuncDetail func; 168 func.init(FuncSignature::build<void, int*, const int*, const int*>(), emitter->environment()); 169 170 FuncFrame frame; 171 frame.init(func); 172 173 // Make XMM0 and XMM1 dirty. VEC group includes XMM|YMM|ZMM registers. 174 frame.add_dirty_regs(vec0, vec1, vec2); 175 176 FuncArgsAssignment args(&func); // Create arguments assignment context. 177 args.assign_all(dst, src_a, src_b); // Assign our registers to arguments. 178 args.update_func_frame(frame); // Reflect our args in FuncFrame. 179 frame.finalize(); 180 181 // Emit prolog and allocate arguments to registers. 182 emitter->emit_prolog(frame); 183 emitter->emit_args_assignment(frame, args); 184 185 emitter->ld1(vec0.b16(), a64::ptr(src_a)); // Load 4 ints from [src_a] to vec0. 186 emitter->ld1(vec1.b16(), a64::ptr(src_b)); // Load 4 ints from [src_b] to vec1. 187 emitter->add(vec2.s4(), vec0.s4(), vec1.s4()); // Add 4 ints of vec0 and vec1 and store to vec2. 188 emitter->st1(vec2.b16(), a64::ptr(dst)); // Store the result (vec2) to [dst]. 189 190 // Emit epilog and return. 191 emitter->emit_epilog(frame); 192 } 193 194 #ifndef ASMJIT_NO_COMPILER 195 // This function works with x86::Compiler, provided for comparison. 196 static void generate_func_with_compiler(a64::Compiler* cc) noexcept { 197 a64::Gp dst = cc->new_gp_ptr("dst"); 198 a64::Gp src_a = cc->new_gp_ptr("src_a"); 199 a64::Gp src_b = cc->new_gp_ptr("src_b"); 200 a64::Vec vec0 = cc->new_vec_q("vec0"); 201 a64::Vec vec1 = cc->new_vec_q("vec1"); 202 a64::Vec vec2 = cc->new_vec_q("vec2"); 203 204 FuncNode* func_node = cc->add_func(FuncSignature::build<void, int*, const int*, const int*>()); 205 func_node->set_arg(0, dst); 206 func_node->set_arg(1, src_a); 207 func_node->set_arg(2, src_b); 208 209 cc->ld1(vec0.b16(), a64::ptr(src_a)); // Load 4 ints from [src_a] to vec0. 210 cc->ld1(vec1.b16(), a64::ptr(src_b)); // Load 4 ints from [src_b] to vec1. 211 cc->add(vec2.s4(), vec0.s4(), vec1.s4()); // Add 4 ints of vec0 and vec1 and store to vec2. 212 cc->st1(vec2.b16(), a64::ptr(dst)); // Store the result (vec2) to [dst]. 213 cc->end_func(); 214 } 215 #endif 216 217 static Error generate_func(CodeHolder& code, EmitterType emitter_type) noexcept { 218 switch (emitter_type) { 219 case EmitterType::kAssembler: { 220 printf("Using a64::Assembler:\n"); 221 a64::Assembler a(&code); 222 generate_func_with_emitter(a.as<a64::Emitter>()); 223 return Error::kOk; 224 } 225 226 #ifndef ASMJIT_NO_BUILDER 227 case EmitterType::kBuilder: { 228 printf("Using a64::Builder:\n"); 229 a64::Builder cb(&code); 230 generate_func_with_emitter(cb.as<a64::Emitter>()); 231 232 return cb.finalize(); 233 } 234 #endif 235 236 #ifndef ASMJIT_NO_COMPILER 237 case EmitterType::kCompiler: { 238 printf("Using a64::Compiler:\n"); 239 a64::Compiler cc(&code); 240 generate_func_with_compiler(&cc); 241 242 return cc.finalize(); 243 } 244 #endif 245 246 default: { 247 printf("** FAILURE: No emitter to use **\n"); 248 exit(1); 249 } 250 } 251 } 252 #endif 253 254 // Testing 255 // ------- 256 257 static uint32_t test_func(JitRuntime& rt, EmitterType emitter_type) noexcept { 258 #ifndef ASMJIT_NO_LOGGING 259 FileLogger logger(stdout); 260 logger.set_indentation(FormatIndentationGroup::kCode, 2); 261 #endif 262 263 CodeHolder code; 264 code.init(rt.environment(), rt.cpu_features()); 265 266 #ifndef ASMJIT_NO_LOGGING 267 code.set_logger(&logger); 268 #endif 269 270 Error err = generate_func(code, emitter_type); 271 if (err != Error::kOk) { 272 printf("** FAILURE: Failed to generate a function: %s **\n", DebugUtils::error_as_string(err)); 273 return 1; 274 } 275 276 // Add the code generated to the runtime. 277 SumIntsFunc fn; 278 err = rt.add(&fn, &code); 279 280 if (err != Error::kOk) { 281 printf("** FAILURE: JitRuntime::add() failed: %s **\n", DebugUtils::error_as_string(err)); 282 return 1; 283 } 284 285 // Execute the generated function. 286 static const int in_a[4] = { 4, 3, 2, 1 }; 287 static const int in_b[4] = { 1, 5, 2, 8 }; 288 int out[4] {}; 289 fn(out, in_a, in_b); 290 291 // Should print {5 8 4 9}. 292 printf("Result = { %d %d %d %d }\n\n", out[0], out[1], out[2], out[3]); 293 294 rt.release(fn); 295 return out[0] == 5 && out[1] == 8 && out[2] == 4 && out[3] == 9; 296 } 297 298 int main() { 299 print_app_info(); 300 301 JitRuntime rt; 302 unsigned failed_count = 0; 303 304 failed_count += !test_func(rt, EmitterType::kAssembler); 305 306 #ifndef ASMJIT_NO_BUILDER 307 failed_count += !test_func(rt, EmitterType::kBuilder); 308 #endif 309 310 #ifndef ASMJIT_NO_COMPILER 311 failed_count += !test_func(rt, EmitterType::kCompiler); 312 #endif 313 314 if (!failed_count) 315 printf("** SUCCESS **\n"); 316 else 317 printf("** FAILURE - %u %s failed ** \n", failed_count, failed_count == 1 ? "test" : "tests"); 318 319 return failed_count ? 1 : 0; 320 } 321 #else 322 int main() { 323 print_app_info(); 324 printf("!! This test is disabled: <ASMJIT_NO_JIT> or unsuitable target architecture !!\n"); 325 return 0; 326 } 327 #endif // ASMJIT_ARCH_X86 && !ASMJIT_NO_X86 && !ASMJIT_NO_JIT