Index: MultiSource/UnitTests/CMakeLists.txt =================================================================== --- MultiSource/UnitTests/CMakeLists.txt +++ MultiSource/UnitTests/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(C++11) +add_subdirectory(Stackmaps) if(ARCH STREQUAL "Mips") add_subdirectory(Mips) endif() Index: MultiSource/UnitTests/Stackmaps/CMakeLists.txt =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/CMakeLists.txt @@ -0,0 +1,13 @@ +file(GLOB llsources ${CMAKE_CURRENT_SOURCE_DIR}/*.ll) +SET_SOURCE_FILES_PROPERTIES(${llsources} PROPERTIES LANGUAGE CXX) + +set(STACKMAP_TEST_COMMON_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/Harness.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/StackMapParser.cpp) + +if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") +llvm_multisource(stackmaps + ${STACKMAP_TEST_COMMON_FILES} + ${CMAKE_CURRENT_SOURCE_DIR}/input_${CMAKE_SYSTEM_PROCESSOR}.ll +) +endif() Index: MultiSource/UnitTests/Stackmaps/Harness.cpp =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/Harness.cpp @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "StackMapParser.h" + +using namespace std; + +StackMapParser *SMPRef = nullptr; +uint64_t NextID = 0; +vector InspectSizes; + +// Defined in the `.ll` file for the platform under test. +extern "C" void run_tests(); + +extern "C" void sm_set_inspect_sizes(int NumArgs, ...) { + va_list VA; + va_start(VA, NumArgs); + + InspectSizes.clear(); + for (int I = 0; I < NumArgs; I++) + InspectSizes.push_back(va_arg(VA, size_t)); + + va_end(VA); +} + +void dumpMem(const char *Ptr, size_t Size) { + for (size_t I = 0; I < Size; I++) { + printf("%02x", (unsigned char)Ptr[I]); + if (I != Size - 1) + printf(" "); + } + printf("\n"); +} + +char *getStackMapSectionAddr(char *Path, void **OutMap, off_t *OutMapSize) { + // First map the binary into our adress space. + struct stat St; + if (stat(Path, &St) == -1) + err(EXIT_FAILURE, "stat"); + + int Fd = open(Path, O_RDONLY); + if (Fd == -1) + err(EXIT_FAILURE, "open"); + + void *Map = mmap(nullptr, St.st_size, PROT_READ, MAP_SHARED, Fd, 0); + if (Map == MAP_FAILED) + err(EXIT_FAILURE, "mmap"); + + close(Fd); + + // Report mmap result back to caller, so it can be unmapped later. + *OutMap = Map; + *OutMapSize = St.st_size; + + // Now find the stackmap section inside what we just mapped in. + Elf64_Ehdr *Ehdr = static_cast(Map); + assert(*Ehdr[0].e_ident == EI_MAG0); + assert(*Ehdr[1].e_ident == EI_MAG1); + assert(*Ehdr[2].e_ident == EI_MAG2); + assert(*Ehdr[3].e_ident == EI_MAG3); + + Elf64_Shdr *Shdrs = static_cast( + static_cast(static_cast(Map) + Ehdr->e_shoff)); + + // Find the string table for the section names. + uint16_t StrTabShIdx = Ehdr->e_shstrndx; + assert(StrTabShIdx != SHN_UNDEF); + if (StrTabShIdx >= SHN_LORESERVE) + return nullptr; // not yet implemented. See `elf(5)` manual. + + Elf64_Shdr *StrTabSh = &Shdrs[StrTabShIdx]; + assert(StrTabSh->sh_type == ELF::SHT_STRTAB); + Elf64_Off StrTabOff = StrTabSh->sh_offset; + + void *Data = nullptr; + for (uint16_t SI = 0; SI < Ehdr->e_shentsize; SI++) { + Elf64_Shdr *Shdr = &Shdrs[SI]; + char *name = static_cast(Map) + StrTabOff + Shdr->sh_name; + if (strcmp(name, ".llvm_stackmaps") == 0) + return static_cast(Map) + Shdr->sh_offset; + } + return nullptr; +} + +// Get a pointer to the start of the storage referenced by `Loc`. +// +// This code assumes that a register is the size of a pointer. We get away with +// this for now since we don't test any vector registers. +char *getMemForLoc(SMLoc *Loc, uintptr_t RegVals[]) { + switch (Loc->Where) { + case Reg: + // Get the saved register values off of the stack. + return reinterpret_cast(&RegVals[Loc->RegNum]); + case Direct: + case Indirect: + // Note: In the case of `Direct`, the record is describing a pointer value + // (pointing to the stack). We report the memory the pointer points to, not + // the memory of the pointer itself. + return reinterpret_cast(RegVals[Loc->RegNum]) + + Loc->OffsetOrSmallConst; + case Const: + // Get the constant directly from the stackmap record. + return reinterpret_cast(&Loc->OffsetOrSmallConst); + case ConstIndex: + errx(EXIT_FAILURE, "ConstIndex locations unimplemented"); + default: + errx(EXIT_FAILURE, "unexpected storage class: %u\n", Loc->Where); + } +} + +extern "C" void do_sm_inspect(uintptr_t RegVals[]) { + SMRec *Rec = SMPRef->getRecord(NextID); + if (Rec == nullptr) + errx(EXIT_FAILURE, "Failed to find stackmap record #%" PRIu64 "\n", NextID); + + cout << "StackMap #" << NextID << "\n"; + size_t LocNum = 0; + vector::iterator InspectSizesIt = InspectSizes.begin(); + for (auto Loc : Rec->Locs) { + char *GotPtr = getMemForLoc(&Loc, RegVals); + size_t InspectSize = *InspectSizesIt; + + cout << " Location #" << LocNum << "\n"; + cout << " Storage: " << getStorageClassName(Loc.Where) << "\n"; + cout << " Storage Size: " << Loc.Size << "\n"; + cout << " Inspect Size: " << InspectSize << "\n"; + cout << " Bytes: "; + dumpMem(GotPtr, InspectSize); + LocNum++; + InspectSizesIt++; + } + NextID++; + cout << "\n"; + assert(InspectSizesIt == InspectSizes.end() && "too many inspect sizes"); +} + +#ifdef __x86_64__ +__attribute__((naked)) extern "C" void sm_inspect() { + asm volatile(".intel_syntax noprefix\n" + // Save register state onto the stack in reverse DWARF register + // number order. + "push r15\n" + "push r14\n" + "push r13\n" + "push r12\n" + "push r11\n" + "push r10\n" + "push r9\n" + "push r8\n" + "push rsp\n" + "push rbp\n" + "push rdi\n" + "push rsi\n" + "push rbx\n" + "push rcx\n" + "push rdx\n" + "push rax\n" + + // Call do_sm_inspect(). + // + // Arg: pointer to saved registers. + "mov rdi, rsp\n" + "call do_sm_inspect\n" + + // Restore registers we saved earlier. + "pop rax\n" + "pop rdx\n" + "pop rcx\n" + "pop rbx\n" + "pop rsi\n" + "pop rdi\n" + "pop rbp\n" + "pop rsp\n" + "pop r8\n" + "pop r9\n" + "pop r10\n" + "pop r11\n" + "pop r12\n" + "pop r13\n" + "pop r14\n" + "pop r15\n" + "ret"); +} +#endif // __x86_64__ + +int main(int argc, char *argv[]) { + void *Map; + off_t MapSize; + char *SMData = getStackMapSectionAddr(argv[0], &Map, &MapSize); + if (SMData == nullptr) + errx(EXIT_FAILURE, "couldn't find stackmaps"); + + StackMapParser SMP(SMData); + munmap(Map, MapSize); + + SMPRef = &SMP; + run_tests(); + + return EXIT_SUCCESS; +} Index: MultiSource/UnitTests/Stackmaps/StackMapParser.h =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/StackMapParser.h @@ -0,0 +1,64 @@ +#ifndef TEST_SUITE_STACKMAP_PARSER_H +#define TEST_SUITE_STACKMAP_PARSER_H + +#include +#include + +#include +#include + +using namespace std; + +const uint8_t SMVersion = 3; + +enum StorageClass { + Reg = 1, + Direct = 2, + Indirect = 3, + Const = 4, + ConstIndex = 5 +}; + +struct SMFunc { + uint64_t Addr; + uint64_t Size; + uint64_t RecCt; +}; + +struct SMLoc { + StorageClass Where; + uint16_t Size; + uint16_t RegNum; + int32_t OffsetOrSmallConst; +}; + +struct SMLiveOut { + uint16_t RegNo; + uint8_t Size; +}; + +struct SMRec { + uint32_t InstrOff; + vector Locs; + vector LiveOuts; +}; + +// A simple (copying) Stackmap parser. +// https://llvm.org/docs/StackMaps.html#stack-map-format +class StackMapParser { +private: + vector Funcs; + vector Consts; + map Recs; + + template T read(char **SMData); + void alignTo(char **SMData, size_t To); + +public: + StackMapParser(char *SMData); + SMRec *getRecord(uint64_t ID); +}; + +string getStorageClassName(StorageClass C); + +#endif // TEST_SUITE_STACKMAP_PARSER_H Index: MultiSource/UnitTests/Stackmaps/StackMapParser.cpp =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/StackMapParser.cpp @@ -0,0 +1,102 @@ +#include +#include + +#include + +#include "StackMapParser.h" + +using namespace std; + +template T StackMapParser::read(char **SMData) { + T Ret = *reinterpret_cast(*SMData); + *SMData += sizeof(T); + return Ret; +} + +void StackMapParser::alignTo(char **SMData, size_t To) { + *SMData += reinterpret_cast(*SMData) % To; +} + +StackMapParser::StackMapParser(char *SMData) { + // Header. + uint8_t Version = read(&SMData); + if (Version != SMVersion) + errx(EXIT_FAILURE, "expected stackmap version '%d', got version '%d'", + SMVersion, Version); + read(&SMData); // Skip reserved. + read(&SMData); // Skip reserved. + + // Multiplicities of tables. + uint32_t NumFunctions = read(&SMData); + uint32_t NumConstants = read(&SMData); + uint32_t NumRecords = read(&SMData); + + // Functions. + for (uint32_t I = 0; I < NumFunctions; I++) + Funcs.push_back({read(&SMData), read(&SMData), + read(&SMData)}); + + // Constants. + for (uint64_t I = 0; I < NumConstants; I++) + Consts.push_back(read(&SMData)); + + // Records. + for (uint64_t I = 0; I < NumRecords; I++) { + uint64_t ID = read(&SMData); + uint32_t InstrOff = read(&SMData); + read(&SMData); // skip reserved. + uint16_t NumLocs = read(&SMData); + + vector Locs; + for (uint16_t J = 0; J < NumLocs; J++) { + uint8_t Where = read(&SMData); + read(&SMData); // Skip reserved. + uint16_t Size = read(&SMData); + uint16_t RegNum = read(&SMData); + read(&SMData); // Skip reserved. + int32_t OffsetOrSmallConst = read(&SMData); + Locs.push_back({StorageClass(Where), Size, RegNum, OffsetOrSmallConst}); + } + + alignTo(&SMData, 8); // Pad if necessary. + read(&SMData); // Skip reserved. + + // Liveouts. + uint16_t NumLiveOuts = read(&SMData); + vector LiveOuts; + for (uint64_t I = 0; I < NumLiveOuts; I++) { + uint16_t RegNum = read(&SMData); + read(&SMData); // Skip reserved. + uint8_t Size = read(&SMData); + LiveOuts.push_back({RegNum, Size}); + } + + alignTo(&SMData, 8); // Pad if necessary. + + Recs.insert({ID, {InstrOff, Locs, LiveOuts}}); + } +} + +SMRec *StackMapParser::getRecord(uint64_t ID) { + map::iterator It = Recs.find(ID); + if (It == Recs.end()) + return nullptr; + return &It->second; +} + +string getStorageClassName(StorageClass C) { + switch (C) { + case Reg: + return "Register"; + case Direct: + return "Direct"; + case Indirect: + return "Indirect"; + case Const: + return "Constant"; + case ConstIndex: + return "ConstIndex"; + default: + errx(EXIT_FAILURE, "unreachable"); + } +} Index: MultiSource/UnitTests/Stackmaps/input_x86_64.ll =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/input_x86_64.ll @@ -0,0 +1,87 @@ +declare void @sm_set_inspect_sizes(i32, ...) +declare void @sm_inspect() +declare void @llvm.experimental.patchpoint.void(i64, i32, i8*, i32, ...) + +define void @integer_tests(i64 %test_input) noinline { + %fptr = bitcast void()* @sm_inspect to i8* + + ; Test constant integers. + call void (i32, ...) @sm_set_inspect_sizes(i32 4, i64 1, i64 2, i64 3, i64 4); + call void(i64, i32, i8*, i32, ...) @llvm.experimental.patchpoint.void( + i64 0, i32 16, i8* %fptr, i32 0, + i8 50, i16 51, i32 52, i64 53) + ret void +} + +; Test integers in registers. +define void @integer_tests2(i64 %test_input) noinline { + %fptr = bitcast void()* @sm_inspect to i8* + %test_input_plus1 = add i64 %test_input, 1 + %test_input_plus2 = add i64 %test_input, 2 + %test_input_plus3 = add i64 %test_input, 3 + %arg8 = trunc i64 %test_input_plus1 to i8 + %arg16 = trunc i64 %test_input_plus2 to i16 + %arg32 = trunc i64 %test_input_plus3 to i32 + call void (i32, ...) @sm_set_inspect_sizes(i32 4, i64 1, i64 2, i64 4, i64 8); + call void(i64, i32, i8*, i32, ...) @llvm.experimental.patchpoint.void( + i64 1, i32 16, i8* %fptr, i32 0, + i8 %arg8, i16 %arg16, i32 %arg32, i64 %test_input) + + ret void +} + +; Test integers on the stack. +define void @integer_tests3(i64 %test_input) noinline { + %fptr = bitcast void()* @sm_inspect to i8* + + %test_input_plus1 = add i64 %test_input, 1 + %test_input_plus2 = add i64 %test_input, 2 + %test_input_plus3 = add i64 %test_input, 3 + %arg8 = trunc i64 %test_input_plus1 to i8 + %arg16 = trunc i64 %test_input_plus2 to i16 + %arg32 = trunc i64 %test_input_plus3 to i32 + + ; Force local variables to be spilled. + call void asm sideeffect "", "~{rax},~{rdx},~{rcx},~{rbx},~{rsi},~{rdi},~{rbp},~{rsp},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15}"() + + call void (i32, ...) @sm_set_inspect_sizes(i32 4, i64 1, i64 2, i64 4, i64 8); + call void(i64, i32, i8*, i32, ...) @llvm.experimental.patchpoint.void( + i64 2, i32 16, i8* %fptr, i32 0, + i8 %arg8, i16 %arg16, i32 %arg32, i64 %test_input) + + ret void +} + +define void @frame_tests() noinline { + %fptr = bitcast void()* @sm_inspect to i8* + + %a8 = alloca i8 + %a16 = alloca i16 + %a32 = alloca i32 + %a64 = alloca i64 + store i8 170, i8* %a8 ; 0xaa + store i16 48059, i16* %a16 ; 0xbbbb + store i32 3435973836, i32* %a32 ; 0xcccccccc + store i64 15987178197214944733, i64* %a64 ; 0xdddddddddddddddd + + call void (i32, ...) @sm_set_inspect_sizes(i32 4, i64 1, i64 2, i64 4, i64 8); + call void(i64, i32, i8*, i32, ...) @llvm.experimental.patchpoint.void( + i64 3, i32 16, i8* %fptr, i32 0, i8* %a8, i16* %a16, i32* %a32, i64* %a64) + + ret void +} + +define void @run_tests() noinline { + ; 0x8877665544332211 + %in1 = call i64 asm sideeffect "nop", "=r,0"(i64 9833440827789222417) + call void @integer_tests(i64 %in1) + + ; 0x1122334455667788 + %in2 = call i64 asm sideeffect "", "=r,0"(i64 4822678189205111) + call void @integer_tests2(i64 %in2) + call void @integer_tests3(i64 %in2) + + call void @frame_tests() + + ret void +} Index: MultiSource/UnitTests/Stackmaps/stackmaps.reference_output.x86_64 =================================================================== --- /dev/null +++ MultiSource/UnitTests/Stackmaps/stackmaps.reference_output.x86_64 @@ -0,0 +1,89 @@ +StackMap #0 + Location #0 + Storage: Constant + Storage Size: 8 + Inspect Size: 1 + Bytes: 32 + Location #1 + Storage: Constant + Storage Size: 8 + Inspect Size: 2 + Bytes: 33 00 + Location #2 + Storage: Constant + Storage Size: 8 + Inspect Size: 3 + Bytes: 34 00 00 + Location #3 + Storage: Constant + Storage Size: 8 + Inspect Size: 4 + Bytes: 35 00 00 00 + +StackMap #1 + Location #0 + Storage: Register + Storage Size: 1 + Inspect Size: 1 + Bytes: 78 + Location #1 + Storage: Register + Storage Size: 2 + Inspect Size: 2 + Bytes: 79 66 + Location #2 + Storage: Register + Storage Size: 4 + Inspect Size: 4 + Bytes: 7a 66 55 44 + Location #3 + Storage: Register + Storage Size: 8 + Inspect Size: 8 + Bytes: 77 66 55 44 33 22 11 00 + +StackMap #2 + Location #0 + Storage: Indirect + Storage Size: 1 + Inspect Size: 1 + Bytes: 78 + Location #1 + Storage: Indirect + Storage Size: 2 + Inspect Size: 2 + Bytes: 79 66 + Location #2 + Storage: Indirect + Storage Size: 4 + Inspect Size: 4 + Bytes: 7a 66 55 44 + Location #3 + Storage: Indirect + Storage Size: 8 + Inspect Size: 8 + Bytes: 77 66 55 44 33 22 11 00 + +StackMap #3 + Location #0 + Storage: Direct + Storage Size: 8 + Inspect Size: 1 + Bytes: aa + Location #1 + Storage: Direct + Storage Size: 8 + Inspect Size: 2 + Bytes: bb bb + Location #2 + Storage: Direct + Storage Size: 8 + Inspect Size: 4 + Bytes: cc cc cc cc + Location #3 + Storage: Direct + Storage Size: 8 + Inspect Size: 8 + Bytes: dd dd dd dd dd dd dd dd + +exit 0 Index: cmake/modules/SingleMultiSource.cmake =================================================================== --- cmake/modules/SingleMultiSource.cmake +++ cmake/modules/SingleMultiSource.cmake @@ -113,6 +113,8 @@ set(REFERENCE_OUTPUT ${name}.reference_output.${ENDIAN}-endian) elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.reference_output.${TARGET_OS}) set(REFERENCE_OUTPUT ${name}.reference_output.${TARGET_OS}) + elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.reference_output.${CMAKE_SYSTEM_PROCESSOR}) + set(REFERENCE_OUTPUT ${name}.reference_output.${CMAKE_SYSTEM_PROCESSOR}) elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.reference_output) set(REFERENCE_OUTPUT ${name}.reference_output) else()