1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 02:32:36 +01:00

Use LLVM 6

This commit is contained in:
Nekotekina 2018-05-01 13:20:36 +03:00
parent 8b704588d0
commit a975ecdc4f
9 changed files with 66 additions and 54 deletions

View File

@ -27,7 +27,7 @@ git:
env: env:
- QTVER=5.10.1 - QTVER=5.10.1
before_install: before_install:
- if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$CXX" = "g++" ]; then - if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$CXX" = "g++" ]; then
export CXX="g++-5" CC="gcc-5" CXXFLAGS="-Wno-format-security"; export CXX="g++-5" CC="gcc-5" CXXFLAGS="-Wno-format-security";
@ -43,11 +43,11 @@ install:
sudo dpkg -i libglew2.0_2.0.0-5_amd64.deb libglew-dev_2.0.0-5_amd64.deb libvulkan1_1.0.61.1+dfsg1-1ubuntu1~16.04.1_amd64.deb libvulkan-dev_1.0.61.1+dfsg1-1ubuntu1~16.04.1_amd64.deb; sudo dpkg -i libglew2.0_2.0.0-5_amd64.deb libglew-dev_2.0.0-5_amd64.deb libvulkan1_1.0.61.1+dfsg1-1ubuntu1~16.04.1_amd64.deb libvulkan-dev_1.0.61.1+dfsg1-1ubuntu1~16.04.1_amd64.deb;
else else
brew update; brew update;
brew install ccache glew llvm40; brew install ccache glew;
fi; fi;
before_script: before_script:
- git submodule update --init asmjit 3rdparty/ffmpeg 3rdparty/pugixml 3rdparty/GSL 3rdparty/libpng Utilities/yaml-cpp 3rdparty/cereal 3rdparty/hidapi 3rdparty/Optional Vulkan/glslang Vulkan/Vulkan-LoaderAndValidationLayers - git submodule update --init llvm asmjit 3rdparty/ffmpeg 3rdparty/pugixml 3rdparty/GSL 3rdparty/libpng Utilities/yaml-cpp 3rdparty/cereal 3rdparty/hidapi 3rdparty/Optional Vulkan/glslang Vulkan/Vulkan-LoaderAndValidationLayers
- mkdir build ; cd build - mkdir build ; cd build
- export CMAKE_PREFIX_PATH=~/Qt/${QTVER}/gcc_64/lib/cmake - export CMAKE_PREFIX_PATH=~/Qt/${QTVER}/gcc_64/lib/cmake
- if [ "$TRAVIS_PULL_REQUEST" = false ]; then - if [ "$TRAVIS_PULL_REQUEST" = false ]; then
@ -59,7 +59,7 @@ before_script:
script: script:
- ninja - ninja
after_script: after_script:
- cd build - cd build
- # AppImage generation - # AppImage generation
@ -79,7 +79,7 @@ after_script:
rm -r ./appdir/usr/share/man ; rm -r ./appdir/usr/share/man ;
rm -r ./appdir/usr/include ; rm -r ./appdir/usr/include ;
export PATH=${TRAVIS_BUILD_DIR}/build/squashfs-root/usr/bin/:${PATH} ; export PATH=${TRAVIS_BUILD_DIR}/build/squashfs-root/usr/bin/:${PATH} ;
echo "Embed newer libstdc++ for distros that don't come with it (ubuntu 14.04)"; echo "Embed newer libstdc++ for distros that don't come with it (ubuntu 14.04)";
mkdir -p appdir/usr/optional/ ; mkdir -p appdir/usr/optional/libstdc++/ ; mkdir -p appdir/usr/optional/ ; mkdir -p appdir/usr/optional/libstdc++/ ;
cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6 ./appdir/usr/optional/libstdc++/ ; cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6 ./appdir/usr/optional/libstdc++/ ;
@ -87,7 +87,7 @@ after_script:
wget -c https://github.com/RPCS3/AppImageKit-checkrt/releases/download/continuous/AppRun-patched-x86_64 -O ./appdir/AppRun ; wget -c https://github.com/RPCS3/AppImageKit-checkrt/releases/download/continuous/AppRun-patched-x86_64 -O ./appdir/AppRun ;
chmod a+x ./appdir/AppRun ; chmod a+x ./appdir/AppRun ;
wget -c https://github.com/RPCS3/AppImageKit-checkrt/releases/download/continuous/exec-x86_64.so -O ./appdir/usr/optional/exec.so ; wget -c https://github.com/RPCS3/AppImageKit-checkrt/releases/download/continuous/exec-x86_64.so -O ./appdir/usr/optional/exec.so ;
echo "Package it up and send it off" ; echo "Package it up and send it off" ;
./linuxdeployqt*.AppImage --appimage-extract ; ./linuxdeployqt*.AppImage --appimage-extract ;
./squashfs-root/usr/bin/appimagetool ${TRAVIS_BUILD_DIR}/build/appdir ; ./squashfs-root/usr/bin/appimagetool ${TRAVIS_BUILD_DIR}/build/appdir ;
@ -100,7 +100,6 @@ addons:
apt: apt:
sources: sources:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
- llvm-toolchain-trusty-4.0
- sourceline: 'ppa:jonathonf/binutils' # We need to update binutils to a newer version to link against the ffmpeg libs on. - sourceline: 'ppa:jonathonf/binutils' # We need to update binutils to a newer version to link against the ffmpeg libs on.
- sourceline: 'ppa:beineri/opt-qt-5.10.1-trusty' # <<WARNING>>: This needs to be updated manually whenever the QT Version changes. Add QT PPA since the installer is quite bad. - sourceline: 'ppa:beineri/opt-qt-5.10.1-trusty' # <<WARNING>>: This needs to be updated manually whenever the QT Version changes. Add QT PPA since the installer is quite bad.
packages: packages:
@ -114,8 +113,6 @@ addons:
#- libvulkan1 #- libvulkan1
#- libvulkan-dev #- libvulkan-dev
- libc6-dev - libc6-dev
- llvm-4.0
- llvm-4.0-dev
# Clang 5.0 is now bundled in travis, so we no longer need the ppa version. # Clang 5.0 is now bundled in travis, so we no longer need the ppa version.
#- clang-4.0 #- clang-4.0
- libedit-dev - libedit-dev

