Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -4794,7 +4794,6 @@ def fblas_matmul_limit_EQ : Joined<["-"], "fblas-matmul-limit=">, Group; def fcheck_EQ : Joined<["-"], "fcheck=">, Group; def fcoarray_EQ : Joined<["-"], "fcoarray=">, Group; -def fconvert_EQ : Joined<["-"], "fconvert=">, Group; def ffpe_trap_EQ : Joined<["-"], "ffpe-trap=">, Group; def ffree_line_length_VALUE : Joined<["-"], "ffree-line-length-">, Group; def finit_character_EQ : Joined<["-"], "finit-character=">, Group; @@ -4895,6 +4894,8 @@ DocBrief<[{Set column after which characters are ignored in typical fixed-form lines in the source file}]>; def ffixed_line_length_VALUE : Joined<["-"], "ffixed-line-length-">, Group, Alias; +def fconvert_EQ : Joined<["-"], "fconvert=">, Group, + HelpText<"Set endian conversion of data for unformatted files">; def fopenacc : Flag<["-"], "fopenacc">, Group, HelpText<"Enable OpenACC">; def fdefault_double_8 : Flag<["-"],"fdefault-double-8">, Group, Index: clang/lib/Driver/ToolChains/Flang.cpp =================================================================== --- clang/lib/Driver/ToolChains/Flang.cpp +++ clang/lib/Driver/ToolChains/Flang.cpp @@ -55,7 +55,8 @@ Args.AddAllArgs(CmdArgs, {options::OPT_module_dir, options::OPT_fdebug_module_writer, options::OPT_fintrinsic_modules_path, options::OPT_pedantic, - options::OPT_std_EQ, options::OPT_W_Joined}); + options::OPT_std_EQ, options::OPT_W_Joined, + options::OPT_fconvert_EQ}); } void Flang::ConstructJob(Compilation &C, const JobAction &JA, Index: flang/include/flang/Frontend/FrontendOptions.h =================================================================== --- flang/include/flang/Frontend/FrontendOptions.h +++ flang/include/flang/Frontend/FrontendOptions.h @@ -16,6 +16,7 @@ #include "flang/Common/Fortran-features.h" #include "flang/Parser/characters.h" #include "flang/Parser/unparse.h" +#include "flang/Runtime/convert.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" #include @@ -258,6 +259,9 @@ // The form to process files in, if specified. FortranForm fortranForm = FortranForm::Unknown; + // Conversion to apply to unformatted IO, if specified. + Fortran::runtime::Convert conversion = Fortran::runtime::Convert::Unknown; + // The column after which characters are ignored in fixed form lines in the // source file. int fixedFormColumns = 72; Index: flang/include/flang/Lower/Bridge.h =================================================================== --- flang/include/flang/Lower/Bridge.h +++ flang/include/flang/Lower/Bridge.h @@ -34,6 +34,9 @@ namespace semantics { class SemanticsContext; } // namespace semantics +namespace runtime { +enum class Convert; +} // namespace runtime namespace lower { @@ -52,9 +55,10 @@ const Fortran::evaluate::IntrinsicProcTable &intrinsics, const Fortran::evaluate::TargetCharacteristics &targetCharacteristics, const Fortran::parser::AllCookedSources &allCooked, - llvm::StringRef triple, fir::KindMapping &kindMap) { + llvm::StringRef triple, fir::KindMapping &kindMap, + Fortran::runtime::Convert conversion) { return LoweringBridge(ctx, defaultKinds, intrinsics, targetCharacteristics, - allCooked, triple, kindMap); + allCooked, triple, kindMap, conversion); } //===--------------------------------------------------------------------===// @@ -83,6 +87,8 @@ /// Get the kind map. const fir::KindMapping &getKindMap() const { return kindMap; } + Fortran::runtime::Convert getConversion() const { return conversion; } + /// Create a folding context. Careful: this is very expensive. Fortran::evaluate::FoldingContext createFoldingContext() const; @@ -107,7 +113,7 @@ const Fortran::evaluate::IntrinsicProcTable &intrinsics, const Fortran::evaluate::TargetCharacteristics &targetCharacteristics, const Fortran::parser::AllCookedSources &cooked, llvm::StringRef triple, - fir::KindMapping &kindMap); + fir::KindMapping &kindMap, Fortran::runtime::Convert conversion); LoweringBridge() = delete; LoweringBridge(const LoweringBridge &) = delete; @@ -118,6 +124,7 @@ mlir::MLIRContext &context; std::unique_ptr module; fir::KindMapping &kindMap; + const Fortran::runtime::Convert conversion; }; } // namespace lower Index: flang/include/flang/Optimizer/Builder/Runtime/Convert.h =================================================================== --- /dev/null +++ flang/include/flang/Optimizer/Builder/Runtime/Convert.h @@ -0,0 +1,32 @@ +//===-- Convert.h -----------------------------------------------*- 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 FORTRAN_OPTIMIZER_BUILDER_RUNTIME_CONVERT_H +#define FORTRAN_OPTIMIZER_BUILDER_RUNTIME_CONVERT_H + +namespace mlir { +class Location; +} // namespace mlir + +namespace fir { +class FirOpBuilder; +} // namespace fir + +namespace Fortran::runtime { +enum class Convert; +} // namespace Fortran::runtime + +namespace fir::runtime { + +/// Generate a call to the runtime routine to set the endian conversion for +/// unformatted files to the mode specified by \p conversion. +void genConvertOption(mlir::Location loc, fir::FirOpBuilder &builder, + Fortran::runtime::Convert conversion); + +} // namespace fir::runtime +#endif // FORTRAN_OPTIMIZER_BUILDER_RUNTIME_CONVERT_H Index: flang/include/flang/Runtime/convert.h =================================================================== --- /dev/null +++ flang/include/flang/Runtime/convert.h @@ -0,0 +1,18 @@ +//===-- Runtime/convert.h ---------------------------------------*- 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 FORTRAN_RUNTIME_RAGGED_H_ +#define FORTRAN_RUNTIME_RAGGED_H_ + +namespace Fortran::runtime { + +// External unformatted I/O data conversions +enum class Convert { Unknown, Native, LittleEndian, BigEndian, Swap }; + +} // namespace Fortran::runtime +#endif // FORTRAN_RUNTIME_RAGGED_H_ Index: flang/include/flang/Runtime/main.h =================================================================== --- flang/include/flang/Runtime/main.h +++ flang/include/flang/Runtime/main.h @@ -15,6 +15,7 @@ FORTRAN_EXTERN_C_BEGIN void RTNAME(ProgramStart)(int, const char *[], const char *[]); void RTNAME(ByteswapOption)(void); // -byteswapio +void RTNAME(ConvertOption)(int); // -fconvert FORTRAN_EXTERN_C_END #endif // FORTRAN_RUNTIME_MAIN_H_ Index: flang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- flang/lib/Frontend/CompilerInvocation.cpp +++ flang/lib/Frontend/CompilerInvocation.cpp @@ -15,6 +15,7 @@ #include "flang/Frontend/CodeGenOptions.h" #include "flang/Frontend/PreprocessorOptions.h" #include "flang/Frontend/TargetOptions.h" +#include "flang/Runtime/convert.h" #include "flang/Semantics/semantics.h" #include "flang/Version.inc" #include "clang/Basic/AllDiagnostics.h" @@ -366,6 +367,27 @@ } } + // Set conversion based on -fconvert= + if (const auto *arg = + args.getLastArg(clang::driver::options::OPT_fconvert_EQ)) { + auto parseConvert = [](const char *s) { + return llvm::StringSwitch>(s) + .Case("unknown", Fortran::runtime::Convert::Unknown) + .Case("native", Fortran::runtime::Convert::Native) + .Case("little-endian", Fortran::runtime::Convert::LittleEndian) + .Case("big-endian", Fortran::runtime::Convert::BigEndian) + .Case("swap", Fortran::runtime::Convert::Swap) + .Default(std::nullopt); + }; + + const char *argValue = arg->getValue(); + if (auto convert = parseConvert(argValue)) + opts.conversion = *convert; + else + diags.Report(clang::diag::err_drv_invalid_value) + << arg->getAsString(args) << argValue; + } + // -f{no-}implicit-none opts.features.Enable( Fortran::common::LanguageFeature::ImplicitNoneTypeAlways, Index: flang/lib/Frontend/FrontendActions.cpp =================================================================== --- flang/lib/Frontend/FrontendActions.cpp +++ flang/lib/Frontend/FrontendActions.cpp @@ -148,7 +148,7 @@ *mlirCtx, defKinds, ci.getInvocation().getSemanticsContext().intrinsics(), ci.getInvocation().getSemanticsContext().targetCharacteristics(), ci.getParsing().allCooked(), ci.getInvocation().getTargetOpts().triple, - kindMap); + kindMap, ci.getInvocation().getFrontendOpts().conversion); // Create a parse tree and lower it to FIR Fortran::parser::Program &parseTree{*ci.getParsing().parseTree()}; Index: flang/lib/Lower/Bridge.cpp =================================================================== --- flang/lib/Lower/Bridge.cpp +++ flang/lib/Lower/Bridge.cpp @@ -31,6 +31,7 @@ #include "flang/Optimizer/Builder/Character.h" #include "flang/Optimizer/Builder/FIRBuilder.h" #include "flang/Optimizer/Builder/Runtime/Character.h" +#include "flang/Optimizer/Builder/Runtime/Convert.h" #include "flang/Optimizer/Builder/Runtime/Ragged.h" #include "flang/Optimizer/Builder/Todo.h" #include "flang/Optimizer/Dialect/FIRAttr.h" @@ -41,6 +42,7 @@ #include "flang/Optimizer/Support/InternalNames.h" #include "flang/Optimizer/Transforms/Passes.h" #include "flang/Parser/parse-tree.h" +#include "flang/Runtime/convert.h" #include "flang/Runtime/iostat.h" #include "flang/Semantics/tools.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" @@ -2752,6 +2754,12 @@ builder->create(loc, zero, altResult); } + if (funit.isMainProgram()) { + auto conversion = bridge.getConversion(); + if (conversion != Fortran::runtime::Convert::Unknown) + fir::runtime::genConvertOption(toLocation(), *builder, conversion); + } + if (Fortran::lower::pft::Evaluation *alternateEntryEval = funit.getEntryEval()) genFIRBranch(alternateEntryEval->lexicalSuccessor->block); @@ -3217,10 +3225,10 @@ const Fortran::evaluate::IntrinsicProcTable &intrinsics, const Fortran::evaluate::TargetCharacteristics &targetCharacteristics, const Fortran::parser::AllCookedSources &cooked, llvm::StringRef triple, - fir::KindMapping &kindMap) + fir::KindMapping &kindMap, Fortran::runtime::Convert conversion) : defaultKinds{defaultKinds}, intrinsics{intrinsics}, targetCharacteristics{targetCharacteristics}, cooked{&cooked}, - context{context}, kindMap{kindMap} { + context{context}, kindMap{kindMap}, conversion{conversion} { // Register the diagnostic handler. context.getDiagEngine().registerHandler([](mlir::Diagnostic &diag) { llvm::raw_ostream &os = llvm::errs(); Index: flang/lib/Optimizer/Builder/CMakeLists.txt =================================================================== --- flang/lib/Optimizer/Builder/CMakeLists.txt +++ flang/lib/Optimizer/Builder/CMakeLists.txt @@ -11,6 +11,7 @@ Runtime/Assign.cpp Runtime/Character.cpp Runtime/Command.cpp + Runtime/Convert.cpp Runtime/Derived.cpp Runtime/Inquiry.cpp Runtime/Numeric.cpp Index: flang/lib/Optimizer/Builder/Runtime/Convert.cpp =================================================================== --- /dev/null +++ flang/lib/Optimizer/Builder/Runtime/Convert.cpp @@ -0,0 +1,26 @@ +//===-- Convert.cpp -------------------------------------------------------===// +// +// 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 "flang/Optimizer/Builder/Runtime/Convert.h" +#include "flang/Optimizer/Builder/FIRBuilder.h" +#include "flang/Optimizer/Builder/Runtime/RTBuilder.h" +#include "flang/Runtime/convert.h" +#include "flang/Runtime/main.h" + +void fir::runtime::genConvertOption(mlir::Location loc, + fir::FirOpBuilder &builder, + Fortran::runtime::Convert conversion) { + mlir::func::FuncOp func = + fir::runtime::getRuntimeFunc(loc, builder); + mlir::FunctionType funcTy = func.getFunctionType(); + mlir::Value conversionVal = builder.createIntegerConstant( + loc, funcTy.getInput(0), static_cast(conversion)); + llvm::SmallVector args = + fir::runtime::createArguments(builder, loc, funcTy, conversionVal); + builder.create(loc, func, args); +} Index: flang/runtime/environment.h =================================================================== --- flang/runtime/environment.h +++ flang/runtime/environment.h @@ -10,6 +10,7 @@ #define FORTRAN_RUNTIME_ENVIRONMENT_H_ #include "flang/Decimal/decimal.h" +#include "flang/Runtime/convert.h" #include namespace Fortran::runtime { @@ -24,10 +25,8 @@ #error host endianness is not known #endif -// External unformatted I/O data conversions -enum class Convert { Unknown, Native, LittleEndian, BigEndian, Swap }; - std::optional GetConvertFromString(const char *, std::size_t); +std::optional GetConvertFromInt(int); struct ExecutionEnvironment { constexpr ExecutionEnvironment(){}; Index: flang/runtime/environment.cpp =================================================================== --- flang/runtime/environment.cpp +++ flang/runtime/environment.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "environment.h" +#include "flang/Runtime/convert.h" #include "memory.h" #include "tools.h" #include @@ -37,6 +38,15 @@ } } +std::optional GetConvertFromInt(int i) { + if (i < static_cast(Convert::Unknown) || + i > static_cast(Convert::Swap)) { + return std::nullopt; + } + + return static_cast(i); +} + void ExecutionEnvironment::Configure( int ac, const char *av[], const char *env[]) { argc = ac; Index: flang/runtime/main.cpp =================================================================== --- flang/runtime/main.cpp +++ flang/runtime/main.cpp @@ -43,4 +43,20 @@ Fortran::runtime::Convert::Swap; } } + +void RTNAME(ConvertOption)(int i) { + // Defer to the current conversion value to allow the environment variable to + // take precedence even if the command-line option was specified. + if (Fortran::runtime::executionEnvironment.conversion != + Fortran::runtime::Convert::Unknown) { + return; + } + + if (auto convert{Fortran::runtime::GetConvertFromInt(i)}) { + Fortran::runtime::executionEnvironment.conversion = *convert; + } else { + Fortran::runtime::Terminator{__FILE__, __LINE__}.Crash( + "Invalid convert option passed to ConvertOption"); + } +} } Index: flang/test/Driver/convert.f90 =================================================================== --- /dev/null +++ flang/test/Driver/convert.f90 @@ -0,0 +1,13 @@ +! Ensure argument -fconvert= works as expected. + +!-------------------------- +! FLANG DRIVER (flang) +!-------------------------- +! RUN: not %flang -fconvert=foobar %s 2>&1 | FileCheck %s --check-prefix=WRONG + +!!----------------------------------------- +! FRONTEND FLANG DRIVER (flang-new -fc1) +!----------------------------------------- +! RUN: not %flang_fc1 -fconvert=foobar %s 2>&1 | FileCheck %s --check-prefix=WRONG + +! WRONG: error: invalid value 'foobar' in '-fconvert=foobar' Index: flang/test/Driver/driver-help-hidden.f90 =================================================================== --- flang/test/Driver/driver-help-hidden.f90 +++ flang/test/Driver/driver-help-hidden.f90 @@ -24,6 +24,7 @@ ! CHECK-NEXT: Enable the old style PARAMETER statement ! CHECK-NEXT: -fbackslash Specify that backslash in string introduces an escape character ! CHECK-NEXT: -fcolor-diagnostics Enable colors in diagnostics +! CHECK-NEXT: -fconvert= Set endian conversion of data for unformatted files ! CHECK-NEXT: -fdefault-double-8 Set the default double precision kind to an 8 byte wide type ! CHECK-NEXT: -fdefault-integer-8 Set the default integer kind to an 8 byte wide type ! CHECK-NEXT: -fdefault-real-8 Set the default real kind to an 8 byte wide type Index: flang/test/Driver/driver-help.f90 =================================================================== --- flang/test/Driver/driver-help.f90 +++ flang/test/Driver/driver-help.f90 @@ -24,6 +24,7 @@ ! HELP-NEXT: Enable the old style PARAMETER statement ! HELP-NEXT: -fbackslash Specify that backslash in string introduces an escape character ! HELP-NEXT: -fcolor-diagnostics Enable colors in diagnostics +! HELP-NEXT: -fconvert= Set endian conversion of data for unformatted files ! HELP-NEXT: -fdefault-double-8 Set the default double precision kind to an 8 byte wide type ! HELP-NEXT: -fdefault-integer-8 Set the default integer kind to an 8 byte wide type ! HELP-NEXT: -fdefault-real-8 Set the default real kind to an 8 byte wide type @@ -78,6 +79,7 @@ ! HELP-FC1-NEXT: Enable the old style PARAMETER statement ! HELP-FC1-NEXT: -fbackslash Specify that backslash in string introduces an escape character ! HELP-FC1-NEXT: -fcolor-diagnostics Enable colors in diagnostics +! HELP-FC1-NEXT: -fconvert= Set endian conversion of data for unformatted files ! HELP-FC1-NEXT: -fdebug-dump-all Dump symbols and the parse tree after the semantic checks ! HELP-FC1-NEXT: -fdebug-dump-parse-tree-no-sema ! HELP-FC1-NEXT: Dump the parse tree (skips the semantic checks) Index: flang/test/Driver/frontend-forwarding.f90 =================================================================== --- flang/test/Driver/frontend-forwarding.f90 +++ flang/test/Driver/frontend-forwarding.f90 @@ -7,6 +7,7 @@ ! RUN: -fdefault-integer-8 \ ! RUN: -fdefault-real-8 \ ! RUN: -flarge-sizes \ +! RUN: -fconvert=little-endian \ ! RUN: -mllvm -print-before-all\ ! RUN: -P \ ! RUN: | FileCheck %s @@ -17,4 +18,5 @@ ! CHECK: "-fdefault-integer-8" ! CHECK: "-fdefault-real-8" ! CHECK: "-flarge-sizes" +! CHECK: "-fconvert=little-endian" ! CHECK: "-mllvm" "-print-before-all" Index: flang/tools/bbc/bbc.cpp =================================================================== --- flang/tools/bbc/bbc.cpp +++ flang/tools/bbc/bbc.cpp @@ -33,6 +33,7 @@ #include "flang/Parser/parsing.h" #include "flang/Parser/provenance.h" #include "flang/Parser/unparse.h" +#include "flang/Runtime/convert.h" #include "flang/Semantics/expression.h" #include "flang/Semantics/runtime-type-info.h" #include "flang/Semantics/semantics.h" @@ -219,7 +220,7 @@ auto burnside = Fortran::lower::LoweringBridge::create( ctx, defKinds, semanticsContext.intrinsics(), semanticsContext.targetCharacteristics(), parsing.allCooked(), "", - kindMap); + kindMap, Fortran::runtime::Convert::Unknown); burnside.lower(parseTree, semanticsContext); mlir::ModuleOp mlirModule = burnside.getModule(); std::error_code ec; Index: flang/unittests/Runtime/ExternalIOTest.cpp =================================================================== --- flang/unittests/Runtime/ExternalIOTest.cpp +++ flang/unittests/Runtime/ExternalIOTest.cpp @@ -149,6 +149,67 @@ << "EndIoStatement() for Close"; } +TEST(ExternalIOTests, TestDirectUnformattedSwappedConvert) { + // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',& + // FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH',CONVERT='NATIVE') + auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)"; + ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)"; + ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)"; + ASSERT_TRUE(IONAME(SetConvert)(io, "NATIVE", 6)) << "SetConvert(NATIVE)"; + + std::int64_t buffer; + static constexpr std::size_t recl{sizeof buffer}; + ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()"; + ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)"; + + int unit{-1}; + ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenNewUnit"; + + static constexpr int records{10}; + for (int j{1}; j <= records; ++j) { + // WRITE(UNIT=unit,REC=j) j + io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; + buffer = j; + ASSERT_TRUE(IONAME(OutputUnformattedBlock)( + io, reinterpret_cast(&buffer), recl, recl)) + << "OutputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OutputUnformattedBlock"; + } + + // Set unformatted conversion to SWAP + RTNAME(ConvertOption)(4); + // OPEN(UNIT=unit,STATUS='OLD') + io = IONAME(BeginOpenUnit)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "OLD", 3)) << "SetStatus(OLD)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenUnit"; + + for (int j{records}; j >= 1; --j) { + // READ(UNIT=unit,REC=j) n + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; + ASSERT_TRUE(IONAME(InputUnformattedBlock)( + io, reinterpret_cast(&buffer), recl, recl)) + << "InputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputUnformattedBlock"; + ASSERT_EQ(buffer >> 56, j) + << "Read back " << (buffer >> 56) << " from direct unformatted record " + << j << ", expected " << j << '\n'; + } + + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Close"; +} + TEST(ExternalIOTests, TestSequentialFixedUnformatted) { // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',& // FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH')