Skip to content

[WIP] [SYCL] Add SYCLConfig helper #859

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sycl/source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ set(SYCL_SOURCES
"detail/builtins_relational.cpp"
"detail/pi.cpp"
"detail/common.cpp"
"detail/config.cpp"
"detail/context_impl.cpp"
"detail/device_impl.cpp"
"detail/device_info.cpp"
Expand Down
117 changes: 117 additions & 0 deletions sycl/source/detail/config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//==---------------- config.cpp ---------------------------------*- C++-*---==//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include <CL/sycl/detail/os_util.hpp>
#include <detail/config.hpp>

#include <cstring>
#include <fstream>
#include <iostream>
#include <limits>

#define STRINGIFY_LINE_HELP(s) #s
#define STRINGIFY_LINE(s) STRINGIFY_LINE_HELP(s)

namespace cl {
namespace sycl {
namespace detail {

#ifndef SYCL_CONFIG_FILE_NAME
#define SYCL_CONFIG_FILE_NAME "sycl.conf"
#endif // SYCL_CONFIG_FILE_NAME

#define CONFIG(Name, MaxSize, CompileTimeDef) \
const char *SYCLConfigBase<Name>::MValueFromFile = nullptr; \
char SYCLConfigBase<Name>::MStorage[MaxSize]; \
const char *const SYCLConfigBase<Name>::MCompileTimeDef = \
getStrOrNullptr(STRINGIFY_LINE(CompileTimeDef)); \
const char *const SYCLConfigBase<Name>::MConfigName = STRINGIFY_LINE(Name);
#include "detail/config.def"
#undef CONFIG

#define MAX_CONFIG_NAME 256
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this smells a lot old C...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will replace with constexpr.


static void initValue(const char *Key, const char *Value) {
#define CONFIG(Name, MaxSize, CompileTimeDef) \
if (0 == strncmp(Key, SYCLConfigBase<Name>::MConfigName, MAX_CONFIG_NAME)) { \
strncpy(SYCLConfigBase<Name>::MStorage, Value, MaxSize); \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And what happens when you reach the limit ? A non-\0 ending string... :-(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. Will fix by guaranteeing \0 at the and of the Value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer just using safer C++ instead of lower-level C at the first place...

SYCLConfigBase<Name>::MValueFromFile = SYCLConfigBase<Name>::MStorage; \
return; \
}
#include "detail/config.def"
#undef CONFIG
}

void readConfig() {
static bool Initialized = false;
if (Initialized)
return;

std::fstream File;
if (const char *ConfigFile = getenv("SYCL_CONFIG_FILE_NAME"))
File.open(ConfigFile, std::ios::in);
else {
const std::string LibSYCLDir = sycl::detail::OSUtil::getCurrentDSODir();
File.open(LibSYCLDir + sycl::detail::OSUtil::DirSep + SYCL_CONFIG_FILE_NAME,
std::ios::in);
}

if (File.is_open()) {
// TODO: Use max size from macro instead of 256
char Key[MAX_CONFIG_NAME] = {0}, Value[256] = {0};
while (!File.eof()) {
// Expected fromat:
// ConfigName=Value\r
// ConfigName=Value
// TODO: Skip spaces before and after '='
File.getline(Key, sizeof(Key), '=');
if (File.fail()) {
// Fail to process the line. Skip it completely and try next one.
// Do we want to restore here? Or just throw an exception?
File.clear(File.rdstate() & ~std::ios_base::failbit);
File.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
continue;
}
File.getline(Value, sizeof(Value), '\n');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what happens on Windows. People tend add \r\n instead of \n.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a logic which removes \r if it exists. Cannot write proper test as we have no env vars sensitive to the additional character at the end. Did manual testing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this \n should be handled transparently by iostream, isn't it???

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, \n is the default value of 3rd argument.. Will remove


if (File.fail()) {
// Fail to process the value while config name is OK. It's likely that
// value is too long. Currently just deal what we have got and ignore
// remaining characters on the line.
// Do we want to restore here? Or just throw an exception?
File.clear(File.rdstate() & ~std::ios_base::failbit);
File.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}

// Handle '\r' by nullifying it
const std::streamsize ReadSybmols = File.gcount();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a Sybmols ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will rename to "characters".

if (ReadSybmols > 1 && '\r' == Value[ReadSybmols - 2])
Value[ReadSybmols - 2] = '\0';

initValue(Key, Value);
}
}
Initialized = true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this is super hackish and obfuscated compared to the obvious algorithm: iterating on a plain getline() and using std::find or another algorithm to tokenize around =


// Prints configs name with their value
void dumpConfig() {
#define CONFIG(Name, MaxSize, CompileTimeDef) \
const char *Val = SYCLConfig<Name>::get(); \
std::cerr << SYCLConfigBase<Name>::MConfigName << " : " \
<< (Val ? Val : "unset") << std::endl;
#include "detail/config.def"
#undef CONFIG
}

} // namespace cl
} // namespace sycl
} // namespace detail

#undef STRINGIFY_LINE_HELP
#undef STRINGIFY_LINE
14 changes: 14 additions & 0 deletions sycl/source/detail/config.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Syntax:
// CONFIG(CONFIG_NAME, VALUE_MAX_SIZE_IN_BYTES, COMPILE_TIME_CONFIG_NAME)
//
// CONFIG_NAME is used as a specifier to access specific config using
// SYCLConfig class. It is also used in order to determine a config key when
// reading the configuration file.
// VALUE_MAX_SIZE_IN_BYTES is used as a maximum size of a storage when reading
// configs from the configuration file.
// COMPILE_TIME_CONFIG_NAME is a name of compile time macro which can be used
// to set a value of a config. COMPILE_TIME_CONFIG_NAME must start with double
// underscore(__).

CONFIG(SYCL_PRINT_EXECUTION_GRAPH, 32, __SYCL_PRINT_EXECUTION_GRAPH)

103 changes: 103 additions & 0 deletions sycl/source/detail/config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//==---------------- config.hpp - SYCL context ------------------*- C++-*---==//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#pragma once

#include <cstdlib>

namespace cl {
namespace sycl {
namespace detail {

#ifdef DISABLE_CONFIG_FROM_ENV
constexpr bool ConfigFromEnvEnabled = false;
#else
constexpr bool ConfigFromEnvEnabled = true;
#endif // DISABLE_CONFIG_FROM_ENV

#ifdef DISABLE_CONFIG_FROM_CONFIG_FILE
constexpr bool ConfigFromFileEnabled = false;
#else
constexpr bool ConfigFromFileEnabled = true;
#endif // DISABLE_CONFIG_FROM_CONFIG_FILE

#ifdef DISABLE_CONFIG_FROM_COMPILE_TIME
constexpr bool ConfigFromCompileDefEnabled = false;
#else
constexpr bool ConfigFromCompileDefEnabled = true;
#endif // DISABLE_CONFIG_FROM_COMPILE_TIME

// Enum of config IDs for accessing other arrays
enum ConfigID {
START = 0,
#define CONFIG(name, ...) name,
#include "config.def"
#undef CONFIG
END
};

// Consider strings starting with __ as unset
constexpr const char *getStrOrNullptr(const char *Str) {
return (Str[0] == '_' && Str[1] == '_') ? nullptr : Str;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened if a string has less than 2 characters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add check for that.

}

template <ConfigID Config> class SYCLConfigBase;

#define CONFIG(Name, MaxSize, CompileTimeDef) \
template <> class SYCLConfigBase<Name> { \
public: \
/*Preallocated storage for config value which is extracted from a config \
* file*/ \
static char MStorage[MaxSize]; \
/*Points to the storage if config is set in the file, nullptr otherwise*/ \
static const char *MValueFromFile; \
/*The name of the config*/ \
static const char *const MConfigName; \
/*Points to the value which is set during compilation, nullptr otherwise. \
* Detection of whether a value is set or not is based on checking the \
* beginning of the string, if it starts with double underscore(__) the \
* value is not set.*/ \
static const char *const MCompileTimeDef; \
};
#include "config.def"
#undef CONFIG

// Intializes configs from the configuration file
void readConfig();

template <ConfigID Config> class SYCLConfig {
using BaseT = SYCLConfigBase<Config>;

public:
static const char *get() {
const char *ValStr = getRawValue();
return ValStr;
}

private:
static const char *getRawValue() {
if (ConfigFromEnvEnabled)
if (const char *ValStr = getenv(BaseT::MConfigName))
return ValStr;

if (ConfigFromFileEnabled) {
readConfig();
if (BaseT::MValueFromFile)
return BaseT::MValueFromFile;
}

if (ConfigFromCompileDefEnabled && BaseT::MCompileTimeDef)
return BaseT::MCompileTimeDef;

return nullptr;
}
};

} // namespace cl
} // namespace sycl
} // namespace detail
3 changes: 2 additions & 1 deletion sycl/source/detail/scheduler/graph_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include <CL/sycl/access/access.hpp>
#include "detail/config.hpp"
#include <CL/sycl/detail/event_impl.hpp>
#include <CL/sycl/detail/memory_manager.hpp>
#include <CL/sycl/detail/queue_impl.hpp>
Expand Down Expand Up @@ -46,7 +47,7 @@ static bool IsSuitableSubReq(const Requirement *Req) {
}

Scheduler::GraphBuilder::GraphBuilder() {
if (const char *EnvVarCStr = std::getenv("SYCL_PRINT_EXECUTION_GRAPH")) {
if (const char *EnvVarCStr = SYCLConfig<SYCL_PRINT_EXECUTION_GRAPH>::get()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use it for all environment variable (SYCL_DEVICE_TYPE, SYCL_DUMP_IMAGES, etc)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use it for environment variables that are "getenv'ed" inside source directory.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to mention this limitation in the commit message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will do during merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated description with new sentence.

std::string GraphPrintOpts(EnvVarCStr);
bool EnableAlways = GraphPrintOpts.find("always") != std::string::npos;

Expand Down
1 change: 1 addition & 0 deletions sycl/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ configure_lit_site_cfg(
list(APPEND SYCL_TEST_DEPS
sycl-toolchain
FileCheck
not
get_device_count_by_type
llvm-config
)
Expand Down
26 changes: 26 additions & 0 deletions sycl/test/config/config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//==---- config.cpp --------------------------------------------------------==//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// RUN: %clangxx -g -O0 -fsycl %s -o %t.out
// RUN: echo "SYCL_PRINT_EXECUTION_GRAPH=always" > %t.cfg
// RUN: env SYCL_CONFIG_FILE_NAME=%t.cfg %t.out
// RUN: ls | grep dot
// RUN: rm *.dot
// RUN: env SYCL_PRINT_EXECUTION_GRAPH=always %t.out
// RUN: ls | grep dot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that this works on Windows if you don't have GNU coreutils installed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reused this idea from clang LIT tests so I expect it works on the same configurations as clang LIT tests work.

// RUN: rm *.dot
// RUN: %t.out
// RUN: ls | not grep dot

#include <CL/sycl.hpp>

using namespace cl;

int main() {
sycl::buffer<int, 1> Buf(sycl::range<1>{1});
auto Acc = Buf.get_access<sycl::access::mode::read>();
}