clover/llvm: Split shared codegen support code into separate file.
This is the common part of the code used to generate a clover::module from LLVM bitcode, shared between the native and LLVM paths. Reviewed-by: Serge Martin <edb+mesa@sigluy.net> Tested-by: Jan Vesely <jan.vesely@rutgers.edu>
This commit is contained in:
@@ -54,6 +54,8 @@ CPP_SOURCES := \
|
|||||||
util/tuple.hpp
|
util/tuple.hpp
|
||||||
|
|
||||||
LLVM_SOURCES := \
|
LLVM_SOURCES := \
|
||||||
|
llvm/codegen/common.cpp \
|
||||||
|
llvm/codegen.hpp \
|
||||||
llvm/compat.hpp \
|
llvm/compat.hpp \
|
||||||
llvm/invocation.cpp \
|
llvm/invocation.cpp \
|
||||||
llvm/metadata.hpp \
|
llvm/metadata.hpp \
|
||||||
|
48
src/gallium/state_trackers/clover/llvm/codegen.hpp
Normal file
48
src/gallium/state_trackers/clover/llvm/codegen.hpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2016 Francisco Jerez
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the "Software"),
|
||||||
|
// to deal in the Software without restriction, including without limitation
|
||||||
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
// and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
// Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
///
|
||||||
|
/// \file
|
||||||
|
/// Tools to generate various forms of binary code from existing LLVM IR in
|
||||||
|
/// the given llvm::Module object and output the result as a clover::module.
|
||||||
|
///
|
||||||
|
|
||||||
|
#ifndef CLOVER_LLVM_CODEGEN_HPP
|
||||||
|
#define CLOVER_LLVM_CODEGEN_HPP
|
||||||
|
|
||||||
|
#include "core/module.hpp"
|
||||||
|
|
||||||
|
#include <llvm/IR/Module.h>
|
||||||
|
|
||||||
|
#include <clang/Frontend/CompilerInstance.h>
|
||||||
|
|
||||||
|
namespace clover {
|
||||||
|
namespace llvm {
|
||||||
|
module
|
||||||
|
build_module_common(const ::llvm::Module &mod,
|
||||||
|
const std::vector<char> &code,
|
||||||
|
const std::map<std::string, unsigned> &offsets,
|
||||||
|
const clang::CompilerInstance &c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
210
src/gallium/state_trackers/clover/llvm/codegen/common.cpp
Normal file
210
src/gallium/state_trackers/clover/llvm/codegen/common.cpp
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2012-2016 Francisco Jerez
|
||||||
|
// Copyright 2012-2016 Advanced Micro Devices, Inc.
|
||||||
|
// Copyright 2015 Zoltan Gilian
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the "Software"),
|
||||||
|
// to deal in the Software without restriction, including without limitation
|
||||||
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
// and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
// Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
///
|
||||||
|
/// \file
|
||||||
|
/// Codegen back-end-independent part of the construction of an executable
|
||||||
|
/// clover::module, including kernel argument metadata extraction and
|
||||||
|
/// formatting of the pre-generated binary code in a form that can be
|
||||||
|
/// understood by pipe drivers.
|
||||||
|
///
|
||||||
|
|
||||||
|
#include "llvm/codegen.hpp"
|
||||||
|
#include "llvm/metadata.hpp"
|
||||||
|
|
||||||
|
#include "CL/cl.h"
|
||||||
|
|
||||||
|
#include "pipe/p_state.h"
|
||||||
|
#include "util/u_math.h"
|
||||||
|
|
||||||
|
#include <clang/Basic/TargetInfo.h>
|
||||||
|
|
||||||
|
using namespace clover;
|
||||||
|
using namespace clover::llvm;
|
||||||
|
|
||||||
|
using ::llvm::Module;
|
||||||
|
using ::llvm::Function;
|
||||||
|
using ::llvm::Type;
|
||||||
|
using ::llvm::isa;
|
||||||
|
using ::llvm::cast;
|
||||||
|
using ::llvm::dyn_cast;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
enum module::argument::type
|
||||||
|
get_image_type(const std::string &type,
|
||||||
|
const std::string &qual) {
|
||||||
|
if (type == "image2d_t" && qual == "read_only")
|
||||||
|
return module::argument::image2d_rd;
|
||||||
|
else if (type == "image2d_t" && qual == "write_only")
|
||||||
|
return module::argument::image2d_wr;
|
||||||
|
else if (type == "image3d_t" && qual == "read_only")
|
||||||
|
return module::argument::image3d_rd;
|
||||||
|
else if (type == "image3d_t" && qual == "write_only")
|
||||||
|
return module::argument::image3d_wr;
|
||||||
|
else
|
||||||
|
unreachable("Unknown image type");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<module::argument>
|
||||||
|
make_kernel_args(const Module &mod, const std::string &kernel_name,
|
||||||
|
const clang::CompilerInstance &c) {
|
||||||
|
std::vector<module::argument> args;
|
||||||
|
const auto address_spaces = c.getTarget().getAddressSpaceMap();
|
||||||
|
const Function &f = *mod.getFunction(kernel_name);
|
||||||
|
::llvm::DataLayout dl(&mod);
|
||||||
|
const auto size_type =
|
||||||
|
dl.getSmallestLegalIntType(mod.getContext(), sizeof(cl_uint) * 8);
|
||||||
|
|
||||||
|
for (const auto &arg : f.args()) {
|
||||||
|
const auto arg_type = arg.getType();
|
||||||
|
|
||||||
|
// OpenCL 1.2 specification, Ch. 6.1.5: "A built-in data
|
||||||
|
// type that is not a power of two bytes in size must be
|
||||||
|
// aligned to the next larger power of two". We need this
|
||||||
|
// alignment for three element vectors, which have
|
||||||
|
// non-power-of-2 store size.
|
||||||
|
const unsigned arg_store_size = dl.getTypeStoreSize(arg_type);
|
||||||
|
const unsigned arg_api_size = util_next_power_of_two(arg_store_size);
|
||||||
|
|
||||||
|
const auto target_type = !arg_type->isIntegerTy() ? arg_type :
|
||||||
|
dl.getSmallestLegalIntType(mod.getContext(), arg_store_size * 8);
|
||||||
|
const unsigned target_size = dl.getTypeStoreSize(target_type);
|
||||||
|
const unsigned target_align = dl.getABITypeAlignment(target_type);
|
||||||
|
|
||||||
|
const auto type_name = get_argument_metadata(f, arg,
|
||||||
|
"kernel_arg_type");
|
||||||
|
|
||||||
|
if (type_name == "image2d_t" || type_name == "image3d_t") {
|
||||||
|
// Image.
|
||||||
|
const auto access_qual = get_argument_metadata(
|
||||||
|
f, arg, "kernel_arg_access_qual");
|
||||||
|
args.emplace_back(get_image_type(type_name, access_qual),
|
||||||
|
arg_store_size, target_size,
|
||||||
|
target_align, module::argument::zero_ext);
|
||||||
|
|
||||||
|
} else if (type_name == "__llvm_image_size") {
|
||||||
|
// Image size implicit argument.
|
||||||
|
args.emplace_back(module::argument::scalar, sizeof(cl_uint),
|
||||||
|
dl.getTypeStoreSize(size_type),
|
||||||
|
dl.getABITypeAlignment(size_type),
|
||||||
|
module::argument::zero_ext,
|
||||||
|
module::argument::image_size);
|
||||||
|
|
||||||
|
} else if (type_name == "__llvm_image_format") {
|
||||||
|
// Image format implicit argument.
|
||||||
|
args.emplace_back(module::argument::scalar, sizeof(cl_uint),
|
||||||
|
dl.getTypeStoreSize(size_type),
|
||||||
|
dl.getABITypeAlignment(size_type),
|
||||||
|
module::argument::zero_ext,
|
||||||
|
module::argument::image_format);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Other types.
|
||||||
|
const auto actual_type =
|
||||||
|
isa<::llvm::PointerType>(arg_type) && arg.hasByValAttr() ?
|
||||||
|
cast<::llvm::PointerType>(arg_type)->getElementType() : arg_type;
|
||||||
|
|
||||||
|
if (actual_type->isPointerTy()) {
|
||||||
|
const unsigned address_space =
|
||||||
|
cast<::llvm::PointerType>(actual_type)->getAddressSpace();
|
||||||
|
|
||||||
|
if (address_space == address_spaces[clang::LangAS::opencl_local
|
||||||
|
- clang::LangAS::Offset]) {
|
||||||
|
args.emplace_back(module::argument::local, arg_api_size,
|
||||||
|
target_size, target_align,
|
||||||
|
module::argument::zero_ext);
|
||||||
|
} else {
|
||||||
|
// XXX: Correctly handle constant address space. There is no
|
||||||
|
// way for r600g to pass a handle for constant buffers back
|
||||||
|
// to clover like it can for global buffers, so
|
||||||
|
// creating constant arguments will break r600g. For now,
|
||||||
|
// continue treating constant buffers as global buffers
|
||||||
|
// until we can come up with a way to create handles for
|
||||||
|
// constant buffers.
|
||||||
|
args.emplace_back(module::argument::global, arg_api_size,
|
||||||
|
target_size, target_align,
|
||||||
|
module::argument::zero_ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const bool needs_sign_ext = f.getAttributes().hasAttribute(
|
||||||
|
arg.getArgNo() + 1, ::llvm::Attribute::SExt);
|
||||||
|
|
||||||
|
args.emplace_back(module::argument::scalar, arg_api_size,
|
||||||
|
target_size, target_align,
|
||||||
|
(needs_sign_ext ? module::argument::sign_ext :
|
||||||
|
module::argument::zero_ext));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append implicit arguments. XXX - The types, ordering and
|
||||||
|
// vector size of the implicit arguments should depend on the
|
||||||
|
// target according to the selected calling convention.
|
||||||
|
args.emplace_back(module::argument::scalar, sizeof(cl_uint),
|
||||||
|
dl.getTypeStoreSize(size_type),
|
||||||
|
dl.getABITypeAlignment(size_type),
|
||||||
|
module::argument::zero_ext,
|
||||||
|
module::argument::grid_dimension);
|
||||||
|
|
||||||
|
args.emplace_back(module::argument::scalar, sizeof(cl_uint),
|
||||||
|
dl.getTypeStoreSize(size_type),
|
||||||
|
dl.getABITypeAlignment(size_type),
|
||||||
|
module::argument::zero_ext,
|
||||||
|
module::argument::grid_offset);
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
module::section
|
||||||
|
make_text_section(const std::vector<char> &code) {
|
||||||
|
const pipe_llvm_program_header header { uint32_t(code.size()) };
|
||||||
|
module::section text { 0, module::section::text, header.num_bytes, {} };
|
||||||
|
|
||||||
|
text.data.insert(text.data.end(), reinterpret_cast<const char *>(&header),
|
||||||
|
reinterpret_cast<const char *>(&header) + sizeof(header));
|
||||||
|
text.data.insert(text.data.end(), code.begin(), code.end());
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module
|
||||||
|
clover::llvm::build_module_common(const Module &mod,
|
||||||
|
const std::vector<char> &code,
|
||||||
|
const std::map<std::string,
|
||||||
|
unsigned> &offsets,
|
||||||
|
const clang::CompilerInstance &c) {
|
||||||
|
module m;
|
||||||
|
|
||||||
|
for (const auto &name : map(std::mem_fn(&Function::getName),
|
||||||
|
get_kernels(mod))) {
|
||||||
|
if (offsets.count(name))
|
||||||
|
m.syms.emplace_back(name, 0, offsets.at(name),
|
||||||
|
make_kernel_args(mod, name, c));
|
||||||
|
}
|
||||||
|
|
||||||
|
m.secs.push_back(make_text_section(code));
|
||||||
|
return m;
|
||||||
|
}
|
@@ -24,6 +24,7 @@
|
|||||||
// OTHER DEALINGS IN THE SOFTWARE.
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "llvm/codegen.hpp"
|
||||||
#include "llvm/compat.hpp"
|
#include "llvm/compat.hpp"
|
||||||
#include "llvm/metadata.hpp"
|
#include "llvm/metadata.hpp"
|
||||||
#include "llvm/util.hpp"
|
#include "llvm/util.hpp"
|
||||||
@@ -249,163 +250,6 @@ namespace {
|
|||||||
pm.run(mod);
|
pm.run(mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum module::argument::type
|
|
||||||
get_image_type(const std::string &type,
|
|
||||||
const std::string &qual) {
|
|
||||||
if (type == "image2d_t" && qual == "read_only")
|
|
||||||
return module::argument::image2d_rd;
|
|
||||||
else if (type == "image2d_t" && qual == "write_only")
|
|
||||||
return module::argument::image2d_wr;
|
|
||||||
else if (type == "image3d_t" && qual == "read_only")
|
|
||||||
return module::argument::image3d_rd;
|
|
||||||
else if (type == "image3d_t" && qual == "write_only")
|
|
||||||
return module::argument::image3d_wr;
|
|
||||||
else
|
|
||||||
unreachable("Unknown image type");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<module::argument>
|
|
||||||
make_kernel_args(const Module &mod, const std::string &kernel_name,
|
|
||||||
const clang::CompilerInstance &c) {
|
|
||||||
std::vector<module::argument> args;
|
|
||||||
const auto address_spaces = c.getTarget().getAddressSpaceMap();
|
|
||||||
const Function &f = *mod.getFunction(kernel_name);
|
|
||||||
::llvm::DataLayout dl(&mod);
|
|
||||||
const auto size_type =
|
|
||||||
dl.getSmallestLegalIntType(mod.getContext(), sizeof(cl_uint) * 8);
|
|
||||||
|
|
||||||
for (const auto &arg : f.args()) {
|
|
||||||
const auto arg_type = arg.getType();
|
|
||||||
|
|
||||||
// OpenCL 1.2 specification, Ch. 6.1.5: "A built-in data
|
|
||||||
// type that is not a power of two bytes in size must be
|
|
||||||
// aligned to the next larger power of two". We need this
|
|
||||||
// alignment for three element vectors, which have
|
|
||||||
// non-power-of-2 store size.
|
|
||||||
const unsigned arg_store_size = dl.getTypeStoreSize(arg_type);
|
|
||||||
const unsigned arg_api_size = util_next_power_of_two(arg_store_size);
|
|
||||||
|
|
||||||
const auto target_type = !arg_type->isIntegerTy() ? arg_type :
|
|
||||||
dl.getSmallestLegalIntType(mod.getContext(), arg_store_size * 8);
|
|
||||||
const unsigned target_size = dl.getTypeStoreSize(target_type);
|
|
||||||
const unsigned target_align = dl.getABITypeAlignment(target_type);
|
|
||||||
|
|
||||||
const auto type_name = get_argument_metadata(f, arg,
|
|
||||||
"kernel_arg_type");
|
|
||||||
|
|
||||||
if (type_name == "image2d_t" || type_name == "image3d_t") {
|
|
||||||
// Image.
|
|
||||||
const auto access_qual = get_argument_metadata(
|
|
||||||
f, arg, "kernel_arg_access_qual");
|
|
||||||
args.emplace_back(get_image_type(type_name, access_qual),
|
|
||||||
arg_store_size, target_size,
|
|
||||||
target_align, module::argument::zero_ext);
|
|
||||||
|
|
||||||
} else if (type_name == "__llvm_image_size") {
|
|
||||||
// Image size implicit argument.
|
|
||||||
args.emplace_back(module::argument::scalar, sizeof(cl_uint),
|
|
||||||
dl.getTypeStoreSize(size_type),
|
|
||||||
dl.getABITypeAlignment(size_type),
|
|
||||||
module::argument::zero_ext,
|
|
||||||
module::argument::image_size);
|
|
||||||
|
|
||||||
} else if (type_name == "__llvm_image_format") {
|
|
||||||
// Image format implicit argument.
|
|
||||||
args.emplace_back(module::argument::scalar, sizeof(cl_uint),
|
|
||||||
dl.getTypeStoreSize(size_type),
|
|
||||||
dl.getABITypeAlignment(size_type),
|
|
||||||
module::argument::zero_ext,
|
|
||||||
module::argument::image_format);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Other types.
|
|
||||||
const auto actual_type =
|
|
||||||
isa<::llvm::PointerType>(arg_type) && arg.hasByValAttr() ?
|
|
||||||
cast<::llvm::PointerType>(arg_type)->getElementType() : arg_type;
|
|
||||||
|
|
||||||
if (actual_type->isPointerTy()) {
|
|
||||||
const unsigned address_space =
|
|
||||||
cast<::llvm::PointerType>(actual_type)->getAddressSpace();
|
|
||||||
|
|
||||||
if (address_space == address_spaces[clang::LangAS::opencl_local
|
|
||||||
- clang::LangAS::Offset]) {
|
|
||||||
args.emplace_back(module::argument::local, arg_api_size,
|
|
||||||
target_size, target_align,
|
|
||||||
module::argument::zero_ext);
|
|
||||||
} else {
|
|
||||||
// XXX: Correctly handle constant address space. There is no
|
|
||||||
// way for r600g to pass a handle for constant buffers back
|
|
||||||
// to clover like it can for global buffers, so
|
|
||||||
// creating constant arguments will break r600g. For now,
|
|
||||||
// continue treating constant buffers as global buffers
|
|
||||||
// until we can come up with a way to create handles for
|
|
||||||
// constant buffers.
|
|
||||||
args.emplace_back(module::argument::global, arg_api_size,
|
|
||||||
target_size, target_align,
|
|
||||||
module::argument::zero_ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const bool needs_sign_ext = f.getAttributes().hasAttribute(
|
|
||||||
arg.getArgNo() + 1, ::llvm::Attribute::SExt);
|
|
||||||
|
|
||||||
args.emplace_back(module::argument::scalar, arg_api_size,
|
|
||||||
target_size, target_align,
|
|
||||||
(needs_sign_ext ? module::argument::sign_ext :
|
|
||||||
module::argument::zero_ext));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append implicit arguments. XXX - The types, ordering and
|
|
||||||
// vector size of the implicit arguments should depend on the
|
|
||||||
// target according to the selected calling convention.
|
|
||||||
args.emplace_back(module::argument::scalar, sizeof(cl_uint),
|
|
||||||
dl.getTypeStoreSize(size_type),
|
|
||||||
dl.getABITypeAlignment(size_type),
|
|
||||||
module::argument::zero_ext,
|
|
||||||
module::argument::grid_dimension);
|
|
||||||
|
|
||||||
args.emplace_back(module::argument::scalar, sizeof(cl_uint),
|
|
||||||
dl.getTypeStoreSize(size_type),
|
|
||||||
dl.getABITypeAlignment(size_type),
|
|
||||||
module::argument::zero_ext,
|
|
||||||
module::argument::grid_offset);
|
|
||||||
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
module::section
|
|
||||||
make_text_section(const std::vector<char> &code) {
|
|
||||||
const pipe_llvm_program_header header { uint32_t(code.size()) };
|
|
||||||
module::section text { 0, module::section::text, header.num_bytes, {} };
|
|
||||||
|
|
||||||
text.data.insert(text.data.end(), reinterpret_cast<const char *>(&header),
|
|
||||||
reinterpret_cast<const char *>(&header) + sizeof(header));
|
|
||||||
text.data.insert(text.data.end(), code.begin(), code.end());
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
module
|
|
||||||
build_module_common(const Module &mod,
|
|
||||||
const std::vector<char> &code,
|
|
||||||
const std::map<std::string,
|
|
||||||
unsigned> &offsets,
|
|
||||||
const clang::CompilerInstance &c) {
|
|
||||||
module m;
|
|
||||||
|
|
||||||
for (const auto &name : map(std::mem_fn(&Function::getName),
|
|
||||||
get_kernels(mod))) {
|
|
||||||
if (offsets.count(name))
|
|
||||||
m.syms.emplace_back(name, 0, offsets.at(name),
|
|
||||||
make_kernel_args(mod, name, c));
|
|
||||||
}
|
|
||||||
|
|
||||||
m.secs.push_back(make_text_section(code));
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<std::string, unsigned>
|
std::map<std::string, unsigned>
|
||||||
get_symbol_offsets(const ::llvm::Module &mod) {
|
get_symbol_offsets(const ::llvm::Module &mod) {
|
||||||
std::map<std::string, unsigned> offsets;
|
std::map<std::string, unsigned> offsets;
|
||||||
|
Reference in New Issue
Block a user