[WebAssembly] Add isEHScopeReturn instruction property

Summary:
So far, `isReturn` property is used to mean both a return instruction
from a functon and the end of an EH scope, a scope that starts with a EH
scope entry BB and ends with a catchret or a cleanupret instruction.
Because WinEH uses funclets, all EH-scope-ending instructions are also
real return instruction from a function. But for wasm, they only serve
as the end marker of an EH scope but not a return instruction that
exits a function. This mismatch caused incorrect prolog and epilog
generation in wasm EH scopes. This patch fixes this.

This patch is in the same vein with rL333045, which splits
`MachineBasicBlock::isEHFuncletEntry` into `isEHFuncletEntry` and
`isEHScopeEntry`.

Reviewers: dschuff

Subscribers: sbc100, jgravelle-google, sunfish, llvm-commits

Differential Revision: https://reviews.llvm.org/D50653

llvm-svn: 340325
This commit is contained in:
Heejin Ahn 2018-08-21 19:44:11 +00:00
parent a3219fe801
commit 8e7bbf9abf
12 changed files with 85 additions and 5 deletions

View File

@ -569,6 +569,12 @@ public:
return !empty() && back().isReturn(); return !empty() && back().isReturn();
} }
/// Convenience function that returns true if the bock ends in a EH scope
/// return instruction.
bool isEHScopeReturnBlock() const {
return !empty() && back().isEHScopeReturn();
}
/// Split the critical edge from this block to the given successor block, and /// Split the critical edge from this block to the given successor block, and
/// return the newly created block, or null if splitting is not possible. /// return the newly created block, or null if splitting is not possible.
/// ///

View File

@ -616,6 +616,12 @@ public:
return hasProperty(MCID::Return, Type); return hasProperty(MCID::Return, Type);
} }
/// Return true if this is an instruction that marks the end of an EH scope,
/// i.e., a catchpad or a cleanuppad instruction.
bool isEHScopeReturn(QueryType Type = AnyInBundle) const {
return hasProperty(MCID::EHScopeReturn, Type);
}
bool isCall(QueryType Type = AnyInBundle) const { bool isCall(QueryType Type = AnyInBundle) const {
return hasProperty(MCID::Call, Type); return hasProperty(MCID::Call, Type);
} }

View File

