New print implementation with support of genx.

This commit is contained in:
Dmitry Ryabtsev authored and aneshlya committed 2020-07-29 16:26:11 -07:00
1 parent 1bfdd8b631
commit 2f6d077e0c
11 files changed
+1187 -361

No files matched your search

+202
View File
@@ -0,0 +1,202 @@
/*
Copyright (c) 2019, Intel Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Intel Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @file builtins-c-common.cpp
@brief Shared code base for __do_print and __do_print_cm.
This library provides instruments for parsing ISPC print format strings,
some useful adapters, and other minor helper instruments.
FIXME: this file can be included only after
StaticString, StaticStringRef, SizeT, RES_STR_SIZE, ARG_STR_SIZE
are defined.
Can be fixed with an additional template argument, but only when CMC includes are fixed.
*/
#include "src/builtins-info.h"
namespace details {
template <typename T> inline T ValueAdapterImpl(T val) { return val; }
static inline const char *ValueAdapterImpl(bool val) { return val ? "true" : "false"; }
// Copies everything from src (starting from srcIdx) to dst (starting from dstIdx), until it meets a separator.
// Separators are defined by SEPS.
// T is anything indexable, meaning you can write src[idx]
template <char... SEPS, typename T, SizeT SIZE>
inline int CopyTillSep(T src, int srcIdx, StaticStringRef<SIZE> dst, int dstIdx, int leftSpace) {
const auto origDstIdx = dstIdx;
while (((src[srcIdx] != SEPS) && ...) && leftSpace) {
dst[dstIdx++] = src[srcIdx++];
--leftSpace;
}
return dstIdx - origDstIdx;
}
// FIXME: ugly copy-paste to work around cmc frontend issues. Remove it when CMC includes are fixed.
template <char... SEPS, SizeT SRC_SIZE, SizeT SIZE>
inline int CopyTillSep(StaticStringRef<SRC_SIZE> src, int srcIdx, StaticStringRef<SIZE> dst, int dstIdx,
int leftSpace) {
const auto origDstIdx = dstIdx;
while (((src[srcIdx] != SEPS) && ...) && leftSpace) {
dst[dstIdx++] = src[srcIdx++];
--leftSpace;
}
return dstIdx - origDstIdx;
}
// If type encodes uniform T, function puts result of uniform2Str<T>() in res and returns true,
// otherwise it does nothing with res and returns false.
template <typename T, typename ArgWriter>
inline bool UniArg2StrIfSuitable(char type, ArgWriter &argWriter, StaticStringRef<ARG_STR_SIZE> res) {
if (type == PrintInfo::getEncoding4Uniform<T>()) {
res = argWriter.template uniform2Str<T>();
return true;
}
return false;
}
// If type encodes varying T, function puts result of varying2Str<T>() in res and returns true,
// otherwise it does nothing with res and returns false.
template <typename T, typename ArgWriter>
inline bool VarArg2StrIfSuitable(char type, ArgWriter &argWriter, StaticStringRef<ARG_STR_SIZE> res) {
if (type == PrintInfo::getEncoding4Varying<T>()) {
res = argWriter.template varying2Str<T>();
return true;
}
return false;
}
// If type encodes uniform T or varying T, function calls corresponding *2Str method, writes its result into res,
// returns true, otherwise function does nothing with res and returns false.
template <typename T, typename ArgWriter>
inline bool Arg2StrIfSuitable(char type, ArgWriter &argWriter, StaticStringRef<ARG_STR_SIZE> res) {
return UniArg2StrIfSuitable<T>(type, argWriter, res) || VarArg2StrIfSuitable<T>(type, argWriter, res);
}
// Requests the next argument from argWriter. Relies on uniform2Str<T> and varying2Str<T> methods of argWriter.
// T is deduced from provided type argument. Returns the string representation of the argument returned from
// uniform2Str<T> or varying2Str<T>.
template <typename ArgWriter> inline StaticString<ARG_STR_SIZE> Arg2Str(char type, ArgWriter &argWriter) {
StaticString<ARG_STR_SIZE> res;
Arg2StrIfSuitable<bool>(type, argWriter, res) || Arg2StrIfSuitable<int>(type, argWriter, res) ||
Arg2StrIfSuitable<unsigned>(type, argWriter, res) || Arg2StrIfSuitable<float>(type, argWriter, res) ||
Arg2StrIfSuitable<long long>(type, argWriter, res) ||
Arg2StrIfSuitable<unsigned long long>(type, argWriter, res) ||
Arg2StrIfSuitable<double>(type, argWriter, res) || Arg2StrIfSuitable<void *>(type, argWriter, res);
return res;
}
} // namespace details
const char UnsupportedTypeStr[] = "UNSUPPORTED TYPE";
const char OffLaneBoolStr[] = "_________";
// For every T except bool it just returns the same value.
// In this case value must have T type.
// For T==bool it returns const char* to "true" or "false".
//
// Function is presumed to be used only for integral types.
template <typename T> inline auto ValueAdapter(T val) { return details::ValueAdapterImpl(val); }
// Copies everything from src to dst (starting from dstIdx), until it meets '%' or '\0'
// Doesn't copy '%' or '\0'. Doesn't null terminate dst string.
template <SizeT SIZE>
inline int CopyPlainText(const char *const src, StaticStringRef<SIZE> dst, const int dstIdx, const int leftSpace) {
return details::CopyTillSep<'%', '\0'>(src, 0, dst, dstIdx, leftSpace);
}
// Copies everything from src to dst (starting from dstIdx), until it meets '\0' (whole string).
// Doesn't copy '\0', so dst string is not null terminated.
template <SizeT SIZE>
inline int CopyFullText(const char *const src, StaticStringRef<SIZE> dst, const int dstIdx, const int leftSpace) {
return details::CopyTillSep<'\0'>(src, 0, dst, dstIdx, leftSpace);
}
// Copies everything from src (starting from srcIdx) to dst (starting from dstIdx), until it meets '\0' (whole string).
// Doesn't copy '\0', so dst string is not null terminated.
template <SizeT SRC_SIZE, SizeT DST_SIZE>
inline int CopyFullText(StaticStringRef<SRC_SIZE> src, const int srcIdx, StaticStringRef<DST_SIZE> dst,
const int dstIdx, const int leftSpace) {
return details::CopyTillSep<'\0'>(src, srcIdx, dst, dstIdx, leftSpace);
}
// Converts type to corresponding printf type specifier
template <typename T> inline const char *type2Specifier();
template <> inline const char *type2Specifier<bool>() {
// %s is because we will eventually print "true" or "false"
return "%s";
}
template <> inline const char *type2Specifier<int>() { return "%d"; }
template <> inline const char *type2Specifier<unsigned>() { return "%u"; }
template <> inline const char *type2Specifier<float>() { return "%f"; }
template <> inline const char *type2Specifier<long long>() { return "%lld"; }
template <> inline const char *type2Specifier<long long unsigned>() { return "%llu"; }
template <> inline const char *type2Specifier<double>() { return "%f"; }
template <> inline const char *type2Specifier<void *>() { return "%p"; }
// This function parses format string and string of arg types.
// User must implement ArgWriter class, which has such template methods as uniform2Str<type> and varying2Str<type>.
// Those functions must return text representation (c-string) of the next argument, the type of argument is provided
// with the template parameter. Returned c-string is placed instead of '%' in format string.
// The function returns resulting string.
template <typename ArgWriter>
inline StaticString<RES_STR_SIZE> GetFormatedStr(const char *format, const char *types, ArgWriter &argWriter) {
StaticString<RES_STR_SIZE> resultingStr;
int haveBeenWritten = 0;
int leftSpace = RES_STR_SIZE - 1; // space for null terminator
for (;; ++format, ++types) {
auto wereCopied = CopyPlainText(format, resultingStr, haveBeenWritten, leftSpace);
format += wereCopied;
haveBeenWritten += wereCopied;
leftSpace -= wereCopied;
if (!leftSpace || *format == '\0') {
// reached the end of format or limits of resultingStr
break;
}
StaticString<ARG_STR_SIZE> argStr = details::Arg2Str(*types, argWriter);
wereCopied = CopyFullText(argStr, 0, resultingStr, haveBeenWritten, leftSpace);
haveBeenWritten += wereCopied;
leftSpace -= wereCopied;
}
resultingStr[haveBeenWritten] = '\0';
return resultingStr;
}
+206
View File
@@ -0,0 +1,206 @@
/*
Copyright (c) 2010-2019, Intel Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Intel Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @file builtins-c-cpu.cpp
@brief Standard library function implementations written in C/C++.
This file provides C/C++ implementations of various functions that can be
called from ispc programs; in other words, this file is *not* linked
into the ispc compiler executable, but rather provides functions that
can be compiled into ispc programs.
When the ispc compiler is built, this file is compiled with clang to
generate LLVM bitcode. This bitcode is later linked in to the program
being compiled by the DefineStdlib() function. The first way to access
definitions from this file is by asking for them name from the
llvm::Module's' symbol table (e.g. as the PrintStmt implementation does
with __do_print() below. Alternatively, if a function defined in this
file has a signature that can be mapped back to ispc types by the
lLLVMTypeToIspcType() function, then its declaration will be made
available to ispc programs at compile time automatically.
*/
#ifndef WASM
#ifdef _MSC_VER
// We do want old school sprintf and don't want secure Microsoft extensions.
// And we also don't want warnings about it, so the define.
#define _CRT_SECURE_NO_WARNINGS
#else
// Some versions of glibc has "fortification" feature, which expands sprintf
// to __builtin___sprintf_chk(..., __builtin_object_size(...), ...).
// We don't want this kind of expansion, as we don't support these intrinsics.
#define _FORTIFY_SOURCE 0
#endif
#ifndef _MSC_VER
// In unistd.h we need the definition of sysconf and _SC_NPROCESSORS_ONLN used as its arguments.
// We should include unistd.h, but it doesn't really work well for cross compilation, as
// requires us to carry around unistd.h, which is not available on Windows out of the box.
#include <unistd.h>
// Just for the reference: these lines are eventually included from unistd.h
// #define _SC_NPROCESSORS_ONLN 58
// long sysconf(int);
#endif // !_MSC_VER
#endif // !WASM
#include <array>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using SizeT = std::size_t;
using MaskT = uint64_t;
constexpr SizeT RES_STR_SIZE = 8196;
constexpr SizeT ARG_STR_SIZE = 1024;
template <typename T, SizeT Size> using StaticContainer = std::array<T, Size>;
template <typename T, SizeT Size> using StaticContainerRef = StaticContainer<T, Size> &;
template <SizeT Size> using StaticString = StaticContainer<char, Size>;
template <SizeT Size> using StaticStringRef = StaticContainerRef<char, Size>;
using WidthT = int;
#include "builtins/builtins-c-common.hpp"
class ArgWriter {
const void *const *const args_;
int curArgIdx_;
WidthT width_;
MaskT mask_;
public:
ArgWriter(const void *const *args, WidthT width, MaskT mask)
: args_(args), curArgIdx_(0), width_(width), mask_(mask) {}
template <typename T> auto uniform2Str() {
auto fmt = type2Specifier<T>();
auto argPtr = getArg();
StaticString<ARG_STR_SIZE> res;
sprintf(&res[0], fmt, ValueAdapter<T>(*argCast<T>(argPtr)));
return res;
}
template <typename T> auto varying2Str() {
auto fmt = type2Specifier<T>();
StaticString<ARG_STR_SIZE> res;
res[0] = '[';
int haveBeenWritten = 1;
auto argPtr = getArg();
for (int lane = 0; lane < width_; ++lane) {
if (mask_ & (1ull << lane)) {
haveBeenWritten += sprintf(&res[haveBeenWritten], fmt, ValueAdapter<T>(argCast<T>(argPtr)[lane]));
} else {
haveBeenWritten = writeOffLane<T>(res, haveBeenWritten, argPtr, lane);
}
res[haveBeenWritten] = lane == width_ - 1 ? ']' : ',';
++haveBeenWritten;
}
res[haveBeenWritten] = '\0';
return res;
}
private:
const void *getArg() { return args_[curArgIdx_++]; }
// casts void* to proper pointer
// T is the type of argument (not pointer)
template <typename T> auto argCast(const void *argPtr) { return reinterpret_cast<const T *>(argPtr); }
// bools are passed as ints
template <> auto argCast<bool>(const void *argPtr) { return reinterpret_cast<const int *>(argPtr); }
template <typename T>
int writeOffLane(StaticString<ARG_STR_SIZE> &res, int haveBeenWritten, const void *argPtr, int lane) {
haveBeenWritten += sprintf(&res[haveBeenWritten], "((");
auto fmt = type2Specifier<T>();
haveBeenWritten += sprintf(&res[haveBeenWritten], fmt, ValueAdapter<T>(argCast<T>(argPtr)[lane]));
haveBeenWritten += sprintf(&res[haveBeenWritten], "))");
return haveBeenWritten;
}
template <>
int writeOffLane<bool>(StaticString<ARG_STR_SIZE> &res, int haveBeenWritten, const void *argPtr, int lane) {
haveBeenWritten += sprintf(&res[haveBeenWritten], OffLaneBoolStr);
return haveBeenWritten;
}
};
/** This function is called by PrintStmt to do the work of printing values
from ispc programs. Note that the function signature here must match
the parameters that PrintStmt::EmitCode() generates.
@param format Print format string
@param types Encoded types of the values being printed.
(See lEncodeType()).
@param width Vector width of the compilation target
@param mask Current lane mask when the print statement is called
@param args Array of pointers to the values to be printed
*/
extern "C" void __do_print(const char *format, const char *types, WidthT width, MaskT mask, const void *const *args) {
ArgWriter argWriter(args, width, mask);
StaticString<RES_STR_SIZE> resultingStr = GetFormatedStr(format, types, argWriter);
fputs(&resultingStr[0], stdout);
fflush(stdout);
}
#ifdef WASM
int __num_cores() { return 1; }
#else // WASM
extern "C" int __num_cores() {
#if defined(_MSC_VER) || defined(__MINGW32__)
// This is quite a hack. Including all of windows.h to get this definition
// pulls in a bunch of stuff that leads to undefined symbols at link time.
// So we don't #include <windows.h> but instead have the equivalent declarations
// here. Presumably this struct declaration won't be changing in the future
// anyway...
struct SYSTEM_INFO {
int pad0[2];
void *pad1[2];
int *pad2;
int dwNumberOfProcessors;
int pad3[3];
};
struct SYSTEM_INFO sysInfo;
extern void __stdcall GetSystemInfo(struct SYSTEM_INFO *);
GetSystemInfo(&sysInfo);
return sysInfo.dwNumberOfProcessors;
#else
return sysconf(_SC_NPROCESSORS_ONLN);
#endif // !_MSC_VER
}
#endif // !WASM
+289
View File
@@ -0,0 +1,289 @@
/*
Copyright (c) 2019, Intel Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Intel Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @file builtins-c-genx.cpp
@brief Standard library function implementations written in CM.
This file provides CM implementations of various functions that can be
called from ispc programs; in other words, this file is *not* linked
into the ispc compiler executable, but rather provides functions that
can be compiled into ispc programs.
When the ispc compiler is built, this file is compiled with clang to
generate LLVM bitcode. This bitcode is later linked in to the program
being compiled by the DefineStdlib() function. The first way to access
definitions from this file is by asking for them name from the
llvm::Module's' symbol table (e.g. as the PrintStmt implementation does
with __do_print_cm() below. Alternatively, if a function defined in this
file has a signature that can be mapped back to ispc types by the
lLLVMTypeToIspcType() function, then its declaration will be made
available to ispc programs at compile time automatically.
*/
using SizeT = unsigned;
using MaskT = unsigned long long;
using ArgT = unsigned;
constexpr SizeT RES_STR_SIZE = 128;
constexpr SizeT ARG_STR_SIZE = 100;
template <typename T, SizeT Size> using StaticContainer = vector<T, Size>;
template <typename T, SizeT Size> using StaticContainerRef = vector_ref<T, Size>;
template <SizeT Size> using StaticString = StaticContainer<char, Size>;
template <SizeT Size> using StaticStringRef = StaticContainerRef<char, Size>;
using WidthT = int;
#include "builtins/builtins-c-common.hpp"
#include <cm/cm.h>
enum LaneState : bool { OFF = false, ON = true };
// returns length of the string
// null character is not taken into account
template <typename T> inline int strLen(T str) {
int len = 0;
for (; str[len] != '\0'; ++len)
;
return len;
}
// is meant to be used only for integral types
template <typename T> inline T max(T a, T b) {
if (a > b)
return a;
return b;
}
template <typename T> inline const char *cmType2Specifier() { return type2Specifier<T>(); }
// cm printf doesn't support %p, so it is emulated with %X
template <> inline const char *cmType2Specifier<void *>() {
if constexpr (sizeof(void *) > sizeof(unsigned)) {
return "%016llX";
} else {
return "%08X";
}
}
// returns how many characters are required to write another vector element (element of varying argument)
template <typename T, LaneState lane> inline int requiredSpace4VecElem() {
auto specifierLen = strLen(cmType2Specifier<T>());
if constexpr (lane) {
// additional 1 for ',' or ']'
return specifierLen + 1;
} else {
// additional 4 for "((" and "))", 1 for ',' or ']'
return specifierLen + 5;
}
}
template <> inline int requiredSpace4VecElem<bool, LaneState::ON>() {
return max(strLen(ValueAdapter<bool>(false)), strLen(ValueAdapter<bool>(true)));
}
template <> inline int requiredSpace4VecElem<bool, LaneState::OFF>() { return strLen(OffLaneBoolStr); }
class ArgWriter {
details::OffsetT offset_;
const ArgT *const args_;
int curArgIdx_;
WidthT width_;
MaskT mask_;
public:
inline ArgWriter(details::OffsetT offset, const ArgT *args, WidthT width, MaskT mask)
: offset_(offset), args_(args), curArgIdx_(0), width_(width), mask_(mask) {}
template <typename T> inline auto uniform2Str() {
auto fmt = cmType2Specifier<T>();
StaticString<ARG_STR_SIZE> res;
auto wereCopied = CopyFullText(fmt, res, 0, ARG_STR_SIZE - 1);
res[wereCopied] = '\0';
writeArg<T>();
return res;
}
template <> inline auto uniform2Str<bool>() {
StaticString<ARG_STR_SIZE> res;
auto wereCopied = CopyFullText(ValueAdapter<bool>(getElementaryArg()), res, 0, ARG_STR_SIZE - 1);
res[wereCopied] = '\0';
return res;
}
template <typename T> inline auto varying2Str() {
StaticString<ARG_STR_SIZE> res;
res[0] = '[';
int haveBeenWritten = 1;
for (int lane = 0; lane < width_; ++lane) {
if (mask_ & (1ull << lane)) {
// require 1 additional character for '\0'
if (haveBeenWritten >= ARG_STR_SIZE - requiredSpace4VecElem<T, LaneState::ON>() - 1) {
// there's not enough space to write another value
break;
}
haveBeenWritten = writeFormat4VecElem<T, LaneState::ON>(res, haveBeenWritten);
} else {
// require 1 additional character for '\0'
if (haveBeenWritten >= ARG_STR_SIZE - requiredSpace4VecElem<T, LaneState::OFF>() - 1) {
// there's not enough space to write another value
break;
}
haveBeenWritten = writeFormat4VecElem<T, LaneState::OFF>(res, haveBeenWritten);
}
res[haveBeenWritten] = lane == width_ - 1 ? ']' : ',';
++haveBeenWritten;
writeArg<T>();
}
res[haveBeenWritten] = '\0';
return res;
}
private:
inline ArgT getElementaryArg() { return args_[curArgIdx_++]; }
// accesses next scalar argument or element of vector argument from args_ (reads 2 elementary args if 64 bit value)
// and writes it to cm printf surface
template <typename T> inline void writeArg() {
ArgT low, high;
if constexpr (sizeof(T) > 4) {
low = getElementaryArg();
high = getElementaryArg();
} else {
low = getElementaryArg();
high = 0;
}
SurfaceIndex BTI = details::__cm_intrinsic_impl_predefined_surface(details::PRINT_SURF_IDX);
details::_cm_print_args_raw<T>(BTI, offset_, low, high);
offset_ += CMPHF_VEC_BSZ;
}
// cm printf doesn't support %p specifier, so it is emulated with unsigned
// look at cmType2Specifier for more details
template <> inline void writeArg<void *>() {
if constexpr (sizeof(void *) > sizeof(unsigned))
writeArg<unsigned long long>();
else
writeArg<unsigned>();
}
// in case of bool we do not push the argument to surface, we directly write true or false into format string
template <> inline void writeArg<bool>() {}
// appends format string for vector element to res starting from position haveBeenWritten
// behaves differently for bools, look at specializations for details
// NOTE: this is a method because bool specialization require access to getElementaryArg
template <typename T, LaneState lane>
inline int writeFormat4VecElem(StaticStringRef<ARG_STR_SIZE> res, int haveBeenWritten) {
auto fmt = cmType2Specifier<T>();
if constexpr (!lane)
haveBeenWritten += CopyFullText("((", res, haveBeenWritten, ARG_STR_SIZE - haveBeenWritten - 1);
haveBeenWritten += CopyFullText(fmt, res, haveBeenWritten, ARG_STR_SIZE - haveBeenWritten - 1);
if constexpr (!lane)
haveBeenWritten += CopyFullText("))", res, haveBeenWritten, ARG_STR_SIZE - haveBeenWritten - 1);
return haveBeenWritten;
}
// in case of bools we directly write "true" or "false" to format string, as cm printf doesn't support %s
template <>
inline int writeFormat4VecElem<bool, LaneState::ON>(StaticStringRef<ARG_STR_SIZE> res, int haveBeenWritten) {
haveBeenWritten += CopyFullText(ValueAdapter<bool>(getElementaryArg()), res, haveBeenWritten,
ARG_STR_SIZE - haveBeenWritten - 1);
return haveBeenWritten;
}
// off lane bool unlike other types doesn't get additional parenthesis (not "((true))"), but becomes "_____"
template <>
inline int writeFormat4VecElem<bool, LaneState::OFF>(StaticStringRef<ARG_STR_SIZE> res, int haveBeenWritten) {
haveBeenWritten += CopyFullText(OffLaneBoolStr, res, haveBeenWritten, ARG_STR_SIZE - haveBeenWritten - 1);
return haveBeenWritten;
}
};
/** This function is called by PrintStmt to do the work of printing values
from ispc programs. Note that the function signature here must match
the parameters that PrintStmt::EmitCode() generates.
@param format Print format string
@param types Encoded types of the values being printed.
(See lEncodeType()).
@param width Vector width of the compilation target
@param mask Current lane mask when the print statement is called
@param args Array of split into uints values
(if a value bigger than uint it stored in 2 uints: low bits, then high bits,
vector values stored by scalar values with the previous rule being applied)
@param numNotBoolUniArgs number of uniform values, except bool ones (details are below)
@param numNotBoolVarArgs number of varying values, except bool ones (details are below)
WARNING!!!
CM supports maximum of 128 characters in format string. If your input doesn't fit, it will be cut.
NOTE: 128 is the maximum size of printf format string, not ISPC print one. So consider space for brackets, commas,
printf type specifiers, e.g. % with a vector of int being passed, will require 3*width+1 characters
(for more details look for explanation below).
Root: cm printf works in the following way. It takes its arguments (format string and the arguments itself),
adds some headers and sends it to host through predefined surface. Host gets all this arguments, and calls
the real printf.
Idea: GetFormatedStr() transforms ISPC format string to normal printf format string:
adds qualifiers (% -> %d,% -> %f,...),
adds brackets for vector values (% -> [%d,((%d)),...,%d]).
Real writing of an argument to surface happens in uniform2Str, varying2Str methods.
FIXME: __declspec(genx_SIMT(1)) is used to make __do_print_cm function be called unpredicated in simd cf.
It is possibly an abuse of this attribute, in this case it's better to solve the issue differently.
*/
extern "C" __declspec(cm_builtin) __declspec(genx_SIMT(1)) void __do_print_cm(const char *format, const char *types,
WidthT width, MaskT mask,
const ArgT *args, int numNotBoolUniArgs,
int numNotBoolVarArgs) {
// boolean args are not included in total_len as we won't write them into the print surface
// they will be written directly to format string
// FIXME: if we won't write all arguments (which can happen if we don't fit in 128 characters), behavior
// on runtime side can be undefined. We should either fix it here (will make this code even more heavier),
// or tighten up contract of print (require from user to not overflow 128 characters).
details::OffsetT total_len =
CMPHF_VEC_BSZ + CMPHF_STR_SZ + (numNotBoolUniArgs + numNotBoolVarArgs * width) * CMPHF_VEC_BSZ;
SurfaceIndex BTI = details::__cm_intrinsic_impl_predefined_surface(details::PRINT_SURF_IDX);
auto init_offset = details::_cm_print_init_offset(BTI, total_len);
auto arg_offset = init_offset + CMPHF_STR_SZ + CMPHF_VEC_BSZ;
ArgWriter argWriter(arg_offset, args, width, mask);
StaticString<RES_STR_SIZE> resultingStr = GetFormatedStr(format, types, argWriter);
details::_cm_print_format(BTI, init_offset, resultingStr);
}
// TODO: implement correctly for genx if possible
extern "C" int __declspec(cm_builtin) __num_cores() { return -1; }
-234
View File
@@ -1,234 +0,0 @@
/*
Copyright (c) 2010-2020, Intel Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Intel Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @file builtins-c.c
@brief Standard library function implementations written in C.
This file provides C implementations of various functions that can be
called from ispc programs; in other words, this file is *not* linked
into the ispc compiler executable, but rather provides functions that
can be compiled into ispc programs.
When the ispc compiler is built, this file is compiled with clang to
generate LLVM bitcode. This bitcode is later linked in to the program
being compiled by the DefineStdlib() function. The first way to access
definitions from this file is by asking for them name from the
llvm::Module's' symbol table (e.g. as the PrintStmt implementation does
with __do_print() below. Alternatively, if a function defined in this
file has a signature that can be mapped back to ispc types by the
lLLVMTypeToIspcType() function, then its declaration will be made
available to ispc programs at compile time automatically.
*/
#ifndef WASM
#ifdef _MSC_VER
// We do want old school sprintf and don't want secure Microsoft extensions.
// And we also don't want warnings about it, so the define.
#define _CRT_SECURE_NO_WARNINGS
#else
// Some versions of glibc has "fortification" feature, which expands sprintf
// to __builtin___sprintf_chk(..., __builtin_object_size(...), ...).
// We don't want this kind of expansion, as we don't support these intrinsics.
#define _FORTIFY_SOURCE 0
#endif
#ifndef _MSC_VER
// In unistd.h we need the definition of sysconf and _SC_NPROCESSORS_ONLN used as its arguments.
// We should include unistd.h, but it doesn't really work well for cross compilation, as
// requires us to carry around unistd.h, which is not available on Windows out of the box.
#include <unistd.h>
// Just for the reference: these lines are eventually included from unistd.h
// #define _SC_NPROCESSORS_ONLN 58
// long sysconf(int);
#endif // !_MSC_VER
#endif // !WASM
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int Bool;
#define PRINT_BUF_SIZE 4096
#define APPEND(str) \
do { \
int offset = bufp - &printString[0]; \
*bufp = '\0'; \
strncat(bufp, str, PRINT_BUF_SIZE - offset); \
bufp += strlen(str); \
if (bufp >= &printString[PRINT_BUF_SIZE]) \
goto done; \
} while (0) /* eat semicolon */
#define PRINT_SCALAR(fmt, type) \
sprintf(tmpBuf, fmt, *((type *)ptr)); \
APPEND(tmpBuf); \
break
#define PRINT_VECTOR(fmt, type) \
*bufp++ = '['; \
if (bufp == &printString[PRINT_BUF_SIZE]) \
break; \
for (int i = 0; i < width; ++i) { \
/* only print the value if the current lane is executing */ \
if (mask & (1ull << i)) \
sprintf(tmpBuf, fmt, ((type *)ptr)[i]); \
else \
sprintf(tmpBuf, "((" fmt "))", ((type *)ptr)[i]); \
APPEND(tmpBuf); \
*bufp++ = (i != width - 1 ? ',' : ']'); \
} \
break
/** This function is called by PrintStmt to do the work of printing values
from ispc programs. Note that the function signature here must match
the parameters that PrintStmt::EmitCode() generates.
@param format Print format string
@param types Encoded types of the values being printed.
(See lEncodeType()).
@param width Vector width of the compilation target
@param mask Current lane mask when the print statemnt is called
@param args Array of pointers to the values to be printed
*/
void __do_print(const char *format, const char *types, int width, uint64_t mask, void **args) {
char printString[PRINT_BUF_SIZE + 1]; // +1 for trailing NUL
char *bufp = &printString[0];
char tmpBuf[256];
int argCount = 0;
while (*format && bufp < &printString[PRINT_BUF_SIZE]) {
// Format strings are just single percent signs.
if (*format != '%') {
*bufp++ = *format;
} else {
if (*types) {
void *ptr = args[argCount++];
// Based on the encoding in the types string, cast the
// value appropriately and print it with a reasonable
// printf() formatting string.
switch (*types) {
case 'b': {
sprintf(tmpBuf, "%s", *((Bool *)ptr) ? "true" : "false");
APPEND(tmpBuf);
break;
}
case 'B': {
*bufp++ = '[';
if (bufp == &printString[PRINT_BUF_SIZE])
break;
for (int i = 0; i < width; ++i) {
if (mask & (1ull << i)) {
sprintf(tmpBuf, "%s", ((Bool *)ptr)[i] ? "true" : "false");
APPEND(tmpBuf);
} else
APPEND("_________");
*bufp++ = (i != width - 1) ? ',' : ']';
}
break;
}
case 'i':
PRINT_SCALAR("%d", int);
case 'I':
PRINT_VECTOR("%d", int);
case 'u':
PRINT_SCALAR("%u", unsigned int);
case 'U':
PRINT_VECTOR("%u", unsigned int);
case 'f':
PRINT_SCALAR("%f", float);
case 'F':
PRINT_VECTOR("%f", float);
case 'l':
PRINT_SCALAR("%lld", long long);
case 'L':
PRINT_VECTOR("%lld", long long);
case 'v':
PRINT_SCALAR("%llu", unsigned long long);
case 'V':
PRINT_VECTOR("%llu", unsigned long long);
case 'd':
PRINT_SCALAR("%f", double);
case 'D':
PRINT_VECTOR("%f", double);
case 'p':
PRINT_SCALAR("%p", void *);
case 'P':
PRINT_VECTOR("%p", void *);
default:
APPEND("UNKNOWN TYPE ");
*bufp++ = *types;
}
++types;
}
}
++format;
}
done:
*bufp = '\0';
fputs(printString, stdout);
fflush(stdout);
}
#ifdef WASM
int __num_cores() { return 1; }
#else // WASM
int __num_cores() {
#if defined(_MSC_VER) || defined(__MINGW32__)
// This is quite a hack. Including all of windows.h to get this definition
// pulls in a bunch of stuff that leads to undefined symbols at link time.
// So we don't #include <windows.h> but instead have the equivalent declarations
// here. Presumably this struct declaration won't be changing in the future
// anyway...
struct SYSTEM_INFO {
int pad0[2];
void *pad1[2];
int *pad2;
int dwNumberOfProcessors;
int pad3[3];
};
struct SYSTEM_INFO sysInfo;
extern void __stdcall GetSystemInfo(struct SYSTEM_INFO *);
GetSystemInfo(&sysInfo);
return sysInfo.dwNumberOfProcessors;
#else
return sysconf(_SC_NPROCESSORS_ONLN);
#endif // !_MSC_VER
}
#endif // !WASM
+3 -3
View File
@@ -4165,7 +4165,7 @@ define void @__send_eot() {
ret void
}
define internal void @__do_print_cm(i8*) alwaysinline {
define internal void @__do_print_cm_str(i8*) alwaysinline {
;; predefined.surface return Value: surface index of the specified id
;; id 2 is stdout
%buf = tail call i32 @llvm.genx.predefined.surface(i32 2)
@@ -4189,7 +4189,7 @@ define void @__do_assert_uniform(i8 *%str, i1 %test, <WIDTH x MASK> %mask) {
br i1 %test, label %ok, label %fail
fail:
call void @__do_print_cm(i8* %str)
call void @__do_print_cm_str(i8* %str)
call void @__send_eot()
ret void
@@ -4208,7 +4208,7 @@ define void @__do_assert_varying(i8 *%str, <WIDTH x MASK> %test,
br i1 %all_ok, label %ok, label %fail
fail:
call void @__do_print_cm(i8* %str)
call void @__do_print_cm_str(i8* %str)
call void @__send_eot()
ret void
+49 -5
View File
@@ -82,7 +82,7 @@ function(ll_to_cpp llFileName bit os_name resultFileName)
endfunction()
function(builtin_to_cpp bit os_name arch supported_archs supported_oses resultFileName)
set(inputFilePath builtins/builtins.c)
set(inputFilePath builtins/builtins-c-cpu.cpp)
set(includePath "")
set(SKIP OFF)
if (NOT ${arch} IN_LIST supported_archs OR NOT ${os_name} IN_LIST supported_oses)
@@ -245,12 +245,12 @@ function(builtin_to_cpp bit os_name arch supported_archs supported_oses resultFi
# Compose target flags
set(target_flags --target=${triple} ${fpic} ${includePath})
set(output ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/builtins-c-${bit}-${os_name}-${target_arch}.cpp)
set(output ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/builtins-cpp-${bit}-${os_name}-${target_arch}.cpp)
if (${os_name} STREQUAL "web")
add_custom_command(
OUTPUT ${output}
COMMAND ${EMCC_EXECUTABLE} -DWASM -s WASM_OBJECT_FILES=0 -c ${inputFilePath} -emit-llvm -c -o -
| (\"${LLVM_DIS_EXECUTABLE}\" - || echo "builtins.c compile error")
COMMAND ${EMCC_EXECUTABLE} -DWASM -s WASM_OBJECT_FILES=0 -I${CMAKE_SOURCE_DIR} -c ${inputFilePath} --std=gnu++17 -emit-llvm -c -o -
| (\"${LLVM_DIS_EXECUTABLE}\" - || echo "builtins-c-*.cpp compile error")
| \"${Python3_EXECUTABLE}\" bitcode2cpp.py c --type=builtins-c --runtime=${bit} --os=${os_name} --arch=${target_arch} --llvm_as ${LLVM_AS_EXECUTABLE}
> ${output}
DEPENDS ${inputFilePath} bitcode2cpp.py
@@ -259,7 +259,7 @@ function(builtin_to_cpp bit os_name arch supported_archs supported_oses resultFi
else()
add_custom_command(
OUTPUT ${output}
COMMAND ${CLANG_EXECUTABLE} ${target_flags} -w -m${bit} -emit-llvm -c ${inputFilePath} -o - | (\"${LLVM_DIS_EXECUTABLE}\" - || echo "builtins.c compile error")
COMMAND ${CLANGPP_EXECUTABLE} ${target_flags} -I${CMAKE_SOURCE_DIR} -m${bit} -emit-llvm --std=gnu++17 -c ${inputFilePath} -o - | (\"${LLVM_DIS_EXECUTABLE}\" - || echo "builtins-c-*.cpp compile error")
| \"${Python3_EXECUTABLE}\" bitcode2cpp.py c --type=builtins-c --runtime=${bit} --os=${os_name} --arch=${target_arch} --llvm_as ${LLVM_AS_EXECUTABLE}
> ${output}
DEPENDS ${inputFilePath} bitcode2cpp.py
@@ -271,6 +271,40 @@ function(builtin_to_cpp bit os_name arch supported_archs supported_oses resultFi
set_source_files_properties(${resultFileName} PROPERTIES GENERATED true)
endfunction()
function(builtin_genx_to_cpp bit resultFileName)
set(inputFilePath builtins/builtins-c-genx.cpp)
set(SKIP OFF)
if (WIN32)
set(os_name "windows")
elseif (APPLE)
set(SKIP ON)
else ()
set(os_name "linux")
endif()
if ("${bit}" STREQUAL "32")
set(target_arch "genx32")
elseif ("${bit}" STREQUAL "64")
set(target_arch "genx64")
else()
set(SKIP ON)
endif()
if (NOT SKIP)
set(output ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/builtins-cm-${bit}.cpp)
add_custom_command(
OUTPUT ${output}
COMMAND ${CMC_EXECUTABLE} -I${CMAKE_SOURCE_DIR} -m${bit} -isystem ${CM_INCLUDE_PATH} -emit-llvm -S ${inputFilePath} -o -
| \"${Python3_EXECUTABLE}\" bitcode2cpp.py cm --type=builtins-c --runtime=${bit} --os=${os_name} --arch=${target_arch} --llvm_as ${LLVM_AS_EXECUTABLE}
> ${output}
DEPENDS ${inputFilePath}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
set(${resultFileName} ${output} PARENT_SCOPE)
set_source_files_properties(${resultFileName} PROPERTIES GENERATED true)
endif()
endfunction()
function (generate_target_builtins resultList)
# Dispatch module for Windows and/or Unix targets.
foreach (os_name ${TARGET_OS_LIST_FOR_LL})
@@ -358,5 +392,15 @@ function (generate_common_builtins resultList)
endforeach()
endforeach()
endforeach()
if (GENX_ENABLED)
foreach (bit 32 64)
builtin_genx_to_cpp(${bit} res_genx_${bit})
list(APPEND tmpList ${res_genx_${bit}} )
if(MSVC)
# Group generated files inside Visual Studio
source_group("Generated Builtins" FILES ${res_genx_${bit}})
endif()
endforeach()
endif()
set(${resultList} ${tmpList} PARENT_SCOPE)
endfunction()
+25
View File
@@ -55,6 +55,31 @@ find_program(CLANG_EXECUTABLE NAMES clang
endif()
message(STATUS "CLANG_EXECUTABLE: ${CLANG_EXECUTABLE}")
find_program(CLANGPP_EXECUTABLE NAMES clang++
PATHS ${LLVM_TOOLS_BINARY_DIR} PATH_SUFFIXES bin NO_DEFAULT_PATH)
if (NOT CLANGPP_EXECUTABLE)
message(FATAL_ERROR "Failed to find clang++" )
endif()
message(STATUS "CLANGPP_EXECUTABLE: ${CLANGPP_EXECUTABLE}")
if (GENX_ENABLED)
find_program(CMC_EXECUTABLE NAMES cmc
PATHS ${LLVM_TOOLS_BINARY_DIR} PATH_SUFFIXES bin NO_DEFAULT_PATH)
if (NOT CMC_EXECUTABLE)
message(FATAL_ERROR "Failed to find cmc" )
endif()
message(STATUS "CMC_EXECUTABLE: ${CMC_EXECUTABLE}")
if (NOT CM_INCLUDE_PATH)
get_filename_component(CM_INCLUDE_PATH ${CMC_EXECUTABLE} DIRECTORY)
set(CM_INCLUDE_PATH ${CM_INCLUDE_PATH}/../include)
get_filename_component(CM_INCLUDE_PATH ${CM_INCLUDE_PATH} ABSOLUTE)
if (NOT EXISTS ${CM_INCLUDE_PATH} OR NOT EXISTS ${CM_INCLUDE_PATH}/cm)
message(FATAL_ERROR "Cannot find path to CM library headers (CM_INCLUDE_PATH)")
endif()
endif()
message(STATUS "CM_INCLUDE_PATH: ${CM_INCLUDE_PATH}")
endif()
find_program(LLVM_DIS_EXECUTABLE NAMES llvm-dis
PATHS ${LLVM_TOOLS_BINARY_DIR} PATH_SUFFIXES bin NO_DEFAULT_PATH)
if (NOT LLVM_DIS_EXECUTABLE)
+69
View File
@@ -0,0 +1,69 @@
/*
Copyright (c) 2019, Intel Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Intel Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @file builtins-info.h
@brief This file contains information shared between code, that uses
builtins and the code that implements them.
*/
#ifndef ISPC_BUILTINS_INFO_H
#define ISPC_BUILTINS_INFO_H 1
namespace PrintInfo {
// get encoding (as a char) for uniform T type, that is used in type argument of __do_print(_cm)
template <typename T> char getEncoding4Uniform();
// get encoding (as a char) for uniform T type, that is used in type argument of __do_print(_cm)
template <typename T> char getEncoding4Varying();
template <> inline constexpr char getEncoding4Uniform<bool>() { return 'b'; }
template <> inline constexpr char getEncoding4Uniform<int>() { return 'i'; }
template <> inline constexpr char getEncoding4Uniform<unsigned>() { return 'u'; }
template <> inline constexpr char getEncoding4Uniform<float>() { return 'f'; }
template <> inline constexpr char getEncoding4Uniform<long long>() { return 'l'; }
template <> inline constexpr char getEncoding4Uniform<unsigned long long>() { return 'v'; }
template <> inline constexpr char getEncoding4Uniform<double>() { return 'd'; }
template <> inline constexpr char getEncoding4Uniform<void *>() { return 'p'; }
template <> inline constexpr char getEncoding4Varying<bool>() { return 'B'; }
template <> inline constexpr char getEncoding4Varying<int>() { return 'I'; }
template <> inline constexpr char getEncoding4Varying<unsigned>() { return 'U'; }
template <> inline constexpr char getEncoding4Varying<float>() { return 'F'; }
template <> inline constexpr char getEncoding4Varying<long long>() { return 'L'; }
template <> inline constexpr char getEncoding4Varying<unsigned long long>() { return 'V'; }
template <> inline constexpr char getEncoding4Varying<double>() { return 'D'; }
template <> inline constexpr char getEncoding4Varying<void *>() { return 'P'; }
} // namespace PrintInfo
#endif // ISPC_BUILTINS_INFO_H
+22 -4
View File
@@ -426,8 +426,9 @@ static void lSetInternalFunctions(llvm::Module *module) {
"__do_print",
#ifdef ISPC_GENX_ENABLED
"__do_print_cm",
"__do_print_cm_str",
"__send_eot",
#endif
#endif //ISPC_GENX_ENABLED
"__doublebits_uniform_int64",
"__doublebits_varying_int64",
"__exclusive_scan_add_double",
@@ -811,9 +812,8 @@ static void lSetInternalFunctions(llvm::Module *module) {
"__task_count",
};
// clang-format on
int count = sizeof(names) / sizeof(names[0]);
for (int i = 0; i < count; ++i) {
llvm::Function *f = module->getFunction(names[i]);
for (auto name : names) {
llvm::Function *f = module->getFunction(name);
if (f != NULL && f->empty() == false) {
f->setLinkage(llvm::GlobalValue::InternalLinkage);
// TO-DO : Revisit adding this back for ARM support.
@@ -822,6 +822,23 @@ static void lSetInternalFunctions(llvm::Module *module) {
}
}
static void lSetAlwaysInlineFunctions(llvm::Module *module) {
std::vector<const char *> names = {
#ifdef ISPC_GENX_ENABLED
"__do_print_cm"
#endif /* ISPC_GENX_ENABLED */
};
for (auto name : names) {
llvm::Function *f = module->getFunction(name);
if (f) {
if (f->hasFnAttribute(llvm::Attribute::NoInline)) {
f->removeFnAttr(llvm::Attribute::NoInline);
}
f->addFnAttr(llvm::Attribute::AlwaysInline);
}
}
}
/** This utility function takes serialized binary LLVM bitcode and adds its
definitions to the given module. Functions in the bitcode that can be
mapped to ispc functions are also added to the symbol table.
@@ -907,6 +924,7 @@ void AddBitcodeToModule(const BitcodeLib *lib, llvm::Module *module, SymbolTable
}
lSetInternalFunctions(module);
lSetAlwaysInlineFunctions(module);
if (symbolTable != NULL)
lAddModuleSymbols(module, symbolTable);
lCheckModuleIntrinsics(module);
Regular → Executable
+299 -115
View File
@@ -36,6 +36,7 @@
*/
#include "stmt.h"
#include "builtins-info.h"
#include "ctx.h"
#include "expr.h"
#include "func.h"
@@ -2898,62 +2899,62 @@ PrintStmt::PrintStmt(const std::string &f, Expr *v, SourcePos p) : Stmt(p, Print
/* Because the pointers to values that are passed to __do_print() are all
void *s (and because ispc print() formatting strings statements don't
encode types), we pass along a string to __do_print() where the i'th
character encodes the type of the i'th value to be printed. Needless to
say, the encoding chosen here and the decoding code in __do_print() need
to agree on the below!
character encodes the type of the i'th value to be printed. The encoding
is defined by getEncoding4Uniform<T> and getEncding4Varying<T> functions.
*/
static char lEncodeType(const Type *t) {
if (Type::Equal(t, AtomicType::UniformBool))
return 'b';
return PrintInfo::getEncoding4Uniform<bool>();
if (Type::Equal(t, AtomicType::VaryingBool))
return 'B';
return PrintInfo::getEncoding4Varying<bool>();
if (Type::Equal(t, AtomicType::UniformInt32))
return 'i';
return PrintInfo::getEncoding4Uniform<int>();
if (Type::Equal(t, AtomicType::VaryingInt32))
return 'I';
return PrintInfo::getEncoding4Varying<int>();
if (Type::Equal(t, AtomicType::UniformUInt32))
return 'u';
return PrintInfo::getEncoding4Uniform<unsigned>();
if (Type::Equal(t, AtomicType::VaryingUInt32))
return 'U';
return PrintInfo::getEncoding4Varying<unsigned>();
if (Type::Equal(t, AtomicType::UniformFloat))
return 'f';
return PrintInfo::getEncoding4Uniform<float>();
if (Type::Equal(t, AtomicType::VaryingFloat))
return 'F';
return PrintInfo::getEncoding4Varying<float>();
if (Type::Equal(t, AtomicType::UniformInt64))
return 'l';
return PrintInfo::getEncoding4Uniform<long long>();
if (Type::Equal(t, AtomicType::VaryingInt64))
return 'L';
return PrintInfo::getEncoding4Varying<long long>();
if (Type::Equal(t, AtomicType::UniformUInt64))
return 'v';
return PrintInfo::getEncoding4Uniform<unsigned long long>();
if (Type::Equal(t, AtomicType::VaryingUInt64))
return 'V';
return PrintInfo::getEncoding4Varying<unsigned long long>();
if (Type::Equal(t, AtomicType::UniformDouble))
return 'd';
return PrintInfo::getEncoding4Uniform<double>();
if (Type::Equal(t, AtomicType::VaryingDouble))
return 'D';
return PrintInfo::getEncoding4Varying<double>();
if (CastType<PointerType>(t) != NULL) {
if (t->IsUniformType())
return 'p';
return PrintInfo::getEncoding4Uniform<void *>();
else
return 'P';
return PrintInfo::getEncoding4Varying<void *>();
} else
return '\0';
}
/** Given an Expr for a value to be printed, emit the code to evaluate the
expression and store the result to alloca'd memory. Update the
argTypes string with the type encoding for this expression.
*/
static llvm::Value *lProcessPrintArg(Expr *expr, FunctionEmitContext *ctx, std::string &argTypes) {
struct ExprWithType {
Expr *expr;
char type;
};
static ExprWithType lProcessPrintArgType(Expr *expr) {
const Type *type = expr->GetType();
if (type == NULL)
return NULL;
return {NULL, '\0'};
if (CastType<ReferenceType>(type) != NULL) {
expr = new RefDerefExpr(expr, expr->pos);
type = expr->GetType();
if (type == NULL)
return NULL;
return {NULL, '\0'};
}
// Just int8 and int16 types to int32s...
@@ -2974,30 +2975,276 @@ static llvm::Value *lProcessPrintArg(Expr *expr, FunctionEmitContext *ctx, std::
"Only atomic types are allowed in print statements; "
"type \"%s\" is illegal.",
type->GetString().c_str());
return NULL;
} else {
if (type->IsBoolType()) {
// Blast bools to ints, but do it here to preserve encoding for
// printing 'true' or 'false'
expr = new TypeCastExpr(type->IsUniformType() ? AtomicType::UniformInt32 : AtomicType::VaryingInt32, expr,
expr->pos);
type = expr->GetType();
}
argTypes.push_back(t);
llvm::Value *ptr = ctx->AllocaInst(type, "print_arg");
llvm::Value *val = expr->GetValue(ctx);
if (!val)
return NULL;
ctx->StoreInst(val, ptr, type);
ptr = ctx->BitCastInst(ptr, LLVMTypes::VoidPointerType);
return ptr;
return {NULL, '\0'};
}
if (type->IsBoolType()) {
// Blast bools to ints, but do it here to preserve encoding for
// printing 'true' or 'false'
expr = new TypeCastExpr(type->IsUniformType() ? AtomicType::UniformInt32 : AtomicType::VaryingInt32, expr,
expr->pos);
}
return {expr, t};
}
#ifdef ISPC_GENX_ENABLED
class PrintArgsBuilder {
// properly dereferenced and size extended value expressions
std::vector<ExprWithType> argExprs_;
FunctionEmitContext *ctx_;
public:
PrintArgsBuilder() : argExprs_(), ctx_(NULL) {}
template <typename Iter>
PrintArgsBuilder(Iter first, Iter last, FunctionEmitContext *ctx) : argExprs_(), ctx_(ctx) {
std::transform(first, last, std::back_inserter(argExprs_),
[](Expr *expr) { return lProcessPrintArgType(expr); });
std::for_each(argExprs_.cbegin(), argExprs_.cend(),
[](const ExprWithType &elem) { Assert(elem.expr && "must have all values processed"); });
}
// the string that will later go as TYPES_IDX argument
const std::string generateArgTypes() const {
std::string argTypes;
std::transform(argExprs_.cbegin(), argExprs_.cend(), std::back_inserter(argTypes),
[](const ExprWithType &argInfo) { return argInfo.type; });
return argTypes;
}
// the value that will later go as ARGS_IDX argument
// Splits arguments into set of uints, stores them in uint[], returns pointer to it
llvm::Value *emitArgCode() {
std::vector<llvm::Value *> argValues(argExprs_.size());
std::transform(argExprs_.cbegin(), argExprs_.cend(), argValues.begin(),
[this](const ExprWithType &argInfo) { return argInfo.expr->GetValue(ctx_); });
std::vector<llvm::Value *> alignedArgValues;
createAlignedArgValues(argValues.cbegin(), argValues.cend(), std::back_inserter(alignedArgValues));
auto argAlloca = storeAlignedArgValues(alignedArgValues.cbegin(), alignedArgValues.cend());
return ctx_->BitCastInst(argAlloca, LLVMTypes::Int32PointerType);
}
// current implementation of __do_print_cm requires the number of uniform arguments with bool excluded
int countNonBoolUniformArgs() const {
return std::count_if(argExprs_.cbegin(), argExprs_.cend(), [](const ExprWithType &argInfo) {
return argInfo.expr->GetType()->IsUniformType() && argInfo.type != PrintInfo::getEncoding4Uniform<bool>();
});
}
// current implementation of __do_print_cm requires the number of varying arguments with bool excluded
int countNonBoolVaryingArgs() const {
return std::count_if(argExprs_.cbegin(), argExprs_.cend(), [](const ExprWithType &argInfo) {
return argInfo.expr->GetType()->IsVaryingType() && argInfo.type != PrintInfo::getEncoding4Varying<bool>();
});
}
private:
// Aligned here means that every value is represented by set of i32
template <typename InputIter, typename OutputIter>
OutputIter createAlignedArgValues(InputIter first, InputIter last, OutputIter dst) {
for (; first != last; ++first) {
auto arg = *first;
if (arg->getType()->isVectorTy())
dst = createAlignedVectorArgValue(arg, dst);
else
dst = createAlignedScalarArgValue(arg, dst);
}
return dst;
}
// vector becomes separate scalars
template <typename OutputIter> OutputIter createAlignedVectorArgValue(llvm::Value *vecArg, OutputIter dst) {
auto width = g->target->getVectorWidth();
for (auto lane = 0; lane < width; ++lane) {
auto scalarArg = ctx_->ExtractInst(vecArg, lane);
dst = createAlignedScalarArgValue(scalarArg, dst);
}
return dst;
}
// 64-bit values must become 2 i32
template <typename OutputIter> OutputIter createAlignedScalarArgValue(llvm::Value *arg, OutputIter dst) {
if (arg->getType()->getPrimitiveSizeInBits() == 64) {
arg = castToIntIfNeeded(arg, LLVMTypes::Int64Type);
auto lowArg = ctx_->TruncInst(arg, LLVMTypes::Int32Type);
auto almostHiArg = ctx_->BinaryOperator(llvm::Instruction::LShr, arg, LLVMInt64(32));
auto hiArg = ctx_->TruncInst(almostHiArg, LLVMTypes::Int32Type);
*dst++ = lowArg;
*dst++ = hiArg;
return dst;
} else {
assert(arg->getType()->getPrimitiveSizeInBits() == 32 && "must be either 32 or 64 bit");
*dst++ = castToIntIfNeeded(arg, LLVMTypes::Int32Type);
return dst;
}
}
// creates alloca, stores values from [first, last) into it
// returns alloca value
template <typename InputIter> llvm::Value *storeAlignedArgValues(InputIter first, InputIter last) {
auto numArgs = last - first;
auto arrTy = llvm::ArrayType::get(LLVMTypes::Int32Type, numArgs);
auto argAlloca = ctx_->AllocaInst(arrTy, "print_arg_ptrs");
for (int i = 0; first != last; ++first, ++i) {
auto curArgPtr = ctx_->AddElementOffset(argAlloca, i, NULL);
ctx_->StoreInst(*first, curArgPtr, NULL, true);
}
return argAlloca;
}
llvm::Value *castToIntIfNeeded(llvm::Value *val, llvm::Type *intTy) {
assert(intTy->isIntegerTy());
assert(val->getType()->getPrimitiveSizeInBits() == intTy->getPrimitiveSizeInBits());
if (val->getType() != intTy)
if (val->getType()->isPointerTy())
return ctx_->PtrToIntInst(val, intTy);
return ctx_->BitCastInst(val, intTy);
return val;
}
};
// prepares arguments for __do_print_cm function
std::vector<llvm::Value *> PrintStmt::getDoPrintCMArgs(FunctionEmitContext *ctx) const {
std::vector<llvm::Value *> doPrintCMArgs(GENX_NUM_IDX);
doPrintCMArgs[FORMAT_IDX] = ctx->GetStringPtr(format);
doPrintCMArgs[WIDTH_IDX] = LLVMInt32(g->target->getVectorWidth());
doPrintCMArgs[MASK_IDX] = ctx->LaneMask(ctx->GenXSimdCFPredicate(LLVMMaskAllOn));
if (values == NULL) {
doPrintCMArgs[ARGS_IDX] = llvm::Constant::getNullValue(LLVMTypes::Int32PointerType);
doPrintCMArgs[TYPES_IDX] = ctx->GetStringPtr("");
doPrintCMArgs[UNI_NUM_IDX] = LLVMInt32(0);
doPrintCMArgs[VAR_NUM_IDX] = LLVMInt32(0);
} else {
PrintArgsBuilder argsBuilder;
ExprList *elist = llvm::dyn_cast<ExprList>(values);
if (elist)
argsBuilder = PrintArgsBuilder(elist->exprs.begin(), elist->exprs.end(), ctx);
else
argsBuilder = PrintArgsBuilder(&values, &values + 1, ctx);
doPrintCMArgs[TYPES_IDX] = ctx->GetStringPtr(argsBuilder.generateArgTypes());
doPrintCMArgs[ARGS_IDX] = argsBuilder.emitArgCode();
doPrintCMArgs[UNI_NUM_IDX] = LLVMInt32(argsBuilder.countNonBoolUniformArgs());
doPrintCMArgs[VAR_NUM_IDX] = LLVMInt32(argsBuilder.countNonBoolVaryingArgs());
}
return doPrintCMArgs;
}
#endif // ISPC_GENX_ENABLED
/** Given an Expr for a value to be printed, emit the code to evaluate the
expression and store the result to alloca's memory. Update the
argTypes string with the type encoding for this expression.
*/
static llvm::Value *lEmitPrintArgCode(Expr *expr, FunctionEmitContext *ctx) {
const Type *type = expr->GetType();
llvm::Type *llvmExprType = type->LLVMType(g->ctx);
llvm::Value *ptr = ctx->AllocaInst(llvmExprType, "print_arg");
llvm::Value *val = expr->GetValue(ctx);
if (!val)
return NULL;
ctx->StoreInst(val, ptr);
ptr = ctx->BitCastInst(ptr, LLVMTypes::VoidPointerType);
return ptr;
}
static bool lProcessPrintArg(Expr *expr, FunctionEmitContext *ctx, llvm::Value *argPtrArray, int offset,
std::string &argTypes) {
if (!expr)
return false;
auto exprType = lProcessPrintArgType(expr);
expr = exprType.expr;
char type = exprType.type;
if (!expr)
return false;
argTypes.push_back(type);
llvm::Value *ptr = lEmitPrintArgCode(expr, ctx);
if (!ptr)
return false;
llvm::Value *arrayPtr = ctx->AddElementOffset(argPtrArray, offset, NULL);
ctx->StoreInst(ptr, arrayPtr);
return true;
}
// prepares arguments for __do_print function
std::vector<llvm::Value *> PrintStmt::getDoPrintArgs(FunctionEmitContext *ctx) const {
std::vector<llvm::Value *> doPrintArgs(STD_NUM_IDX);
std::string argTypes;
if (values == NULL) {
llvm::Type *ptrPtrType = llvm::PointerType::get(LLVMTypes::VoidPointerType, 0);
doPrintArgs[ARGS_IDX] = llvm::Constant::getNullValue(ptrPtrType);
} else {
// Get the values passed to the print() statement evaluated and
// stored in memory so that we set up the array of pointers to them
// for the 5th __do_print() argument
ExprList *elist = llvm::dyn_cast<ExprList>(values);
int nArgs = elist ? elist->exprs.size() : 1;
// Allocate space for the array of pointers to values to be printed
llvm::Type *argPtrArrayType = llvm::ArrayType::get(LLVMTypes::VoidPointerType, nArgs);
llvm::Value *argPtrArray = ctx->AllocaInst(argPtrArrayType, "print_arg_ptrs");
// Store the array pointer as a void **, which is what __do_print()
// expects
doPrintArgs[ARGS_IDX] = ctx->BitCastInst(argPtrArray, llvm::PointerType::get(LLVMTypes::VoidPointerType, 0));
// Now, for each of the arguments, emit code to evaluate its value
// and store the value into alloca's storage. Then store the
// pointer to the alloca's storage into argPtrArray.
if (elist) {
for (unsigned int i = 0; i < elist->exprs.size(); ++i) {
Expr *expr = elist->exprs[i];
if (!lProcessPrintArg(expr, ctx, argPtrArray, i, argTypes)) {
return {};
}
}
} else {
if (lProcessPrintArg(values, ctx, argPtrArray, 0, argTypes)) {
return {};
}
}
}
llvm::Value *mask = ctx->GetFullMask();
// Set up the rest of the parameters to it
doPrintArgs[FORMAT_IDX] = ctx->GetStringPtr(format);
doPrintArgs[TYPES_IDX] = ctx->GetStringPtr(argTypes);
doPrintArgs[WIDTH_IDX] = LLVMInt32(g->target->getVectorWidth());
doPrintArgs[MASK_IDX] = ctx->LaneMask(mask);
return doPrintArgs;
}
// prepares arguments for __do_print(_cm) function depending on target
std::vector<llvm::Value *> PrintStmt::getPrintImplArgs(FunctionEmitContext *ctx) const {
std::vector<llvm::Value *> printImplArgs;
switch (g->target->getISA()) {
#ifdef ISPC_GENX_ENABLED
case Target::GENX:
printImplArgs = getDoPrintCMArgs(ctx);
break;
#endif // ISPC_GENX_ENABLED
default:
printImplArgs = getDoPrintArgs(ctx);
break;
}
return printImplArgs;
}
// Returns pointer to __do_print(_cm) function
llvm::Function *getPrintImplFunc() {
llvm::Function *printImplFunc = nullptr;
switch (g->target->getISA()) {
#ifdef ISPC_GENX_ENABLED
case Target::GENX:
printImplFunc = m->module->getFunction("__do_print_cm");
break;
#endif /* ISPC_GENX_ENABLED */
default:
printImplFunc = m->module->getFunction("__do_print");
break;
}
return printImplFunc;
}
/* PrintStmt works closely with the __do_print() function implemented in
the builtins-c.c file. In particular, the EmitCode() method here needs to
the builtins-c-cpu.cpp file. In particular, the EmitCode() method here needs to
take the arguments passed to it from ispc and generate a valid call to
__do_print() with the information that __do_print() then needs to do the
actual printing work at runtime.
@@ -3005,75 +3252,12 @@ static llvm::Value *lProcessPrintArg(Expr *expr, FunctionEmitContext *ctx, std::
void PrintStmt::EmitCode(FunctionEmitContext *ctx) const {
if (!ctx->GetCurrentBasicBlock())
return;
ctx->SetDebugPos(pos);
// __do_print takes 5 arguments; we'll get them stored in the args[] array
// in the code emitted below
//
// 1. the format string
// 2. a string encoding the types of the values being printed,
// one character per value
// 3. the number of running program instances (i.e. the target's
// vector width)
// 4. the current lane mask
// 5. a pointer to an array of pointers to the values to be printed
llvm::Value *args[5];
std::string argTypes;
if (values == NULL) {
llvm::Type *ptrPtrType = llvm::PointerType::get(LLVMTypes::VoidPointerType, 0);
args[4] = llvm::Constant::getNullValue(ptrPtrType);
} else {
// Get the values passed to the print() statement evaluated and
// stored in memory so that we set up the array of pointers to them
// for the 5th __do_print() argument
ExprList *elist = llvm::dyn_cast<ExprList>(values);
int nArgs = elist ? elist->exprs.size() : 1;
// Allocate space for the array of pointers to values to be printed
llvm::Type *argPtrArrayType = llvm::ArrayType::get(LLVMTypes::VoidPointerType, nArgs);
llvm::Value *argPtrArray = ctx->AllocaInst(argPtrArrayType, "print_arg_ptrs");
// Store the array pointer as a void **, which is what __do_print()
// expects
args[4] = ctx->BitCastInst(argPtrArray, llvm::PointerType::get(LLVMTypes::VoidPointerType, 0));
// Now, for each of the arguments, emit code to evaluate its value
// and store the value into alloca'd storage. Then store the
// pointer to the alloca'd storage into argPtrArray.
if (elist) {
for (unsigned int i = 0; i < elist->exprs.size(); ++i) {
Expr *expr = elist->exprs[i];
if (!expr)
return;
llvm::Value *ptr = lProcessPrintArg(expr, ctx, argTypes);
if (!ptr)
return;
llvm::Value *arrayPtr = ctx->AddElementOffset(argPtrArray, i, NULL);
ctx->StoreInst(ptr, arrayPtr);
}
} else {
llvm::Value *ptr = lProcessPrintArg(values, ctx, argTypes);
if (!ptr)
return;
llvm::Value *arrayPtr = ctx->AddElementOffset(argPtrArray, 0, NULL);
ctx->StoreInst(ptr, arrayPtr);
}
}
// Now we can emit code to call __do_print()
llvm::Function *printFunc = m->module->getFunction("__do_print");
AssertPos(pos, printFunc);
llvm::Value *mask = ctx->GetFullMask();
// Set up the rest of the parameters to it
args[0] = ctx->GetStringPtr(format);
args[1] = ctx->GetStringPtr(argTypes);
args[2] = LLVMInt32(g->target->getVectorWidth());
args[3] = ctx->LaneMask(mask);
std::vector<llvm::Value *> argVec(&args[0], &args[5]);
ctx->CallInst(printFunc, NULL, argVec, "");
auto printImplArgs = getPrintImplArgs(ctx);
Assert(!printImplArgs.empty() && "Haven't managed to produce __do_print(_cm) args");
auto printImplFunc = getPrintImplFunc();
AssertPos(pos, printImplFunc);
ctx->CallInst(printImplFunc, NULL, printImplArgs, "");
}
void PrintStmt::Print(int indent) const { printf("%*cPrint Stmt (%s)", indent, ' ', format.c_str()); }
@@ -3098,7 +3282,7 @@ void AssertStmt::EmitCode(FunctionEmitContext *ctx) const {
}
bool isUniform = type->IsUniformType();
// The actual functionality to do the check and then handle falure is
// The actual functionality to do the check and then handle failure is
// done via a builtin written in bitcode in builtins/util.m4.
llvm::Function *assertFunc =
isUniform ? m->module->getFunction("__do_assert_uniform") : m->module->getFunction("__do_assert_varying");
Regular → Executable
+23
View File
@@ -483,6 +483,29 @@ class PrintStmt : public Stmt {
Stmt *TypeCheck();
int EstimateCost() const;
std::vector<llvm::Value *> getDoPrintArgs(FunctionEmitContext *ctx) const;
std::vector<llvm::Value *> getPrintImplArgs(FunctionEmitContext *ctx) const;
#ifdef ISPC_GENX_ENABLED
std::vector<llvm::Value *> getDoPrintCMArgs(FunctionEmitContext *ctx) const;
#endif // ISPC_GENX_ENABLED
enum {
FORMAT_IDX, // the format string
TYPES_IDX, // a string encoding the types of the values being printed,
// one character per value
WIDTH_IDX, // the number of running program instances (i.e. the target's
// vector width)
MASK_IDX, // the current lane mask
ARGS_IDX, // a pointer to an array of pointers to the values to be printed
#ifdef ISPC_GENX_ENABLED
UNI_NUM_IDX, // number of uniform nonbool values
VAR_NUM_IDX, // number of varying nonbool values
GENX_NUM_IDX, // number of arguments of __do_print_cm
#endif // ISPC_GENX_ENABLED
// number of arguments of __do_print
STD_NUM_IDX = ARGS_IDX + 1
};
/** Format string for the print() statement. */
const std::string format;
/** This holds the arguments passed to the print() statement. If more