mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-01-03 06:10:23 +00:00
1f8790050b
Original-commit: flang-compiler/f18@9fe84f45d7 Reviewed-on: https://github.com/flang-compiler/f18/pull/1094
414 lines
14 KiB
C++
414 lines
14 KiB
C++
//===-- runtime/unit.cpp ----------------------------------------*- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "unit.h"
|
|
#include "io-error.h"
|
|
#include "lock.h"
|
|
#include "unit-map.h"
|
|
|
|
namespace Fortran::runtime::io {
|
|
|
|
// The per-unit data structures are created on demand so that Fortran I/O
|
|
// should work without a Fortran main program.
|
|
static Lock unitMapLock;
|
|
static UnitMap *unitMap{nullptr};
|
|
static ExternalFileUnit *defaultOutput{nullptr};
|
|
|
|
void FlushOutputOnCrash(const Terminator &terminator) {
|
|
if (!defaultOutput) {
|
|
return;
|
|
}
|
|
CriticalSection critical{unitMapLock};
|
|
if (defaultOutput) {
|
|
IoErrorHandler handler{terminator};
|
|
handler.HasIoStat(); // prevent nested crash if flush has error
|
|
defaultOutput->Flush(handler);
|
|
}
|
|
}
|
|
|
|
ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
|
|
return GetUnitMap().LookUp(unit);
|
|
}
|
|
|
|
ExternalFileUnit &ExternalFileUnit::LookUpOrCrash(
|
|
int unit, const Terminator &terminator) {
|
|
ExternalFileUnit *file{LookUp(unit)};
|
|
if (!file) {
|
|
terminator.Crash("Not an open I/O unit number: %d", unit);
|
|
}
|
|
return *file;
|
|
}
|
|
|
|
ExternalFileUnit &ExternalFileUnit::LookUpOrCreate(
|
|
int unit, const Terminator &terminator, bool *wasExtant) {
|
|
return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant);
|
|
}
|
|
|
|
ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
|
|
return GetUnitMap().LookUpForClose(unit);
|
|
}
|
|
|
|
int ExternalFileUnit::NewUnit(const Terminator &terminator) {
|
|
return GetUnitMap().NewUnit(terminator).unitNumber();
|
|
}
|
|
|
|
void ExternalFileUnit::OpenUnit(OpenStatus status, Position position,
|
|
OwningPtr<char> &&newPath, std::size_t newPathLength,
|
|
IoErrorHandler &handler) {
|
|
if (IsOpen()) {
|
|
if (status == OpenStatus::Old &&
|
|
(!newPath.get() ||
|
|
(path() && pathLength() == newPathLength &&
|
|
std::memcmp(path(), newPath.get(), newPathLength) == 0))) {
|
|
// OPEN of existing unit, STATUS='OLD', not new FILE=
|
|
newPath.reset();
|
|
return;
|
|
}
|
|
// Otherwise, OPEN on open unit with new FILE= implies CLOSE
|
|
Flush(handler);
|
|
Close(CloseStatus::Keep, handler);
|
|
}
|
|
set_path(std::move(newPath), newPathLength);
|
|
Open(status, position, handler);
|
|
}
|
|
|
|
void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
|
|
Flush(handler);
|
|
Close(status, handler);
|
|
}
|
|
|
|
void ExternalFileUnit::DestroyClosed() {
|
|
GetUnitMap().DestroyClosed(*this); // destroys *this
|
|
}
|
|
|
|
UnitMap &ExternalFileUnit::GetUnitMap() {
|
|
if (unitMap) {
|
|
return *unitMap;
|
|
}
|
|
CriticalSection critical{unitMapLock};
|
|
if (unitMap) {
|
|
return *unitMap;
|
|
}
|
|
Terminator terminator{__FILE__, __LINE__};
|
|
unitMap = &New<UnitMap>{}(terminator);
|
|
ExternalFileUnit &out{ExternalFileUnit::LookUpOrCreate(6, terminator)};
|
|
out.Predefine(1);
|
|
out.set_mayRead(false);
|
|
out.set_mayWrite(true);
|
|
out.set_mayPosition(false);
|
|
defaultOutput = &out;
|
|
ExternalFileUnit &in{ExternalFileUnit::LookUpOrCreate(5, terminator)};
|
|
in.Predefine(0);
|
|
in.set_mayRead(true);
|
|
in.set_mayWrite(false);
|
|
in.set_mayPosition(false);
|
|
// TODO: Set UTF-8 mode from the environment
|
|
return *unitMap;
|
|
}
|
|
|
|
void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
|
|
CriticalSection critical{unitMapLock};
|
|
if (unitMap) {
|
|
unitMap->CloseAll(handler);
|
|
FreeMemoryAndNullify(unitMap);
|
|
}
|
|
defaultOutput = nullptr;
|
|
}
|
|
|
|
bool ExternalFileUnit::Emit(
|
|
const char *data, std::size_t bytes, IoErrorHandler &handler) {
|
|
auto furthestAfter{std::max(furthestPositionInRecord,
|
|
positionInRecord + static_cast<std::int64_t>(bytes))};
|
|
if (furthestAfter > recordLength.value_or(furthestAfter)) {
|
|
handler.SignalError(IostatRecordWriteOverrun);
|
|
return false;
|
|
}
|
|
WriteFrame(frameOffsetInFile_, recordOffsetInFrame_ + furthestAfter, handler);
|
|
std::memcpy(Frame() + positionInRecord, data, bytes);
|
|
positionInRecord += bytes;
|
|
furthestPositionInRecord = furthestAfter;
|
|
return true;
|
|
}
|
|
|
|
std::optional<char32_t> ExternalFileUnit::GetCurrentChar(
|
|
IoErrorHandler &handler) {
|
|
isReading_ = true; // TODO: manage read/write transitions
|
|
if (isUnformatted) {
|
|
handler.Crash("GetCurrentChar() called for unformatted input");
|
|
return std::nullopt;
|
|
}
|
|
std::size_t chunk{256}; // for stream input
|
|
if (recordLength.has_value()) {
|
|
if (positionInRecord >= *recordLength) {
|
|
return std::nullopt;
|
|
}
|
|
chunk = *recordLength - positionInRecord;
|
|
}
|
|
auto at{recordOffsetInFrame_ + positionInRecord};
|
|
std::size_t need{static_cast<std::size_t>(at + 1)};
|
|
std::size_t want{need + chunk};
|
|
auto got{ReadFrame(frameOffsetInFile_, want, handler)};
|
|
if (got <= need) {
|
|
endfileRecordNumber = currentRecordNumber;
|
|
handler.SignalEnd();
|
|
return std::nullopt;
|
|
}
|
|
const char *p{Frame() + at};
|
|
if (isUTF8) {
|
|
// TODO: UTF-8 decoding
|
|
}
|
|
return *p;
|
|
}
|
|
|
|
void ExternalFileUnit::SetLeftTabLimit() {
|
|
leftTabLimit = furthestPositionInRecord;
|
|
positionInRecord = furthestPositionInRecord;
|
|
}
|
|
|
|
bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
|
|
bool ok{true};
|
|
if (isReading_) {
|
|
if (access == Access::Sequential) {
|
|
if (isUnformatted) {
|
|
NextSequentialUnformattedInputRecord(handler);
|
|
} else {
|
|
NextSequentialFormattedInputRecord(handler);
|
|
}
|
|
}
|
|
} else if (!isUnformatted) {
|
|
if (recordLength.has_value()) {
|
|
// fill fixed-size record
|
|
if (furthestPositionInRecord < *recordLength) {
|
|
WriteFrame(frameOffsetInFile_, *recordLength, handler);
|
|
std::memset(Frame() + recordOffsetInFrame_ + furthestPositionInRecord,
|
|
' ', *recordLength - furthestPositionInRecord);
|
|
}
|
|
} else {
|
|
positionInRecord = furthestPositionInRecord + 1;
|
|
ok &= Emit("\n", 1, handler); // TODO: Windows CR+LF
|
|
frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
|
|
recordOffsetInFrame_ = 0;
|
|
}
|
|
}
|
|
++currentRecordNumber;
|
|
positionInRecord = 0;
|
|
furthestPositionInRecord = 0;
|
|
leftTabLimit.reset();
|
|
return ok;
|
|
}
|
|
|
|
void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) {
|
|
if (!isReading_) {
|
|
handler.Crash("ExternalFileUnit::BackspaceRecord() called during writing");
|
|
// TODO: create endfile record, &c.
|
|
}
|
|
if (access == Access::Sequential) {
|
|
if (isUnformatted) {
|
|
BackspaceSequentialUnformattedRecord(handler);
|
|
} else {
|
|
BackspaceSequentialFormattedRecord(handler);
|
|
}
|
|
} else {
|
|
// TODO
|
|
}
|
|
positionInRecord = 0;
|
|
furthestPositionInRecord = 0;
|
|
leftTabLimit.reset();
|
|
}
|
|
|
|
void ExternalFileUnit::FlushIfTerminal(IoErrorHandler &handler) {
|
|
if (isTerminal()) {
|
|
Flush(handler);
|
|
}
|
|
}
|
|
|
|
void ExternalFileUnit::EndIoStatement() {
|
|
frameOffsetInFile_ += recordOffsetInFrame_;
|
|
recordOffsetInFrame_ = 0;
|
|
io_.reset();
|
|
u_.emplace<std::monostate>();
|
|
lock_.Drop();
|
|
}
|
|
|
|
void ExternalFileUnit::NextSequentialUnformattedInputRecord(
|
|
IoErrorHandler &handler) {
|
|
std::int32_t header{0}, footer{0};
|
|
// Retain previous footer (if any) in frame for more efficient BACKSPACE
|
|
std::size_t retain{sizeof header};
|
|
if (recordLength) { // not first record - advance to next
|
|
++currentRecordNumber;
|
|
if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) {
|
|
handler.SignalEnd();
|
|
return;
|
|
}
|
|
frameOffsetInFile_ +=
|
|
recordOffsetInFrame_ + *recordLength + 2 * sizeof header;
|
|
recordOffsetInFrame_ = 0;
|
|
} else {
|
|
retain = 0;
|
|
}
|
|
std::size_t need{retain + sizeof header};
|
|
std::size_t got{ReadFrame(frameOffsetInFile_ - retain, need, handler)};
|
|
// Try to emit informative errors to help debug corrupted files.
|
|
const char *error{nullptr};
|
|
if (got < need) {
|
|
if (got == retain) {
|
|
handler.SignalEnd();
|
|
} else {
|
|
error = "Unformatted sequential file input failed at record #%jd (file "
|
|
"offset %jd): truncated record header";
|
|
}
|
|
} else {
|
|
std::memcpy(&header, Frame() + retain, sizeof header);
|
|
need = retain + header + 2 * sizeof header;
|
|
got = ReadFrame(frameOffsetInFile_ - retain,
|
|
need + sizeof header /* next one */, handler);
|
|
if (got < need) {
|
|
error = "Unformatted sequential file input failed at record #%jd (file "
|
|
"offset %jd): hit EOF reading record with length %jd bytes";
|
|
} else {
|
|
const char *start{Frame() + retain + sizeof header};
|
|
std::memcpy(&footer, start + header, sizeof footer);
|
|
if (footer != header) {
|
|
error = "Unformatted sequential file input failed at record #%jd (file "
|
|
"offset %jd): record header has length %jd that does not match "
|
|
"record footer (%jd)";
|
|
} else {
|
|
recordLength = header;
|
|
}
|
|
}
|
|
}
|
|
if (error) {
|
|
handler.SignalError(error, static_cast<std::intmax_t>(currentRecordNumber),
|
|
static_cast<std::intmax_t>(frameOffsetInFile_),
|
|
static_cast<std::intmax_t>(header), static_cast<std::intmax_t>(footer));
|
|
}
|
|
positionInRecord = sizeof header;
|
|
}
|
|
|
|
void ExternalFileUnit::NextSequentialFormattedInputRecord(
|
|
IoErrorHandler &handler) {
|
|
static constexpr std::size_t chunk{256};
|
|
std::size_t length{0};
|
|
if (recordLength.has_value()) {
|
|
// not first record - advance to next
|
|
++currentRecordNumber;
|
|
if (endfileRecordNumber && currentRecordNumber >= *endfileRecordNumber) {
|
|
handler.SignalEnd();
|
|
return;
|
|
}
|
|
if (Frame()[*recordLength] == '\r') {
|
|
++*recordLength;
|
|
}
|
|
recordOffsetInFrame_ += *recordLength + 1;
|
|
}
|
|
while (true) {
|
|
std::size_t got{ReadFrame(
|
|
frameOffsetInFile_, recordOffsetInFrame_ + length + chunk, handler)};
|
|
if (got <= recordOffsetInFrame_ + length) {
|
|
handler.SignalEnd();
|
|
break;
|
|
}
|
|
const char *frame{Frame() + recordOffsetInFrame_};
|
|
if (const char *nl{reinterpret_cast<const char *>(
|
|
std::memchr(frame + length, '\n', chunk))}) {
|
|
recordLength = nl - (frame + length) + 1;
|
|
if (*recordLength > 0 && frame[*recordLength - 1] == '\r') {
|
|
--*recordLength;
|
|
}
|
|
return;
|
|
}
|
|
length += got;
|
|
}
|
|
}
|
|
|
|
void ExternalFileUnit::BackspaceSequentialUnformattedRecord(
|
|
IoErrorHandler &handler) {
|
|
std::int32_t header{0}, footer{0};
|
|
RUNTIME_CHECK(handler, currentRecordNumber > 1);
|
|
--currentRecordNumber;
|
|
int overhead{static_cast<int>(2 * sizeof header)};
|
|
// Error conditions here cause crashes, not file format errors, because the
|
|
// validity of the file structure before the current record will have been
|
|
// checked informatively in NextSequentialUnformattedInputRecord().
|
|
RUNTIME_CHECK(handler, frameOffsetInFile_ >= overhead);
|
|
std::size_t got{
|
|
ReadFrame(frameOffsetInFile_ - sizeof footer, sizeof footer, handler)};
|
|
RUNTIME_CHECK(handler, got >= sizeof footer);
|
|
std::memcpy(&footer, Frame(), sizeof footer);
|
|
RUNTIME_CHECK(handler, frameOffsetInFile_ >= footer + overhead);
|
|
frameOffsetInFile_ -= footer + 2 * sizeof footer;
|
|
auto extra{std::max<std::size_t>(sizeof footer, frameOffsetInFile_)};
|
|
std::size_t want{extra + footer + 2 * sizeof footer};
|
|
got = ReadFrame(frameOffsetInFile_ - extra, want, handler);
|
|
RUNTIME_CHECK(handler, got >= want);
|
|
std::memcpy(&header, Frame() + extra, sizeof header);
|
|
RUNTIME_CHECK(handler, header == footer);
|
|
positionInRecord = sizeof header;
|
|
recordLength = footer;
|
|
}
|
|
|
|
// There's no portable memrchr(), unfortunately, and strrchr() would
|
|
// fail on a record with a NUL, so we have to do it the hard way.
|
|
static const char *FindLastNewline(const char *str, std::size_t length) {
|
|
for (const char *p{str + length}; p-- > str;) {
|
|
if (*p == '\n') {
|
|
return p;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ExternalFileUnit::BackspaceSequentialFormattedRecord(
|
|
IoErrorHandler &handler) {
|
|
std::int64_t start{frameOffsetInFile_ + recordOffsetInFrame_};
|
|
--currentRecordNumber;
|
|
RUNTIME_CHECK(handler, currentRecordNumber > 0);
|
|
if (currentRecordNumber == 1) {
|
|
// To simplify the code below, treat a backspace to the first record
|
|
// as a special case;
|
|
RUNTIME_CHECK(handler, start > 0);
|
|
*recordLength = start - 1;
|
|
frameOffsetInFile_ = 0;
|
|
recordOffsetInFrame_ = 0;
|
|
ReadFrame(0, *recordLength + 1, handler);
|
|
} else {
|
|
RUNTIME_CHECK(handler, start > 1);
|
|
std::int64_t at{start - 2}; // byte before previous record's newline
|
|
while (true) {
|
|
if (const char *p{
|
|
FindLastNewline(Frame(), at - frameOffsetInFile_ + 1)}) {
|
|
// This is the newline that ends the record before the previous one.
|
|
recordOffsetInFrame_ = p - Frame() + 1;
|
|
*recordLength = start - 1 - (frameOffsetInFile_ + recordOffsetInFrame_);
|
|
break;
|
|
}
|
|
RUNTIME_CHECK(handler, frameOffsetInFile_ > 0);
|
|
at = frameOffsetInFile_ - 1;
|
|
if (auto bytesBefore{BytesBufferedBeforeFrame()}) {
|
|
frameOffsetInFile_ = FrameAt() - bytesBefore;
|
|
} else {
|
|
static constexpr int chunk{1024};
|
|
frameOffsetInFile_ = std::max<std::int64_t>(0, at - chunk);
|
|
}
|
|
std::size_t want{static_cast<std::size_t>(start - frameOffsetInFile_)};
|
|
std::size_t got{ReadFrame(frameOffsetInFile_, want, handler)};
|
|
RUNTIME_CHECK(handler, got >= want);
|
|
}
|
|
}
|
|
std::size_t want{
|
|
static_cast<std::size_t>(recordOffsetInFrame_ + *recordLength + 1)};
|
|
RUNTIME_CHECK(handler, FrameLength() >= want);
|
|
RUNTIME_CHECK(handler, Frame()[recordOffsetInFrame_ + *recordLength] == '\n');
|
|
if (*recordLength > 0 &&
|
|
Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
|
|
--*recordLength;
|
|
}
|
|
}
|
|
} // namespace Fortran::runtime::io
|