Skip to content

src/TargetCode.cpp

This file implements the class TargetCode, which can be used to add code fragments and to generate new code (i.e., for outlining OpenMP target region) from these fragments.

Source code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
//===-- sotoc/src/TargetCode ------------------------ ---------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//

#include <sstream>
#include <string>

#include "clang/AST/Decl.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtOpenMP.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/APInt.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/raw_ostream.h"

#include "Debug.h"
#include "OmpPragma.h"
#include "TargetCode.h"

bool TargetCode::addCodeFragment(std::shared_ptr<TargetCodeFragment> Frag,
                                 bool PushFront) {
  for (auto &F : CodeFragments) {
    // Reject Fragments which are inside Fragments which we already have
    if ((SM.isPointWithin(Frag->getRealRange().getBegin(),
                          F->getRealRange().getBegin(),
                          F->getRealRange().getEnd()) &&
         Frag->getRealRange().getBegin() != F->getRealRange().getBegin()) &&
        SM.isPointWithin(Frag->getRealRange().getEnd(),
                         F->getRealRange().getBegin(),
                         F->getRealRange().getEnd())) {
      return false;
    }
  }

  if (PushFront) {
    CodeFragments.push_front(Frag);
  } else {
    CodeFragments.push_back(Frag);
  }
  return true;
}

// TODO: Is this needed for something?
bool TargetCode::addCodeFragmentFront(
    std::shared_ptr<TargetCodeFragment> Frag) {
  return addCodeFragment(Frag, true);
}

void TargetCode::generateCode(llvm::raw_ostream &Out) {

  bool stdlib = false;
  bool unistd = false;

  for (auto &i : SystemHeaders) {
    std::string Header(i);
    size_t include_pos = Header.rfind("nclude/");
    if (include_pos != std::string::npos) {
      Header.erase(0, include_pos + strlen("nclude/"));
    }
    Out << "#include <" << Header << ">\n";
    if (Header.compare("unistd.h") == 0) {
      unistd = true;
    } else if (Header.compare("stdlib.h") == 0) {
      stdlib = true;
    }
  }

  // Add extra libs for the debugging helper function
  if (!stdlib && std::atoi(llvm::sys::Process::GetEnv("NEC_TARGET_DELAY")
                               .getValueOr("0")
                               .c_str())) {
    Out << "#include <stdlib.h>\n";
  }
  if (!unistd && std::atoi(llvm::sys::Process::GetEnv("NEC_TARGET_DELAY")
                               .getValueOr("0")
                               .c_str())) {
    Out << "#include <unistd.h>\n";
  }

  // Override omp_is_initial_device() with macro, because this
  //   Out << "static inline int omp_is_initial_device(void) {return 0;}\n";
  // fails with the clang compiler. This still might cause problems, if
  // someone tries to include the omp.h header after the prolouge.
  Out << "#define omp_is_initial_device() 0\n";
  Out << "#define omp_get_thread_limit() omp_get_num_threads()\n";

  for (auto i = CodeFragments.begin(), e = CodeFragments.end(); i != e; ++i) {

    std::shared_ptr<TargetCodeFragment> Frag = *i;
    auto *TCR = llvm::dyn_cast<TargetCodeRegion>(Frag.get());

    if (TCR) {
      generateFunctionPrologue(TCR, Out);
    }

    Out << Frag->PrintPretty();

    if (TCR) {
      generateFunctionEpilogue(TCR, Out);
    }

    if (Frag->NeedsSemicolon) {
      Out << ";";
    }
    Out << "\n";
  }
  Out << "#undef omp_is_initial_device\n";
  Out << "#undef omp_get_thread_limit\n";
}

void TargetCode::generateArgument(const TargetRegionVariable &Arg,
                                  llvm::raw_ostream &Out) {
  std::string LHSStore;
  std::string RHSStore;
  llvm::raw_string_ostream LHS(LHSStore);
  llvm::raw_string_ostream RHS(RHSStore);

  for (auto &Shape : Arg.shapes()) {
    if (Shape.isPointer()) {
      LHS << "(*";
      RHS << ")";
    } else if (Shape.isConstantArray()) {
      RHS << "[" << Shape.getConstantDimensionExpr() << "]";
    } else if (Shape.isVariableArray()) {
      RHS << "[]";
    }
  }

  Out << LHS.str() << Arg.name() << RHS.str();
}