@ -120,6 +120,7 @@ enum Flag {
HasOptionalDef, HasOptionalDef,
Pseudo, Pseudo,
Return, Return,
EHScopeReturn,
Call, Call,
Barrier, Barrier,
Terminator, Terminator,

View File

@ -439,6 +439,7 @@ class Instruction {
// instruction. // instruction.
bit isReturn = 0; // Is this instruction a return instruction? bit isReturn = 0; // Is this instruction a return instruction?
bit isBranch = 0; // Is this instruction a branch instruction? bit isBranch = 0; // Is this instruction a branch instruction?
bit isEHScopeReturn = 0; // Does this instruction end an EH scope?
bit isIndirectBranch = 0; // Is this instruction an indirect branch? bit isIndirectBranch = 0; // Is this instruction an indirect branch?
bit isCompare = 0; // Is this instruction a comparison instruction? bit isCompare = 0; // Is this instruction a comparison instruction?
bit isMoveImm = 0; // Is this instruction a move immediate instruction? bit isMoveImm = 0; // Is this instruction a move immediate instruction?

View File

@ -650,7 +650,7 @@ static void collectEHScopeMembers(
// Returns are boundaries where scope transfer can occur, don't follow // Returns are boundaries where scope transfer can occur, don't follow
// successors. // successors.
if (Visiting->isReturnBlock()) if (Visiting->isEHScopeReturnBlock())
continue; continue;
for (const MachineBasicBlock *Succ : Visiting->successors()) for (const MachineBasicBlock *Succ : Visiting->successors())

View File

@ -185,10 +185,8 @@ defm CATCH_ALL : NRI<(outs), (ins), [], "catch_all", 0x05>;
} }
// Pseudo instructions: cleanupret / catchret // Pseudo instructions: cleanupret / catchret
// They are not return instructions in wasm, but setting 'isReturn' to true as
// in X86 is necessary for computing EH scope membership.
let isTerminator = 1, hasSideEffects = 1, isBarrier = 1, hasCtrlDep = 1, let isTerminator = 1, hasSideEffects = 1, isBarrier = 1, hasCtrlDep = 1,
isCodeGenOnly = 1, isReturn = 1 in { isCodeGenOnly = 1, isEHScopeReturn = 1 in {
defm CLEANUPRET : NRI<(outs), (ins), [(cleanupret)], "", 0>; defm CLEANUPRET : NRI<(outs), (ins), [(cleanupret)], "", 0>;
defm CATCHRET : NRI<(outs), (ins bb_op:$dst, bb_op:$from), defm CATCHRET : NRI<(outs), (ins bb_op:$dst, bb_op:$from),
[(catchret bb:$dst, bb:$from)], "", 0>; [(catchret bb:$dst, bb:$from)], "", 0>;

View File

@ -178,7 +178,7 @@ def EH_RETURN64 : I<0xC3, RawFrm, (outs), (ins GR64:$addr),
} }
let isTerminator = 1, hasSideEffects = 1, isBarrier = 1, hasCtrlDep = 1, let isTerminator = 1, hasSideEffects = 1, isBarrier = 1, hasCtrlDep = 1,
isCodeGenOnly = 1, isReturn = 1 in { isCodeGenOnly = 1, isReturn = 1, isEHScopeReturn = 1 in {
def CLEANUPRET : I<0, Pseudo, (outs), (ins), "# CLEANUPRET", [(cleanupret)]>; def CLEANUPRET : I<0, Pseudo, (outs), (ins), "# CLEANUPRET", [(cleanupret)]>;
// CATCHRET needs a custom inserter for SEH. // CATCHRET needs a custom inserter for SEH.

View File

@ -150,7 +150,71 @@ terminate10: ; preds = %ehcleanup7
unreachable unreachable
} }
; Tests prologues and epilogues are not generated within EH scopes.
; They should not be treated as funclets; BBs starting with a catch instruction
; should not have a prologue, and BBs ending with a catchret/cleanupret should
; not have an epilogue. This is separate from __stack_pointer restoring
; instructions after a catch instruction.
; CHECK-LABEL: test_no_prolog_epilog_in_ehpad
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: i32.catch
; CHECK-NOT: get_global $push{{.+}}=, __stack_pointer@GLOBAL
; CHECK: try
; CHECK: call foo@FUNCTION
; CHECK: catch_all
; TODO This should be removed too in a later patch
; CHECK-NO T: get_global $push{{.+}}=, __stack_pointer@GLOBAL
; CHECK: call __cxa_end_catch@FUNCTION
; CHECK-NOT: set_global __stack_pointer@GLOBAL, $pop{{.+}}
; CHECK: end_try
; CHECK-NOT: set_global __stack_pointer@GLOBAL, $pop{{.+}}
; CHECK: end_try
define void @test_no_prolog_epilog_in_ehpad() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
entry:
%stack_var = alloca i32, align 4
call void @bar(i32* %stack_var)
invoke void @foo()
to label %try.cont unwind label %catch.dispatch
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind to caller
catch.start: ; preds = %catch.dispatch
%1 = catchpad within %0 [i8* bitcast (i8** @_ZTIi to i8*)]
%2 = call i8* @llvm.wasm.get.exception(token %1)
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
%4 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*))
%matches = icmp eq i32 %3, %4
br i1 %matches, label %catch, label %rethrow
catch: ; preds = %catch.start
%5 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ]
%6 = bitcast i8* %5 to float*
%7 = load float, float* %6, align 4
invoke void @foo() [ "funclet"(token %1) ]
to label %invoke.cont1 unwind label %ehcleanup
invoke.cont1: ; preds = %catch
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %try.cont
rethrow: ; preds = %catch.start
call void @__cxa_rethrow() [ "funclet"(token %1) ]
unreachable
try.cont: ; preds = %entry, %invoke.cont1
ret void
ehcleanup: ; preds = %catch
%8 = cleanuppad within %1 []
call void @__cxa_end_catch() [ "funclet"(token %8) ]
cleanupret from %8 unwind to caller
}
declare void @foo() declare void @foo()
declare void @bar(i32*)
declare i32 @__gxx_wasm_personality_v0(...) declare i32 @__gxx_wasm_personality_v0(...)
declare i8* @llvm.wasm.get.exception(token) declare i8* @llvm.wasm.get.exception(token)
declare i32 @llvm.wasm.get.ehselector(token) declare i32 @llvm.wasm.get.ehselector(token)

