Index: lib/Target/WebAssembly/Disassembler/WebAssemblyDisassembler.cpp =================================================================== --- lib/Target/WebAssembly/Disassembler/WebAssemblyDisassembler.cpp +++ lib/Target/WebAssembly/Disassembler/WebAssemblyDisassembler.cpp @@ -93,6 +93,7 @@ const MCOperandInfo &Info = Desc.OpInfo[i]; switch (Info.OperandType) { case MCOI::OPERAND_IMMEDIATE: + case WebAssembly::OPERAND_TABLE: case WebAssembly::OPERAND_P2ALIGN: case WebAssembly::OPERAND_BASIC_BLOCK: { if (Pos + sizeof(uint64_t) > Bytes.size()) Index: lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h =================================================================== --- lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h +++ lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h @@ -49,7 +49,9 @@ /// 64-bit floating-point immediates. OPERAND_FP64IMM, /// p2align immediate for load and store address alignment. - OPERAND_P2ALIGN + OPERAND_P2ALIGN, + /// table immediate to specify which indirect call table to use. + OPERAND_TABLE }; /// WebAssembly-specific directive identifiers. Index: lib/Target/WebAssembly/WebAssemblyFastISel.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyFastISel.cpp +++ lib/Target/WebAssembly/WebAssemblyFastISel.cpp @@ -697,6 +697,17 @@ auto MIB = BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DbgLoc, TII.get(Opc)); + // Add the table operand, default to zero + if (!IsDirect) { + if (MDNode *Idx = Call->getMetadata("wasm.index")) + MIB.addImm(cast(Idx->getOperand(0)) + ->getValue() + ->getUniqueInteger() + .getSExtValue()); + else + MIB.addImm(0); + } + if (!IsVoid) MIB.addReg(ResultReg, RegState::Define); @@ -1102,7 +1113,7 @@ BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DbgLoc, TII.get(Opc)) .addMBB(TBB) .addReg(CondReg); - + finishCondBranch(Br->getParent(), TBB, FBB); return true; } Index: lib/Target/WebAssembly/WebAssemblyInstrCall.td =================================================================== --- lib/Target/WebAssembly/WebAssemblyInstrCall.td +++ lib/Target/WebAssembly/WebAssemblyInstrCall.td @@ -29,9 +29,10 @@ def CALL_#vt : I<(outs vt:$dst), (ins i32imm:$callee, variable_ops), [(set vt:$dst, (WebAssemblycall1 (i32 imm:$callee)))], !strconcat(prefix, "call\t$dst, $callee")>; - def CALL_INDIRECT_#vt : I<(outs vt:$dst), (ins I32:$callee, variable_ops), + def CALL_INDIRECT_#vt : I<(outs vt:$dst), + (ins table_op:$table, I32:$callee, variable_ops), [(set vt:$dst, (WebAssemblycall1 I32:$callee))], - !strconcat(prefix, "call_indirect\t$dst, $callee")>; + !strconcat(prefix, "call_indirect.$table\t$dst, $callee")>; } let Uses = [SP32, SP64], isCall = 1 in { defm : CALL; @@ -41,10 +42,11 @@ def CALL_VOID : I<(outs), (ins i32imm:$callee, variable_ops), [(WebAssemblycall0 (i32 imm:$callee))], - "call \t$callee">; - def CALL_INDIRECT_VOID : I<(outs), (ins I32:$callee, variable_ops), + "call\t$callee">; + def CALL_INDIRECT_VOID : I<(outs), + (ins table_op:$table, I32:$callee, variable_ops), [(WebAssemblycall0 I32:$callee)], - "call_indirect\t$callee">; + "call_indirect.$table\t$callee">; } // Uses = [SP32,SP64], isCall = 1 } // Defs = [ARGUMENTS] Index: lib/Target/WebAssembly/WebAssemblyInstrInfo.td =================================================================== --- lib/Target/WebAssembly/WebAssemblyInstrInfo.td +++ lib/Target/WebAssembly/WebAssemblyInstrInfo.td @@ -83,6 +83,9 @@ } } // OperandType = "OPERAND_P2ALIGN" +let OperandType = "OPERAND_TABLE" in +def table_op : Operand; + } // OperandNamespace = "WebAssembly" //===----------------------------------------------------------------------===// Index: lib/Target/WebAssembly/WebAssemblyRegStackify.cpp =================================================================== --- lib/Target/WebAssembly/WebAssemblyRegStackify.cpp +++ lib/Target/WebAssembly/WebAssemblyRegStackify.cpp @@ -202,13 +202,13 @@ switch (MI->getOpcode()) { case WebAssembly::CALL_VOID: case WebAssembly::CALL_INDIRECT_VOID: - QueryCallee(MI, 0, Read, Write, Effects, StackPointer); + QueryCallee(MI, 1, Read, Write, Effects, StackPointer); break; case WebAssembly::CALL_I32: case WebAssembly::CALL_I64: case WebAssembly::CALL_F32: case WebAssembly::CALL_F64: case WebAssembly::CALL_INDIRECT_I32: case WebAssembly::CALL_INDIRECT_I64: case WebAssembly::CALL_INDIRECT_F32: case WebAssembly::CALL_INDIRECT_F64: - QueryCallee(MI, 1, Read, Write, Effects, StackPointer); + QueryCallee(MI, 2, Read, Write, Effects, StackPointer); break; default: llvm_unreachable("unexpected call opcode"); Index: lib/Transforms/IPO/LowerTypeTests.cpp =================================================================== --- lib/Transforms/IPO/LowerTypeTests.cpp +++ lib/Transforms/IPO/LowerTypeTests.cpp @@ -288,7 +288,8 @@ Int64Ty = Type::getInt64Ty(M->getContext()); IntPtrTy = DL.getIntPtrType(M->getContext(), 0); - IndirectIndex = 0; + // In WebAssembly, the default table is at index 0, so start creating at 1 + IndirectIndex = 1; TypeTestCallSites.clear(); @@ -828,9 +829,10 @@ 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. +// Assign each disjoint set to a different indirect function call table using +// an incrementing counter. Then, eliminate explicit type tests. This is safe +// because the type homogeneity of each disjoint set is checked at load-time. +// Additionally, calls to out of bounds table entries will trap at runtime. void LowerTypeTests::buildBitSetsFromFunctionsWASM( ArrayRef TypeIds, ArrayRef Functions) { assert(!Functions.empty()); @@ -847,15 +849,50 @@ ArrayRef(ConstantAsMetadata::get( ConstantInt::get(Int64Ty, IndirectIndex)))); F->setMetadata("wasm.index", MD); + } + + // Tag the indirect call with the table index, and eliminate the explicit type test. + for (Metadata *TypeId : TypeIds) { + for (CallInst *TypeTest : TypeTestCallSites[TypeId]) { + // Find the branch statement immediately following the bitset test call. + BranchInst *BI = nullptr; + for (Use &U : TypeTest->uses()) { + if ((isa(U.getUser()))) { + BI = cast(U.getUser()); + break; + } + } + + // Find the original indirect call in the 'cont' block. + CallInst *CI = nullptr; + if (BI && BI->getNumSuccessors() > 0) { + for (Instruction &I : *BI->getSuccessor(0)) { + if ((isa(I)) && !cast(I).getCalledFunction()) { + CI = &cast(I); + break; + } + } + } - GlobalLayout[F] = IndirectIndex++; + // Tag the indirect call with the table index + if (CI) { + MDNode *MD = MDNode::get(CI->getContext(), + ArrayRef(ConstantAsMetadata::get( + ConstantInt::get(Int64Ty, IndirectIndex)))); + CI->setMetadata("wasm.index", MD); + } + + // Replace each type test with true, and let simplifycfg remove the branch. + ConstantInt *True = ConstantInt::getTrue(TypeTest->getContext()); + TypeTest->replaceAllUsesWith(True); + TypeTest->eraseFromParent(); + + ++NumTypeTestCallsLowered; + } } - // The indirect function table index space starts at zero, so pass a NULL - // pointer as the subtracted "jump table" offset. - lowerTypeTestCalls(TypeIds, - ConstantPointerNull::get(cast(Int32PtrTy)), - GlobalLayout); + // Increment the counter to put each disjoint set in a different table. + IndirectIndex += 1; } void LowerTypeTests::buildBitSetsFromDisjointSet( Index: test/Transforms/LowerTypeTests/function-disjoint.ll =================================================================== --- test/Transforms/LowerTypeTests/function-disjoint.ll +++ test/Transforms/LowerTypeTests/function-disjoint.ll @@ -33,12 +33,14 @@ call void @f() call void @g() ; X64: icmp eq i64 {{.*}}, ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT0]] to i64) - ; WASM32: icmp eq i64 {{.*}}, 0 + ; WASM32-NOT: icmp ult 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 + ; WASM32-NOT: icmp ult i64 {{.*}}, 1 %y = call i1 @llvm.type.test(i8* %p, metadata !"typeid2") %z = add i1 %x, %y + ; WASM32-NOT: br i1 {{.*}} label %cont, label %trap + ; WASM32: add i1 true, true ret i1 %z } Index: test/Transforms/LowerTypeTests/function-ext.ll =================================================================== --- test/Transforms/LowerTypeTests/function-ext.ll +++ test/Transforms/LowerTypeTests/function-ext.ll @@ -12,8 +12,9 @@ define i1 @bar(i8* %ptr) { ; X64: icmp eq i64 {{.*}}, ptrtoint ([1 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64) - ; WASM32: sub i64 {{.*}}, 0 - ; WASM32: icmp ult i64 {{.*}}, 1 + ; WASM32-NOT: sub i64 {{.*}}, 0 + ; WASM32-NOT: icmp ult i64 {{.*}}, 1 + ; WASM32: ret i1 true %p = call i1 @llvm.type.test(i8* %ptr, metadata !"void") ret i1 %p } Index: test/Transforms/LowerTypeTests/function.ll =================================================================== --- test/Transforms/LowerTypeTests/function.ll +++ test/Transforms/LowerTypeTests/function.ll @@ -12,13 +12,13 @@ ; 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 ()*) ; X64: define private void @[[FNAME]]() -; WASM32: define void @f() !type !{{[0-9]+}} !wasm.index ![[I0:[0-9]+]] +; WASM32: define void @f() !type !{{[0-9]+}} !wasm.index ![[I:[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]+]] +; WASM32: define void @g() !type !{{[0-9]+}} !wasm.index ![[I]] define void @g() !type !0 { ret void } @@ -31,11 +31,12 @@ call void @f() call void @g() ; X64: sub i64 {{.*}}, ptrtoint ([2 x <{ i8, i32, i8, i8, i8 }>]* @[[JT]] to i64) - ; WASM32: sub i64 {{.*}}, 0 - ; WASM32: icmp ult i64 {{.*}}, 2 + ; WASM32-NOT: sub i64 {{.*}}, 0 + ; WASM32-NOT: icmp ult i64 {{.*}}, 2 + ; WASM32-NOT: br i1 {{.*}} label %cont, label %trap + ; WASM32: ret i1 true %x = call i1 @llvm.type.test(i8* %p, metadata !"typeid1") ret i1 %x } -; WASM32: ![[I0]] = !{i64 0} -; WASM32: ![[I1]] = !{i64 1} +; WASM32: ![[I]] = !{i64 0}