View File

@ -72,7 +72,7 @@ static void* s_next = s_memory;
static std::deque<std::vector<RUNTIME_FUNCTION>> s_unwater; static std::deque<std::vector<RUNTIME_FUNCTION>> s_unwater;
static std::vector<std::vector<RUNTIME_FUNCTION>> s_unwind; // .pdata static std::vector<std::vector<RUNTIME_FUNCTION>> s_unwind; // .pdata
#else #else
static std::deque<std::tuple<u8*, u64, std::size_t>> s_unfire; static std::deque<std::pair<u8*, std::size_t>> s_unfire;
#endif #endif
// Reset memory manager // Reset memory manager
@ -89,27 +89,9 @@ extern void jit_finalize()
s_unwind.clear(); s_unwind.clear();
#else #else
struct MemoryManager : llvm::RTDyldMemoryManager
{
u8* allocateCodeSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name) override
{
return nullptr;
}
u8* allocateDataSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name, bool is_ro) override
{
return nullptr;
}
bool finalizeMemory(std::string* = nullptr) override
{
return false;
}
} mem;
for (auto&& t : s_unfire) for (auto&& t : s_unfire)
{ {
mem.deregisterEHFrames(std::get<0>(t), std::get<1>(t), std::get<2>(t)); llvm::RTDyldMemoryManager::deregisterEHFramesInProcess(t.first, t.second);
} }
s_unfire.clear(); s_unfire.clear();
@ -287,13 +269,13 @@ struct MemoryManager : llvm::RTDyldMemoryManager
s_unwind.emplace_back(std::move(pdata)); s_unwind.emplace_back(std::move(pdata));
} }
#else #else
s_unfire.push_front(std::make_tuple(addr, load_addr, size)); s_unfire.push_front(std::make_pair(addr, size));
#endif #endif
return RTDyldMemoryManager::registerEHFrames(addr, load_addr, size); return RTDyldMemoryManager::registerEHFrames(addr, load_addr, size);
} }
void deregisterEHFrames(u8* addr, u64 load_addr, std::size_t size) override void deregisterEHFrames() override
{ {
} }
}; };
@ -380,8 +362,8 @@ public:
{ {
if (fs::file cached{path, fs::read}) if (fs::file cached{path, fs::read})
{ {
auto buf = llvm::MemoryBuffer::getNewUninitMemBuffer(cached.size()); auto buf = llvm::WritableMemoryBuffer::getNewUninitMemBuffer(cached.size());
cached.read(const_cast<char*>(buf->getBufferStart()), buf->getBufferSize()); cached.read(buf->getBufferStart(), buf->getBufferSize());
return buf; return buf;
} }
@ -417,19 +399,32 @@ std::string jit_compiler::cpu(const std::string& _cpu)
m_cpu == "broadwell" || m_cpu == "broadwell" ||
m_cpu == "skylake" || m_cpu == "skylake" ||
m_cpu == "skylake-avx512" || m_cpu == "skylake-avx512" ||
m_cpu == "cannonlake") m_cpu == "cannonlake" ||
m_cpu == "icelake")
{ {
// Downgrade if AVX is not supported by some chips
if (!utils::has_avx()) if (!utils::has_avx())
{ {
m_cpu = "nehalem"; m_cpu = "nehalem";
} }
} }
if (m_cpu == "skylake-avx512" ||
m_cpu == "cannonlake" ||
m_cpu == "icelake")
{
// Downgrade if AVX-512 is disabled or not supported
if (!utils::has_512())
{
m_cpu = "skylake";
}
}
} }
return m_cpu; return m_cpu;
} }
jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu) jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu, bool large)
: m_link(_link) : m_link(_link)
, m_cpu(cpu(_cpu)) , m_cpu(cpu(_cpu))
{ {
@ -441,7 +436,7 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
m_engine.reset(llvm::EngineBuilder(std::make_unique<llvm::Module>("null_", m_context)) m_engine.reset(llvm::EngineBuilder(std::make_unique<llvm::Module>("null_", m_context))
.setErrorStr(&result) .setErrorStr(&result)
.setOptLevel(llvm::CodeGenOpt::Aggressive) .setOptLevel(llvm::CodeGenOpt::Aggressive)
.setCodeModel(llvm::CodeModel::Small) .setCodeModel(large ? llvm::CodeModel::Large : llvm::CodeModel::Small)
.setMCPU(m_cpu) .setMCPU(m_cpu)
.create()); .create());
} }
@ -455,7 +450,7 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
.setErrorStr(&result) .setErrorStr(&result)
.setMCJITMemoryManager(std::move(mem)) .setMCJITMemoryManager(std::move(mem))
.setOptLevel(llvm::CodeGenOpt::Aggressive) .setOptLevel(llvm::CodeGenOpt::Aggressive)
.setCodeModel(llvm::CodeModel::Small) .setCodeModel(large ? llvm::CodeModel::Large : llvm::CodeModel::Small)
.setMCPU(m_cpu) .setMCPU(m_cpu)
.create()); .create());
@ -492,6 +487,19 @@ void jit_compiler::add(std::unique_ptr<llvm::Module> module, const std::string&
} }
} }
void jit_compiler::add(std::unique_ptr<llvm::Module> module)
{
const auto ptr = module.get();
m_engine->addModule(std::move(module));
m_engine->generateCodeForModule(ptr);
for (auto& func : ptr->functions())
{
// Delete IR to lower memory consumption
func.deleteBody();
}
}
void jit_compiler::add(const std::string& path) void jit_compiler::add(const std::string& path)
{ {
m_engine->addObjectFile(std::move(llvm::object::ObjectFile::createObjectFile(*ObjectCache::load(path)).get())); m_engine->addObjectFile(std::move(llvm::object::ObjectFile::createObjectFile(*ObjectCache::load(path)).get()));

View File

@ -40,7 +40,7 @@ class jit_compiler final
std::string m_cpu; std::string m_cpu;
public: public:
jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu); jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu, bool large = false);
~jit_compiler(); ~jit_compiler();
// Get LLVM context // Get LLVM context
@ -49,9 +49,17 @@ public:
return m_context; return m_context;
} }
auto& get_engine() const
{
return *m_engine;
}
// Add module (path to obj cache dir) // Add module (path to obj cache dir)
void add(std::unique_ptr<llvm::Module> module, const std::string& path); void add(std::unique_ptr<llvm::Module> module, const std::string& path);
// Add module (not cached)
void add(std::unique_ptr<llvm::Module> module);
// Add object (path to obj file) // Add object (path to obj file)
void add(const std::string& path); void add(const std::string& path);

