source: CLRX/CLRadeonExtender/trunk/clhelper/CLHelper.cpp @ 4654

Last change on this file since 4654 was 4654, checked in by matszpk, 19 months ago

CLRadeonExtender: Do not change binary format if has already been set for ROCm platform.

File size: 19.6 KB
Line 
1/*
2 *  CLRadeonExtender - Unofficial OpenCL Radeon Extensions Library
3 *  Copyright (C) 2014-2018 Mateusz Szpakowski
4 *
5 *  This library is free software; you can redistribute it and/or
6 *  modify it under the terms of the GNU Lesser General Public
7 *  License as published by the Free Software Foundation; either
8 *  version 2.1 of the License, or (at your option) any later version.
9 *
10 *  This library is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 *  Lesser General Public License for more details.
14 *
15 *  You should have received a copy of the GNU Lesser General Public
16 *  License along with this library; if not, write to the Free Software
17 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19
20#define CL_USE_DEPRECATED_OPENCL_1_2_APIS
21#define CL_USE_DEPRECATED_OPENCL_2_0_APIS
22
23#include <CLRX/Config.h>
24#include <algorithm>
25#include <iostream>
26#include <fstream>
27#include <vector>
28#include <cstdio>
29#include <cstring>
30#include <memory>
31#include <utility>
32#include <CLRX/amdasm/Assembler.h>
33#include <CLRX/clhelper/CLHelper.h>
34
35using namespace CLRX;
36
37CLError::CLError() : error(0)
38{ }
39
40CLError::CLError(const char* _description) : Exception(_description), error(0)
41{ }
42
43CLError::CLError(cl_int _error, const char* _description): error(_error)
44{
45    // construct message
46    char buf[20];
47    ::snprintf(buf, 20, "%d", _error);
48    message = "CLError code: ";
49    message += buf;
50    message += ", Desc: ";
51    message += _description;
52}
53
54CLError::~CLError() noexcept
55{ }
56
57const char* CLError::what() const noexcept
58{
59    // print no error if no error
60    return (!message.empty()) ? message.c_str() : "No error!";
61}
62
63// strip CString (remove spaces from first and last characters
64static char* stripCString(char* str)
65{
66    while (*str==' ') str++;
67    char* last = str+::strlen(str);
68    while (last!=str && (*last==0||*last==' '))
69        last--;
70    if (*last!=0) last[1] = 0;
71    return str;
72}
73
74/* main routines */
75
76static std::vector<cl_platform_id> chooseCLPlatformsForCLRXInt(bool single = true)
77{
78    cl_int error = CL_SUCCESS;
79    cl_uint platformsNum;
80    std::unique_ptr<cl_platform_id[]> platforms;
81    error = clGetPlatformIDs(0, nullptr, &platformsNum);
82    if (error != CL_SUCCESS)
83        throw CLError(error, "clGetPlatformIDs");
84    platforms.reset(new cl_platform_id[platformsNum]);
85    error = clGetPlatformIDs(platformsNum, platforms.get(), nullptr);
86    if (error != CL_SUCCESS)
87        throw CLError(error, "clGetPlatformIDs");
88   
89    std::vector<cl_platform_id> choosenPlatforms;
90    /// find platform with AMD or GalliumCompute devices
91    for (cl_uint i = 0; i < platformsNum; i++)
92    {
93        size_t platformNameSize;
94        std::unique_ptr<char[]> platformName;
95        error = clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, 0, nullptr,
96                           &platformNameSize);
97        if (error != CL_SUCCESS)
98            throw CLError(error, "clGetPlatformInfo");
99        platformName.reset(new char[platformNameSize]);
100        error = clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, platformNameSize,
101                   platformName.get(), nullptr);
102        if (error != CL_SUCCESS)
103            throw CLError(error, "clGetPlatformInfo");
104       
105        // find correct platform (supported GalliumCompute and AMD APP)
106        const char* splatformName = stripCString(platformName.get());
107        if (::strcmp(splatformName, "AMD Accelerated Parallel Processing")==0 ||
108            ::strcmp(splatformName, "Clover")==0)
109        {
110            choosenPlatforms.push_back(platforms[i]);
111            if (single)
112                break;
113        }
114    }
115   
116    if (choosenPlatforms.empty())
117        throw Exception("PlatformNotFound");
118   
119    return choosenPlatforms;
120}
121
122cl_platform_id CLRX::chooseCLPlatformForCLRX()
123{
124    return chooseCLPlatformsForCLRXInt()[0];
125}
126
127std::vector<cl_platform_id> CLRX::chooseCLPlatformsForCLRX()
128{
129    return chooseCLPlatformsForCLRXInt(false);
130}
131
132CLAsmSetup CLRX::assemblerSetupForCLDevice(cl_device_id clDevice, Flags flags,
133            Flags asmFlags)
134{
135    cl_int error = CL_SUCCESS;
136    cl_platform_id platform;
137    BinaryFormat binaryFormat = BinaryFormat::GALLIUM;
138    cxuint amdappVersion = 0;
139    const cxuint useCL = (flags & CLHELPER_USEAMDCL2) ? 2 :
140            (flags & CLHELPER_USEAMDLEGACY) ? 1 : 0;
141    bool defaultCL2ForDriver = false;
142   
143    // get platform from device
144    error = clGetDeviceInfo(clDevice, CL_DEVICE_PLATFORM, sizeof(cl_platform_id),
145                &platform, nullptr);
146    if (error != CL_SUCCESS)
147        throw CLError(error, "clGetDeviceInfoPlatform");
148   
149    // get platform name to check whether is suitable platform
150    size_t platformNameSize;
151    std::unique_ptr<char[]> platformName;
152    error = clGetPlatformInfo(platform, CL_PLATFORM_NAME, 0, nullptr,
153                        &platformNameSize);
154    if (error != CL_SUCCESS)
155        throw CLError(error, "clGetPlatformInfo");
156    platformName.reset(new char[platformNameSize]);
157    error = clGetPlatformInfo(platform, CL_PLATFORM_NAME, platformNameSize,
158                platformName.get(), nullptr);
159    if (error != CL_SUCCESS)
160        throw CLError(error, "clGetPlatformInfo");
161   
162    const char* splatformName = stripCString(platformName.get());
163    if (::strcmp(splatformName, "AMD Accelerated Parallel Processing")==0 ||
164        ::strcmp(splatformName, "Clover")==0)
165    {
166        cl_platform_id choosenPlatform = platform;
167        binaryFormat = ::strcmp(platformName.get(), "Clover")==0 ?
168                BinaryFormat::GALLIUM : BinaryFormat::AMD;
169       
170        if (binaryFormat == BinaryFormat::AMD)
171        {
172            // get amdappVersion
173            size_t platformVersionSize;
174            std::unique_ptr<char[]> platformVersion;
175            error = clGetPlatformInfo(choosenPlatform, CL_PLATFORM_VERSION, 0, nullptr,
176                                    &platformVersionSize);
177            if (error != CL_SUCCESS)
178                throw CLError(error, "clGetPlatformInfoVersion");
179            platformVersion.reset(new char[platformVersionSize]);
180            error = clGetPlatformInfo(choosenPlatform, CL_PLATFORM_VERSION,
181                            platformVersionSize, platformVersion.get(), nullptr);
182            if (error != CL_SUCCESS)
183                throw CLError(error, "clGetPlatformInfoVersion");
184           
185            const char* amdappPart = strstr(platformVersion.get(), " (");
186            if (amdappPart!=nullptr)
187            {
188                // parse AMDAPP version
189                try
190                {
191                    const char* majorVerPart = amdappPart+2;
192                    const char* minorVerPart;
193                    const char* end;
194                    cxuint majorVersion = cstrtoui(majorVerPart, nullptr,
195                                    minorVerPart);
196                   
197                    if (*minorVerPart!=0)
198                    {
199                        minorVerPart++; // skip '.'
200                        cxuint minorVersion = cstrtoui(minorVerPart, nullptr, end);
201                        amdappVersion = majorVersion*100U + minorVersion;
202                    }
203                }
204                catch(const ParseException& ex)
205                { } // ignore error
206            }
207        }
208       
209        if (binaryFormat == BinaryFormat::AMD && useCL==2)
210            binaryFormat = BinaryFormat::AMDCL2;
211        // for driver 2004.6 OpenCL 2.0 binary format is default
212        if (binaryFormat == BinaryFormat::AMD && amdappVersion >= 200406)
213            defaultCL2ForDriver = true;
214    }
215    else // if not good OpenCL platform
216        throw Exception("OpenCL platform not suitable for CLRX programs");
217   
218    // check whether is GPU device
219    cl_device_type clDevType;
220    error = clGetDeviceInfo(clDevice, CL_DEVICE_TYPE, sizeof(cl_device_type),
221                    &clDevType, nullptr);
222    if ((clDevType & CL_DEVICE_TYPE_GPU) == 0)
223        throw Exception("Device is not GPU");
224   
225    cl_uint bits = 32;
226    if (binaryFormat != BinaryFormat::GALLIUM)
227    {
228        // get address Bits from device info (for AMDAPP)
229        error = clGetDeviceInfo(clDevice, CL_DEVICE_ADDRESS_BITS, sizeof(cl_uint),
230                                 &bits, nullptr);
231        if (error != CL_SUCCESS)
232            throw CLError(error, "clGetDeviceAddressBits");
233    }
234   
235    // get device and print that
236    size_t deviceNameSize;
237    std::unique_ptr<char[]> deviceName;
238    error = clGetDeviceInfo(clDevice, CL_DEVICE_NAME, 0, nullptr, &deviceNameSize);
239    if (error != CL_SUCCESS)
240        throw CLError(error, "clGetDeviceInfoName");
241    deviceName.reset(new char[deviceNameSize]);
242    error = clGetDeviceInfo(clDevice, CL_DEVICE_NAME, deviceNameSize,
243                             deviceName.get(), nullptr);
244    if (error != CL_SUCCESS)
245        throw CLError(error, "clGetDeviceInfoName");
246   
247    // get bits from device name (LLVM version)
248    cxuint llvmVersion = 0;
249    cxuint mesaVersion = 0;
250   
251    if (binaryFormat == BinaryFormat::GALLIUM)
252    {
253        const char* llvmPart = strstr(deviceName.get(), "LLVM ");
254        if (llvmPart!=nullptr)
255        {
256            try
257            {
258                // parse LLVM version
259                const char* majorVerPart = llvmPart+5;
260                const char* minorVerPart;
261                const char* end;
262                cxuint majorVersion = cstrtoui(majorVerPart, nullptr, minorVerPart);
263                if (*minorVerPart!=0)
264                {
265                    minorVerPart++; // skip '.'
266                    cxuint minorVersion = cstrtoui(minorVerPart, nullptr, end);
267                    llvmVersion = majorVersion*10000U + minorVersion*100U;
268#if HAVE_64BIT
269                    if (majorVersion*10000U + minorVersion*100U >= 30900U)
270                        bits = 64; // use 64-bit
271#endif
272                }
273            }
274            catch(const ParseException& ex)
275            { } // ignore error
276        }
277       
278        // get device version - used for getting Mesa3D version and LLVM version
279        size_t deviceVersionSize;
280        std::unique_ptr<char[]> deviceVersion;
281        error = clGetDeviceInfo(clDevice, CL_DEVICE_VERSION, 0, nullptr,
282                                &deviceVersionSize);
283        if (error != CL_SUCCESS)
284            throw CLError(error, "clGetDeviceInfoVersion");
285        deviceVersion.reset(new char[deviceVersionSize]);
286        error = clGetDeviceInfo(clDevice, CL_DEVICE_VERSION, deviceVersionSize,
287                                deviceVersion.get(), nullptr);
288        if (error != CL_SUCCESS)
289            throw CLError(error, "clGetDeviceInfoVersion");
290       
291        const char* mesaPart = strstr(deviceVersion.get(), "Mesa ");
292        if (mesaPart==nullptr)
293            mesaPart = strstr(deviceVersion.get(), "MESA ");
294        if (mesaPart!=nullptr)
295        {
296            try
297            {
298                // parse Mesa3D version
299                const char* majorVerPart = mesaPart+5;
300                const char* minorVerPart;
301                const char* end;
302                cxuint majorVersion = cstrtoui(majorVerPart, nullptr, minorVerPart);
303                if (*minorVerPart!=0)
304                {
305                    minorVerPart++; // skip '.'
306                    cxuint minorVersion = cstrtoui(minorVerPart, nullptr, end);
307                    mesaVersion = majorVersion*10000U + minorVersion*100U;
308                }
309            }
310            catch(const ParseException& ex)
311            { } // ignore error
312        }
313    }
314    else
315    {
316        // check whether is ROCm-OpenCL
317        // get driver version - used for getting ROCm-OpenCL info
318        size_t driverVersionSize;
319        std::unique_ptr<char[]> driverVersion;
320        error = clGetDeviceInfo(clDevice, CL_DRIVER_VERSION, 0, nullptr,
321                        &driverVersionSize);
322        if (error != CL_SUCCESS)
323            throw CLError(error, "clGetDriverInfoVersion");
324        driverVersion.reset(new char[driverVersionSize]);
325        error = clGetDeviceInfo(clDevice, CL_DRIVER_VERSION, driverVersionSize,
326                                driverVersion.get(), nullptr);
327        if (error != CL_SUCCESS)
328            throw CLError(error, "clGetDriverInfoVersion");
329       
330        // parse ROCm-OpenCL (HSAXXX,LC)
331        const char* hsaStr = strstr(driverVersion.get(), "(HSA");
332        if (hsaStr != nullptr)
333        {
334            // now we check LC
335            const char* lcStr = strstr(hsaStr+4, ",LC)");
336            if (lcStr != nullptr)
337                // we have ROCm binary format
338                binaryFormat = BinaryFormat::ROCM;
339        }
340    }
341   
342    /// determine device type
343    char* sdeviceName = stripCString(deviceName.get());
344    char* devNamePtr = sdeviceName;
345    if (binaryFormat==BinaryFormat::GALLIUM)
346    {
347        char* sptr = ::strstr(sdeviceName, "(AMD ");
348        // if form 'AMD Radeon xxx (AMD CODENAME /...)
349        if (sptr != nullptr) // if found 'AMD ';
350            devNamePtr = sptr+5;
351        else
352        {
353            // if form 'AMD CODENAME (....
354            sptr = ::strstr(sdeviceName, "AMD ");
355            if (sptr != nullptr) // if found 'AMD ';
356                devNamePtr = sptr+4;
357        }
358    }
359    char* devNameEnd = devNamePtr;
360    while (isAlnum(*devNameEnd)) devNameEnd++;
361    char oldChar = *devNameEnd; // for revert changes
362    *devNameEnd = 0; // finish at first word
363    GPUDeviceType devType = GPUDeviceType::CAPE_VERDE;
364    try
365    { devType = getGPUDeviceTypeFromName(devNamePtr); }
366    catch(const GPUIdException& ex)
367    {
368        // yet another fallback for gallium device name
369        if (binaryFormat==BinaryFormat::GALLIUM)
370        {
371            char* sptr = sdeviceName;
372            while (true)
373            {
374                *devNameEnd = oldChar;
375                sptr = ::strchr(sptr, '(');
376                if (sptr == nullptr)
377                    throw; // nothing found
378                devNamePtr = sptr+1;
379                // try again
380                devNameEnd = devNamePtr;
381                while (isAlnum(*devNameEnd)) devNameEnd++;
382                oldChar = *devNameEnd;
383                *devNameEnd = 0; // finish at first word
384                try
385                {
386                    devType = getGPUDeviceTypeFromName(devNamePtr);
387                    break; // if found
388                }
389                catch(const GPUIdException& ex2)
390                { sptr++; /* not found skip this '(' */ }
391            }
392        }
393        else
394            throw;
395    }
396    /* change binary format to AMDCL2 if default for this driver version and
397     * architecture >= GCN 1.1 */
398    bool useLegacy = false;
399    if (defaultCL2ForDriver && binaryFormat != BinaryFormat::ROCM &&
400        getGPUArchitectureFromDeviceType(devType) >= GPUArchitecture::GCN1_1)
401    {
402        if (useCL!=1) // if not cl1/old
403            binaryFormat = BinaryFormat::AMDCL2;
404        else // use legacy
405            useLegacy = true;
406    }
407   
408    // create OpenCL CLRX assembler setup
409    CLAsmSetup asmSetup { };
410    asmSetup.deviceType = devType;
411    asmSetup.is64Bit = bits==64;
412    asmSetup.binaryFormat = binaryFormat;
413    // setting version (LLVM and driverVersion)
414    if (binaryFormat == BinaryFormat::GALLIUM && llvmVersion != 0)
415        asmSetup.llvmVersion = llvmVersion;
416    if (binaryFormat == BinaryFormat::GALLIUM && mesaVersion != 0)
417        asmSetup.driverVersion = mesaVersion;
418    else if ((binaryFormat == BinaryFormat::AMD || binaryFormat == BinaryFormat::ROCM ||
419            binaryFormat == BinaryFormat::AMDCL2) && amdappVersion != 0)
420        asmSetup.driverVersion = amdappVersion;
421    // base OpenCL options for program
422    asmSetup.options = (binaryFormat==BinaryFormat::AMDCL2) ? "-cl-std=CL2.0" :
423               (useLegacy ? "-legacy" : "");
424    asmSetup.asmFlags = asmFlags;
425    if (binaryFormat == BinaryFormat::ROCM)
426        asmSetup.newROCmBinFormat = true;
427    return asmSetup;
428}
429
430Array<cxbyte> CLRX::createBinaryForOpenCL(const CLAsmSetup& asmSetup,
431                const char* sourceCode, size_t sourceCodeLen)
432{
433    return createBinaryForOpenCL(asmSetup, {}, sourceCode, sourceCodeLen);
434}
435
436Array<cxbyte> CLRX::createBinaryForOpenCL(const CLAsmSetup& asmSetup,
437                const std::vector<std::pair<CString, uint64_t> >& defSymbols,
438                const char* sourceCode, size_t sourceCodeLen)
439{
440    /// determine device type
441    const size_t scodeLen = (sourceCodeLen == 0) ? ::strlen(sourceCode) : sourceCodeLen;
442    ArrayIStream astream(scodeLen, sourceCode);
443    // by default assembler put logs to stderr
444    Assembler assembler("", astream, asmSetup.asmFlags, asmSetup.binaryFormat,
445                        asmSetup.deviceType);
446    assembler.set64Bit(asmSetup.is64Bit);
447    // setting version (LLVM and driverVersion)
448    const BinaryFormat binaryFormat = asmSetup.binaryFormat;
449    if (binaryFormat == BinaryFormat::GALLIUM && asmSetup.llvmVersion != 0)
450        assembler.setLLVMVersion(asmSetup.llvmVersion);
451    if (asmSetup.driverVersion != 0)
452        assembler.setDriverVersion(asmSetup.driverVersion);
453    assembler.setNewROCmBinFormat(asmSetup.newROCmBinFormat);
454    // initial def symbols
455    for (const auto& symbol: defSymbols)
456        assembler.addInitialDefSym(symbol.first, symbol.second);
457   
458    assembler.assemble();
459    // write binary
460    Array<cxbyte> binary;
461    assembler.writeBinary(binary);
462    return binary;
463}
464
465
466cl_program CLRX::createProgramForCLDevice(cl_context clContext, cl_device_id clDevice,
467            const CLAsmSetup& asmSetup, const Array<cxbyte>& binary,
468            CString* buildLog)
469{
470    cl_program clProgram;
471    cl_int error = CL_SUCCESS;
472   
473    size_t binarySize = binary.size();
474    const cxbyte* binaryContent = binary.data();
475    clProgram = clCreateProgramWithBinary(clContext, 1, &clDevice, &binarySize,
476                        &binaryContent, nullptr, &error);
477    if (clProgram==nullptr)
478        throw CLError(error, "clCreateProgramWithBinary");
479    // build program
480    error = clBuildProgram(clProgram, 1, &clDevice, asmSetup.options.c_str(),
481               nullptr, nullptr);
482    if (error != CL_SUCCESS)
483    {
484        /* get build logs */
485        if (buildLog != nullptr)
486        {
487            buildLog->clear();
488            size_t buildLogSize;
489            std::unique_ptr<char[]> tempBuildLog;
490            cl_int lerror = clGetProgramBuildInfo(clProgram, clDevice, CL_PROGRAM_BUILD_LOG,
491                            0, nullptr, &buildLogSize);
492            if (lerror == CL_SUCCESS)
493            {
494                tempBuildLog.reset(new char[buildLogSize]);
495                lerror = clGetProgramBuildInfo(clProgram, clDevice, CL_PROGRAM_BUILD_LOG,
496                            buildLogSize, tempBuildLog.get(), nullptr);
497                if (lerror == CL_SUCCESS) // print build log
498                    buildLog->assign(tempBuildLog.get());
499            }
500        }
501        throw CLError(error, "clBuildProgram");
502    }
503   
504    return clProgram;
505}
506
507// simple helper for this two stage of building
508cl_program CLRX::createProgramForCLDevice(cl_context clContext, cl_device_id clDevice,
509            const CLAsmSetup& asmSetup, const char* sourceCode, size_t sourceCodeLen,
510            CString* buildLog)
511{
512    const Array<cxbyte>& binary = createBinaryForOpenCL(asmSetup,
513                            sourceCode, sourceCodeLen);
514    return createProgramForCLDevice(clContext, clDevice, asmSetup, binary, buildLog);
515}
516
517cl_program CLRX::createProgramForCLDevice(cl_context clContext, cl_device_id clDevice,
518            const CLAsmSetup& asmSetup,
519            const std::vector<std::pair<CString, uint64_t> >& defSymbols,
520            const char* sourceCode, size_t sourceCodeLen, CString* buildLog)
521{
522    const Array<cxbyte>& binary = createBinaryForOpenCL(asmSetup, defSymbols,
523                            sourceCode, sourceCodeLen);
524    return createProgramForCLDevice(clContext, clDevice, asmSetup, binary, buildLog);
525}
Note: See TracBrowser for help on using the repository browser.