Index: lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h =================================================================== --- lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h +++ lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h @@ -59,6 +59,7 @@ DotResult = UINT64_MAX - 1, ///< .result DotLocal = UINT64_MAX - 2, ///< .local DotEndFunc = UINT64_MAX - 3, ///< .endfunc + DotIndIdx = UINT64_MAX - 4, /// < .indidx }; } // end namespace WebAssembly @@ -123,7 +124,8 @@ case WebAssembly::STORE_I64: case WebAssembly::STORE_F64: return 3; - default: llvm_unreachable("Only loads and stores have p2align values"); + default: + llvm_unreachable("Only loads and stores have p2align values"); } } Index: lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h =================================================================== --- lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h +++ lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h @@ -43,6 +43,8 @@ size_t NumResults) { llvm_unreachable("emitIndirectFunctionType not implemented"); } + /// .indidx + virtual void emitIndIdx(const MCExpr *Value) = 0; }; /// This part is for ascii assembly output @@ -59,6 +61,7 @@ void emitIndirectFunctionType(StringRef name, SmallVectorImpl &SignatureVTs, size_t NumResults) override; + void emitIndIdx(const MCExpr *Value) override; }; /// This part is for ELF object output @@ -70,6 +73,7 @@ void emitResult(ArrayRef Types) override; void emitLocal(ArrayRef Types) override; void emitEndFunc() override; + void emitIndIdx(const MCExpr *Value) override; }; } // end namespace llvm Index: lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp =================================================================== --- lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp +++ lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp @@ -67,13 +67,18 @@ void WebAssemblyTargetAsmStreamer::emitIndirectFunctionType( StringRef name, SmallVectorImpl &SignatureVTs, size_t NumResults) { OS << "\t.functype\t" << name; - if (NumResults == 0) OS << ", void"; + if (NumResults == 0) + OS << ", void"; for (auto Ty : SignatureVTs) { OS << ", " << WebAssembly::TypeToString(Ty); } OS << "\n"; } +void WebAssemblyTargetAsmStreamer::emitIndIdx(const MCExpr *Value) { + OS << "\t.indidx \t" << *Value << '\n'; +} + // FIXME: What follows is not the real binary encoding. static void EncodeTypes(MCStreamer &Streamer, ArrayRef Types) { @@ -100,3 +105,8 @@ void WebAssemblyTargetELFStreamer::emitEndFunc() { Streamer.EmitIntValue(WebAssembly::DotEndFunc, sizeof(uint64_t)); } + +void WebAssemblyTargetELFStreamer::emitIndIdx(const MCExpr *Value) { + Streamer.EmitIntValue(WebAssembly::DotIndIdx, sizeof(uint64_t)); + Streamer.EmitValue(Value, sizeof(uint64_t)); +} Index: lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -183,6 +183,14 @@ SmallVector ResultVTs; const Function &F(*MF->getFunction()); + + // Emit the function index. + if (MDNode *Idx = F.getMetadata("wasm.index")) { + assert(Idx->getNumOperands() == 1); + + getTargetStreamer()->emitIndIdx(AsmPrinter::lowerConstant(cast(Idx->getOperand(0))->getValue())); + } + ComputeLegalValueVTs(F, TM, F.getReturnType(), ResultVTs); // If the return type needs to be legalized it will get converted into Index: lib/Transforms/IPO/LowerTypeTests.cpp =================================================================== --- lib/Transforms/IPO/LowerTypeTests.cpp +++ lib/Transforms/IPO/LowerTypeTests.cpp @@ -13,7 +13,6 @@ //===----------------------------------------------------------------------===// #include "llvm/Transforms/IPO/LowerTypeTests.h" -#include "llvm/Transforms/IPO.h" #include "llvm/ADT/EquivalenceClasses.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/Triple.h" @@ -30,6 +29,7 @@ #include "llvm/Pass.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/IPO.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" using namespace llvm; @@ -79,8 +79,7 @@ if (!Result) return false; COffset += APOffset.getZExtValue(); - return containsValue(DL, GlobalLayout, GEP->getPointerOperand(), - COffset); + return containsValue(DL, GlobalLayout, GEP->getPointerOperand(), COffset); } if (auto Op = dyn_cast(V)) { @@ -222,6 +221,9 @@ IntegerType *Int64Ty; IntegerType *IntPtrTy; + // Indirect function call index assignment counter for WebAssembly + uint64_t IndirectIndex; + // Mapping from type identifiers to the call sites that test them. DenseMap> TypeTestCallSites; @@ -250,6 +252,10 @@ void verifyTypeMDNode(GlobalObject *GO, MDNode *Type); void buildBitSetsFromFunctions(ArrayRef TypeIds, ArrayRef Functions); + void buildBitSetsFromFunctionsX86(ArrayRef TypeIds, + ArrayRef Functions); + void buildBitSetsFromFunctionsWASM(ArrayRef TypeIds, + ArrayRef Functions); void buildBitSetsFromDisjointSet(ArrayRef TypeIds, ArrayRef Globals); bool lower(); @@ -282,6 +288,8 @@ Int64Ty = Type::getInt64Ty(M->getContext()); IntPtrTy = DL.getIntPtrType(M->getContext(), 0); + IndirectIndex = 0; + TypeTestCallSites.clear(); return false; @@ -290,8 +298,7 @@ /// Build a bit set for TypeId using the object layouts in /// GlobalLayout. BitSetInfo LowerTypeTests::buildBitSet( - Metadata *TypeId, - const DenseMap &GlobalLayout) { + Metadata *TypeId, const DenseMap &GlobalLayout) { BitSetBuilder BSB; // Compute the byte offset of each address associated with this type @@ -304,8 +311,9 @@ if (Type->getOperand(1) != TypeId) continue; uint64_t Offset = - cast(cast(Type->getOperand(0)) - ->getValue())->getZExtValue(); + cast( + cast(Type->getOperand(0))->getValue()) + ->getZExtValue(); BSB.addOffset(GlobalAndOffset.second + Offset); } } @@ -334,8 +342,8 @@ // we know the offset and mask to use. auto ByteArrayGlobal = new GlobalVariable( *M, Int8Ty, /*isConstant=*/true, GlobalValue::PrivateLinkage, nullptr); - auto MaskGlobal = new GlobalVariable( - *M, Int8Ty, /*isConstant=*/true, GlobalValue::PrivateLinkage, nullptr); + auto MaskGlobal = new GlobalVariable(*M, Int8Ty, /*isConstant=*/true, + GlobalValue::PrivateLinkage, nullptr); ByteArrayInfos.emplace_back(); ByteArrayInfo *BAI = &ByteArrayInfos.back(); @@ -611,8 +619,7 @@ void LowerTypeTests::verifyTypeMDNode(GlobalObject *GO, MDNode *Type) { if (Type->getNumOperands() != 2) - report_fatal_error( - "All operands of type metadata must have 2 elements"); + report_fatal_error("All operands of type metadata must have 2 elements"); if (GO->isThreadLocal()) report_fatal_error("Bit set element may not be thread-local"); @@ -635,9 +642,6 @@ static const unsigned kX86JumpTableEntrySize = 8; unsigned LowerTypeTests::getJumpTableEntrySize() { - if (Arch != Triple::x86 && Arch != Triple::x86_64) - report_fatal_error("Unsupported architecture for jump tables"); - return kX86JumpTableEntrySize; } @@ -648,9 +652,6 @@ Constant *LowerTypeTests::createJumpTableEntry(GlobalObject *Src, Function *Dest, unsigned Distance) { - if (Arch != Triple::x86 && Arch != Triple::x86_64) - report_fatal_error("Unsupported architecture for jump tables"); - const unsigned kJmpPCRel32Code = 0xe9; const unsigned kInt3Code = 0xcc; @@ -675,9 +676,6 @@ } Type *LowerTypeTests::getJumpTableEntryType() { - if (Arch != Triple::x86 && Arch != Triple::x86_64) - report_fatal_error("Unsupported architecture for jump tables"); - return StructType::get(M->getContext(), {Int8Ty, Int32Ty, Int8Ty, Int8Ty, Int8Ty}, /*Packed=*/true); @@ -687,6 +685,17 @@ /// for the functions, build the bit sets and lower the llvm.type.test calls. void LowerTypeTests::buildBitSetsFromFunctions(ArrayRef TypeIds, ArrayRef Functions) { + if (Arch == Triple::x86 || Arch == Triple::x86_64) { + buildBitSetsFromFunctionsX86(TypeIds, Functions); + } else if (Arch == Triple::wasm32 || Arch == Triple::wasm64) { + buildBitSetsFromFunctionsWASM(TypeIds, Functions); + } else { + report_fatal_error("Unsupported architecture for jump tables"); + } +} + +void LowerTypeTests::buildBitSetsFromFunctionsX86( + ArrayRef TypeIds, ArrayRef Functions) { // Unlike the global bitset builder, the function bitset builder cannot // re-arrange functions in a particular order and base its calculations on the // layout of the functions' entry points, as we have no idea how large a @@ -818,6 +827,32 @@ ConstantArray::get(JumpTableType, JumpTableEntries)); } +// Assign a dummy layout based using an incrementing counter, and store this as +// metadata. During generation of the indexed indirect function call table, the +// backend will ensure that the appropriate indexes are used. +void LowerTypeTests::buildBitSetsFromFunctionsWASM( + ArrayRef TypeIds, ArrayRef Functions) { + assert(!Functions.empty()); + + // Build a dummy layout. + DenseMap GlobalLayout; + for (Function *F : Functions) + GlobalLayout[F] = IndirectIndex++; + + // Pass a NULL pointer as the subtracted "jump table" offset. + lowerTypeTestCalls(TypeIds, + ConstantPointerNull::get(cast(Int32PtrTy)), + GlobalLayout); + + // Generate metadata for the indirect function indexes. + for (const auto &P : GlobalLayout) { + MDNode *MD = MDNode::get(P.first->getContext(), + ArrayRef(ConstantAsMetadata::get( + ConstantInt::get(Int64Ty, P.second)))); + cast(P.first)->setMetadata("wasm.index", MD); + } +} + void LowerTypeTests::buildBitSetsFromDisjointSet( ArrayRef TypeIds, ArrayRef Globals) { llvm::DenseMap TypeIdIndices; @@ -923,8 +958,7 @@ auto BitSetMDVal = dyn_cast(CI->getArgOperand(1)); if (!BitSetMDVal) - report_fatal_error( - "Second argument of llvm.type.test must be metadata"); + report_fatal_error("Second argument of llvm.type.test must be metadata"); auto BitSet = BitSetMDVal->getMetadata(); // Add the call site to the list of call sites for this type identifier. We @@ -962,7 +996,8 @@ for (GlobalClassesTy::iterator I = GlobalClasses.begin(), E = GlobalClasses.end(); I != E; ++I) { - if (!I->isLeader()) continue; + if (!I->isLeader()) + continue; ++NumTypeIdDisjointSets; unsigned MaxIndex = 0; Index: test/CodeGen/WebAssembly/cfi.ll =================================================================== --- /dev/null +++ test/CodeGen/WebAssembly/cfi.ll @@ -0,0 +1,57 @@ +; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt | FileCheck %s + +; Tests that we correctly assign indexes for control flow integrity. + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +@0 = private constant [0 x i8] zeroinitializer + +; CHECK-LABEL: f: +; CHECK: .indidx 0 +define void @f() !type !0 !wasm.index !1 { + ret void +} + +; CHECK-LABEL: g: +; CHECK: .indidx 1 +define void @g() !type !2 !wasm.index !3 { + ret void +} + +; Function Attrs: nounwind readnone +declare i1 @llvm.type.test(i8*, metadata) #0 + +; Function Attrs: noreturn nounwind +declare void @llvm.trap() #3 + +; CHECK-LABEL: foo: +; CHECK: br_if +; CHECK: br_if +; CHECK: unreachable +define i1 @foo(i8* %p) { + %1 = ptrtoint i8* %p to i32 + %2 = icmp eq i32 %1, 0 + br i1 %2, label %cont1, label %trap + +trap: ; preds = %entry + tail call void @llvm.trap() #1 + unreachable + +cont1: + %3 = ptrtoint i8* %p to i32 + %4 = icmp eq i32 %3, 1 + br i1 %4, label %cont2, label %trap + +cont2: + %z = add i1 %2, %4 + ret i1 %z +} + +attributes #0 = { nounwind readnone } +attributes #1 = { noreturn nounwind } + +!0 = !{i32 0, !"typeid1"} +!1 = !{i64 0} +!2 = !{i32 0, !"typeid2"} +!3 = !{i64 1} Index: test/Transforms/LowerTypeTests/function-disjoint.ll =================================================================== --- /dev/null +++ test/Transforms/LowerTypeTests/function-disjoint.ll @@ -0,0 +1,44 @@ +; RUN: opt -S -lowertypetests -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck --check-prefix=X64 %s +; RUN: opt -S -lowertypetests -mtriple=wasm32-unknown-unknown < %s | FileCheck --check-prefix=WASM32 %s + +; Tests that we correctly handle bitsets with disjoint call target sets. + +target datalayout = "e-p:64:64" + +; X64: @[[JT0:.*]] = private constant [1 x <{ i8, i32, i8, i8, i8 }>] [<{ i8, i32, i8, i8, i8 }> <{ i8 -23, i32 trunc (i64 sub (i64 sub (i64 ptrtoint (void ()* @[[FNAME:.*]] to i64), i64 ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT0]] to i64)), i64 5) to i32), i8 -52, i8 -52, i8 -52 }>], section ".text" +; X64: @[[JT1:.*]] = private constant [1 x <{ i8, i32, i8, i8, i8 }>] [<{ i8, i32, i8, i8, i8 }> <{ i8 -23, i32 trunc (i64 sub (i64 sub (i64 ptrtoint (void ()* @[[GNAME:.*]] to i64), i64 ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT1]] to i64)), i64 5) to i32), i8 -52, i8 -52, i8 -52 }>], section ".text" +; WASM32: private constant [0 x i8] zeroinitializer + +; X64: @f = alias void (), bitcast ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT0]] to void ()*) +; X64: @g = alias void (), bitcast ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT1]] to void ()*) + +; X64: define private void @[[FNAME]]() +; WASM32: define void @f() !type !{{[0-9]+}} !wasm.index ![[I0:[0-9]+]] +define void @f() !type !0 { + ret void +} + +; X64: define private void @[[GNAME]]() +; WASM32: define void @g() !type !{{[0-9]+}} !wasm.index ![[I1:[0-9]+]] +define void @g() !type !1 { + ret void +} + +!0 = !{i32 0, !"typeid1"} +!1 = !{i32 0, !"typeid2"} + +declare i1 @llvm.type.test(i8* %ptr, metadata %bitset) nounwind readnone + +define i1 @foo(i8* %p) { + ; X64: icmp eq i64 {{.*}}, ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT0]] to i64) + ; WASM32: icmp eq i64 {{.*}}, 0 + %x = call i1 @llvm.type.test(i8* %p, metadata !"typeid1") + ; X64: icmp eq i64 {{.*}}, ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT1]] to i64) + ; WASM32: icmp eq i64 {{.*}}, 1 + %y = call i1 @llvm.type.test(i8* %p, metadata !"typeid2") + %z = add i1 %x, %y + ret i1 %z +} + +; WASM32: ![[I0]] = !{i64 0} +; WASM32: ![[I1]] = !{i64 1} Index: test/Transforms/LowerTypeTests/function-ext.ll =================================================================== --- test/Transforms/LowerTypeTests/function-ext.ll +++ test/Transforms/LowerTypeTests/function-ext.ll @@ -1,16 +1,18 @@ -; RUN: opt -S -lowertypetests < %s | FileCheck %s +; RUN: opt -S -lowertypetests -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck --check-prefix=X64 %s +; RUN: opt -S -lowertypetests -mtriple=wasm32-unknown-unknown < %s | FileCheck --check-prefix=WASM32 %s ; Tests that we correctly handle external references, including the case where ; all functions in a bitset are external references. -target triple = "x86_64-unknown-linux-gnu" +; X64: @[[JT:.*]] = private constant [1 x <{ i8, i32, i8, i8, i8 }>] [<{ i8, i32, i8, i8, i8 }> <{ i8 -23, i32 trunc (i64 sub (i64 sub (i64 ptrtoint (void ()* @foo to i64), i64 ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64)), i64 5) to i32), i8 -52, i8 -52, i8 -52 }>], section ".text" +; WASM32: private constant [0 x i8] zeroinitializer +; WASM32: declare !type !{{[0-9]+}} !wasm.index ![[I0:[0-9]+]] void @foo() declare !type !0 void @foo() -; CHECK: @[[JT:.*]] = private constant [1 x <{ i8, i32, i8, i8, i8 }>] [<{ i8, i32, i8, i8, i8 }> <{ i8 -23, i32 trunc (i64 sub (i64 sub (i64 ptrtoint (void ()* @foo to i64), i64 ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64)), i64 5) to i32), i8 -52, i8 -52, i8 -52 }>], section ".text" - define i1 @bar(i8* %ptr) { - ; CHECK: icmp eq i64 {{.*}}, ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64) + ; X64: icmp eq i64 {{.*}}, ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64) + ; WASM32: icmp eq i64 {{.*}}, 0 %p = call i1 @llvm.type.test(i8* %ptr, metadata !"void") ret i1 %p } @@ -18,3 +20,4 @@ declare i1 @llvm.type.test(i8* %ptr, metadata %bitset) nounwind readnone !0 = !{i64 0, !"void"} +; WASM: ![[I0]] = !{i64 0} Index: test/Transforms/LowerTypeTests/function.ll =================================================================== --- test/Transforms/LowerTypeTests/function.ll +++ test/Transforms/LowerTypeTests/function.ll @@ -1,22 +1,24 @@ -; RUN: opt -S -lowertypetests < %s | FileCheck %s +; RUN: opt -S -lowertypetests -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck --check-prefix=X64 %s +; RUN: opt -S -lowertypetests -mtriple=wasm32-unknown-unknown < %s | FileCheck --check-prefix=WASM32 %s -; Tests that we correctly create a jump table for bitsets containing 2 or more -; functions. +; Tests that we correctly handle bitsets containing 2 or more functions. -target triple = "x86_64-unknown-linux-gnu" target datalayout = "e-p:64:64" -; CHECK: @[[JT:.*]] = private constant [2 x <{ i8, i32, i8, i8, i8 }>] [<{ i8, i32, i8, i8, i8 }> <{ i8 -23, i32 trunc (i64 sub (i64 sub (i64 ptrtoint (void ()* @[[FNAME:.*]] to i64), i64 ptrtoint ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64)), i64 5) to i32), i8 -52, i8 -52, i8 -52 }>, <{ i8, i32, i8, i8, i8 }> <{ i8 -23, i32 trunc (i64 sub (i64 sub (i64 ptrtoint (void ()* @[[GNAME:.*]] to i64), i64 ptrtoint ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64)), i64 13) to i32), i8 -52, i8 -52, i8 -52 }>], section ".text" +; X64: @[[JT:.*]] = private constant [2 x <{ i8, i32, i8, i8, i8 }>] [<{ i8, i32, i8, i8, i8 }> <{ i8 -23, i32 trunc (i64 sub (i64 sub (i64 ptrtoint (void ()* @[[FNAME:.*]] to i64), i64 ptrtoint ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64)), i64 5) to i32), i8 -52, i8 -52, i8 -52 }>, <{ i8, i32, i8, i8, i8 }> <{ i8 -23, i32 trunc (i64 sub (i64 sub (i64 ptrtoint (void ()* @[[GNAME:.*]] to i64), i64 ptrtoint ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64)), i64 13) to i32), i8 -52, i8 -52, i8 -52 }>], section ".text" +; WASM32: private constant [0 x i8] zeroinitializer -; CHECK: @f = alias void (), bitcast ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to void ()*) -; CHECK: @g = alias void (), bitcast (<{ i8, i32, i8, i8, i8 }>* getelementptr inbounds ([2 x <{ i8, i32, i8, i8, i8 }>], [2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]], i64 0, i64 1) to void ()*) +; X64: @f = alias void (), bitcast ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to void ()*) +; X64: @g = alias void (), bitcast (<{ i8, i32, i8, i8, i8 }>* getelementptr inbounds ([2 x <{ i8, i32, i8, i8, i8 }>], [2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]], i64 0, i64 1) to void ()*) -; CHECK: define private void @[[FNAME]]() +; X64: define private void @[[FNAME]]() +; WASM32: define void @f() !type !{{[0-9]+}} !wasm.index ![[I0:[0-9]+]] define void @f() !type !0 { ret void } -; CHECK: define private void @[[GNAME]]() +; X64: define private void @[[GNAME]]() +; WASM32: define void @g() !type !{{[0-9]+}} !wasm.index ![[I1:[0-9]+]] define void @g() !type !0 { ret void } @@ -26,7 +28,12 @@ declare i1 @llvm.type.test(i8* %ptr, metadata %bitset) nounwind readnone define i1 @foo(i8* %p) { - ; CHECK: sub i64 {{.*}}, ptrtoint ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64) + ; X64: sub i64 {{.*}}, ptrtoint ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64) + ; WASM32: sub i64 {{.*}}, 0 + ; WASM32: icmp ult i64 {{.*}}, 2 %x = call i1 @llvm.type.test(i8* %p, metadata !"typeid1") ret i1 %x } + +; WASM32: ![[I0]] = !{i64 0} +; WASM32: ![[I1]] = !{i64 1}