Skip to content

Commit 61af05f

Browse files
[flang] Add runtime and lowering implementation for extended intrinsic PUTENV (llvm#134412)
Implement extended intrinsic PUTENV, both function and subroutine forms. Add PUTENV documentation to flang/docs/Intrinsics.md. Add functional and semantic unit tests.
1 parent 3a859b1 commit 61af05f

File tree

13 files changed

+350
-4
lines changed

13 files changed

+350
-4
lines changed

flang-rt/include/flang-rt/runtime/environment.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ struct ExecutionEnvironment {
4545
const char *GetEnv(
4646
const char *name, std::size_t name_length, const Terminator &terminator);
4747

48+
std::int32_t SetEnv(const char *name, std::size_t name_length,
49+
const char *value, std::size_t value_length,
50+
const Terminator &terminator);
51+
52+
std::int32_t UnsetEnv(
53+
const char *name, std::size_t name_length, const Terminator &terminator);
54+
4855
int argc{0};
4956
const char **argv{nullptr};
5057
char **envp{nullptr};

flang-rt/lib/runtime/command.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,55 @@ std::int32_t RTNAME(Hostnm)(
309309
return status;
310310
}
311311

312+
std::int32_t RTNAME(PutEnv)(
313+
const char *str, size_t str_length, const char *sourceFile, int line) {
314+
Terminator terminator{sourceFile, line};
315+
316+
RUNTIME_CHECK(terminator, str && str_length);
317+
318+
// Note: don't trim the input string, because the user should be able
319+
// to set the value to all spaces if necessary.
320+
321+
// While Fortran's putenv() extended intrinsic sementics loosly follow
322+
// Linux C library putenv(), don't actually use putenv() on Linux, because
323+
// it takes the passed string pointer and incorporates it into the
324+
// environment without copy. To make this safe, one would have to copy
325+
// the passed string into some allocated memory, but then there's no good
326+
// way to deallocate it. Instead, use the implementation from
327+
// ExecutionEnvironment, which does the right thing for both Windows and
328+
// Linux.
329+
330+
std::int32_t status{0};
331+
332+
// Split the input string into name and value substrings. Note:
333+
// if input string is in "name=value" form, then we set variable "name" with
334+
// value "value". If the input string is in "name=" form, then we delete
335+
// the variable "name".
336+
337+
const char *str_end = str + str_length;
338+
const char *str_sep = std::find(str, str_end, '=');
339+
if (str_sep == str_end) {
340+
// No separator, invalid input string
341+
status = EINVAL;
342+
} else if ((str_sep + 1) == str_end) {
343+
// "name=" form, which means we need to delete this variable
344+
status = executionEnvironment.UnsetEnv(str, str_sep - str, terminator);
345+
} else {
346+
// Example: consider str "abc=defg", str_length = 8
347+
//
348+
// addr: 05 06 07 08 09 10 11 12 13
349+
// str@addr: a b c = d e f g ??
350+
//
351+
// str = 5, str_end = 13, str_sep = 8, name length: str_sep - str = 3
352+
// value ptr: str_sep + 1 = 9, value length: 4
353+
//
354+
status = executionEnvironment.SetEnv(
355+
str, str_sep - str, str_sep + 1, str_end - str_sep - 1, terminator);
356+
}
357+
358+
return status;
359+
}
360+
312361
std::int32_t RTNAME(Unlink)(
313362
const char *str, size_t strLength, const char *sourceFile, int line) {
314363
Terminator terminator{sourceFile, line};
@@ -324,4 +373,5 @@ std::int32_t RTNAME(Unlink)(
324373

325374
return status;
326375
}
376+
327377
} // namespace Fortran::runtime

flang-rt/lib/runtime/environment.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,68 @@ const char *ExecutionEnvironment::GetEnv(
181181

182182
return std::getenv(cStyleName.get());
183183
}
184+
185+
std::int32_t ExecutionEnvironment::SetEnv(const char *name,
186+
std::size_t name_length, const char *value, std::size_t value_length,
187+
const Terminator &terminator) {
188+
189+
RUNTIME_CHECK(terminator, name && name_length && value && value_length);
190+
191+
OwningPtr<char> cStyleName{
192+
SaveDefaultCharacter(name, name_length, terminator)};
193+
RUNTIME_CHECK(terminator, cStyleName);
194+
195+
OwningPtr<char> cStyleValue{
196+
SaveDefaultCharacter(value, value_length, terminator)};
197+
RUNTIME_CHECK(terminator, cStyleValue);
198+
199+
std::int32_t status{0};
200+
201+
#ifdef _WIN32
202+
203+
status = _putenv_s(cStyleName.get(), cStyleValue.get());
204+
205+
#else
206+
207+
constexpr int overwrite = 1;
208+
status = setenv(cStyleName.get(), cStyleValue.get(), overwrite);
209+
210+
#endif
211+
212+
if (status != 0) {
213+
status = errno;
214+
}
215+
216+
return status;
217+
}
218+
219+
std::int32_t ExecutionEnvironment::UnsetEnv(
220+
const char *name, std::size_t name_length, const Terminator &terminator) {
221+
222+
RUNTIME_CHECK(terminator, name && name_length);
223+
224+
OwningPtr<char> cStyleName{
225+
SaveDefaultCharacter(name, name_length, terminator)};
226+
RUNTIME_CHECK(terminator, cStyleName);
227+
228+
std::int32_t status{0};
229+
230+
#ifdef _WIN32
231+
232+
// Passing empty string as value will unset the variable
233+
status = _putenv_s(cStyleName.get(), "");
234+
235+
#else
236+
237+
status = unsetenv(cStyleName.get());
238+
239+
#endif
240+
241+
if (status != 0) {
242+
status = errno;
243+
}
244+
245+
return status;
246+
}
247+
184248
} // namespace Fortran::runtime

flang/docs/Intrinsics.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,41 @@ PROGRAM example_hostnm
10401040
END PROGRAM
10411041
```
10421042

1043+
### Non-Standard Intrinsics: PUTENV
1044+
1045+
#### Description
1046+
`PUTENV(STR [, STATUS])` sets or deletes environment variable.
1047+
1048+
This intrinsic is provided in both subroutine and function forms; however, only
1049+
one form can be used in any given program unit.
1050+
1051+
| ARGUMENT | INTENT | TYPE | KIND | Description |
1052+
|----------|--------|-------------|---------|---------------------------------|
1053+
| `STR` | `IN` | `CHARACTER` | default | String in the form "name=value" (see below) |
1054+
| `STATUS` | `OUT` | `INTEGER` | default | Optional. Returns 0 on success, C's `errno` on failure. |
1055+
1056+
#### Usage and Info
1057+
1058+
- **Standard:** extension
1059+
- **Class:** Subroutine, function
1060+
- **Syntax:** `CALL PUTENV(STR [, STATUS])`, `STATUS = PUTENV(STR)`
1061+
1062+
The passed string can be in the form "name=value" to set environment variable "name" to value "value". It can also be of the form "name=" to delete environment variable "name".
1063+
1064+
The environment variables set by PUTENV can be read by GET_ENVIRONMENT_VARIABLE.
1065+
1066+
#### Example
1067+
```Fortran
1068+
integer :: status
1069+
1070+
! Set variable my_var to value my_value
1071+
putenv("my_var=my_value", status)
1072+
1073+
! Delete variable my_var
1074+
putenv("my_var=")
1075+
end
1076+
```
1077+
10431078

10441079
### Non-standard Intrinsics: RENAME
10451080
`RENAME(OLD, NEW[, STATUS])` renames/moves a file on the filesystem.
@@ -1094,7 +1129,7 @@ function form.
10941129
### Non-Standard Intrinsics: TIME
10951130

10961131
#### Description
1097-
`TIME()` returns the current time of the system as a INTEGER(8).
1132+
`TIME()` returns the current time of the system as a INTEGER(8).
10981133

10991134
#### Usage and Info
11001135

@@ -1269,7 +1304,7 @@ by `ISIZE`.
12691304
`COMPAR` function takes the addresses of element `A` and `B` and must return:
12701305
- a negative value if `A` < `B`
12711306
- zero if `A` == `B`
1272-
- a positive value otherwise.
1307+
- a positive value otherwise.
12731308

12741309
#### Usage and Info
12751310

flang/include/flang/Optimizer/Builder/IntrinsicCall.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,8 @@ struct IntrinsicLibrary {
382382
mlir::Value genPoppar(mlir::Type, llvm::ArrayRef<mlir::Value>);
383383
fir::ExtendedValue genPresent(mlir::Type, llvm::ArrayRef<fir::ExtendedValue>);
384384
fir::ExtendedValue genProduct(mlir::Type, llvm::ArrayRef<fir::ExtendedValue>);
385+
fir::ExtendedValue genPutenv(std::optional<mlir::Type>,
386+
llvm::ArrayRef<fir::ExtendedValue>);
385387
void genRandomInit(llvm::ArrayRef<fir::ExtendedValue>);
386388
void genRandomNumber(llvm::ArrayRef<fir::ExtendedValue>);
387389
void genRandomSeed(llvm::ArrayRef<fir::ExtendedValue>);

flang/include/flang/Optimizer/Builder/Runtime/Command.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ mlir::Value genHostnm(fir::FirOpBuilder &builder, mlir::Location loc,
6868
void genPerror(fir::FirOpBuilder &builder, mlir::Location loc,
6969
mlir::Value string);
7070

71+
/// Generate a call to the runtime function which implements the PUTENV
72+
/// intrinsic.
73+
mlir::Value genPutEnv(fir::FirOpBuilder &builder, mlir::Location loc,
74+
mlir::Value str, mlir::Value strLength);
75+
7176
/// Generate a call to the Unlink runtime function which implements
7277
/// the UNLINK intrinsic.
7378
mlir::Value genUnlink(fir::FirOpBuilder &builder, mlir::Location loc,

flang/include/flang/Runtime/command.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,15 @@ std::int32_t RTNAME(GetCwd)(
6464
std::int32_t RTNAME(Hostnm)(
6565
const Descriptor &res, const char *sourceFile, int line);
6666

67+
std::int32_t RTNAME(PutEnv)(
68+
const char *str, size_t str_length, const char *sourceFile, int line);
69+
6770
// Calls unlink()
6871
std::int32_t RTNAME(Unlink)(
6972
const char *path, size_t pathLength, const char *sourceFile, int line);
7073

7174
} // extern "C"
75+
7276
} // namespace Fortran::runtime
7377

7478
#endif // FORTRAN_RUNTIME_COMMAND_H_

flang/lib/Evaluate/intrinsics.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,8 @@ static const IntrinsicInterface genericIntrinsicFunction[]{
856856
DefaultInt, Rank::scalar, IntrinsicClass::inquiryFunction},
857857
{"present", {{"a", Addressable, Rank::anyOrAssumedRank}}, DefaultLogical,
858858
Rank::scalar, IntrinsicClass::inquiryFunction},
859+
{"putenv", {{"str", DefaultChar, Rank::scalar}}, DefaultInt, Rank::scalar,
860+
IntrinsicClass::transformationalFunction},
859861
{"radix",
860862
{{"x", AnyIntOrReal, Rank::anyOrAssumedRank, Optionality::required,
861863
common::Intent::In,
@@ -1639,6 +1641,12 @@ static const IntrinsicInterface intrinsicSubroutine[]{
16391641
{}, Rank::elemental, IntrinsicClass::pureSubroutine},
16401642
{"perror", {{"string", DefaultChar, Rank::scalar}}, {}, Rank::elemental,
16411643
IntrinsicClass::impureSubroutine},
1644+
{"putenv",
1645+
{{"str", DefaultChar, Rank::scalar, Optionality::required,
1646+
common::Intent::In},
1647+
{"status", DefaultInt, Rank::scalar, Optionality::optional,
1648+
common::Intent::Out}},
1649+
{}, Rank::elemental, IntrinsicClass::impureSubroutine},
16421650
{"mvbits",
16431651
{{"from", SameIntOrUnsigned}, {"frompos", AnyInt}, {"len", AnyInt},
16441652
{"to", SameIntOrUnsigned, Rank::elemental, Optionality::required,
@@ -2874,8 +2882,8 @@ bool IntrinsicProcTable::Implementation::IsDualIntrinsic(
28742882
// Collection for some intrinsics with function and subroutine form,
28752883
// in order to pass the semantic check.
28762884
static const std::string dualIntrinsic[]{{"chdir"}, {"etime"}, {"fseek"},
2877-
{"ftell"}, {"getcwd"}, {"hostnm"}, {"rename"}, {"second"}, {"system"},
2878-
{"unlink"}};
2885+
{"ftell"}, {"getcwd"}, {"hostnm"}, {"putenv"s}, {"rename"}, {"second"},
2886+
{"system"}, {"unlink"}};
28792887
return llvm::is_contained(dualIntrinsic, name);
28802888
}
28812889

flang/lib/Optimizer/Builder/IntrinsicCall.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,10 @@ static constexpr IntrinsicHandler handlers[]{
793793
{"dim", asValue},
794794
{"mask", asBox, handleDynamicOptional}}},
795795
/*isElemental=*/false},
796+
{"putenv",
797+
&I::genPutenv,
798+
{{{"str", asAddr}, {"status", asAddr, handleDynamicOptional}}},
799+
/*isElemental=*/false},
796800
{"random_init",
797801
&I::genRandomInit,
798802
{{{"repeatable", asValue}, {"image_distinct", asValue}}},
@@ -7329,6 +7333,39 @@ IntrinsicLibrary::genProduct(mlir::Type resultType,
73297333
"PRODUCT", resultType, args);
73307334
}
73317335

7336+
// PUTENV
7337+
fir::ExtendedValue
7338+
IntrinsicLibrary::genPutenv(std::optional<mlir::Type> resultType,
7339+
llvm::ArrayRef<fir::ExtendedValue> args) {
7340+
assert((resultType.has_value() && args.size() == 1) ||
7341+
(!resultType.has_value() && args.size() >= 1 && args.size() <= 2));
7342+
7343+
mlir::Value str = fir::getBase(args[0]);
7344+
mlir::Value strLength = fir::getLen(args[0]);
7345+
mlir::Value statusValue =
7346+
fir::runtime::genPutEnv(builder, loc, str, strLength);
7347+
7348+
if (resultType.has_value()) {
7349+
// Function form, return status.
7350+
return builder.createConvert(loc, *resultType, statusValue);
7351+
}
7352+
7353+
// Subroutine form, store status and return none.
7354+
const fir::ExtendedValue &status = args[1];
7355+
if (!isStaticallyAbsent(status)) {
7356+
mlir::Value statusAddr = fir::getBase(status);
7357+
mlir::Value statusIsPresentAtRuntime =
7358+
builder.genIsNotNullAddr(loc, statusAddr);
7359+
builder.genIfThen(loc, statusIsPresentAtRuntime)
7360+
.genThen([&]() {
7361+
builder.createStoreWithConvert(loc, statusValue, statusAddr);
7362+
})
7363+
.end();
7364+
}
7365+
7366+
return {};
7367+
}
7368+
73327369
// RANDOM_INIT
73337370
void IntrinsicLibrary::genRandomInit(llvm::ArrayRef<fir::ExtendedValue> args) {
73347371
assert(args.size() == 2);

flang/lib/Optimizer/Builder/Runtime/Command.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,20 @@ void fir::runtime::genPerror(fir::FirOpBuilder &builder, mlir::Location loc,
126126
builder.create<fir::CallOp>(loc, runtimeFunc, args);
127127
}
128128

129+
mlir::Value fir::runtime::genPutEnv(fir::FirOpBuilder &builder,
130+
mlir::Location loc, mlir::Value str,
131+
mlir::Value strLength) {
132+
mlir::func::FuncOp func =
133+
fir::runtime::getRuntimeFunc<mkRTKey(PutEnv)>(loc, builder);
134+
auto runtimeFuncTy = func.getFunctionType();
135+
mlir::Value sourceFile = fir::factory::locationToFilename(builder, loc);
136+
mlir::Value sourceLine =
137+
fir::factory::locationToLineNo(builder, loc, runtimeFuncTy.getInput(1));
138+
llvm::SmallVector<mlir::Value> args = fir::runtime::createArguments(
139+
builder, loc, runtimeFuncTy, str, strLength, sourceFile, sourceLine);
140+
return builder.create<fir::CallOp>(loc, func, args).getResult(0);
141+
}
142+
129143
mlir::Value fir::runtime::genUnlink(fir::FirOpBuilder &builder,
130144
mlir::Location loc, mlir::Value path,
131145
mlir::Value pathLength) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
!RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
2+
3+
!CHECK-LABEL: func.func @_QPputenv_test
4+
!CHECK-SAME: %[[dummyStr:.*]]: !fir.boxchar<1> {fir.bindc_name = "str"}) -> i32 {
5+
integer function putenv_test(str)
6+
CHARACTER(len=255) :: str
7+
8+
!CHECK-DAG: %[[func_result:.*]] = fir.alloca i32 {bindc_name = "putenv_test", uniq_name = "_QFputenv_testEputenv_test"}
9+
!CHECK-DAG: %[[func_result_decl:.*]]:{{.*}} = hlfir.declare %[[func_result]] {uniq_name = "_QFputenv_testEputenv_test"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
10+
!CHECK-DAG: %[[src_str_addr:.*]] = fir.address_of(@_{{.*}}) : !fir.ref<!fir.char<1,{{.*}}>
11+
!CHECK-DAG: %[[line_value:.*]] = arith.constant {{.*}} : i64
12+
!CHECK-DAG: %[[str:.*]] = fir.convert {{.*}} (!fir.ref<!fir.char<1,{{.*}}>>) -> !fir.ref<i8>
13+
!CHECK-DAG: %[[str_len:.*]] = fir.convert {{.*}} : (index) -> i64
14+
!CHECK-DAG: %[[src_str:.*]] = fir.convert %[[src_str_addr]] : (!fir.ref<!fir.char<1,{{.*}}>) -> !fir.ref<i8>
15+
!CHECK-DAG: %[[line:.*]] = fir.convert %[[line_value]] : (i64) -> i32
16+
!CHECK: %[[putenv_result:.*]] = fir.call @_FortranAPutEnv(%[[str]], %[[str_len]], %[[src_str]], %[[line]])
17+
!CHECK-SAME: -> i32
18+
19+
! Check _FortranAPutEnv result code handling
20+
!CHECK-DAG: hlfir.assign %[[putenv_result]] to %[[func_result_decl]]#0 : i32, !fir.ref<i32>
21+
!CHECK-DAG: %[[load_result:.*]] = fir.load %[[func_result_decl]]#0 : !fir.ref<i32>
22+
!CHECK: return %[[load_result]] : i32
23+
putenv_test = putenv(str)
24+
end function putenv_test

0 commit comments

Comments
 (0)