void TargetCode::generateVariableDecl(const TargetRegionVariable &Var,
                                      llvm::raw_ostream &Out) {
  std::string lValueStore = std::string(Var.name());;
  std::string rValueStore = std::string();

  for (auto Shape : Var.shapes()) {
    switch (Shape.getKind()) {
    case TargetRegionVariableShape::ShapeKind::Pointer:
      lValueStore = "*" + lValueStore;
      rValueStore = "*" + rValueStore;
      break;
    case TargetRegionVariableShape::ShapeKind::Paren:
      if (rValueStore.empty()) {
        break;
      }
      lValueStore = "(" + lValueStore + ")";
      rValueStore = "(" + rValueStore + ")";
      break;
    case TargetRegionVariableShape::ShapeKind::ConstantArray:
      if (rValueStore.empty()) {
        // make first dimension of array implicit in cast
        lValueStore = "(* " + lValueStore + ")";
        rValueStore = "(*)";
        break;
      }
      lValueStore = lValueStore +
                    "[" + Shape.getConstantDimensionExpr().str() + "]";
      rValueStore = rValueStore +
                    "[" + Shape.getConstantDimensionExpr().str() + "]";
      break;
    case TargetRegionVariableShape::ShapeKind::VariableArray:
    if (rValueStore.empty()) {
        // make first dimension of array implicit
        lValueStore = "(* " + lValueStore + ")";
        rValueStore = "(*)";
        break;
      }
      lValueStore = lValueStore + "[" + "__sotoc_vla_dim" +
                    std::to_string(Shape.getVariableDimensionIndex()) + "_" +
                    Var.name().str() + "]";
      rValueStore = rValueStore + "[" + "__sotoc_vla_dim" +
                    std::to_string(Shape.getVariableDimensionIndex()) + "_" +
                    Var.name().str() + "]";
      break;
    }
  }

  // Finish the l-value (by adding the base type)
  lValueStore = Var.baseTypeName().str() + " " + lValueStore;

  // Finish the r-value (modify value and then add transferred variable name)
  if (rValueStore.empty()) {
    // Scalar being passed by pointer; dereference transferred value
    rValueStore = "*";
  } else {
    // Currently rValueStore contains a type usefull for an explicit cast of the
    // transferred variable, which would be completed as follows:
    //rValueStore = "(" + Var.baseTypeName().str() + rValueStore + ") ";
    // We currently do not need this cast, so we simply empty rValueStore
    rValueStore = "";
  }
  rValueStore = rValueStore + "__sotoc_var_" + Var.name().str();

  // Output the finished declaration to the output stream
  Out << "  " << lValueStore << " = " << rValueStore << ";\n";
}

