// AsmJit - Machine code generation for C++ // // * Official AsmJit Home Page: https://asmjit.com // * Official Github Repository: https://github.com/asmjit/asmjit // // Copyright (c) 2008-2020 The AsmJit Authors // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include #include #include #include using namespace asmjit; // Signature of the generated function. typedef void (*SumIntsFunc)(int* dst, const int* a, const int* b); // This function works with both x86::Assembler and x86::Builder. It shows how // `x86::Emitter` can be used to make your code more generic. static void makeRawFunc(x86::Emitter* emitter) noexcept { // Decide which registers will be mapped to function arguments. Try changing // registers of `dst`, `src_a`, and `src_b` and see what happens in function's // prolog and epilog. x86::Gp dst = emitter->zax(); x86::Gp src_a = emitter->zcx(); x86::Gp src_b = emitter->zdx(); // Decide which vector registers to use. We use these to keep the code generic, // you can switch to any other registers when needed. x86::Xmm vec0 = x86::xmm0; x86::Xmm vec1 = x86::xmm1; // Create and initialize `FuncDetail` and `FuncFrame`. FuncDetail func; func.init(FuncSignatureT(CallConv::kIdHost), emitter->environment()); FuncFrame frame; frame.init(func); // Make XMM0 and XMM1 dirty. VEC group includes XMM|YMM|ZMM registers. frame.addDirtyRegs(x86::xmm0, x86::xmm1); FuncArgsAssignment args(&func); // Create arguments assignment context. args.assignAll(dst, src_a, src_b); // Assign our registers to arguments. args.updateFuncFrame(frame); // Reflect our args in FuncFrame. frame.finalize(); // Emit prolog and allocate arguments to registers. emitter->emitProlog(frame); emitter->emitArgsAssignment(frame, args); emitter->movdqu(vec0, x86::ptr(src_a)); // Load 4 ints from [src_a] to XMM0. emitter->movdqu(vec1, x86::ptr(src_b)); // Load 4 ints from [src_b] to XMM1. emitter->paddd(vec0, vec1); // Add 4 ints in XMM1 to XMM0. emitter->movdqu(x86::ptr(dst), vec0); // Store the result to [dst]. // Emit epilog and return. emitter->emitEpilog(frame); } #ifndef ASMJIT_NO_COMPILER // This function works with x86::Compiler, provided for comparison. static void makeCompiledFunc(x86::Compiler* cc) noexcept { x86::Gp dst = cc->newIntPtr(); x86::Gp src_a = cc->newIntPtr(); x86::Gp src_b = cc->newIntPtr(); x86::Xmm vec0 = cc->newXmm(); x86::Xmm vec1 = cc->newXmm(); cc->addFunc(FuncSignatureT(CallConv::kIdHost)); cc->setArg(0, dst); cc->setArg(1, src_a); cc->setArg(2, src_b); cc->movdqu(vec0, x86::ptr(src_a)); cc->movdqu(vec1, x86::ptr(src_b)); cc->paddd(vec0, vec1); cc->movdqu(x86::ptr(dst), vec0); cc->endFunc(); } #endif static uint32_t testFunc(JitRuntime& rt, uint32_t emitterType) noexcept { #ifndef ASMJIT_NO_LOGGING FileLogger logger(stdout); logger.setIndentation(FormatOptions::kIndentationCode, 2); #endif CodeHolder code; code.init(rt.environment()); #ifndef ASMJIT_NO_LOGGING code.setLogger(&logger); #endif Error err = kErrorOk; switch (emitterType) { case BaseEmitter::kTypeAssembler: { printf("Using x86::Assembler:\n"); x86::Assembler a(&code); makeRawFunc(a.as()); break; } #ifndef ASMJIT_NO_BUILDER case BaseEmitter::kTypeBuilder: { printf("Using x86::Builder:\n"); x86::Builder cb(&code); makeRawFunc(cb.as()); err = cb.finalize(); if (err) { printf("x86::Builder::finalize() failed: %s\n", DebugUtils::errorAsString(err)); return 1; } break; } #endif #ifndef ASMJIT_NO_COMPILER case BaseEmitter::kTypeCompiler: { printf("Using x86::Compiler:\n"); x86::Compiler cc(&code); makeCompiledFunc(&cc); err = cc.finalize(); if (err) { printf("x86::Compiler::finalize() failed: %s\n", DebugUtils::errorAsString(err)); return 1; } break; } #endif } // Add the code generated to the runtime. SumIntsFunc fn; err = rt.add(&fn, &code); if (err) { printf("JitRuntime::add() failed: %s\n", DebugUtils::errorAsString(err)); return 1; } // Execute the generated function. int inA[4] = { 4, 3, 2, 1 }; int inB[4] = { 1, 5, 2, 8 }; int out[4]; fn(out, inA, inB); // Should print {5 8 4 9}. printf("Result = { %d %d %d %d }\n\n", out[0], out[1], out[2], out[3]); rt.release(fn); return !(out[0] == 5 && out[1] == 8 && out[2] == 4 && out[3] == 9); } int main() { printf("AsmJit X86 Emitter Test\n\n"); JitRuntime rt; unsigned nFailed = 0; nFailed += testFunc(rt, BaseEmitter::kTypeAssembler); #ifndef ASMJIT_NO_BUILDER nFailed += testFunc(rt, BaseEmitter::kTypeBuilder); #endif #ifndef ASMJIT_NO_COMPILER nFailed += testFunc(rt, BaseEmitter::kTypeCompiler); #endif if (!nFailed) printf("Success:\n All tests passed\n"); else printf("Failure:\n %u %s failed\n", nFailed, nFailed == 1 ? "test" : "tests"); return nFailed ? 1 : 0; }