View File

@ -5,12 +5,11 @@ image: Visual Studio 2015
environment: environment:
QTDIR: C:\Qt\5.10\msvc2015_64 QTDIR: C:\Qt\5.10\msvc2015_64
LLVMLIBS: https://drive.google.com/uc?export=download&id=0B8A6NaxhQAGRY2k3Q2Yya05lcm8 LLVMLIBS: https://github.com/RPCS3/llvm/releases/download/continuous-release_60/llvmlibs.7z
VULKAN: https://drive.google.com/uc?export=download&id=1A2eOMmCO714i0U7J0qI4aEMKnuWl8l_R VULKAN: https://drive.google.com/uc?export=download&id=1A2eOMmCO714i0U7J0qI4aEMKnuWl8l_R
COMPATDB: https://rpcs3.net/compatibility?api=v1&export COMPATDB: https://rpcs3.net/compatibility?api=v1&export
cache: cache:
- llvmlibs.7z -> appveyor.yml
- vulkan.7z -> appveyor.yml - vulkan.7z -> appveyor.yml
- compat_database.dat - compat_database.dat
@ -19,7 +18,7 @@ install:
$env:COMM_TAG = $(git describe --tags $(git rev-list --tags --max-count=1)) $env:COMM_TAG = $(git describe --tags $(git rev-list --tags --max-count=1))
$env:COMM_COUNT = $(git rev-list --count HEAD) $env:COMM_COUNT = $(git rev-list --count HEAD)
$env:COMM_HASH = $env:APPVEYOR_REPO_COMMIT.Substring(0,8) $env:COMM_HASH = $env:APPVEYOR_REPO_COMMIT.Substring(0,8)
if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { if ($env:APPVEYOR_PULL_REQUEST_NUMBER) {
$env:BUILD = "rpcs3-{0}-{1}_win64.7z" -f $env:COMM_TAG, $env:COMM_HASH $env:BUILD = "rpcs3-{0}-{1}_win64.7z" -f $env:COMM_TAG, $env:COMM_HASH
$env:AVVER = "{0}-{1}" -f $env:COMM_TAG.TrimStart("v"), $env:COMM_HASH $env:AVVER = "{0}-{1}" -f $env:COMM_TAG.TrimStart("v"), $env:COMM_HASH
@ -101,6 +100,6 @@ artifacts:
- path: openssl_win64.7z.sha256 - path: openssl_win64.7z.sha256
name: openssl sha256 hash name: openssl sha256 hash
on_finish: on_finish:
- ps: | # update appveyor build version, done last to prevent webhook breakage - ps: | # update appveyor build version, done last to prevent webhook breakage
update-appveyorbuild -version $env:AVVER update-appveyorbuild -version $env:AVVER

2
llvm

@ -1 +1 @@
Subproject commit 4423e351176a92975739dd4ea43c2ff5877236ae Subproject commit 6154c0dcaf1a5a105afffa106ea3298b3020dffb

View File

@ -181,9 +181,9 @@ set(CMAKE_MODULE_PATH "${RPCS3_SRC_DIR}/cmake_modules")
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
find_package(OpenAL REQUIRED) find_package(OpenAL REQUIRED)
if(NOT WITHOUT_LLVM) if(NOT WITHOUT_LLVM)
find_package(LLVM 4.0 CONFIG) find_package(LLVM 999.666 CONFIG)
if(NOT LLVM_FOUND) if(NOT LLVM_FOUND)
message("System LLVM was not found, LLVM will be built from the submodule.") message("LLVM will be built from the submodule.")
set(LLVM_TARGETS_TO_BUILD "X86" CACHE INTERNAL "") set(LLVM_TARGETS_TO_BUILD "X86" CACHE INTERNAL "")
option(LLVM_BUILD_RUNTIME OFF) option(LLVM_BUILD_RUNTIME OFF)
@ -200,7 +200,7 @@ if(NOT WITHOUT_LLVM)
set(LLVM_DIR "${CMAKE_CURRENT_BINARY_DIR}/../llvm_build/lib/cmake/llvm/") set(LLVM_DIR "${CMAKE_CURRENT_BINARY_DIR}/../llvm_build/lib/cmake/llvm/")
# now tries to find LLVM again # now tries to find LLVM again
find_package(LLVM 4.0 CONFIG) find_package(LLVM 6.0 CONFIG)
if(NOT LLVM_FOUND) if(NOT LLVM_FOUND)
message(WARNING "Couldn't build LLVM from the submodule. You might need to run `git submodule update --init`") message(WARNING "Couldn't build LLVM from the submodule. You might need to run `git submodule update --init`")
endif() endif()
@ -309,7 +309,7 @@ if(WIN32)
endif() endif()
if(NOT LLVM_FOUND) if(NOT LLVM_FOUND)
message("LLVM 4.0 not found. RPCS3 will be compiled without LLVM support.") message("LLVM submodule not found. RPCS3 will be compiled without LLVM support.")
else() else()
add_definitions(${LLVM_DEFINITIONS}) add_definitions(${LLVM_DEFINITIONS})
add_definitions(-DLLVM_AVAILABLE) add_definitions(-DLLVM_AVAILABLE)
@ -486,7 +486,7 @@ add_custom_command(TARGET rpcs3 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/bin/Icons $<TARGET_FILE_DIR:rpcs3>/Icons) ${CMAKE_SOURCE_DIR}/bin/Icons $<TARGET_FILE_DIR:rpcs3>/Icons)
endif() endif()
# Unix installation # Unix installation
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
# Install the binary # Install the binary

View File

@ -14,7 +14,7 @@ const ppu_decoder<PPUTranslator> s_ppu_decoder;
PPUTranslator::PPUTranslator(LLVMContext& context, Module* module, const ppu_module& info) PPUTranslator::PPUTranslator(LLVMContext& context, Module* module, const ppu_module& info)
: cpu_translator(module, false) : cpu_translator(module, false)
, m_info(info) , m_info(info)
, m_pure_attr(AttributeSet::get(m_context, AttributeSet::FunctionIndex, {Attribute::NoUnwind, Attribute::ReadNone})) , m_pure_attr(AttributeList::get(m_context, AttributeList::FunctionIndex, {Attribute::NoUnwind, Attribute::ReadNone}))
{ {
// Bind context // Bind context
m_context = context; m_context = context;

View File

@ -15,7 +15,7 @@ class PPUTranslator final : public cpu_translator
std::map<u64, const ppu_reloc*> m_relocs; std::map<u64, const ppu_reloc*> m_relocs;
// Attributes for function calls which are "pure" and may be optimized away if their results are unused // Attributes for function calls which are "pure" and may be optimized away if their results are unused
const llvm::AttributeSet m_pure_attr; const llvm::AttributeList m_pure_attr;
// LLVM function // LLVM function
llvm::Function* m_function; llvm::Function* m_function;
@ -297,17 +297,17 @@ public:
// Call a function with attribute list // Call a function with attribute list
template<typename... Args> template<typename... Args>
llvm::CallInst* Call(llvm::Type* ret, llvm::AttributeSet attr, llvm::StringRef name, Args... args) llvm::CallInst* Call(llvm::Type* ret, llvm::AttributeList attr, llvm::StringRef name, Args... args)
{ {
// Call the function // Call the function
return m_ir->CreateCall(m_module->getOrInsertFunction(name, llvm::FunctionType::get(ret, {args->getType()...}, false), attr), {args...}); return m_ir->CreateCall(m_module->getOrInsertFunction(name, attr, ret, args->getType()...), {args...});
} }
// Call a function // Call a function
template<typename... Args> template<typename... Args>
llvm::CallInst* Call(llvm::Type* ret, llvm::StringRef name, Args... args) llvm::CallInst* Call(llvm::Type* ret, llvm::StringRef name, Args... args)
{ {
return Call(ret, llvm::AttributeSet{}, name, args...); return Call(ret, llvm::AttributeList{}, name, args...);
} }
// Handle compilation errors // Handle compilation errors

View File

@ -10,7 +10,7 @@
<Lib> <Lib>
<AdditionalLibraryDirectories Condition="'$(Configuration)'=='Debug - LLVM'">..\llvm_build\Debug\lib</AdditionalLibraryDirectories> <AdditionalLibraryDirectories Condition="'$(Configuration)'=='Debug - LLVM'">..\llvm_build\Debug\lib</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories Condition="'$(Configuration)'=='Release - LLVM'">..\llvm_build\Release\lib</AdditionalLibraryDirectories> <AdditionalLibraryDirectories Condition="'$(Configuration)'=='Release - LLVM'">..\llvm_build\Release\lib</AdditionalLibraryDirectories>
<AdditionalDependencies>LLVMProfileData.lib;LLVMDebugInfoCodeView.lib;LLVMDebugInfoMSF.lib;LLVMInstrumentation.lib;LLVMMCJIT.lib;LLVMRuntimeDyld.lib;LLVMVectorize.lib;LLVMX86CodeGen.lib;LLVMGlobalISel.lib;LLVMX86Disassembler.lib;LLVMExecutionEngine.lib;LLVMAsmPrinter.lib;LLVMSelectionDAG.lib;LLVMCodeGen.lib;LLVMScalarOpts.lib;LLVMInstCombine.lib;LLVMTransformUtils.lib;LLVMAnalysis.lib;LLVMTarget.lib;LLVMX86Desc.lib;LLVMX86AsmPrinter.lib;LLVMObject.lib;LLVMMCParser.lib;LLVMBitReader.lib;LLVMCore.lib;LLVMX86Utils.lib;LLVMMC.lib;LLVMX86Info.lib;LLVMSupport.lib;LLVMMCDisassembler.lib;LLVMipo.lib</AdditionalDependencies> <AdditionalDependencies>LLVMProfileData.lib;LLVMDebugInfoCodeView.lib;LLVMDebugInfoMSF.lib;LLVMInstrumentation.lib;LLVMMCJIT.lib;LLVMRuntimeDyld.lib;LLVMVectorize.lib;LLVMX86CodeGen.lib;LLVMGlobalISel.lib;LLVMX86Disassembler.lib;LLVMExecutionEngine.lib;LLVMAsmPrinter.lib;LLVMSelectionDAG.lib;LLVMCodeGen.lib;LLVMScalarOpts.lib;LLVMInstCombine.lib;LLVMTransformUtils.lib;LLVMAnalysis.lib;LLVMTarget.lib;LLVMX86Desc.lib;LLVMX86AsmPrinter.lib;LLVMObject.lib;LLVMMCParser.lib;LLVMBitReader.lib;LLVMCore.lib;LLVMX86Utils.lib;LLVMMC.lib;LLVMX86Info.lib;LLVMSupport.lib;LLVMMCDisassembler.lib;LLVMipo.lib;LLVMBinaryFormat.lib;LLVMPasses.lib;LLVMIRReader.lib;LLVMLinker.lib;LLVMAsmParser.lib</AdditionalDependencies>
</Lib> </Lib>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup /> <ItemGroup />