View File

@ -302,6 +302,7 @@ CodeGenInstruction::CodeGenInstruction(Record *R)
AsmString = R->getValueAsString("AsmString"); AsmString = R->getValueAsString("AsmString");
isReturn = R->getValueAsBit("isReturn"); isReturn = R->getValueAsBit("isReturn");
isEHScopeReturn = R->getValueAsBit("isEHScopeReturn");
isBranch = R->getValueAsBit("isBranch"); isBranch = R->getValueAsBit("isBranch");
isIndirectBranch = R->getValueAsBit("isIndirectBranch"); isIndirectBranch = R->getValueAsBit("isIndirectBranch");
isCompare = R->getValueAsBit("isCompare"); isCompare = R->getValueAsBit("isCompare");

View File

@ -222,6 +222,7 @@ template <typename T> class ArrayRef;
// Various boolean values we track for the instruction. // Various boolean values we track for the instruction.
bool isReturn : 1; bool isReturn : 1;
bool isEHScopeReturn : 1;
bool isBranch : 1; bool isBranch : 1;
bool isIndirectBranch : 1; bool isIndirectBranch : 1;
bool isCompare : 1; bool isCompare : 1;

View File

@ -100,6 +100,7 @@ void EmitInstrDocs(RecordKeeper &RK, raw_ostream &OS) {
#define str(s) #s #define str(s) #s
#define FLAG(f) if (II->f) { FlagStrings.push_back(str(f)); } #define FLAG(f) if (II->f) { FlagStrings.push_back(str(f)); }
FLAG(isReturn) FLAG(isReturn)
FLAG(isEHScopeReturn)
FLAG(isBranch) FLAG(isBranch)
FLAG(isIndirectBranch) FLAG(isIndirectBranch)
FLAG(isCompare) FLAG(isCompare)

View File

@ -569,6 +569,7 @@ void InstrInfoEmitter::emitRecord(const CodeGenInstruction &Inst, unsigned Num,
// Emit all of the target independent flags... // Emit all of the target independent flags...
if (Inst.isPseudo) OS << "|(1ULL<<MCID::Pseudo)"; if (Inst.isPseudo) OS << "|(1ULL<<MCID::Pseudo)";
if (Inst.isReturn) OS << "|(1ULL<<MCID::Return)"; if (Inst.isReturn) OS << "|(1ULL<<MCID::Return)";
if (Inst.isEHScopeReturn) OS << "|(1ULL<<MCID::EHScopeReturn)";
if (Inst.isBranch) OS << "|(1ULL<<MCID::Branch)"; if (Inst.isBranch) OS << "|(1ULL<<MCID::Branch)";
if (Inst.isIndirectBranch) OS << "|(1ULL<<MCID::IndirectBranch)"; if (Inst.isIndirectBranch) OS << "|(1ULL<<MCID::IndirectBranch)";
if (Inst.isCompare) OS << "|(1ULL<<MCID::Compare)"; if (Inst.isCompare) OS << "|(1ULL<<MCID::Compare)";