diff --git a/llvm/include/llvm/MC/MCSymbolWasm.h b/llvm/include/llvm/MC/MCSymbolWasm.h --- a/llvm/include/llvm/MC/MCSymbolWasm.h +++ b/llvm/include/llvm/MC/MCSymbolWasm.h @@ -26,6 +26,7 @@ wasm::WasmSignature *Signature = nullptr; Optional GlobalType; Optional EventType; + Optional TableIndex; /// An expression describing how to calculate the size of a symbol. If a /// symbol has no size this field will be NULL. @@ -114,6 +115,13 @@ return EventType.getValue(); } void setEventType(wasm::WasmEventType ET) { EventType = ET; } + + bool hasTableIndex() const { return isFunction() && TableIndex; } + uint64_t getTableIndex() const { + assert(TableIndex.hasValue()); + return TableIndex.getValue(); + } + void setTableIndex(uint64_t idx) { TableIndex = idx; } }; } // end namespace llvm diff --git a/llvm/include/llvm/MC/MCWasmObjectWriter.h b/llvm/include/llvm/MC/MCWasmObjectWriter.h --- a/llvm/include/llvm/MC/MCWasmObjectWriter.h +++ b/llvm/include/llvm/MC/MCWasmObjectWriter.h @@ -28,6 +28,10 @@ public: virtual ~MCWasmObjectTargetWriter(); + // When we create the indirect function table we start at 1, so that there is + // an empty slot at 0 and therefore calling a null function pointer will trap. + static const uint32_t InitialTableOffset = 1; + Triple::ObjectFormatType getFormat() const override { return Triple::Wasm; } static bool classof(const MCObjectTargetWriter *W) { return W->getFormat() == Triple::Wasm; diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp --- a/llvm/lib/MC/WasmObjectWriter.cpp +++ b/llvm/lib/MC/WasmObjectWriter.cpp @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// #include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/BinaryFormat/Wasm.h" #include "llvm/Config/llvm-config.h" #include "llvm/MC/MCAsmBackend.h" @@ -39,10 +39,6 @@ namespace { -// Went we ceate the indirect function table we start at 1, so that there is -// and emtpy slot at 0 and therefore calling a null function pointer will trap. -static const uint32_t InitialTableOffset = 1; - // For patching purposes, we need to remember where each section starts, both // for patching up the section size field, and for patching up references to // locations within the section. @@ -183,6 +179,22 @@ } #endif +struct WasmTablePair { + uint32_t TableIndex; + uint32_t FunctionIndex; + + WasmTablePair(uint32_t TIdx, uint32_t FIdx) + : TableIndex(TIdx), FunctionIndex(FIdx) {} + + bool operator<(const WasmTablePair &Other) const { + return this->TableIndex < Other.TableIndex; + } + + bool operator==(const WasmTablePair &Other) const { + return this->TableIndex == Other.TableIndex; + } +}; + // Write X as an (unsigned) LEB value at offset Offset in Stream, padded // to allow patching. template @@ -330,7 +342,7 @@ uint32_t NumElements); void writeFunctionSection(ArrayRef Functions); void writeExportSection(ArrayRef Exports); - void writeElemSection(ArrayRef TableElems); + void writeElemSection(const SmallSet &TableElems); void writeDataCountSection(); uint32_t writeCodeSection(const MCAssembler &Asm, const MCAsmLayout &Layout, ArrayRef Functions); @@ -531,7 +543,12 @@ if (FixupSection.isWasmData()) { DataRelocations.push_back(Rec); } else if (FixupSection.getKind().isText()) { - CodeRelocations.push_back(Rec); + // Insert symbols with pre-assigned indices at the front, to avoid + // subsequent conflict if an index has already been assigned + if (Rec.Symbol->hasTableIndex()) + CodeRelocations.insert(CodeRelocations.begin(), Rec); + else + CodeRelocations.push_back(Rec); } else if (FixupSection.getKind().isMetadata()) { CustomSectionsRelocations[&FixupSection].push_back(Rec); } else { @@ -564,7 +581,7 @@ cast(Layout.getBaseSymbol(*RelEntry.Symbol)); assert(Base->isFunction()); if (RelEntry.Type == wasm::R_WASM_TABLE_INDEX_REL_SLEB) - return TableIndices[Base] - InitialTableOffset; + return TableIndices[Base] - MCWasmObjectTargetWriter::InitialTableOffset; else return TableIndices[Base]; } @@ -864,7 +881,8 @@ endSection(Section); } -void WasmObjectWriter::writeElemSection(ArrayRef TableElems) { +void WasmObjectWriter::writeElemSection( + const SmallSet &TableElems) { if (TableElems.empty()) return; @@ -876,12 +894,12 @@ // init expr for starting offset W.OS << char(wasm::WASM_OPCODE_I32_CONST); - encodeSLEB128(InitialTableOffset, W.OS); + encodeSLEB128(MCWasmObjectTargetWriter::InitialTableOffset, W.OS); W.OS << char(wasm::WASM_OPCODE_END); encodeULEB128(TableElems.size(), W.OS); - for (uint32_t Elem : TableElems) - encodeULEB128(Elem, W.OS); + for (auto &P : TableElems) + encodeULEB128(P.FunctionIndex, W.OS); endSection(Section); } @@ -1186,7 +1204,7 @@ // Collect information from the available symbols. SmallVector Functions; - SmallVector TableElems; + SmallSet TableElems; SmallVector Imports; SmallVector Exports; SmallVector Events; @@ -1620,12 +1638,23 @@ const MCSymbolWasm *Base = cast(Layout.getBaseSymbol(*Rel.Symbol)); uint32_t FunctionIndex = WasmIndices.find(Base)->second; - uint32_t TableIndex = TableElems.size() + InitialTableOffset; - if (TableIndices.try_emplace(Base, TableIndex).second) { + uint32_t TableIndex = + Rel.Symbol->hasTableIndex() + ? Rel.Symbol->getTableIndex() + : TableElems.size() + + MCWasmObjectTargetWriter::InitialTableOffset; + auto P = TableIndices.try_emplace(Base, TableIndex); + if (P.second) { LLVM_DEBUG(dbgs() << " -> adding " << Base->getName() << " to table: " << TableIndex << "\n"); - TableElems.push_back(FunctionIndex); + if (!TableElems.insert(WasmTablePair(TableIndex, FunctionIndex)).second) + report_fatal_error(Twine(Base->getName()) + ": table index " + + Twine(TableIndex) + " has already been assigned!"); registerFunctionType(*Base); + } else if (Rel.Symbol->hasTableIndex() && P.first->second != TableIndex) { + report_fatal_error(Twine(Rel.Symbol->getName()) + + ": symbol has conflicting table indices " + + Twine(P.first->second) + ", " + Twine(TableIndex)); } }; diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h @@ -81,7 +81,7 @@ void emitLocal(ArrayRef Types) override; void emitEndFunc() override; void emitFunctionType(const MCSymbolWasm *Sym) override {} - void emitIndIdx(const MCExpr *Value) override; + void emitIndIdx(const MCExpr *Value) override {} void emitGlobalType(const MCSymbolWasm *Sym) override {} void emitEventType(const MCSymbolWasm *Sym) override {} void emitImportModule(const MCSymbolWasm *Sym, diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp @@ -123,7 +123,3 @@ void WebAssemblyTargetWasmStreamer::emitEndFunc() { llvm_unreachable(".end_func is not needed for direct wasm output"); } - -void WebAssemblyTargetWasmStreamer::emitIndIdx(const MCExpr *Value) { - llvm_unreachable(".indidx encoding not yet implemented"); -} diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -118,6 +118,16 @@ // delcarations work in asm files. getTargetStreamer()->emitFunctionType(Sym); + // Set/emit the assigned function index for CFI. + if (MDNode *Idx = F.getMetadata("wasm.index")) { + assert(Idx->getNumOperands() == 1); + + auto V = cast(Idx->getOperand(0))->getValue(); + Sym->setTableIndex(cast(V)->getZExtValue()); + + getTargetStreamer()->emitIndIdx(AsmPrinter::lowerConstant(V)); + } + if (TM.getTargetTriple().isOSBinFormatWasm() && F.hasFnAttribute("wasm-import-module")) { StringRef Name = @@ -307,12 +317,14 @@ // FIXME: clean up how params and results are emitted (use signatures) getTargetStreamer()->emitFunctionType(WasmSym); - // Emit the function index. + // Set/emit the assigned function index for CFI. if (MDNode *Idx = F.getMetadata("wasm.index")) { assert(Idx->getNumOperands() == 1); - getTargetStreamer()->emitIndIdx(AsmPrinter::lowerConstant( - cast(Idx->getOperand(0))->getValue())); + auto V = cast(Idx->getOperand(0))->getValue(); + WasmSym->setTableIndex(cast(V)->getZExtValue()); + + getTargetStreamer()->emitIndIdx(AsmPrinter::lowerConstant(V)); } SmallVector Locals; diff --git a/llvm/lib/Transforms/IPO/LowerTypeTests.cpp b/llvm/lib/Transforms/IPO/LowerTypeTests.cpp --- a/llvm/lib/Transforms/IPO/LowerTypeTests.cpp +++ b/llvm/lib/Transforms/IPO/LowerTypeTests.cpp @@ -55,6 +55,7 @@ #include "llvm/IR/User.h" #include "llvm/IR/Value.h" #include "llvm/InitializePasses.h" +#include "llvm/MC/MCWasmObjectWriter.h" #include "llvm/Pass.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Casting.h" @@ -400,7 +401,7 @@ IntegerType *IntPtrTy = M.getDataLayout().getIntPtrType(M.getContext(), 0); // Indirect function call index assignment counter for WebAssembly - uint64_t IndirectIndex = 1; + uint64_t IndirectIndex = MCWasmObjectTargetWriter::InitialTableOffset; // Mapping from type identifiers to the call sites that test them, as well as // whether the type identifier needs to be exported to ThinLTO backends as @@ -767,15 +768,19 @@ // result, causing the comparison to fail if they are nonzero. The rotate // also conveniently gives us a bit offset to use during the load from // the bitset. - Value *OffsetSHR = - B.CreateLShr(PtrOffset, ConstantExpr::getZExt(TIL.AlignLog2, IntPtrTy)); - Value *OffsetSHL = B.CreateShl( - PtrOffset, ConstantExpr::getZExt( - ConstantExpr::getSub( - ConstantInt::get(Int8Ty, DL.getPointerSizeInBits(0)), - TIL.AlignLog2), - IntPtrTy)); - Value *BitOffset = B.CreateOr(OffsetSHR, OffsetSHL); + Value *BitOffset; + if (!TIL.AlignLog2->isZeroValue()) { + Value *OffsetSHR = + B.CreateLShr(PtrOffset, ConstantExpr::getZExt(TIL.AlignLog2, IntPtrTy)); + Value *OffsetSHL = B.CreateShl( + PtrOffset, ConstantExpr::getZExt( + ConstantExpr::getSub( + ConstantInt::get(Int8Ty, DL.getPointerSizeInBits(0)), + TIL.AlignLog2), + IntPtrTy)); + BitOffset = B.CreateOr(OffsetSHR, OffsetSHL); + } else + BitOffset = PtrOffset; Value *OffsetInRange = B.CreateICmpULE(BitOffset, TIL.SizeM1); @@ -914,22 +919,33 @@ ExportSummary->getOrInsertTypeIdSummary(TypeId).TTRes; TTRes.TheKind = TIL.TheKind; - auto ExportGlobal = [&](StringRef Name, Constant *C) { - GlobalAlias *GA = - GlobalAlias::create(Int8Ty, 0, GlobalValue::ExternalLinkage, - "__typeid_" + TypeId + "_" + Name, C, &M); - GA->setVisibility(GlobalValue::HiddenVisibility); + auto ExportGlobal = [&](StringRef Name, Constant *C, bool useAlias) { + GlobalValue *GV; + const auto GVName = ("__typeid_" + TypeId + "_" + Name).str(); + if (useAlias) + GV = GlobalAlias::create(Int8Ty, 0, GlobalValue::ExternalLinkage, GVName, + C, &M); + else + GV = new GlobalVariable(M, C->getType(), true, + GlobalValue::ExternalLinkage, C, GVName); + GV->setVisibility(GlobalValue::HiddenVisibility); }; auto ExportConstant = [&](StringRef Name, uint64_t &Storage, Constant *C) { if (shouldExportConstantsAsAbsoluteSymbols()) - ExportGlobal(Name, ConstantExpr::getIntToPtr(C, Int8PtrTy)); + ExportGlobal(Name, ConstantExpr::getIntToPtr(C, Int8PtrTy), true); else Storage = cast(C)->getZExtValue(); }; - if (TIL.TheKind != TypeTestResolution::Unsat) - ExportGlobal("global_addr", TIL.OffsetedGlobal); + if (TIL.TheKind != TypeTestResolution::Unsat) { + auto *CE = dyn_cast(TIL.OffsetedGlobal); + if (CE && CE->getOpcode() == Instruction::IntToPtr) { + assert(!shouldExportConstantsAsAbsoluteSymbols()); + ExportGlobal("global_addr", CE->getOperand(0), false); + } else + ExportGlobal("global_addr", TIL.OffsetedGlobal, true); + } if (TIL.TheKind == TypeTestResolution::ByteArray || TIL.TheKind == TypeTestResolution::Inline || @@ -945,9 +961,9 @@ } if (TIL.TheKind == TypeTestResolution::ByteArray) { - ExportGlobal("byte_array", TIL.TheByteArray); + ExportGlobal("byte_array", TIL.TheByteArray, true); if (shouldExportConstantsAsAbsoluteSymbols()) - ExportGlobal("bit_mask", TIL.BitMask); + ExportGlobal("bit_mask", TIL.BitMask, true); else return &TTRes.BitMask; } @@ -1120,7 +1136,9 @@ void LowerTypeTestsModule::lowerTypeTestCalls( ArrayRef TypeIds, Constant *CombinedGlobalAddr, const DenseMap &GlobalLayout) { - CombinedGlobalAddr = ConstantExpr::getBitCast(CombinedGlobalAddr, Int8PtrTy); + if (CombinedGlobalAddr) + CombinedGlobalAddr = + ConstantExpr::getBitCast(CombinedGlobalAddr, Int8PtrTy); // For each type identifier in this disjoint set... for (Metadata *TypeId : TypeIds) { @@ -1136,8 +1154,13 @@ ByteArrayInfo *BAI = nullptr; TypeIdLowering TIL; - TIL.OffsetedGlobal = ConstantExpr::getGetElementPtr( - Int8Ty, CombinedGlobalAddr, ConstantInt::get(IntPtrTy, BSI.ByteOffset)), + TIL.OffsetedGlobal = + CombinedGlobalAddr + ? ConstantExpr::getGetElementPtr( + Int8Ty, CombinedGlobalAddr, + ConstantInt::get(IntPtrTy, BSI.ByteOffset)) + : ConstantExpr::getIntToPtr( + ConstantInt::get(IntPtrTy, BSI.ByteOffset), Int8PtrTy); TIL.AlignLog2 = ConstantInt::get(Int8Ty, BSI.AlignLog2); TIL.SizeM1 = ConstantInt::get(IntPtrTy, BSI.BitSize - 1); if (BSI.isAllOnes()) { @@ -1579,11 +1602,12 @@ // Build consecutive monotonic integer ranges for each call target set DenseMap GlobalLayout; + // Perform two passes; first one for imports only for (GlobalTypeMember *GTM : Functions) { Function *F = cast(GTM->getGlobal()); - // Skip functions that are not address taken, to avoid bloating the table - if (!F->hasAddressTaken()) + // Skip non-address-taken functions, or functions that are not imports + if (!F->hasAddressTaken() || !F->isDeclaration()) continue; // Store metadata with the index for each function @@ -1596,10 +1620,27 @@ GlobalLayout[GTM] = IndirectIndex++; } - // The indirect function table index space starts at zero, so pass a NULL - // pointer as the subtracted "jump table" offset. - lowerTypeTestCalls(TypeIds, ConstantPointerNull::get(Int32PtrTy), - GlobalLayout); + // Perform two passes; second one for definitions only + for (GlobalTypeMember *GTM : Functions) { + Function *F = cast(GTM->getGlobal()); + + // Skip non-address-taken functions, or functions that are imports + if (!F->hasAddressTaken() || F->isDeclaration()) + continue; + + // Store metadata with the index for each function + MDNode *MD = MDNode::get(F->getContext(), + ArrayRef(ConstantAsMetadata::get( + ConstantInt::get(Int64Ty, IndirectIndex)))); + F->setMetadata("wasm.index", MD); + + // Assign the counter value + GlobalLayout[GTM] = IndirectIndex++; + } + + // Don't pass CombinedGlobal because no jump table is used and absolute + // symbols are not supported + lowerTypeTestCalls(TypeIds, nullptr, GlobalLayout); } void LowerTypeTestsModule::buildBitSetsFromDisjointSet( diff --git a/llvm/test/CodeGen/WebAssembly/cfi-obj.ll b/llvm/test/CodeGen/WebAssembly/cfi-obj.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/cfi-obj.ll @@ -0,0 +1,65 @@ +; RUN: opt -S -lowertypetests < %s | llc -filetype=obj | obj2yaml | 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 unnamed_addr constant [2 x void (...)*] [void (...)* bitcast (void ()* @f to void (...)*), void (...)* bitcast (void ()* @g to void (...)*)], align 16 + +define void @h() !type !0 { + ret void +} + +define void @f() !type !0 { + ret void +} + +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 +declare void @llvm.trap() nounwind noreturn + +define i1 @foo(i8* %p) { + %x = call i1 @llvm.type.test(i8* %p, metadata !"typeid1") + br i1 %x, label %contx, label %trap + +trap: + tail call void @llvm.trap() #1 + unreachable + +contx: + %y = call i1 @llvm.type.test(i8* %p, metadata !"typeid2") + br i1 %y, label %conty, label %trap + +conty: + %z = add i1 %x, %y + ret i1 %z +} + +; CHECK: - Type: ELEM +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1 +; CHECK-NEXT: Functions: [ 1, 2 ] + +; CHECK: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: Version: 2 +; CHECK-NEXT: SymbolTable: +; CHECK: - Index: 1 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Name: f +; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: Function: 1 +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Name: g +; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: Function: 2 diff --git a/llvm/test/MC/WebAssembly/cfi.ll b/llvm/test/MC/WebAssembly/cfi.ll new file mode 100644 --- /dev/null +++ b/llvm/test/MC/WebAssembly/cfi.ll @@ -0,0 +1,55 @@ +; RUN: llc -O2 -filetype=obj %s -o - | obj2yaml | FileCheck %s + +target triple = "wasm32-unknown-unknown" + +declare i32 @import1() +declare !wasm.index !0 i32 @import2() +declare i32 @import3() + +; call the imports to make sure they are included in the imports section +define hidden void @call_imports() { +entry: + %call1 = call i32 @import1() + %call3 = call i32 @import3() + ret void +} + +define i32 @define1() !wasm.index !1 { + ret i32 0 +} + +; take the address of the second import and the first definition. they should be +; placed at indices 1 and 2 in the table. +define hidden void @call_indirect() { +entry: + %addr_i2 = alloca i32 ()*, align 4 + %addr_d1 = alloca i32 ()*, align 4 + store i32 ()* @import2, i32 ()** %addr_i2, align 4 + store i32 ()* @define1, i32 ()** %addr_d1, align 4 + ret void +} + +!0 = !{i64 1} +!1 = !{i64 2} + +; CHECK: - Type: ELEM +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1 +; CHECK-NEXT: Functions: [ 2, 4 ] + +; CHECK: - Type: CUSTOM +; CHECK-NEXT: Name: linking +; CHECK-NEXT: Version: 2 +; CHECK-NEXT: SymbolTable: +; CHECK: - Index: 3 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Name: define1 +; CHECK-NEXT: Flags: [ ] +; CHECK-NEXT: Function: 4 +; CHECK: - Index: 6 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Name: import2 +; CHECK-NEXT: Flags: [ UNDEFINED ] +; CHECK-NEXT: Function: 2 diff --git a/llvm/test/Transforms/LowerTypeTests/function-disjoint.ll b/llvm/test/Transforms/LowerTypeTests/function-disjoint.ll --- a/llvm/test/Transforms/LowerTypeTests/function-disjoint.ll +++ b/llvm/test/Transforms/LowerTypeTests/function-disjoint.ll @@ -30,10 +30,10 @@ define i1 @foo(i8* %p) { ; X64: icmp eq i64 {{.*}}, ptrtoint (void ()* @[[JT0]] to i64) - ; WASM32: icmp eq i64 {{.*}}, ptrtoint (i8* getelementptr (i8, i8* null, i64 1) to i64) + ; WASM32: icmp eq i64 {{.*}}, 1 %x = call i1 @llvm.type.test(i8* %p, metadata !"typeid1") ; X64: icmp eq i64 {{.*}}, ptrtoint (void ()* @[[JT1]] to i64) - ; WASM32: icmp eq i64 {{.*}}, mul (i64 ptrtoint (i8* getelementptr (i8, i8* null, i32 1) to i64), i64 2) + ; WASM32: icmp eq i64 {{.*}}, 2 %y = call i1 @llvm.type.test(i8* %p, metadata !"typeid2") %z = add i1 %x, %y ret i1 %z diff --git a/llvm/test/Transforms/LowerTypeTests/function-ext.ll b/llvm/test/Transforms/LowerTypeTests/function-ext.ll --- a/llvm/test/Transforms/LowerTypeTests/function-ext.ll +++ b/llvm/test/Transforms/LowerTypeTests/function-ext.ll @@ -29,6 +29,7 @@ ; CHECK-LABEL: @addrtaken define void()* @addrtaken() { ; X64: ret void ()* @[[JT:.*]] + ; WASM32: ret void ()* @foo1 ret void()* @foo1 } diff --git a/llvm/test/Transforms/LowerTypeTests/function.ll b/llvm/test/Transforms/LowerTypeTests/function.ll --- a/llvm/test/Transforms/LowerTypeTests/function.ll +++ b/llvm/test/Transforms/LowerTypeTests/function.ll @@ -42,7 +42,7 @@ define i1 @foo(i8* %p) { ; NATIVE: sub i64 {{.*}}, ptrtoint (void ()* @[[JT]] to i64) - ; WASM32: sub i64 {{.*}}, ptrtoint (i8* getelementptr (i8, i8* null, i64 1) to i64) + ; WASM32: sub i64 {{.*}}, 1 ; WASM32: icmp ule i64 {{.*}}, 1 %x = call i1 @llvm.type.test(i8* %p, metadata !"typeid1") ret i1 %x