[clang] [libcxx] [Clang][AIX] Fix fopen 'x' mode via open(O_CREAT|O_EXCL)+fdopen (PR #209705)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 15 02:22:50 PDT 2026
https://github.com/Himadhith created https://github.com/llvm/llvm-project/pull/209705
AIX fopen(3) does not atomically handle the C11/C23 'x' mode.
Redirect fopen(path, "...x...") to a helper using open(O_CREAT|
O_EXCL)+fdopen. Remove XFAIL: LIBCXX-AIX-FIXME from fstream tests.
>From 0e35816ce0a42f3e497f97f791c7dca3c23ca758 Mon Sep 17 00:00:00 2001
From: himadhith <himadhith.v at ibm.com>
Date: Wed, 15 Jul 2026 14:41:42 +0530
Subject: [PATCH] [Clang][AIX] Fix fopen 'x' mode via
open(O_CREAT|O_EXCL)+fdopen
AIX fopen(3) does not atomically handle the C11/C23 'x' mode.
Redirect fopen(path, "...x...") to a helper using open(O_CREAT|
O_EXCL)+fdopen. Remove XFAIL: LIBCXX-AIX-FIXME from fstream tests.
---
clang/lib/CodeGen/CGExpr.cpp | 21 +++
clang/lib/CodeGen/CodeGenModule.cpp | 176 ++++++++++++++++++
clang/lib/CodeGen/CodeGenModule.h | 4 +
.../filebuf.members/open_pointer.pass.cpp | 2 -
.../fstreams/fstream.cons/pointer.pass.cpp | 2 -
.../fstream.members/open_pointer.pass.cpp | 2 -
.../fstreams/ofstream.cons/pointer.pass.cpp | 2 -
.../ofstream.members/open_pointer.pass.cpp | 2 -
8 files changed, 201 insertions(+), 10 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 6a0d127fb451b..7f42ac03bfa98 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -6517,6 +6517,27 @@ RValue CodeGenFunction::EmitCallExpr(const CallExpr *E,
return EmitCXXPseudoDestructorExpr(callee.getPseudoDestructorExpr());
}
+ // On AIX, redirect fopen(path, "...x...") to a helper that uses
+ // open(O_CREAT|O_EXCL)+fdopen, which handles exclusive creation atomically.
+ if (CGM.getTriple().isOSAIX()) {
+ if (const FunctionDecl *FD = E->getDirectCallee()) {
+ if (FD->getDeclName().isIdentifier() &&
+ FD->getName() == "fopen" && E->getNumArgs() == 2) {
+ const Expr *ModeArg = E->getArg(1)->IgnoreImpCasts();
+ if (const StringLiteral *SL = dyn_cast<StringLiteral>(ModeArg)) {
+ if (SL->getString().contains('x')) {
+ llvm::Function *Helper = CGM.getOrCreateAIXFOpenExclusiveHelper();
+ llvm::Value *PathVal = EmitScalarExpr(E->getArg(0));
+ llvm::Value *ModeVal = EmitScalarExpr(E->getArg(1));
+ llvm::CallInst *CI = Builder.CreateCall(Helper, {PathVal, ModeVal});
+ CI->setCallingConv(Helper->getCallingConv());
+ return RValue::get(CI);
+ }
+ }
+ }
+ }
+ }
+
return EmitCall(E->getCallee()->getType(), callee, E, ReturnValue,
/*Chain=*/nullptr, CallOrInvoke);
}
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index e155fdd752d7f..4d3449d2476f6 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -57,8 +57,12 @@
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/IR/AttributeMask.h"
+#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CallingConv.h"
+#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
@@ -8855,3 +8859,175 @@ void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) {
// even if destructor is only declared.
addDeferredDeclToEmit(VectorDtorGD);
}
+
+// Stable open(2) flag values from AIX <fcntl.h>.
+static constexpr unsigned AIX_O_RDONLY = 0x0;
+static constexpr unsigned AIX_O_WRONLY = 0x1;
+static constexpr unsigned AIX_O_RDWR = 0x2;
+static constexpr unsigned AIX_O_ACCMODE = 0x3;
+static constexpr unsigned AIX_O_APPEND = 0x8;
+static constexpr unsigned AIX_O_CREAT = 0x100;
+static constexpr unsigned AIX_O_TRUNC = 0x200;
+static constexpr unsigned AIX_O_EXCL = 0x400;
+
+// On AIX, fopen(3) does not atomically implement the C11/C23 'x' exclusive
+// mode. Lazily synthesises __aix_fopen_exclusive(path, mode) which uses
+// open(O_CREAT|O_EXCL)+fdopen so that exclusive creation is truly atomic.
+llvm::Function *CodeGenModule::getOrCreateAIXFOpenExclusiveHelper() {
+ assert(getTriple().isOSAIX() &&
+ "AIX fopen exclusive helper is only needed on AIX");
+
+ constexpr llvm::StringLiteral HelperName("__aix_fopen_exclusive");
+ llvm::Module &M = getModule();
+
+ if (llvm::Function *F = M.getFunction(HelperName))
+ return F;
+
+ llvm::LLVMContext &Ctx = getLLVMContext();
+ llvm::PointerType *PtrTy = llvm::PointerType::getUnqual(Ctx);
+ llvm::IntegerType *Int32Ty = llvm::Type::getInt32Ty(Ctx);
+ llvm::IntegerType *Int8Ty = llvm::Type::getInt8Ty(Ctx);
+ llvm::ArrayType *Arr8Ty = llvm::ArrayType::get(Int8Ty, 8);
+
+ llvm::Value *Zero32 = llvm::ConstantInt::get(Int32Ty, 0);
+ llvm::Value *One32 = llvm::ConstantInt::get(Int32Ty, 1);
+ llvm::Value *MinusOne32 = llvm::ConstantInt::getSigned(Int32Ty, -1);
+ llvm::Value *ZeroI8 = llvm::ConstantInt::get(Int8Ty, 0);
+ llvm::Value *NullPtr = llvm::ConstantPointerNull::get(PtrTy);
+ llvm::Value *CreatExclFlags =
+ llvm::ConstantInt::get(Int32Ty, AIX_O_CREAT | AIX_O_EXCL);
+
+ llvm::FunctionType *FnTy =
+ llvm::FunctionType::get(PtrTy, {PtrTy, PtrTy}, /*isVarArg=*/false);
+ llvm::Function *Fn = llvm::Function::Create(
+ FnTy, llvm::GlobalValue::LinkOnceODRLinkage, HelperName, &M);
+ Fn->setVisibility(llvm::GlobalValue::HiddenVisibility);
+ Fn->addFnAttr(llvm::Attribute::NoUnwind);
+ Fn->setDSOLocal(true);
+
+ llvm::Argument *ArgPath = Fn->getArg(0);
+ llvm::Argument *ArgMode = Fn->getArg(1);
+ ArgPath->setName("path");
+ ArgMode->setName("mode");
+
+ llvm::FunctionCallee OpenFn = M.getOrInsertFunction(
+ "open",
+ llvm::FunctionType::get(Int32Ty, {PtrTy, Int32Ty}, /*isVarArg=*/true));
+ llvm::FunctionCallee FdopenFn = M.getOrInsertFunction(
+ "fdopen",
+ llvm::FunctionType::get(PtrTy, {Int32Ty, PtrTy}, /*isVarArg=*/false));
+ llvm::FunctionCallee CloseFn = M.getOrInsertFunction(
+ "close",
+ llvm::FunctionType::get(Int32Ty, {Int32Ty}, /*isVarArg=*/false));
+
+ using BB = llvm::BasicBlock;
+ llvm::IRBuilder<> B(Ctx);
+
+ BB *EntryBB = BB::Create(Ctx, "entry", Fn);
+ BB *OpenCallBB = BB::Create(Ctx, "open.call", Fn);
+ BB *OpenOkBB = BB::Create(Ctx, "open.ok", Fn);
+ BB *OpenFailBB = BB::Create(Ctx, "open.fail", Fn);
+ BB *FdopenOkBB = BB::Create(Ctx, "fdopen.ok", Fn);
+ BB *FdopenFailBB = BB::Create(Ctx, "fdopen.fail",Fn);
+ BB *RetBB = BB::Create(Ctx, "ret", Fn);
+
+ B.SetInsertPoint(EntryBB);
+
+ llvm::AllocaInst *BmodeBuf = B.CreateAlloca(Arr8Ty, nullptr, "bmode");
+ BmodeBuf->setAlignment(llvm::Align(1));
+
+ // Derive open(2) access flags from mode[0]: 'w'→O_WRONLY|O_TRUNC,
+ // 'a'→O_WRONLY|O_APPEND, otherwise O_RDONLY.
+ llvm::Value *Mode0 = B.CreateLoad(Int8Ty, ArgMode, "mode0");
+ llvm::Value *AccFlags = B.CreateSelect(
+ B.CreateICmpEQ(Mode0, llvm::ConstantInt::get(Int8Ty, 'w'), "is.w"),
+ llvm::ConstantInt::get(Int32Ty, AIX_O_WRONLY | AIX_O_TRUNC),
+ B.CreateSelect(
+ B.CreateICmpEQ(Mode0, llvm::ConstantInt::get(Int8Ty, 'a'), "is.a"),
+ llvm::ConstantInt::get(Int32Ty, AIX_O_WRONLY | AIX_O_APPEND),
+ llvm::ConstantInt::get(Int32Ty, AIX_O_RDONLY)),
+ "acc.flags");
+
+ // If '+' appears anywhere in mode, upgrade to O_RDWR (unrolled 6 iters).
+ llvm::Value *HasPlus = llvm::ConstantInt::get(llvm::Type::getInt1Ty(Ctx), 0);
+ for (int i = 0; i < 6; ++i) {
+ llvm::Value *Ch = B.CreateLoad(Int8Ty,
+ B.CreateConstGEP1_64(Int8Ty, ArgMode, i, ("mp" + llvm::Twine(i)).str()),
+ ("mc" + llvm::Twine(i)).str());
+ HasPlus = B.CreateOr(HasPlus,
+ B.CreateICmpEQ(Ch, llvm::ConstantInt::get(Int8Ty, '+'),
+ ("eq.plus" + llvm::Twine(i)).str()),
+ ("has.plus" + llvm::Twine(i)).str());
+ }
+ llvm::Value *AccMask = llvm::ConstantInt::get(Int32Ty, ~AIX_O_ACCMODE);
+ llvm::Value *AccFlagsFinal = B.CreateSelect(HasPlus,
+ B.CreateOr(B.CreateAnd(AccFlags, AccMask),
+ llvm::ConstantInt::get(Int32Ty, AIX_O_RDWR)),
+ AccFlags, "acc.final");
+
+ llvm::Value *OpenFlags =
+ B.CreateOr(AccFlagsFinal, CreatExclFlags, "open.flags");
+
+ // Build a mode string without 'x' in BmodeBuf (unrolled 6 iters).
+ llvm::AllocaInst *WIdxAlloca = B.CreateAlloca(Int32Ty, nullptr, "widx");
+ B.CreateStore(Zero32, WIdxAlloca);
+ llvm::Value *XChar = llvm::ConstantInt::get(Int8Ty, 'x');
+ for (int i = 0; i < 6; ++i) {
+ llvm::Value *Ch = B.CreateLoad(Int8Ty,
+ B.CreateConstGEP1_64(Int8Ty, ArgMode, i, ("sp" + llvm::Twine(i)).str()),
+ ("ch" + llvm::Twine(i)).str());
+ BB *DoStore = BB::Create(Ctx, ("store" + llvm::Twine(i)).str(), Fn);
+ BB *AfterIter = BB::Create(Ctx, ("after" + llvm::Twine(i)).str(), Fn);
+ B.CreateCondBr(
+ B.CreateOr(B.CreateICmpEQ(Ch, XChar), B.CreateICmpEQ(Ch, ZeroI8),
+ ("skip" + llvm::Twine(i)).str()),
+ AfterIter, DoStore);
+ B.SetInsertPoint(DoStore);
+ llvm::Value *WIdx = B.CreateLoad(Int32Ty, WIdxAlloca, "wi");
+ B.CreateStore(Ch, B.CreateGEP(Arr8Ty, BmodeBuf,
+ {Zero32, WIdx}, ("dp" + llvm::Twine(i)).str()));
+ B.CreateStore(B.CreateAdd(WIdx, One32), WIdxAlloca);
+ B.CreateBr(AfterIter);
+ B.SetInsertPoint(AfterIter);
+ }
+ llvm::Value *WIdx = B.CreateLoad(Int32Ty, WIdxAlloca, "wi.fin");
+ B.CreateStore(ZeroI8,
+ B.CreateGEP(Arr8Ty, BmodeBuf, {Zero32, WIdx}, "nuldst"));
+ llvm::Value *BmodePtr =
+ B.CreateConstGEP2_32(Arr8Ty, BmodeBuf, 0, 0, "bmode.ptr");
+ B.CreateBr(OpenCallBB);
+
+ B.SetInsertPoint(OpenCallBB);
+ llvm::Value *Mode0666 = llvm::ConstantInt::get(Int32Ty, 0666);
+ llvm::CallInst *FdVal =
+ B.CreateCall(OpenFn, {ArgPath, OpenFlags, Mode0666}, "fd");
+ FdVal->setDoesNotThrow();
+ B.CreateCondBr(B.CreateICmpEQ(FdVal, MinusOne32, "open.fail.cmp"),
+ OpenFailBB, OpenOkBB);
+
+ B.SetInsertPoint(OpenFailBB);
+ B.CreateBr(RetBB);
+
+ B.SetInsertPoint(OpenOkBB);
+ llvm::CallInst *FpVal =
+ B.CreateCall(FdopenFn, {FdVal, BmodePtr}, "fp");
+ FpVal->setDoesNotThrow();
+ B.CreateCondBr(B.CreateICmpEQ(FpVal, NullPtr, "fdopen.fail.cmp"),
+ FdopenFailBB, FdopenOkBB);
+
+ B.SetInsertPoint(FdopenFailBB);
+ B.CreateCall(CloseFn, {FdVal})->setDoesNotThrow();
+ B.CreateBr(RetBB);
+
+ B.SetInsertPoint(FdopenOkBB);
+ B.CreateBr(RetBB);
+
+ B.SetInsertPoint(RetBB);
+ llvm::PHINode *Ret = B.CreatePHI(PtrTy, 3, "retval");
+ Ret->addIncoming(NullPtr, OpenFailBB);
+ Ret->addIncoming(NullPtr, FdopenFailBB);
+ Ret->addIncoming(FpVal, FdopenOkBB);
+ B.CreateRet(Ret);
+
+ return Fn;
+}
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index b1e70a7c347db..4578ae2257a01 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1414,6 +1414,10 @@ class CodeGenModule : public CodeGenTypeCache {
llvm::Constant *CreateRuntimeVariable(llvm::Type *Ty,
StringRef Name);
+ // On AIX, fopen(3) with 'x' mode is not atomic. Lazily emits a helper
+ // __aix_fopen_exclusive that uses open(O_CREAT|O_EXCL)+fdopen instead.
+ llvm::Function *getOrCreateAIXFOpenExclusiveHelper();
+
///@name Custom Blocks Runtime Interfaces
///@{
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/filebuf.members/open_pointer.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.members/open_pointer.pass.cpp
index 9f617dc1e5a89..ade3affe03821 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/filebuf.members/open_pointer.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.members/open_pointer.pass.cpp
@@ -13,8 +13,6 @@
// In C++23 and later, this test requires support for P2467R1 in the dylib (a3f17ba3febbd546f2342ffc780ac93b694fdc8d)
// XFAIL: (!c++03 && !c++11 && !c++14 && !c++17 && !c++20) && using-built-library-before-llvm-18
-// XFAIL: LIBCXX-AIX-FIXME
-
#include <fstream>
#include <cassert>
#include "test_macros.h"
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/pointer.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/pointer.pass.cpp
index 2e0ebcd684d79..3db29707c27e7 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/pointer.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/fstream.cons/pointer.pass.cpp
@@ -16,8 +16,6 @@
// In C++23 and later, this test requires support for P2467R1 in the dylib (a3f17ba3febbd546f2342ffc780ac93b694fdc8d)
// XFAIL: (!c++03 && !c++11 && !c++14 && !c++17 && !c++20) && using-built-library-before-llvm-18
-// XFAIL: LIBCXX-AIX-FIXME
-
#include <fstream>
#include <cassert>
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/fstream.members/open_pointer.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/fstream.members/open_pointer.pass.cpp
index 0d83d681b1dfc..a54444f4b2f53 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/fstream.members/open_pointer.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/fstream.members/open_pointer.pass.cpp
@@ -16,8 +16,6 @@
// In C++23 and later, this test requires support for P2467R1 in the dylib (a3f17ba3febbd546f2342ffc780ac93b694fdc8d)
// XFAIL: (!c++03 && !c++11 && !c++14 && !c++17 && !c++20) && using-built-library-before-llvm-18
-// XFAIL: LIBCXX-AIX-FIXME
-
#include <fstream>
#include <cassert>
#include "test_macros.h"
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/ofstream.cons/pointer.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/ofstream.cons/pointer.pass.cpp
index fbb03f1e85841..3c4a06c2efa21 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/ofstream.cons/pointer.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/ofstream.cons/pointer.pass.cpp
@@ -16,8 +16,6 @@
// In C++23 and later, this test requires support for P2467R1 in the dylib (a3f17ba3febbd546f2342ffc780ac93b694fdc8d)
// XFAIL: (!c++03 && !c++11 && !c++14 && !c++17 && !c++20) && using-built-library-before-llvm-18
-// XFAIL: LIBCXX-AIX-FIXME
-
#include <fstream>
#include <cassert>
#include <ios>
diff --git a/libcxx/test/std/input.output/file.streams/fstreams/ofstream.members/open_pointer.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/ofstream.members/open_pointer.pass.cpp
index 73a474277a933..6446ca16cb409 100644
--- a/libcxx/test/std/input.output/file.streams/fstreams/ofstream.members/open_pointer.pass.cpp
+++ b/libcxx/test/std/input.output/file.streams/fstreams/ofstream.members/open_pointer.pass.cpp
@@ -16,8 +16,6 @@
// In C++23 and later, this test requires support for P2467R1 in the dylib (a3f17ba3febbd546f2342ffc780ac93b694fdc8d)
// XFAIL: (!c++03 && !c++11 && !c++14 && !c++17 && !c++20) && using-built-library-before-llvm-18
-// XFAIL: LIBCXX-AIX-FIXME
-
#include <fstream>
#include <cassert>
#include "test_macros.h"
More information about the cfe-commits
mailing list