LCOV - code coverage report
Current view: top level - lepton - CompiledExpression.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage (other modules) Lines: 284 293 96.9 %
Date: 2026-11-05 10:19:06 Functions: 20 20 100.0 %

          Line data    Source code
       1             : /* -------------------------------------------------------------------------- *
       2             :  *                                   lepton                                   *
       3             :  * -------------------------------------------------------------------------- *
       4             :  * This is part of the lepton expression parser.                              *
       5             :  * See https://openmm.org/development.                                        *
       6             :  *                                                                            *
       7             :  * Portions copyright (c) 2013-2022 Stanford University and the Authors.      *
       8             :  * Authors: Peter Eastman                                                     *
       9             :  * Contributors:                                                              *
      10             :  *                                                                            *
      11             :  * Permission is hereby granted, free of charge, to any person obtaining a    *
      12             :  * copy of this software and associated documentation files (the "Software"), *
      13             :  * to deal in the Software without restriction, including without limitation  *
      14             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
      15             :  * and/or sell copies of the Software, and to permit persons to whom the      *
      16             :  * Software is furnished to do so, subject to the following conditions:       *
      17             :  *                                                                            *
      18             :  * The above copyright notice and this permission notice shall be included in *
      19             :  * all copies or substantial portions of the Software.                        *
      20             :  *                                                                            *
      21             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
      22             :  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
      23             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
      24             :  * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,    *
      25             :  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR      *
      26             :  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE  *
      27             :  * USE OR OTHER DEALINGS IN THE SOFTWARE.                                     *
      28             :  * -------------------------------------------------------------------------- */
      29             : 
      30             : #include "CompiledExpression.h"
      31             : #include "Operation.h"
      32             : #include "ParsedExpression.h"
      33             : #ifdef __PLUMED_HAS_ASMJIT
      34             :     #include "asmjit/a64.h"
      35             :     #include "asmjit/x86.h"
      36             : #endif
      37             : #include <utility>
      38             : #include <cstdlib>
      39             : #include <limits>
      40             : 
      41             : namespace PLMD {
      42             : using namespace lepton;
      43             : using namespace std;
      44             : #ifdef __PLUMED_HAS_ASMJIT
      45             :     using namespace asmjit;
      46             : #endif
      47             : 
      48       65477 : bool lepton::useAsmJit() {
      49             : #ifdef __PLUMED_HAS_ASMJIT
      50         356 :   static const bool use=[](){
      51         356 :     if(auto s=std::getenv("PLUMED_USE_ASMJIT")) {
      52           3 :       auto ss=std::string(s);
      53           3 :       if(ss=="yes") return true;
      54           1 :       if(ss=="no") return false;
      55           0 :       throw Exception("PLUMED_USE_ASMJIT variable is set to " + ss + "; should be yes or no");
      56             :     }
      57             :     return true; // by default use asmjit
      58       65477 :   }();
      59       65477 :   return use;
      60             : #else
      61             :   return false;
      62             : #endif
      63             : }
      64             : 
      65       27908 : AsmJitRuntimePtr::AsmJitRuntimePtr()
      66             : #ifdef __PLUMED_HAS_ASMJIT
      67       27908 :   : ptr(useAsmJit()?new asmjit::JitRuntime:nullptr)
      68             : #endif
      69       27908 : {}
      70             : 
      71       27908 : AsmJitRuntimePtr::~AsmJitRuntimePtr()
      72             : {
      73             : #ifdef __PLUMED_HAS_ASMJIT
      74       27908 :   if(useAsmJit()) delete static_cast<asmjit::JitRuntime*>(ptr);
      75             : #endif
      76       27908 : }
      77             : 
      78         284 : CompiledExpression::CompiledExpression() : jitCode(NULL) {
      79         284 : }
      80             : 
      81        9304 : CompiledExpression::CompiledExpression(const ParsedExpression& expression) : jitCode(NULL) {
      82        9304 :     ParsedExpression expr = expression.optimize(); // Just in case it wasn't already optimized.
      83        9304 :     vector<pair<ExpressionTreeNode, int> > temps;
      84        9304 :     compileExpression(expr.getRootNode(), temps);
      85             :     int maxArguments = 1;
      86       46229 :     for (int i = 0; i < (int) operation.size(); i++)
      87       36925 :         if (operation[i]->getNumArguments() > maxArguments)
      88        5639 :             maxArguments = operation[i]->getNumArguments();
      89        9304 :     argValues.resize(maxArguments);
      90             : #ifdef __PLUMED_HAS_ASMJIT
      91        9304 :     if(useAsmJit()) generateJitCode();
      92             : #endif
      93       18608 : }
      94             : 
      95       27908 : CompiledExpression::~CompiledExpression() {
      96      139109 :     for (int i = 0; i < (int) operation.size(); i++)
      97      111201 :         if (operation[i] != NULL)
      98      111201 :             delete operation[i];
      99      111632 : }
     100             : 
     101       18320 : CompiledExpression::CompiledExpression(const CompiledExpression& expression) : jitCode(NULL) {
     102       18320 :     *this = expression;
     103       18320 : }
     104             : 
     105       18574 : CompiledExpression& CompiledExpression::operator=(const CompiledExpression& expression) {
     106       18574 :     arguments = expression.arguments;
     107       18574 :     target = expression.target;
     108             :     variableIndices = expression.variableIndices;
     109             :     variableNames = expression.variableNames;
     110       18574 :     workspace.resize(expression.workspace.size());
     111       18574 :     argValues.resize(expression.argValues.size());
     112       18574 :     operation.resize(expression.operation.size());
     113       92850 :     for (int i = 0; i < (int) operation.size(); i++)
     114       74276 :         operation[i] = expression.operation[i]->clone();
     115       18574 :     setVariableLocations(variablePointers);
     116       18574 :     return *this;
     117             : }
     118             : 
     119       59454 : void CompiledExpression::compileExpression(const ExpressionTreeNode& node, vector<pair<ExpressionTreeNode, int> >& temps) {
     120       59454 :     if (findTempIndex(node, temps) != -1)
     121       10129 :         return; // We have already processed a node identical to this one.
     122             : 
     123             :     // Process the child nodes.
     124             : 
     125       49325 :     vector<int> args;
     126       99475 :     for (int i = 0; i < node.getChildren().size(); i++) {
     127       50150 :         compileExpression(node.getChildren()[i], temps);
     128       50150 :         args.push_back(findTempIndex(node.getChildren()[i], temps));
     129             :     }
     130             : 
     131             :     // Process this node.
     132             : 
     133       49325 :     if (node.getOperation().getId() == Operation::VARIABLE) {
     134       12400 :         variableIndices[node.getOperation().getName()] = (int) workspace.size();
     135       24800 :         variableNames.insert(node.getOperation().getName());
     136             :     }
     137             :     else {
     138       36925 :         int stepIndex = (int) arguments.size();
     139       36925 :         arguments.push_back(vector<int>());
     140       36925 :         target.push_back((int) workspace.size());
     141       36925 :         operation.push_back(node.getOperation().clone());
     142       36925 :         if (args.size() == 0)
     143        1348 :             arguments[stepIndex].push_back(0); // The value won't actually be used.  We just need something there.
     144             :         else {
     145             :             // If the arguments are sequential, we can just pass a pointer to the first one.
     146             : 
     147             :             bool sequential = true;
     148       50150 :             for (int i = 1; i < args.size(); i++)
     149       14573 :                 if (args[i] != args[i-1]+1)
     150             :                     sequential = false;
     151       35577 :             if (sequential)
     152       25927 :                 arguments[stepIndex].push_back(args[0]);
     153             :             else
     154        9650 :                 arguments[stepIndex] = args;
     155             :         }
     156             :     }
     157       49325 :     temps.push_back(make_pair(node, (int) workspace.size()));
     158       49325 :     workspace.push_back(0.0);
     159             : }
     160             : 
     161      109604 : int CompiledExpression::findTempIndex(const ExpressionTreeNode& node, vector<pair<ExpressionTreeNode, int> >& temps) {
     162     1872402 :     for (int i = 0; i < (int) temps.size(); i++)
     163     1823077 :         if (temps[i].first == node)
     164             :             return i;
     165             :     return -1;
     166             : }
     167             : 
     168        4214 : const set<string>& CompiledExpression::getVariables() const {
     169        4214 :     return variableNames;
     170             : }
     171             : 
     172       78167 : double& CompiledExpression::getVariableReference(const string& name) {
     173             :     map<string, double*>::iterator pointer = variablePointers.find(name);
     174       78167 :     if (pointer != variablePointers.end())
     175         100 :         return *pointer->second;
     176             :     map<string, int>::iterator index = variableIndices.find(name);
     177       78067 :     if (index == variableIndices.end())
     178       39207 :         throw Exception("getVariableReference: Unknown variable '"+name+"'");
     179       64998 :     return workspace[index->second];
     180             : }
     181             : 
     182       18674 : void CompiledExpression::setVariableLocations(map<string, double*>& variableLocations) {
     183             :   variablePointers = variableLocations;
     184       18674 :   static const bool asmjit=useAsmJit();
     185       18674 :   if(asmjit) {
     186             : #ifdef __PLUMED_HAS_ASMJIT
     187             :     // Rebuild the JIT code.
     188             : 
     189       18350 :     if (workspace.size() > 0)
     190       18350 :         generateJitCode();
     191             : #endif
     192             :   }
     193             :     // Make a list of all variables we will need to copy before evaluating the expression.
     194             : 
     195       18674 :     variablesToCopy.clear();
     196       43547 :     for (map<string, int>::const_iterator iter = variableIndices.begin(); iter != variableIndices.end(); ++iter) {
     197       24873 :         map<string, double*>::iterator pointer = variablePointers.find(iter->first);
     198       24873 :         if (pointer != variablePointers.end())
     199         100 :             variablesToCopy.push_back(make_pair(&workspace[iter->second], pointer->second));
     200             :     }
     201       18674 : }
     202             : 
     203    85788486 : double CompiledExpression::evaluate() const {
     204             : #ifdef __PLUMED_HAS_ASMJIT
     205    85788486 :     if(jitCode) return jitCode();
     206             : #endif
     207       16443 :     for (int i = 0; i < variablesToCopy.size(); i++)
     208           0 :         *variablesToCopy[i].first = *variablesToCopy[i].second;
     209             : 
     210             :     // Loop over the operations and evaluate each one.
     211             : 
     212       49272 :     for (int step = 0; step < operation.size(); step++) {
     213             :         const vector<int>& args = arguments[step];
     214       32829 :         if (args.size() == 1)
     215       30198 :             workspace[target[step]] = operation[step]->evaluate(&workspace[args[0]], dummyVariables);
     216             :         else {
     217        8097 :             for (int i = 0; i < args.size(); i++)
     218        5466 :                 argValues[i] = workspace[args[i]];
     219        2631 :             workspace[target[step]] = operation[step]->evaluate(&argValues[0], dummyVariables);
     220             :         }
     221             :     }
     222       16443 :     return workspace[workspace.size()-1];
     223             : }
     224             : 
     225             : #ifdef __PLUMED_HAS_ASMJIT
     226             : #if defined(__aarch64__) || defined(_M_ARM64)
     227             : static void generateSingleArgCall(a64::Compiler&, arm::Vec&, arm::Vec&, double (*)(double));
     228             : static void generateTwoArgCall(a64::Compiler&, arm::Vec&, arm::Vec&, arm::Vec&, double (*)(double, double));
     229             : #else
     230             : static void generateSingleArgCall(x86::Compiler&, x86::Xmm&, x86::Xmm&, double (*)(double));
     231             : static void generateTwoArgCall(x86::Compiler&, x86::Xmm&, x86::Xmm&, x86::Xmm&, double (*)(double, double));
     232             : #endif
     233        3010 : static double evaluateOperation(Operation* op, double* args) {
     234        3010 :     static map<string, double> dummyVariables;
     235        3010 :     return op->evaluate(args, dummyVariables);
     236             : }
     237             : 
     238       27492 : void CompiledExpression::findPowerGroups(vector<vector<int> >& groups, vector<vector<int> >& groupPowers, vector<int>& stepGroup) {
     239             :     // Identify every step that raises an argument to an integer power.
     240             : 
     241       27492 :     vector<int> stepPower(operation.size(), 0);
     242       27492 :     vector<int> stepArg(operation.size(), -1);
     243      138021 :     for (int step = 0; step < operation.size(); step++) {
     244      110529 :         Operation& op = *operation[step];
     245             :         int power = 0;
     246      110529 :         if (op.getId() == Operation::SQUARE)
     247             :             power = 2;
     248       97745 :         else if (op.getId() == Operation::CUBE)
     249             :             power = 3;
     250       97502 :         else if (op.getId() == Operation::POWER_CONSTANT) {
     251         631 :             double realPower = dynamic_cast<const Operation::PowerConstant*>(&op)->getValue();
     252         631 :             if (realPower == (int) realPower)
     253             :                 power = (int) realPower;
     254             :         }
     255         576 :         if (power != 0) {
     256       13603 :             stepPower[step] = power;
     257       13603 :             stepArg[step] = arguments[step][0];
     258             :         }
     259             :     }
     260             : 
     261             :     // Find groups that operate on the same argument and whose powers have the same sign.
     262             : 
     263       27492 :     stepGroup.resize(operation.size(), -1);
     264      138021 :     for (int i = 0; i < operation.size(); i++) {
     265      110529 :         if (stepGroup[i] != -1)
     266         231 :             continue;
     267      110298 :         vector<int> group, power;
     268     2582910 :         for (int j = i; j < operation.size(); j++) {
     269     2472612 :             if (stepArg[i] == stepArg[j] && stepPower[i]*stepPower[j] > 0) {
     270       13603 :                 stepGroup[j] = groups.size();
     271       13603 :                 group.push_back(j);
     272       13603 :                 power.push_back(stepPower[j]);
     273             :             }
     274             :         }
     275      110298 :         groups.push_back(group);
     276      110298 :         groupPowers.push_back(power);
     277             :     }
     278       27492 : }
     279             : 
     280             : #if defined(__aarch64__) || defined(_M_ARM64)
     281             : void CompiledExpression::generateJitCode() {
     282             :     auto& runtime = *static_cast<asmjit::JitRuntime*>(runtimeptr.get());
     283             :     if (jitCode) {
     284             :         runtime.release(jitCode);
     285             :         jitCode = nullptr;
     286             :     }
     287             :     CodeHolder code;
     288             :     code.init(runtime.environment());
     289             :     a64::Compiler c(&code);
     290             :     c.addFunc(FuncSignatureT<double>());
     291             :     vector<arm::Vec> workspaceVar(workspace.size());
     292             :     for (int i = 0; i < (int) workspaceVar.size(); i++)
     293             :         workspaceVar[i] = c.newVecD();
     294             :     arm::Gp argsPointer = c.newIntPtr();
     295             :     c.mov(argsPointer, imm(&argValues[0]));
     296             :     vector<vector<int> > groups, groupPowers;
     297             :     vector<int> stepGroup;
     298             :     findPowerGroups(groups, groupPowers, stepGroup);
     299             : 
     300             :     // Load the arguments into variables.
     301             : 
     302             :     for (set<string>::const_iterator iter = variableNames.begin(); iter != variableNames.end(); ++iter) {
     303             :         map<string, int>::iterator index = variableIndices.find(*iter);
     304             :         arm::Gp variablePointer = c.newIntPtr();
     305             :         c.mov(variablePointer, imm(&getVariableReference(index->first)));
     306             :         c.ldr(workspaceVar[index->second], arm::ptr(variablePointer, 0));
     307             :     }
     308             : 
     309             :     // Make a list of all constants that will be needed for evaluation.
     310             : 
     311             :     vector<int> operationConstantIndex(operation.size(), -1);
     312             :     for (int step = 0; step < (int) operation.size(); step++) {
     313             :         // Find the constant value (if any) used by this operation.
     314             : 
     315             :         Operation& op = *operation[step];
     316             :         double value;
     317             :         if (op.getId() == Operation::CONSTANT)
     318             :             value = dynamic_cast<Operation::Constant&>(op).getValue();
     319             :         else if (op.getId() == Operation::ADD_CONSTANT)
     320             :             value = dynamic_cast<Operation::AddConstant&>(op).getValue();
     321             :         else if (op.getId() == Operation::MULTIPLY_CONSTANT)
     322             :             value = dynamic_cast<Operation::MultiplyConstant&>(op).getValue();
     323             :         else if (op.getId() == Operation::RECIPROCAL)
     324             :             value = 1.0;
     325             :         else if (op.getId() == Operation::STEP)
     326             :             value = 1.0;
     327             :         else if (op.getId() == Operation::DELTA)
     328             :             value = std::numeric_limits<double>::infinity();
     329             :         else if (op.getId() == Operation::POWER_CONSTANT) {
     330             :             if (stepGroup[step] == -1)
     331             :                 value = dynamic_cast<Operation::PowerConstant&>(op).getValue();
     332             :             else
     333             :                 value = 1.0;
     334             :         }
     335             :         else
     336             :             continue;
     337             : 
     338             :         // See if we already have a variable for this constant.
     339             : 
     340             :         for (int i = 0; i < (int) constants.size(); i++)
     341             :             if (value == constants[i]) {
     342             :                 operationConstantIndex[step] = i;
     343             :                 break;
     344             :             }
     345             :         if (operationConstantIndex[step] == -1) {
     346             :             operationConstantIndex[step] = constants.size();
     347             :             constants.push_back(value);
     348             :         }
     349             :     }
     350             : 
     351             :     // Load constants into variables.
     352             : 
     353             :     vector<arm::Vec> constantVar(constants.size());
     354             :     if (constants.size() > 0) {
     355             :         arm::Gp constantsPointer = c.newIntPtr();
     356             :         c.mov(constantsPointer, imm(&constants[0]));
     357             :         for (int i = 0; i < (int) constants.size(); i++) {
     358             :             constantVar[i] = c.newVecD();
     359             :             c.ldr(constantVar[i], arm::ptr(constantsPointer, 8*i));
     360             :         }
     361             :     }
     362             : 
     363             :     // Evaluate the operations.
     364             : 
     365             :     vector<bool> hasComputedPower(operation.size(), false);
     366             :     for (int step = 0; step < (int) operation.size(); step++) {
     367             :         if (hasComputedPower[step])
     368             :             continue;
     369             : 
     370             :         // When one or more steps involve raising the same argument to multiple integer
     371             :         // powers, we can compute them all together for efficiency.
     372             : 
     373             :         if (stepGroup[step] != -1) {
     374             :             vector<int>& group = groups[stepGroup[step]];
     375             :             vector<int>& powers = groupPowers[stepGroup[step]];
     376             :             arm::Vec multiplier = c.newVecD();
     377             :             if (powers[0] > 0)
     378             :                 c.fmov(multiplier, workspaceVar[arguments[step][0]]);
     379             :             else {
     380             :                 c.fdiv(multiplier, constantVar[operationConstantIndex[step]], workspaceVar[arguments[step][0]]);
     381             :                 for (int i = 0; i < powers.size(); i++)
     382             :                     powers[i] = -powers[i];
     383             :             }
     384             :             vector<bool> hasAssigned(group.size(), false);
     385             :             bool done = false;
     386             :             while (!done) {
     387             :                 done = true;
     388             :                 for (int i = 0; i < group.size(); i++) {
     389             :                     if (powers[i]%2 == 1) {
     390             :                         if (!hasAssigned[i])
     391             :                             c.fmov(workspaceVar[target[group[i]]], multiplier);
     392             :                         else
     393             :                             c.fmul(workspaceVar[target[group[i]]], workspaceVar[target[group[i]]], multiplier);
     394             :                         hasAssigned[i] = true;
     395             :                     }
     396             :                     powers[i] >>= 1;
     397             :                     if (powers[i] != 0)
     398             :                         done = false;
     399             :                 }
     400             :                 if (!done)
     401             :                     c.fmul(multiplier, multiplier, multiplier);
     402             :             }
     403             :             for (int step : group)
     404             :                 hasComputedPower[step] = true;
     405             :             continue;
     406             :         }
     407             : 
     408             :         // Evaluate the step.
     409             : 
     410             :         Operation& op = *operation[step];
     411             :         vector<int> args = arguments[step];
     412             :         if (args.size() == 1) {
     413             :             // One or more sequential arguments.  Fill out the list.
     414             : 
     415             :             for (int i = 1; i < op.getNumArguments(); i++)
     416             :                 args.push_back(args[0]+i);
     417             :         }
     418             : 
     419             :         // Generate instructions to execute this operation.
     420             : 
     421             :         switch (op.getId()) {
     422             :             case Operation::CONSTANT:
     423             :                 c.fmov(workspaceVar[target[step]], constantVar[operationConstantIndex[step]]);
     424             :                 break;
     425             :             case Operation::ADD:
     426             :                 c.fadd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     427             :                 break;
     428             :             case Operation::SUBTRACT:
     429             :                 c.fsub(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     430             :                 break;
     431             :             case Operation::MULTIPLY:
     432             :                 c.fmul(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     433             :                 break;
     434             :             case Operation::DIVIDE:
     435             :                 c.fdiv(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     436             :                 break;
     437             :             case Operation::POWER:
     438             :                 generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], pow);
     439             :                 break;
     440             :             case Operation::NEGATE:
     441             :                 c.fneg(workspaceVar[target[step]], workspaceVar[args[0]]);
     442             :                 break;
     443             :             case Operation::SQRT:
     444             :                 c.fsqrt(workspaceVar[target[step]], workspaceVar[args[0]]);
     445             :                 break;
     446             :             case Operation::EXP:
     447             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], exp);
     448             :                 break;
     449             :             case Operation::LOG:
     450             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], log);
     451             :                 break;
     452             :             case Operation::SIN:
     453             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sin);
     454             :                 break;
     455             :             case Operation::COS:
     456             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cos);
     457             :                 break;
     458             :             case Operation::TAN:
     459             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tan);
     460             :                 break;
     461             :             case Operation::ASIN:
     462             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], asin);
     463             :                 break;
     464             :             case Operation::ACOS:
     465             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], acos);
     466             :                 break;
     467             :             case Operation::ATAN:
     468             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], atan);
     469             :                 break;
     470             :             case Operation::ATAN2:
     471             :                 generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], atan2);
     472             :                 break;
     473             :             case Operation::SINH:
     474             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sinh);
     475             :                 break;
     476             :             case Operation::COSH:
     477             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cosh);
     478             :                 break;
     479             :             case Operation::TANH:
     480             :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tanh);
     481             :                 break;
     482             :             case Operation::STEP:
     483             :                 c.fcmge(workspaceVar[target[step]], workspaceVar[args[0]], imm(0));
     484             :                 c.and_(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]);
     485             :                 break;
     486             :             case Operation::DELTA:
     487             :                 c.fcmeq(workspaceVar[target[step]], workspaceVar[args[0]], imm(0));
     488             :                 c.and_(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]);
     489             :                 break;
     490             :             case Operation::SQUARE:
     491             :                 c.fmul(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]);
     492             :                 break;
     493             :             case Operation::CUBE:
     494             :                 c.fmul(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]);
     495             :                 c.fmul(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]]);
     496             :                 break;
     497             :             case Operation::RECIPROCAL:
     498             :                 c.fdiv(workspaceVar[target[step]], constantVar[operationConstantIndex[step]], workspaceVar[args[0]]);
     499             :                 break;
     500             :             case Operation::ADD_CONSTANT:
     501             :                 c.fadd(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]);
     502             :                 break;
     503             :             case Operation::MULTIPLY_CONSTANT:
     504             :                 c.fmul(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]);
     505             :                 break;
     506             :             case Operation::POWER_CONSTANT:
     507             :                 generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]], pow);
     508             :                 break;
     509             :             case Operation::MIN:
     510             :                 c.fmin(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     511             :                 break;
     512             :             case Operation::MAX:
     513             :                 c.fmax(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     514             :                 break;
     515             :             case Operation::ABS:
     516             :                 c.fabs(workspaceVar[target[step]], workspaceVar[args[0]]);
     517             :                 break;
     518             :             case Operation::FLOOR:
     519             :                 c.frintm(workspaceVar[target[step]], workspaceVar[args[0]]);
     520             :                 break;
     521             :             case Operation::CEIL:
     522             :                 c.frintp(workspaceVar[target[step]], workspaceVar[args[0]]);
     523             :                 break;
     524             :             case Operation::SELECT:
     525             :                 c.fcmeq(workspaceVar[target[step]], workspaceVar[args[0]], imm(0));
     526             :                 c.bsl(workspaceVar[target[step]], workspaceVar[args[2]], workspaceVar[args[1]]);
     527             :                 break;
     528             :             default:
     529             :                 // Just invoke evaluateOperation().
     530             : 
     531             :                 for (int i = 0; i < (int) args.size(); i++)
     532             :                     c.str(workspaceVar[args[i]], arm::ptr(argsPointer, 8*i));
     533             :                 arm::Gp fn = c.newIntPtr();
     534             :                 c.mov(fn, imm((void*) evaluateOperation));
     535             :                 InvokeNode* invoke;
     536             :                 c.invoke(&invoke, fn, FuncSignatureT<double, Operation*, double*>());
     537             :                 invoke->setArg(0, imm(&op));
     538             :                 invoke->setArg(1, imm(&argValues[0]));
     539             :                 invoke->setRet(0, workspaceVar[target[step]]);
     540             :         }
     541             :     }
     542             :     c.ret(workspaceVar[workspace.size()-1]);
     543             :     c.endFunc();
     544             :     Error error = c.finalize();
     545             :     if (!error)
     546             :         error = runtime.add(&jitCode, &code);
     547             :     if (error)
     548             :         throw Exception(string("AsmJit compilation failed: ") + DebugUtils::errorAsString(error));
     549             : }
     550             : 
     551             : static void generateSingleArgCall(a64::Compiler& c, arm::Vec& dest, arm::Vec& arg, double (*function)(double)) {
     552             :     arm::Gp fn = c.newIntPtr();
     553             :     c.mov(fn, imm((void*) function));
     554             :     InvokeNode* invoke;
     555             :     c.invoke(&invoke, fn, FuncSignatureT<double, double>());
     556             :     invoke->setArg(0, arg);
     557             :     invoke->setRet(0, dest);
     558             : }
     559             : 
     560             : static void generateTwoArgCall(a64::Compiler& c, arm::Vec& dest, arm::Vec& arg1, arm::Vec& arg2, double (*function)(double, double)) {
     561             :     arm::Gp fn = c.newIntPtr();
     562             :     c.mov(fn, imm((void*) function));
     563             :     InvokeNode* invoke;
     564             :     c.invoke(&invoke, fn, FuncSignatureT<double, double, double>());
     565             :     invoke->setArg(0, arg1);
     566             :     invoke->setArg(1, arg2);
     567             :     invoke->setRet(0, dest);
     568             : }
     569             : #else
     570       27492 : void CompiledExpression::generateJitCode() {
     571       27492 :     const CpuInfo& cpu = CpuInfo::host();
     572       27492 :     if (!cpu.hasFeature(CpuFeatures::X86::kAVX))
     573           0 :         return;
     574             :     auto& runtime = *static_cast<asmjit::JitRuntime*>(runtimeptr.get());
     575       27492 :     if (jitCode) {
     576             :         runtime.release(jitCode);
     577         100 :         jitCode = nullptr;
     578             :     }
     579       27492 :     CodeHolder code;
     580       27492 :     code.init(runtime.environment());
     581       27492 :     x86::Compiler c(&code);
     582       27492 :     FuncNode* funcNode = c.addFunc(FuncSignatureT<double>());
     583             :     funcNode->frame().setAvxEnabled();
     584       27492 :     vector<x86::Xmm> workspaceVar(workspace.size());
     585      174910 :     for (int i = 0; i < (int) workspaceVar.size(); i++)
     586      147418 :         workspaceVar[i] = c.newXmmSd();
     587             :     x86::Gp argsPointer = c.newIntPtr();
     588           0 :     c.mov(argsPointer, imm(&argValues[0]));
     589       27492 :     vector<vector<int> > groups, groupPowers;
     590       27492 :     vector<int> stepGroup;
     591       27492 :     findPowerGroups(groups, groupPowers, stepGroup);
     592             : 
     593             :     // Load the arguments into variables.
     594             : 
     595             :     x86::Gp variablePointer = c.newIntPtr();
     596       64381 :     for (set<string>::const_iterator iter = variableNames.begin(); iter != variableNames.end(); ++iter) {
     597             :         map<string, int>::iterator index = variableIndices.find(*iter);
     598       73778 :         c.mov(variablePointer, imm(&getVariableReference(index->first)));
     599       36889 :         c.vmovsd(workspaceVar[index->second], x86::ptr(variablePointer, 0, 0));
     600             :     }
     601             : 
     602             :     // Make a list of all constants that will be needed for evaluation.
     603             : 
     604       27492 :     vector<int> operationConstantIndex(operation.size(), -1);
     605      138021 :     for (int step = 0; step < (int) operation.size(); step++) {
     606             :         // Find the constant value (if any) used by this operation.
     607             : 
     608      110529 :         Operation& op = *operation[step];
     609             :         double value;
     610      110529 :         if (op.getId() == Operation::CONSTANT)
     611        3838 :             value = dynamic_cast<Operation::Constant&>(op).getValue();
     612      106691 :         else if (op.getId() == Operation::ADD_CONSTANT)
     613        7148 :             value = dynamic_cast<Operation::AddConstant&>(op).getValue();
     614       99543 :         else if (op.getId() == Operation::MULTIPLY_CONSTANT)
     615       25026 :             value = dynamic_cast<Operation::MultiplyConstant&>(op).getValue();
     616       74517 :         else if (op.getId() == Operation::RECIPROCAL)
     617        1652 :             value = 1.0;
     618       72865 :         else if (op.getId() == Operation::STEP)
     619        1075 :             value = 1.0;
     620       71790 :         else if (op.getId() == Operation::DELTA)
     621         738 :             value = std::numeric_limits<double>::infinity();
     622       71052 :         else if (op.getId() == Operation::ABS) {
     623             :             long long mask = 0x7FFFFFFFFFFFFFFF;
     624          46 :             value = *reinterpret_cast<double*>(&mask);
     625             :         }
     626       71006 :         else if (op.getId() == Operation::POWER_CONSTANT) {
     627         631 :             if (stepGroup[step] == -1)
     628          55 :                 value = dynamic_cast<Operation::PowerConstant&>(op).getValue();
     629             :             else
     630         576 :                 value = 1.0;
     631             :         }
     632             :         else
     633       70375 :             continue;
     634             : 
     635             :         // See if we already have a variable for this constant.
     636             : 
     637      388801 :         for (int i = 0; i < (int) constants.size(); i++)
     638      354910 :             if (value == constants[i]) {
     639        6263 :                 operationConstantIndex[step] = i;
     640        6263 :                 break;
     641             :             }
     642       40154 :         if (operationConstantIndex[step] == -1) {
     643       33891 :             operationConstantIndex[step] = constants.size();
     644       33891 :             constants.push_back(value);
     645             :         }
     646             :     }
     647             : 
     648             :     // Load constants into variables.
     649             : 
     650       27492 :     vector<x86::Xmm> constantVar(constants.size());
     651       27492 :     if (constants.size() > 0) {
     652             :         x86::Gp constantsPointer = c.newIntPtr();
     653       14028 :         c.mov(constantsPointer, imm(&constants[0]));
     654       48019 :         for (int i = 0; i < (int) constants.size(); i++) {
     655       33991 :             constantVar[i] = c.newXmmSd();
     656       33991 :             c.vmovsd(constantVar[i], x86::ptr(constantsPointer, 8*i, 0));
     657             :         }
     658             :     }
     659             : 
     660             :     // Evaluate the operations.
     661             : 
     662       27492 :     vector<bool> hasComputedPower(operation.size(), false);
     663      138021 :     for (int step = 0; step < (int) operation.size(); step++) {
     664      110529 :         if (hasComputedPower[step])
     665       13603 :             continue;
     666             : 
     667             :         // When one or more steps involve raising the same argument to multiple integer
     668             :         // powers, we can compute them all together for efficiency.
     669             : 
     670      110298 :         if (stepGroup[step] != -1) {
     671       13372 :             vector<int>& group = groups[stepGroup[step]];
     672             :             vector<int>& powers = groupPowers[stepGroup[step]];
     673             :             x86::Xmm multiplier = c.newXmmSd();
     674       13372 :             if (powers[0] > 0)
     675       13366 :                 c.vmovsd(multiplier, workspaceVar[arguments[step][0]], workspaceVar[arguments[step][0]]);
     676             :             else {
     677           6 :                 c.vdivsd(multiplier, constantVar[operationConstantIndex[step]], workspaceVar[arguments[step][0]]);
     678          18 :                 for (int i = 0; i < powers.size(); i++)
     679          12 :                     powers[i] = -powers[i];
     680             :             }
     681       13372 :             vector<bool> hasAssigned(group.size(), false);
     682             :             bool done = false;
     683       27222 :             while (!done) {
     684             :                 done = true;
     685       55232 :                 for (int i = 0; i < group.size(); i++) {
     686       28010 :                     if (powers[i]%2 == 1) {
     687       14308 :                         if (!hasAssigned[i])
     688       13603 :                             c.vmovsd(workspaceVar[target[group[i]]], multiplier, multiplier);
     689             :                         else
     690         705 :                             c.vmulsd(workspaceVar[target[group[i]]], workspaceVar[target[group[i]]], multiplier);
     691             :                         hasAssigned[i] = true;
     692             :                     }
     693       28010 :                     powers[i] >>= 1;
     694       28010 :                     if (powers[i] != 0)
     695             :                         done = false;
     696             :                 }
     697       27222 :                 if (!done)
     698             :                     c.vmulsd(multiplier, multiplier, multiplier);
     699             :             }
     700       26975 :             for (int step : group)
     701             :                 hasComputedPower[step] = true;
     702             :             continue;
     703             :         }
     704             : 
     705             :         // Evaluate the step.
     706             : 
     707       96926 :         Operation& op = *operation[step];
     708       96926 :         vector<int> args = arguments[step];
     709       96926 :         if (args.size() == 1) {
     710             :             // One or more sequential arguments.  Fill out the list.
     711             : 
     712       82501 :             for (int i = 1; i < op.getNumArguments(); i++)
     713       14559 :                 args.push_back(args[0]+i);
     714             :         }
     715             : 
     716             :         // Generate instructions to execute this operation.
     717             : 
     718       96926 :         switch (op.getId()) {
     719        3838 :             case Operation::CONSTANT:
     720        3838 :                 c.vmovsd(workspaceVar[target[step]], constantVar[operationConstantIndex[step]], constantVar[operationConstantIndex[step]]);
     721       96832 :                 break;
     722             :             case Operation::ADD:
     723       16863 :                 c.vaddsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     724             :                 break;
     725             :             case Operation::SUBTRACT:
     726        4031 :                 c.vsubsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     727             :                 break;
     728             :             case Operation::MULTIPLY:
     729       14127 :                 c.vmulsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     730             :                 break;
     731             :             case Operation::DIVIDE:
     732        8213 :                 c.vdivsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     733             :                 break;
     734             :             case Operation::POWER:
     735           6 :                 generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], pow);
     736             :                 break;
     737        2710 :             case Operation::NEGATE:
     738        2710 :                 c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]);
     739        2710 :                 c.vsubsd(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]]);
     740             :                 break;
     741             :             case Operation::SQRT:
     742        1645 :                 c.vsqrtsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]);
     743             :                 break;
     744             :             case Operation::EXP:
     745        5399 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], exp);
     746             :                 break;
     747             :             case Operation::LOG:
     748         495 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], log);
     749             :                 break;
     750             :             case Operation::SIN:
     751        1291 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sin);
     752             :                 break;
     753             :             case Operation::COS:
     754        1879 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cos);
     755             :                 break;
     756             :             case Operation::TAN:
     757           6 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tan);
     758             :                 break;
     759             :             case Operation::ASIN:
     760           3 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], asin);
     761             :                 break;
     762             :             case Operation::ACOS:
     763          57 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], acos);
     764             :                 break;
     765             :             case Operation::ATAN:
     766           3 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], atan);
     767             :                 break;
     768             :             case Operation::ATAN2:
     769          79 :                 generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], atan2);
     770             :                 break;
     771             :             case Operation::SINH:
     772         123 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sinh);
     773             :                 break;
     774             :             case Operation::COSH:
     775          45 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cosh);
     776             :                 break;
     777             :             case Operation::TANH:
     778         104 :                 generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tanh);
     779             :                 break;
     780        1075 :             case Operation::STEP:
     781        1075 :                 c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]);
     782        1075 :                 c.vcmpsd(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]], imm(18)); // Comparison mode is _CMP_LE_OQ = 18
     783        1075 :                 c.vandps(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]);
     784             :                 break;
     785         738 :             case Operation::DELTA:
     786         738 :                 c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]);
     787         738 :                 c.vcmpsd(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]], imm(16)); // Comparison mode is _CMP_EQ_OS = 16
     788         738 :                 c.vandps(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]);
     789             :                 break;
     790             :             case Operation::SQUARE:
     791           0 :                 c.vmulsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]);
     792             :                 break;
     793             :             case Operation::CUBE:
     794           0 :                 c.vmulsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]);
     795           0 :                 c.vmulsd(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]]);
     796             :                 break;
     797             :             case Operation::RECIPROCAL:
     798        1652 :                 c.vdivsd(workspaceVar[target[step]], constantVar[operationConstantIndex[step]], workspaceVar[args[0]]);
     799             :                 break;
     800        7148 :             case Operation::ADD_CONSTANT:
     801        7148 :                 c.vaddsd(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]);
     802             :                 break;
     803       25026 :             case Operation::MULTIPLY_CONSTANT:
     804       25026 :                 c.vmulsd(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]);
     805             :                 break;
     806          55 :             case Operation::POWER_CONSTANT:
     807          55 :                 generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]], pow);
     808             :                 break;
     809             :             case Operation::MIN:
     810           6 :                 c.vminsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     811             :                 break;
     812             :             case Operation::MAX:
     813           6 :                 c.vmaxsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]);
     814             :                 break;
     815          46 :             case Operation::ABS:
     816          46 :                 c.vandpd(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]);
     817             :                 break;
     818           3 :             case Operation::FLOOR:
     819           3 :                 c.vroundsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]], imm(1));
     820           3 :                 break;
     821           3 :             case Operation::CEIL:
     822           3 :                 c.vroundsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]], imm(2));
     823           3 :                 break;
     824         157 :             case Operation::SELECT:
     825             :             {
     826             :                 x86::Xmm mask = c.newXmmSd();
     827             :                 c.vxorps(mask, mask, mask);
     828         157 :                 c.vcmpsd(mask, mask, workspaceVar[args[0]], imm(0)); // Comparison mode is _CMP_EQ_OQ = 0
     829         157 :                 c.vblendvps(workspaceVar[target[step]], workspaceVar[args[1]], workspaceVar[args[2]], mask);
     830             :                 break;
     831             :             }
     832             :             default:
     833             :                 // Just invoke evaluateOperation().
     834             : 
     835         188 :                 for (int i = 0; i < (int) args.size(); i++)
     836          94 :                     c.vmovsd(x86::ptr(argsPointer, 8*i, 0), workspaceVar[args[i]]);
     837             :                 x86::Gp fn = c.newIntPtr();
     838          94 :                 c.mov(fn, imm((void*) evaluateOperation));
     839             :                 InvokeNode* invoke;
     840           0 :                 c.invoke(&invoke, fn, FuncSignatureT<double, Operation*, double*>());
     841          94 :                 invoke->setArg(0, imm(&op));
     842          94 :                 invoke->setArg(1, imm(&argValues[0]));
     843          94 :                 invoke->setRet(0, workspaceVar[target[step]]);
     844             :         }
     845             :     }
     846       27492 :     c.ret(workspaceVar[workspace.size()-1]);
     847       27492 :     c.endFunc();
     848       27492 :     Error error = c.finalize();
     849       27492 :     if (!error)
     850       27492 :         error = runtime.add(&jitCode, &code);
     851       27492 :     if (error)
     852           0 :         throw Exception(string("AsmJit compilation failed: ") + DebugUtils::errorAsString(error));
     853       54984 : }
     854             : 
     855        9405 : static void generateSingleArgCall(x86::Compiler& c, x86::Xmm& dest, x86::Xmm& arg, double (*function)(double)) {
     856             :     x86::Gp fn = c.newIntPtr();
     857        9405 :     c.mov(fn, imm((void*) function));
     858             :     InvokeNode* invoke;
     859        9405 :     c.invoke(&invoke, fn, FuncSignatureT<double, double>());
     860        9405 :     invoke->setArg(0, arg);
     861             :     invoke->setRet(0, dest);
     862        9405 : }
     863             : 
     864         140 : static void generateTwoArgCall(x86::Compiler& c, x86::Xmm& dest, x86::Xmm& arg1, x86::Xmm& arg2, double (*function)(double, double)) {
     865             :     x86::Gp fn = c.newIntPtr();
     866         140 :     c.mov(fn, imm((void*) function));
     867             :     InvokeNode* invoke;
     868         140 :     c.invoke(&invoke, fn, FuncSignatureT<double, double, double>());
     869         140 :     invoke->setArg(0, arg1);
     870             :     invoke->setArg(1, arg2);
     871             :     invoke->setRet(0, dest);
     872         140 : }
     873             : #endif
     874             : #endif
     875             : 
     876             : } // namespace PLMD

Generated by: LCOV version 1.16