void TargetCode::generateFunctionPrologue(TargetCodeRegion *TCR,
                                          llvm::raw_ostream &Out) {
  bool first = true;

  Out << "void " << generateFunctionName(TCR) << "(";

  for (auto &Var : TCR->capturedVars()) {
    if (!first) {
      Out << ", ";
    } else {
      first = false;
    }

    if (Var.containsArray()) {
      for (auto &d : Var.variableArrayShapes()) {
        Out << "unsigned long long __sotoc_vla_dim"
            << d.getVariableDimensionIndex() << "_" << Var.name() << ", ";
      }
    }
    // Because arrays (and nested pointers) are passed by reference and
    // (for our purposes) their type is 'void', the rest of their handling
    // is the same as for scalars.
    if (Var.containsArray() || Var.containsPointer()) {
      Out << "void ";
    } else {
      // In cases where we get a first-private float, we want to recieve the
      // full 64 bit we input into veo. We then later can change the type back
      // to float. I suspect some weirdness with IEEE 754 and the change of
      // variable length from 32 to 64 and back to 32 bit.
      if (!Var.passedByPointer() && Var.baseTypeName() == "float") {
        Out << "unsigned long long __sotoc_conv_var_";
      } else {
        Out << Var.baseTypeName() << " ";
      }
    }

    if (Var.passedByPointer()) {
      Out << "*__sotoc_var_" << Var.name();
    } else {
      generateArgument(Var, Out);
    }
  }

  unsigned int clauseParam = 0;
  for (auto C : TCR->getOMPClauses()) {
    if ((C->getClauseKind() == clang::OpenMPClauseKind::OMPC_num_threads ||
         C->getClauseKind() == clang::OpenMPClauseKind::OMPC_thread_limit) &&
        !C->isImplicit()) {
      if (!first) {
        Out << ", ";
      } else {
        first = false;
      }
      Out << "int __sotoc_clause_param_" << std::to_string(clauseParam) << " ";
      clauseParam++;
    }
  }

  Out << ")\n{\n";

  // Target Delay
  if (std::atoi(llvm::sys::Process::GetEnv("NEC_TARGET_DELAY")
                    .getValueOr("0")
                    .c_str())) {
    Out << "sleep(atoi((getenv(\"NEC_TARGET_DELAY\") != NULL) ? "
           "getenv(\"NEC_TARGET_DELAY\") : \"0\"));\n";
  }

  // bring captured scalars into scope
  for (auto &Var : TCR->capturedVars()) {
    // Ignore everything not passed by reference here
    if (Var.passedByPointer()) {
      generateVariableDecl(Var, Out);

      // We may also have to adjust the array bounds if we only get a slice
      // of the array; Move the bounds if we have a slice here
      // (Only necessary in the first dimesion. All other dimensions should
      // not require adjustment as their slicing is ignored)
      if (Var.containsArray()) {
        // Move the bounds if we have a slice
        auto LowerBound = Var.arrayLowerBound();
        if (LowerBound.hasValue()) {
          Out << "  " << Var.name() << " = " << Var.name() << " - ";
          LowerBound.getValue()->printPretty(Out, NULL, TCR->getPP());
          Out << ";\n";
        }
      }

      // After recieving floats as unsigned long long we want to change them
      // back to floats but without conversion as they already are formated
      // according to 32 bit floating point spec.
    } else if (!Var.passedByPointer() && Var.baseTypeName() == "float") {
      Out << "float " << Var.name() << " = *(float*)&(__sotoc_conv_var_"
          << Var.name() << ");\n";
    }
  }

  // Generate local declarations.
  for (auto *privateVar : TCR->privateVars()) {
    Out << "  " << privateVar->getType().getAsString() << " "
        << privateVar->getName() << ";\n";
  }

  // The runtime can decide to only create one team.
  // Therfore, replace target teams constructs.
  if (TCR->hasCombineConstruct()) {
    OmpPragma Pragma(TCR);
    Pragma.printReplacement(Out);
    if (Pragma.needsStructuredBlock()) {
      Out << "\n{";
    }
  }
  Out << "\n";
}

void TargetCode::generateFunctionEpilogue(TargetCodeRegion *TCR,
                                          llvm::raw_ostream &Out) {
  if (OmpPragma(TCR).needsStructuredBlock()) {
    Out << "\n}";
  }

  Out << "\n";
  // copy values from scalars from scoped vars back into pointers
  for (auto &Var : TCR->capturedVars()) {
    if (Var.passedByPointer() &&
        !Var.containsPointer() && !Var.containsArray()) {
      Out << "\n  *__sotoc_var_" << Var.name() << " = " << Var.name() << ";";
    } else if (Var.containsPointer()){
      Out << "\n  __sotoc_var_" << Var.name() << " = " << Var.name() << ";";
    }
  }

  Out << "\n}\n";
}

std::string TargetCode::generateFunctionName(TargetCodeRegion *TCR) {
  // TODO: this function needs error handling
  llvm::sys::fs::UniqueID ID;
  clang::PresumedLoc PLoc =
      SM.getPresumedLoc(TCR->getTargetDirectiveLocation());
  llvm::sys::fs::getUniqueID(PLoc.getFilename(), ID);
  uint64_t DeviceID = ID.getDevice();
  uint64_t FileID = ID.getFile();
  unsigned LineNum = PLoc.getLine();
  std::string FunctionName;

  llvm::raw_string_ostream fns(FunctionName);
  fns << "__omp_offloading" << llvm::format("_%x", DeviceID)
      << llvm::format("_%x_", FileID) << TCR->getParentFuncName() << "_l"
      << LineNum;
  return FunctionName;
}

Last update: 2021-11-24
Back to top