From 42046b6e2f1c0cc61b2628b4bf7f64f6697f7a17 Mon Sep 17 00:00:00 2001 From: NeverSightAI Date: Thu, 15 Oct 2026 05:57:42 -0700 Subject: [PATCH] [X86] Preserve source SEH state ordering during nonlocal unwind --- llvm/lib/CodeGen/CMakeLists.txt | 1 + llvm/lib/CodeGen/WinEHPrepare.cpp | 2 + llvm/lib/CodeGen/WinEHX86State.cpp | 76 +++++++++++++++ llvm/lib/CodeGen/WinEHX86State.h | 20 ++++ .../X86/win32-seh-source-state-order.ll | 95 +++++++++++++++++++ 5 files changed, 194 insertions(+) create mode 100644 llvm/lib/CodeGen/WinEHX86State.cpp create mode 100644 llvm/lib/CodeGen/WinEHX86State.h create mode 100644 llvm/test/CodeGen/X86/win32-seh-source-state-order.ll diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt index 1456065b62d5bc..7e3e9b200aa10f 100644 --- a/llvm/lib/CodeGen/CMakeLists.txt +++ b/llvm/lib/CodeGen/CMakeLists.txt @@ -258,6 +258,7 @@ add_llvm_component_library(LLVMCodeGen WindowsSecureHotPatching.cpp WinEHPrepare.cpp WinEHRewrite.cpp + WinEHX86State.cpp XRayInstrumentation.cpp ${GeneratedMLSources} diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp index 23e7a61fafb00a..ba069e02eee8be 100644 --- a/llvm/lib/CodeGen/WinEHPrepare.cpp +++ b/llvm/lib/CodeGen/WinEHPrepare.cpp @@ -16,6 +16,7 @@ //===----------------------------------------------------------------------===// #include "llvm/CodeGen/WinEHPrepare.h" +#include "WinEHX86State.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/STLExtras.h" @@ -672,6 +673,7 @@ void llvm::calculateSEHStateNumbers(const Function *Fn, ::calculateSEHStateNumbers(FuncInfo, FirstNonPHI, -1); } + orderWinX86SEHRewriteStates(*Fn, FuncInfo); calculateStateNumbersForInvokes(Fn, FuncInfo); bool IsEHa = Fn->getParent()->getModuleFlag("eh-asynch"); diff --git a/llvm/lib/CodeGen/WinEHX86State.cpp b/llvm/lib/CodeGen/WinEHX86State.cpp new file mode 100644 index 00000000000000..323b7edb8cadb9 --- /dev/null +++ b/llvm/lib/CodeGen/WinEHX86State.cpp @@ -0,0 +1,76 @@ +//===- WinEHX86State.cpp - Rewrite-owned x86 SEH states -------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "WinEHX86State.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/CodeGen/WinEHFuncInfo.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/TargetParser/Triple.h" +#include + +using namespace llvm; + +void llvm::orderWinX86SEHRewriteStates(const Function &F, WinEHFuncInfo &Info) { + const Triple &Target = F.getParent()->getTargetTriple(); + if (!F.hasFnAttribute(mc_rewrite::RewriteWinX86RegistrationStateAttribute) || + Target.getArch() != Triple::x86 || !Target.isWindowsMSVCEnvironment() || + Info.SEHUnwindMap.empty()) + return; + + // The CRT stops local unwind when the current numeric state is at or below + // the saved state. A depth-first ordering preserves enclosing edges but can + // reverse two sibling regions, changing setjmp/longjmp behavior. Complete + // source scope identities also specify the ordering of those siblings. + // Unannotated frontend graphs retain their normal numbering. + if (llvm::any_of(Info.SEHUnwindMap, + [](const auto &Entry) { return !Entry.RewriteSemantic; })) + return; + SmallVector Order(Info.SEHUnwindMap.size()); + std::iota(Order.begin(), Order.end(), 0); + for (const auto &Entry : Info.SEHUnwindMap) { + const auto &Token = *Entry.RewriteSemantic; + if (Token.Kind != mc_rewrite::RewriteWinEHSemanticKind::SEHScope || + Token.Clause) + report_fatal_error("invalid x86 SEH rewrite state identity"); + } + auto SourceState = [&](unsigned Old) { + return Info.SEHUnwindMap[Old].RewriteSemantic->Region; + }; + llvm::sort(Order, [&](unsigned A, unsigned B) { + return SourceState(A) < SourceState(B); + }); + SmallVector Remap(Order.size()); + for (unsigned New = 0; New != Order.size(); ++New) { + if (New && SourceState(Order[New - 1]) == SourceState(Order[New])) + report_fatal_error("duplicate x86 SEH rewrite state identity"); + Remap[Order[New]] = New; + } + auto MapState = [&](int State) { + if (State < 0) + return State; + if (unsigned(State) >= Remap.size()) + report_fatal_error("invalid x86 SEH rewrite state reference"); + return Remap[State]; + }; + SmallVector Ordered; + for (unsigned Old : Order) { + auto Entry = Info.SEHUnwindMap[Old]; + Entry.ToState = MapState(Entry.ToState); + if (Entry.ToState >= int(Ordered.size())) + report_fatal_error( + "x86 SEH rewrite enclosing state must precede its scope"); + Ordered.push_back(std::move(Entry)); + } + for (auto &[Pad, State] : Info.EHPadStateMap) + State = MapState(State); + for (auto &[Pad, State] : Info.FuncletBaseStateMap) + State = MapState(State); + Info.SEHUnwindMap = std::move(Ordered); +} diff --git a/llvm/lib/CodeGen/WinEHX86State.h b/llvm/lib/CodeGen/WinEHX86State.h new file mode 100644 index 00000000000000..133187767bcc06 --- /dev/null +++ b/llvm/lib/CodeGen/WinEHX86State.h @@ -0,0 +1,20 @@ +//===- WinEHX86State.h - Rewrite-owned x86 SEH states -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_CODEGEN_WINEHX86STATE_H +#define LLVM_LIB_CODEGEN_WINEHX86STATE_H + +namespace llvm { +class Function; +struct WinEHFuncInfo; + +/// Preserve source state comparisons before assigning invoke and async states. +void orderWinX86SEHRewriteStates(const Function &F, WinEHFuncInfo &Info); +} // namespace llvm + +#endif diff --git a/llvm/test/CodeGen/X86/win32-seh-source-state-order.ll b/llvm/test/CodeGen/X86/win32-seh-source-state-order.ll new file mode 100644 index 00000000000000..d99793474e00be --- /dev/null +++ b/llvm/test/CodeGen/X86/win32-seh-source-state-order.ll @@ -0,0 +1,95 @@ +; RUN: llc -O0 < %s | FileCheck %s +; RUN: llc -O2 < %s | FileCheck %s +; RUN: opt -passes='default' -S < %s | llc -O2 | FileCheck %s +; RUN: llc -O2 -filetype=obj < %s -o %t.obj +; RUN: sed 's/i8 1, i32 1, i32 0/i8 1, i32 0, i32 0/' %s | not --crash llc -filetype=obj -o /dev/null 2>&1 | FileCheck %s --check-prefix=DUPLICATE +; RUN: sed 's/i8 1, i32 0, i32 0/i8 1, i32 9, i32 0/' %s | not --crash llc -filetype=obj -o /dev/null 2>&1 | FileCheck %s --check-prefix=OUTER +; +; DUPLICATE: duplicate x86 SEH rewrite state identity +; OUTER: x86 SEH rewrite enclosing state must precede its scope +; +; The CRT compares the live state with the saved numeric setjmp bound. The +; predecessor traversal used to number this source graph 0,3,4,1,2, causing +; longjmp from scope 4 to scope 1 to skip the finally entirely. + +target datalayout = "e-m:x-p:32:32-i64:64-n8:16:32-S32" +target triple = "i686-pc-windows-msvc" + +@llvm.compiler.used = appending global [2 x ptr] [ptr @filter, ptr @cleanup], section "llvm.metadata" + +; CHECK-LABEL: _parent: +; CHECK: calll __setjmp3 +; CHECK-DAG: Lparent$seh_state$0$1$2$3$4 = 0 +; CHECK-DAG: Lparent$seh_state$1$5$6$7$8 = 1 +; CHECK-DAG: Lparent$seh_state$2$9$A$B$C = 2 +; CHECK-DAG: Lparent$seh_state$3$D$E$F$10 = 3 +; CHECK-DAG: Lparent$seh_state$4$11$12$13$14 = 4 + +define void @parent(ptr %buffer) #0 personality ptr @_except_handler3 { +entry: + invoke void @may_throw(ptr null) to label %capture unwind label %scope2 +capture: + %saved = invoke i32 (ptr, i32, ...) @_setjmp3(ptr %buffer, i32 0) + to label %active unwind label %scope1 +active: + invoke void @may_throw(ptr %buffer) to label %done unwind label %finally4 +scope0: + %switch0 = catchswitch within none [label %handler0] unwind to caller +handler0: + %pad0 = catchpad within %switch0 [ptr @filter], !llvm.rewrite.windows-eh.semantic !0 + catchret from %pad0 to label %done +scope1: + %switch1 = catchswitch within none [label %handler1] unwind label %scope0 +handler1: + %pad1 = catchpad within %switch1 [ptr @filter], !llvm.rewrite.windows-eh.semantic !1 + catchret from %pad1 to label %done +scope2: + %switch2 = catchswitch within none [label %handler2] unwind label %scope1 +handler2: + %pad2 = catchpad within %switch2 [ptr @filter], !llvm.rewrite.windows-eh.semantic !2 + catchret from %pad2 to label %done +scope3: + %switch3 = catchswitch within none [label %handler3] unwind label %scope0 +handler3: + %pad3 = catchpad within %switch3 [ptr @filter], !llvm.rewrite.windows-eh.semantic !3 + catchret from %pad3 to label %done +finally4: + %pad4 = cleanuppad within none [], !llvm.rewrite.windows-eh.semantic !4 + %frame = call ptr @llvm.localaddress() + call void @cleanup(i8 1, ptr %frame) [ "funclet"(token %pad4) ] + cleanupret from %pad4 unwind label %scope3 +done: + ret void +} + +define internal i32 @filter() #1 { +entry: + %runtime = call ptr @llvm.frameaddress.p0(i32 1) + %frame = call ptr @llvm.eh.recoverfp(ptr @parent, ptr %runtime) + call void @llvm.x86.seh.setstate(ptr @parent, ptr %frame, i32 2, i64 9, i64 10, i64 11, i64 12, i64 1) + ret i32 -1 +} + +define internal void @cleanup(i8 %abnormal, ptr %frame) #1 { +entry: + call void @llvm.x86.seh.setstate(ptr @parent, ptr %frame, i32 3, i64 13, i64 14, i64 15, i64 16, i64 2) + call void @may_throw(ptr %frame) + ret void +} + +declare void @may_throw(ptr) +declare i32 @_except_handler3(...) +declare i32 @_setjmp3(ptr, i32, ...) returns_twice +declare ptr @llvm.localaddress() +declare ptr @llvm.frameaddress.p0(i32 immarg) +declare ptr @llvm.eh.recoverfp(ptr, ptr) +declare void @llvm.x86.seh.setstate(ptr, ptr, i32 immarg, i64 immarg, i64 immarg, i64 immarg, i64 immarg, i64 immarg) + +attributes #0 = { noinline "frame-pointer"="all" "llvm.rewrite.win-x86-registration-state" } +attributes #1 = { noinline "frame-pointer"="all" } + +!0 = !{i32 1, i8 1, i32 0, i32 0, i64 1, i64 2, i64 3, i64 4} +!1 = !{i32 1, i8 1, i32 1, i32 0, i64 5, i64 6, i64 7, i64 8} +!2 = !{i32 1, i8 1, i32 2, i32 0, i64 9, i64 10, i64 11, i64 12} +!3 = !{i32 1, i8 1, i32 3, i32 0, i64 13, i64 14, i64 15, i64 16} +!4 = !{i32 1, i8 1, i32 4, i32 0, i64 17, i64 18, i64 19, i64 20}