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

Last change on this file since 3450 was 3450, checked in by matszpk, 2 years ago

CLRadeonExtender: Add new library: CLRXCLHelper that facilitate creating binary/program for OpenCL. Use CLRXCLHelper in samples.

File size: 15.9 KB
Line 
1/*
2 *  CLRadeonExtender - Unofficial OpenCL Radeon Extensions Library
3 *  Copyright (C) 2014-2017 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 <CLRX/amdasm/Assembler.h>
32#include <CLRX/clhelper/CLHelper.h>
33
34using namespace CLRX;
35
36CLError::CLError() : error(0)
37{ }
38
39CLError::CLError(const char* _description) : Exception(_description), error(0)
40{ }
41
42CLError::CLError(cl_int _error, const char* _description): error(_error)
43{
44    // construct message
45    char buf[20];
46    ::snprintf(buf, 20, "%d", _error);
47    message = "CLError code: ";
48    message += buf;
49    message += ", Desc: ";
50    message += _description;
51}
52
53CLError::~CLError() noexcept
54{ }
55
56const char* CLError::what() const noexcept
57{
58    // print no error if no error
59    return (!message.empty()) ? message.c_str() : "No error!";
60}
61
62// strip CString (remove spaces from first and last characters
63static char* stripCString(char* str)
64{
65    while (*str==' ') str++;
66    char* last = str+::strlen(str);
67    while (last!=str && (*last==0||*last==' '))
68        last--;
69    if (*last!=0) last[1] = 0;
70    return str;
71}
72
73/* main routines */
74
75cl_platform_id CLRX::chooseCLPlatformForCLRX()
76{
77    cl_int error = CL_SUCCESS;
78    cl_uint platformsNum;
79    std::unique_ptr<cl_platform_id[]> platforms;
80    error = clGetPlatformIDs(0, nullptr, &platformsNum);
81    if (error != CL_SUCCESS)
82        throw CLError(error, "clGetPlatformIDs");
83    platforms.reset(new cl_platform_id[platformsNum]);
84    error = clGetPlatformIDs(platformsNum, platforms.get(), nullptr);
85    if (error != CL_SUCCESS)
86        throw CLError(error, "clGetPlatformIDs");
87   
88    cl_platform_id choosenPlatform = nullptr;
89    /// find platform with AMD or GalliumCompute devices
90    for (cl_uint i = 0; i < platformsNum; i++)
91    {
92        size_t platformNameSize;
93        std::unique_ptr<char[]> platformName;
94        error = clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, 0, nullptr,
95                           &platformNameSize);
96        if (error != CL_SUCCESS)
97            throw CLError(error, "clGetPlatformInfo");
98        platformName.reset(new char[platformNameSize]);
99        error = clGetPlatformInfo(platforms[i], CL_PLATFORM_NAME, platformNameSize,
100                   platformName.get(), nullptr);
101        if (error != CL_SUCCESS)
102            throw CLError(error, "clGetPlatformInfo");
103       
104        // find correct platform (supported GalliumCompute and AMD APP)
105        const char* splatformName = stripCString(platformName.get());
106        if (::strcmp(splatformName, "AMD Accelerated Parallel Processing")==0 ||
107            ::strcmp(splatformName, "Clover")==0)
108        {
109            choosenPlatform = platforms[i];
110            break;
111        }
112    }
113   
114    if (choosenPlatform==nullptr)
115        throw Exception("PlatformNotFound");
116   
117    return choosenPlatform;
118}
119
120CLAsmSetup CLRX::assemblerSetupForCLDevice(cl_device_id clDevice, Flags flags)
121{
122    cl_int error = CL_SUCCESS;
123    cl_platform_id platform;
124    BinaryFormat binaryFormat = BinaryFormat::GALLIUM;
125    cxuint amdappVersion = 0;
126    const cxuint useCL = (flags & CLHELPER_USEAMDCL2) ? 2 :
127            (flags & CLHELPER_USEAMDLEGACY) ? 1 : 0;
128    bool defaultCL2ForDriver = false;
129   
130    // get platform from device
131    error = clGetDeviceInfo(clDevice, CL_DEVICE_PLATFORM, sizeof(cl_platform_id),
132                &platform, nullptr);
133    if (error != CL_SUCCESS)
134        throw CLError(error, "clGetDeviceInfoPlatform");
135   
136    // get platform name to check whether is suitable platform
137    size_t platformNameSize;
138    std::unique_ptr<char[]> platformName;
139    error = clGetPlatformInfo(platform, CL_PLATFORM_NAME, 0, nullptr,
140                        &platformNameSize);
141    if (error != CL_SUCCESS)
142        throw CLError(error, "clGetPlatformInfo");
143    platformName.reset(new char[platformNameSize]);
144    error = clGetPlatformInfo(platform, CL_PLATFORM_NAME, platformNameSize,
145                platformName.get(), nullptr);
146    if (error != CL_SUCCESS)
147        throw CLError(error, "clGetPlatformInfo");
148   
149    const char* splatformName = stripCString(platformName.get());
150    if (::strcmp(splatformName, "AMD Accelerated Parallel Processing")==0 ||
151        ::strcmp(splatformName, "Clover")==0)
152    {
153        cl_platform_id choosenPlatform = platform;
154        binaryFormat = ::strcmp(platformName.get(), "Clover")==0 ?
155                BinaryFormat::GALLIUM : BinaryFormat::AMD;
156       
157        if (binaryFormat == BinaryFormat::AMD)
158        {
159            // get amdappVersion
160            size_t platformVersionSize;
161            std::unique_ptr<char[]> platformVersion;
162            error = clGetPlatformInfo(choosenPlatform, CL_PLATFORM_VERSION, 0, nullptr,
163                                    &platformVersionSize);
164            if (error != CL_SUCCESS)
165                throw CLError(error, "clGetPlatformInfoVersion");
166            platformVersion.reset(new char[platformVersionSize]);
167            error = clGetPlatformInfo(choosenPlatform, CL_PLATFORM_VERSION,
168                            platformVersionSize, platformVersion.get(), nullptr);
169            if (error != CL_SUCCESS)
170                throw CLError(error, "clGetPlatformInfoVersion");
171           
172            const char* amdappPart = strstr(platformVersion.get(), "AMD-APP (");
173            if (amdappPart!=nullptr)
174            {
175                // parse AMDAPP version
176                try
177                {
178                    const char* majorVerPart = amdappPart+9;
179                    const char* minorVerPart;
180                    const char* end;
181                    cxuint majorVersion = cstrtoui(majorVerPart, nullptr,
182                                    minorVerPart);
183                   
184                    if (*minorVerPart!=0)
185                    {
186                        minorVerPart++; // skip '.'
187                        cxuint minorVersion = cstrtoui(minorVerPart, nullptr, end);
188                        amdappVersion = majorVersion*100U + minorVersion;
189                    }
190                }
191                catch(const ParseException& ex)
192                { } // ignore error
193            }
194        }
195       
196        if (binaryFormat == BinaryFormat::AMD && useCL==2)
197            binaryFormat = BinaryFormat::AMDCL2;
198        // for driver 2004.6 OpenCL 2.0 binary format is default
199        if (binaryFormat == BinaryFormat::AMD && amdappVersion >= 200406)
200            defaultCL2ForDriver = true;
201    }
202    else // if not good OpenCL platform
203        throw Exception("OpenCL platform not suitable for CLRX programs");
204   
205    // check whether is GPU device
206    cl_device_type clDevType;
207    error = clGetDeviceInfo(clDevice, CL_DEVICE_TYPE, sizeof(cl_device_type),
208                    &clDevType, nullptr);
209    if ((clDevType & CL_DEVICE_TYPE_GPU) == 0)
210        throw Exception("Device is not GPU");
211   
212    cl_uint bits = 32;
213    if (binaryFormat != BinaryFormat::GALLIUM)
214    {
215        // get address Bits from device info (for AMDAPP)
216        error = clGetDeviceInfo(clDevice, CL_DEVICE_ADDRESS_BITS, sizeof(cl_uint),
217                                 &bits, nullptr);
218        if (error != CL_SUCCESS)
219            throw CLError(error, "clGetDeviceAddressBits");
220    }
221   
222    // get device and print that
223    size_t deviceNameSize;
224    std::unique_ptr<char[]> deviceName;
225    error = clGetDeviceInfo(clDevice, CL_DEVICE_NAME, 0, nullptr, &deviceNameSize);
226    if (error != CL_SUCCESS)
227        throw CLError(error, "clGetDeviceInfoName");
228    deviceName.reset(new char[deviceNameSize]);
229    error = clGetDeviceInfo(clDevice, CL_DEVICE_NAME, deviceNameSize,
230                             deviceName.get(), nullptr);
231    if (error != CL_SUCCESS)
232        throw CLError(error, "clGetDeviceInfoName");
233   
234    // get device version - used for getting Mesa3D version and LLVM version
235    size_t deviceVersionSize;
236    std::unique_ptr<char[]> deviceVersion;
237    error = clGetDeviceInfo(clDevice, CL_DEVICE_VERSION, 0, nullptr, &deviceVersionSize);
238    if (error != CL_SUCCESS)
239        throw CLError(error, "clGetDeviceInfoVersion");
240    deviceVersion.reset(new char[deviceVersionSize]);
241    error = clGetDeviceInfo(clDevice, CL_DEVICE_VERSION, deviceVersionSize,
242                            deviceVersion.get(), nullptr);
243    if (error != CL_SUCCESS)
244        throw CLError(error, "clGetDeviceInfoVersion");
245   
246    // get bits from device name (LLVM version)
247    cxuint llvmVersion = 0;
248    cxuint mesaVersion = 0;
249   
250    if (binaryFormat == BinaryFormat::GALLIUM)
251    {
252        const char* llvmPart = strstr(deviceName.get(), "LLVM ");
253        if (llvmPart!=nullptr)
254        {
255            try
256            {
257                // parse LLVM version
258                const char* majorVerPart = llvmPart+5;
259                const char* minorVerPart;
260                const char* end;
261                cxuint majorVersion = cstrtoui(majorVerPart, nullptr, minorVerPart);
262                if (*minorVerPart!=0)
263                {
264                    minorVerPart++; // skip '.'
265                    cxuint minorVersion = cstrtoui(minorVerPart, nullptr, end);
266                    llvmVersion = majorVersion*10000U + minorVersion*100U;
267#if HAVE_64BIT
268                    if (majorVersion*10000U + minorVersion*100U >= 30900U)
269                        bits = 64; // use 64-bit
270#endif
271                }
272            }
273            catch(const ParseException& ex)
274            { } // ignore error
275        }
276       
277        const char* mesaPart = strstr(deviceVersion.get(), "Mesa ");
278        if (mesaPart==nullptr)
279            mesaPart = strstr(deviceVersion.get(), "MESA ");
280        if (mesaPart!=nullptr)
281        {
282            try
283            {
284                // parse Mesa3D version
285                const char* majorVerPart = mesaPart+5;
286                const char* minorVerPart;
287                const char* end;
288                cxuint majorVersion = cstrtoui(majorVerPart, nullptr, minorVerPart);
289                if (*minorVerPart!=0)
290                {
291                    minorVerPart++; // skip '.'
292                    cxuint minorVersion = cstrtoui(minorVerPart, nullptr, end);
293                    mesaVersion = majorVersion*10000U + minorVersion*100U;
294                }
295            }
296            catch(const ParseException& ex)
297            { } // ignore error
298        }
299    }
300   
301    /// determine device type
302    char* sdeviceName = stripCString(deviceName.get());
303    char* devNamePtr = sdeviceName;
304    if (binaryFormat==BinaryFormat::GALLIUM)
305    {
306        char* sptr = ::strstr(sdeviceName, "(AMD ");
307        // if form 'AMD Radeon xxx (AMD CODENAME /...)
308        if (sptr != nullptr) // if found 'AMD ';
309            devNamePtr = sptr+5;
310        else
311        {
312            // if form 'AMD CODENAME (....
313            sptr = ::strstr(sdeviceName, "AMD ");
314            if (sptr != nullptr) // if found 'AMD ';
315                devNamePtr = sptr+4;
316        }
317    }
318    char* devNameEnd = devNamePtr;
319    while (isAlnum(*devNameEnd)) devNameEnd++;
320    *devNameEnd = 0; // finish at first word
321    const GPUDeviceType devType = getGPUDeviceTypeFromName(devNamePtr);
322    /* change binary format to AMDCL2 if default for this driver version and
323     * architecture >= GCN 1.1 */
324    bool useLegacy = false;
325    if (defaultCL2ForDriver &&
326        getGPUArchitectureFromDeviceType(devType) >= GPUArchitecture::GCN1_1)
327    {
328        if (useCL!=1) // if not cl1/old
329            binaryFormat = BinaryFormat::AMDCL2;
330        else // use legacy
331            useLegacy = true;
332    }
333   
334    // create OpenCL CLRX assembler setup
335    CLAsmSetup asmSetup { };
336    asmSetup.deviceType = devType;
337    asmSetup.is64Bit = bits==64;
338    asmSetup.binaryFormat = binaryFormat;
339    // setting version (LLVM and driverVersion)
340    if (binaryFormat == BinaryFormat::GALLIUM && llvmVersion != 0)
341        asmSetup.llvmVersion = llvmVersion;
342    if (binaryFormat == BinaryFormat::GALLIUM && mesaVersion != 0)
343        asmSetup.driverVersion = mesaVersion;
344    else if ((binaryFormat == BinaryFormat::AMD ||
345            binaryFormat == BinaryFormat::AMDCL2) && amdappVersion != 0)
346        asmSetup.driverVersion = amdappVersion;
347    // base OpenCL options for program
348    asmSetup.options = (binaryFormat==BinaryFormat::AMDCL2) ? "-cl-std=CL2.0" :
349               (useLegacy ? "-legacy" : "");
350    return asmSetup;
351}
352
353Array<cxbyte> CLRX::createBinaryForOpenCL(const CLAsmSetup& asmSetup,
354                const char* sourceCode, size_t sourceCodeLen)
355{
356    /// determine device type
357    const size_t scodeLen = (sourceCodeLen == 0) ? ::strlen(sourceCode) : sourceCodeLen;
358    ArrayIStream astream(scodeLen, sourceCode);
359    // by default assembler put logs to stderr
360    Assembler assembler("", astream, 0, asmSetup.binaryFormat, asmSetup.deviceType);
361    assembler.set64Bit(asmSetup.is64Bit);
362    // setting version (LLVM and driverVersion)
363    const BinaryFormat binaryFormat = asmSetup.binaryFormat;
364    if (binaryFormat == BinaryFormat::GALLIUM && asmSetup.llvmVersion != 0)
365        assembler.setLLVMVersion(asmSetup.llvmVersion);
366    if (asmSetup.driverVersion != 0)
367        assembler.setDriverVersion(asmSetup.driverVersion);
368    assembler.assemble();
369    // write binary
370    Array<cxbyte> binary;
371    assembler.writeBinary(binary);
372    return binary;
373}
374
375cl_program CLRX::createProgramForCLDevice(cl_context clContext, cl_device_id clDevice,
376            const CLAsmSetup& asmSetup, const Array<cxbyte>& binary,
377            CString* buildLog)
378{
379    cl_program clProgram;
380    cl_int error = CL_SUCCESS;
381   
382    size_t binarySize = binary.size();
383    const cxbyte* binaryContent = binary.data();
384    clProgram = clCreateProgramWithBinary(clContext, 1, &clDevice, &binarySize,
385                        &binaryContent, nullptr, &error);
386    if (clProgram==nullptr)
387        throw CLError(error, "clCreateProgramWithBinary");
388    // build program
389    error = clBuildProgram(clProgram, 1, &clDevice, asmSetup.options.c_str(),
390               nullptr, nullptr);
391    if (error != CL_SUCCESS)
392    {
393        /* get build logs */
394        if (buildLog != nullptr)
395        {
396            buildLog->clear();
397            size_t buildLogSize;
398            std::unique_ptr<char[]> tempBuildLog;
399            cl_int lerror = clGetProgramBuildInfo(clProgram, clDevice, CL_PROGRAM_BUILD_LOG,
400                            0, nullptr, &buildLogSize);
401            if (lerror == CL_SUCCESS)
402            {
403                tempBuildLog.reset(new char[buildLogSize]);
404                lerror = clGetProgramBuildInfo(clProgram, clDevice, CL_PROGRAM_BUILD_LOG,
405                            buildLogSize, tempBuildLog.get(), nullptr);
406                if (lerror == CL_SUCCESS) // print build log
407                    buildLog->assign(tempBuildLog.get());
408            }
409        }
410        throw CLError(error, "clBuildProgram");
411    }
412   
413    return clProgram;
414}
415
416// simple helper for this two stage of building
417cl_program CLRX::createProgramForCLDevice(cl_context clContext, cl_device_id clDevice,
418            const CLAsmSetup& asmSetup, const char* sourceCode, size_t sourceCodeLen,
419            CString* buildLogs)
420{
421    const Array<cxbyte>& binary = createBinaryForOpenCL(asmSetup,
422                            sourceCode, sourceCodeLen);
423    return createProgramForCLDevice(clContext, clDevice, asmSetup, binary, buildLogs);
424}
Note: See TracBrowser for help on using the repository browser.