Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++17 #177

Merged
merged 4 commits into from
Oct 2, 2024
Merged

C++17 #177

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint-cpp-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Invoke clang
shell: bash
run: >+
clang++ -DVERSION=0.0.0 -DNDEBUG -WCL4 -Wnon-gcc -Wimplicit-fallthrough -Werror -std=c++14 -o /dev/null
clang++ -DVERSION=0.0.0 -DNDEBUG -WCL4 -Wnon-gcc -Wimplicit-fallthrough -Werror -std=c++17 -o /dev/null
-target "$(clang -dumpmachine | sed -E 's/^\w+-/${{ matrix.arch }}-/')" src/*.cpp
build-native-windows:
runs-on: windows-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
shell: bash
run: |
shopt -s globstar extglob
g++ -Og -Isrc tests/unit/**/*.cpp src/!(main).cpp
g++ -Og -Isrc -std=c++17 tests/unit/**/*.cpp src/!(main).cpp
./a.out
cli-tests-unix:
strategy:
Expand Down
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,21 +334,15 @@ Trilangle is Turing-complete because it can interpret [Qdeql], a TC queue-based

### Compiling natively

The specific compiler used shouldn't matter. It should be compatible with clang and GCC, as well as MSVC from Visual Studio 2017 or later. Incompatibility with these compilers is considered a bug, and any issues should be reported on [the issues page][issues].
The specific compiler used shouldn't matter. It should be compatible with clang and GCC, as well as MSVC from Visual Studio 2017 v15.8 or later. Incompatibility with these compilers is considered a bug, and any issues should be reported on [the issues page][issues].

To enable the `--version` flag, the version must be set at compile time. For example, if invoking GCC directly from bash, this could be done with `-DVERSION="$(git describe --always)"`.

Optionally, you can control the behavior of the date/time instructions with `TRILANGLE_CLOCK`. It may be the name of an existing clock in the chrono header (e.g. `-DTRILANGLE_CLOCK=std::chrono::utc_clock`) or C++ code for a class definition (i.e. `-DTRILANGLE_CLOCK='class trilangle_clock { ... };'`). The class must satisfy *[Clock]*. If not specified, defaults to `std::chrono::system_clock`.

If the macro `NO_BUFFER` is defined, output to stdout will not be buffered.

C++14 (`201304L`) or later is required to compile this project. Certain features from newer versions will be used if they are available, which may affect the performance of the compiled binary. These features include, but are not limited to:

- `noexcept` in function types (C++17, `201510L`)
- `constexpr` lambdas (C++17, `201603L`)
- the `[[unlikely]]` attribute (C++20, `201803L`)

As such, it is recommended to use C++20 or later if performance is a concern. (If it is, why? How are you possibly using this language in a performance-sensitive environment?)
C++17 or later is required to compile this project. Certain features from C++20 and C++23 will be used if available, such as the `[[unlikely]]` atribute. As such, it is recommended to use C++20 or later if performance is a concern. (If it is, why? How are you possibly using this language in a performance-sensitive environment?)

### Compiling for the web

Expand Down
94 changes: 18 additions & 76 deletions src/compat.hh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include <cassert>
#include <utility>

#ifdef __has_include
#if __has_include(<sal.h>) && !defined(__clang__)
#include <sal.h>
#define _INCLUDED_SAL
Expand All @@ -13,25 +12,14 @@
#include <sysexits.h>
#endif

#if __has_include(<stdfloat>)
#include <stdfloat>
#define _INCLUDED_STDFLOAT
#endif

#if __has_include(<version>)
#include <version>
#define _INCLUDED_VERSION
#endif
#endif

#ifndef _INCLUDED_VERSION
#else
#include <algorithm>
#include <string>
#include <vector>
#endif

#undef _INCLUDED_VERSION

#ifndef EX_USAGE
constexpr int EX_USAGE = 64;
#endif
Expand All @@ -44,20 +32,23 @@ constexpr int EX_DATAERR = 65;
constexpr int EX_NOINPUT = 66;
#endif

#if __has_include(<stdfloat>)
#include <stdfloat>

#if defined(_INCLUDED_STDFLOAT) && defined(__STDCPP_FLOAT16_T__)
#ifdef __STDCPP_FLOAT16_T__
// The smallest supported float type.
typedef std::float16_t small_float;
#elif defined(_INCLUDED_STDFLOAT) && defined(__STDCPP_BFLOAT16_T__)
#define small_float std::float16_t
#elif defined(__STDCPP_BFLOAT16_T__)
// The smallest supported float type.
typedef std::bfloat16_t small_float;
#else
#define small_float std::bfloat16_t
#endif
#endif

#ifndef small_float
// The smallest supported float type.
typedef float small_float;
#endif

#undef _INCLUDED_STDFLOAT


#if !defined(__GNUC__) && defined(_MSC_VER)
// 1 if the compiler is really MSVC, and not clang pretending to be MSVC. 0 for clang and GCC.
Expand All @@ -68,22 +59,6 @@ typedef float small_float;
#endif


#ifdef __cpp_constinit
// constinit, constexpr, or just const, depending on where lambdas are allowed
#define CONSTINIT_LAMBDA constinit const
#elif defined(__cpp_constexpr)
#if __cpp_constexpr >= 201603L
// constinit, constexpr, or just const, depending on where lambdas are allowed
#define CONSTINIT_LAMBDA constexpr
#endif
#endif

#ifndef CONSTINIT_LAMBDA
// constinit, constexpr, or just const, depending on where lambdas are allowed
#define CONSTINIT_LAMBDA const
#endif


#ifdef __cpp_lib_constexpr_vector
// constexpr if std::vector is constexpr
#define CONSTEXPR_VECTOR constexpr
Expand Down Expand Up @@ -124,69 +99,36 @@ typedef float small_float;
#define CONSTEXPR_UNION inline
#endif


#ifdef __has_cpp_attribute
#if __has_cpp_attribute(maybe_unused)
// mark that it's okay if a function is never called
#define MAYBE_UNUSED [[maybe_unused]]

// explicitly throw out the result of a [[nodiscard]] function
#define DISCARD [[maybe_unused]] auto _ =
#if REALLY_MSVC
#if _MSC_VER >= 1929
// mark that the padding of this member can be used for other members
#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#endif
#endif

#ifdef __has_cpp_attribute
#if __has_cpp_attribute(unlikely)
// mark an execution branch as being very unlikely, and that it's acceptable for it to be slow
#define UNLIKELY [[unlikely]]
#endif

#if __has_cpp_attribute(fallthrough)
// mark an intentional lack of a break statement in a switch block
#define FALLTHROUGH [[fallthrough]];
#endif

#if REALLY_MSVC
#if _MSC_VER >= 1929
// mark that the padding of this member can be used for other members
#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#endif
#elif __has_cpp_attribute(no_unique_address)
#if !defined(NO_UNIQUE_ADDRESS) && __has_cpp_attribute(no_unique_address)
// mark that the padding of this member can be used for other members
#define NO_UNIQUE_ADDRESS [[no_unique_address]]
#endif
#endif

#ifndef MAYBE_UNUSED
// mark that it's okay if a function is never called
#define MAYBE_UNUSED
// explicitly throw out the result of a [[nodiscard]] function
#define DISCARD (void)
#endif

#ifndef UNLIKELY
// mark an execution branch as being very unlikely, and that it's acceptable for it to be slow
#define UNLIKELY
#endif

#ifndef FALLTHROUGH
// mark an intentional lack of a break statement in a switch block
#define FALLTHROUGH
#endif

#ifndef NO_UNIQUE_ADDRESS
// mark that the padding of this member can be used for other members
#define NO_UNIQUE_ADDRESS
#endif


#ifdef __cpp_noexcept_function_type
// noexcept if it's part of the type
#define NOEXCEPT_T noexcept
#else
// noexcept if it's part of the type
#define NOEXCEPT_T
#endif


#ifdef __cpp_size_t_suffix
// mark a literal as a size_t
#define SIZE_C(x) x##UZ
Expand Down
5 changes: 2 additions & 3 deletions src/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ using std::cerr;
using std::endl;

void compiler::write_state(std::ostream& os) {
if (m_fragments == nullptr) {
if (!m_fragments.has_value()) {
build_state();
}

Expand Down Expand Up @@ -40,8 +40,7 @@ void compiler::get_c_code(const instruction& i, std::ostream& os, bool assume_as
switch (i.m_op) {
case op::BNG: {
os << "if (lws_top(stack) < 0) goto lbl";
const auto& choice = i.m_arg.choice;
const auto &dest1 = choice.first, &dest2 = choice.second;
const auto& [dest1, dest2] = i.m_arg.choice;
os << dest2.first << '_' << dest2.second << "; goto lbl" << dest1.first << '_' << dest1.second << ';';
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/disassembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ void disassembler::to_str(const instruction& i, std::ostream& os) {
}

void disassembler::write_state(std::ostream& os) {
if (m_fragments == nullptr) {
if (!m_fragments.has_value()) {
build_state();
}

Expand Down
22 changes: 11 additions & 11 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ static constexpr const char* FLAGS_HELP =
"\t--null, -z \tRead the program until null terminator instead of EOF.";

namespace flag_container {
static CONSTINIT_LAMBDA std::tuple<NONNULL_PTR(const char), char, void (*)(flags&) NOEXCEPT_T> FLAGS[] = {
{ "null", 'z', [](flags& f) NOEXCEPT_T { f.null_terminated = true; } },
{ "debug", 'd', [](flags& f) NOEXCEPT_T { f.debug = true; } },
{ "ascii", 'a', [](flags& f) NOEXCEPT_T { f.assume_ascii = true; } },
{ "expand", 'e', [](flags& f) NOEXCEPT_T { f.expand = true; } },
{ "compile", 'c', [](flags& f) NOEXCEPT_T { f.compile = true; } },
{ "warnings", 'w', [](flags& f) NOEXCEPT_T { f.warnings = true; } },
{ "pipekill", 'f', [](flags& f) NOEXCEPT_T { f.pipekill = true; } },
{ "hide-nops", 'n', [](flags& f) NOEXCEPT_T { f.hide_nops = true; } },
{ "show-stack", 's', [](flags& f) NOEXCEPT_T { f.show_stack = true; } },
{ "disassemble", 'D', [](flags& f) NOEXCEPT_T { f.disassemble = true; } },
static constexpr std::tuple<NONNULL_PTR(const char), char, void (*)(flags&) noexcept> FLAGS[] = {
{ "null", 'z', [](flags& f) noexcept { f.null_terminated = true; } },
{ "debug", 'd', [](flags& f) noexcept { f.debug = true; } },
{ "ascii", 'a', [](flags& f) noexcept { f.assume_ascii = true; } },
{ "expand", 'e', [](flags& f) noexcept { f.expand = true; } },
{ "compile", 'c', [](flags& f) noexcept { f.compile = true; } },
{ "warnings", 'w', [](flags& f) noexcept { f.warnings = true; } },
{ "pipekill", 'f', [](flags& f) noexcept { f.pipekill = true; } },
{ "hide-nops", 'n', [](flags& f) noexcept { f.hide_nops = true; } },
{ "show-stack", 's', [](flags& f) noexcept { f.show_stack = true; } },
{ "disassemble", 'D', [](flags& f) noexcept { f.disassemble = true; } },
};

[[noreturn]] static inline void invalid_flags() {
Expand Down
2 changes: 1 addition & 1 deletion src/input.hh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct flags {
};

// Read input file or STDIN, and return its contents. Parse other flags as appropriate.
MAYBE_UNUSED std::string parse_args(int argc, _In_reads_z_(argc) const char** argv, flags& f);
[[maybe_unused]] std::string parse_args(int argc, _In_reads_z_(argc) const char** argv, flags& f);

// Gets a single unicode character from STDIN. Returns -1 for EOF.
inline int24_t get_unichar() noexcept {
Expand Down
2 changes: 1 addition & 1 deletion src/instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ instruction::instruction(instruction_pointer ip, const program& program) noexcep
case PSC:
program_walker::advance(ip, program.side_length());
m_arg.number = program.at(ip.coords.first, ip.coords.second);
FALLTHROUGH
[[fallthrough]];
// Most things are direction-insensitive and take no arguments
default:
m_op = static_cast<operation>(op);
Expand Down
36 changes: 17 additions & 19 deletions src/instruction_scanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ using std::vector;
void instruction_scanner::build_state() {
// https://langdev.stackexchange.com/a/659/15

m_fragments = new vector<vector<instruction>*>{ nullptr };
m_fragments.emplace({ std::optional<vector<instruction>>() });
// A map of IP -> location in fragments
std::unordered_map<instruction_pointer, pair<size_t, size_t>> location_map;

Expand All @@ -20,12 +20,10 @@ void instruction_scanner::build_state() {

// !empty-back-pop reflects the while let loop in the Rust code in the answer linked above
while (!unvisited_fragments.empty()) {
pair<size_t, instruction_pointer> p = unvisited_fragments.back();
auto [index, ip] = unvisited_fragments.back();
unvisited_fragments.pop_back();
size_t& index = p.first;
instruction_pointer& ip = p.second;

auto* fragment = new vector<instruction>();
vector<instruction> fragment;

int24_t op = m_program->at(ip.coords.first, ip.coords.second);
while (!is_branch(op, ip.dir)) {
Expand All @@ -42,15 +40,15 @@ void instruction_scanner::build_state() {
location_map.insert({ ip, loc->second });

// jump to the other one
fragment->push_back(instruction::jump_to(loc->second));
fragment.push_back(instruction::jump_to(loc->second));
// Yeah, yeah, goto is bad practice. You look at this loop and tell me you'd rather do it
// another way.
goto continue_outer;
}
}

location_map.insert({ ip, { index, fragment->size() } });
fragment->push_back(i);
location_map.insert({ ip, { index, fragment.size() } });
fragment.push_back(i);

if (i.is_exit()) {
// doesn't end in a jump nor a branch
Expand All @@ -72,7 +70,7 @@ void instruction_scanner::build_state() {
case BNG_SW:
case THR_E:
case THR_W:
program_walker::branch(ip.dir, op, []() NOEXCEPT_T -> bool {
program_walker::branch(ip.dir, op, []() noexcept -> bool {
unreachable("is_branch should've returned true");
});
break;
Expand All @@ -87,7 +85,7 @@ void instruction_scanner::build_state() {

program_walker::advance(ip, m_program->side_length());
} else {
fragment->push_back(instruction::jump_to(loc->second));
fragment.push_back(instruction::jump_to(loc->second));
goto continue_outer;
}

Expand All @@ -99,15 +97,15 @@ void instruction_scanner::build_state() {
// Determine whether to emit a jump or branch. If it's a branch, determine what the targets are.
auto loc = location_map.find(ip);
if (loc == location_map.end()) {
location_map.insert({ ip, { index, fragment->size() } });
location_map.insert({ ip, { index, fragment.size() } });

// BNG requires that its first target go right and its second go left. TSP doesn't really care.
instruction_pointer first_ip = ip;
program_walker::branch(first_ip.dir, op, []() NOEXCEPT_T { return false; });
program_walker::branch(first_ip.dir, op, []() noexcept { return false; });
program_walker::advance(first_ip, m_program->side_length());

instruction_pointer second_ip = ip;
program_walker::branch(second_ip.dir, op, []() NOEXCEPT_T { return true; });
program_walker::branch(second_ip.dir, op, []() noexcept { return true; });
program_walker::advance(second_ip, m_program->side_length());

pair<size_t, size_t> first_dest, second_dest;
Expand All @@ -120,7 +118,7 @@ void instruction_scanner::build_state() {
if (loc == location_map.end()) {
first_dest = { m_fragments->size(), SIZE_C(0) };
unvisited_fragments.push_back({ m_fragments->size(), first_ip });
m_fragments->push_back(nullptr);
m_fragments->push_back(std::nullopt);
} else {
first_dest = loc->second;
}
Expand All @@ -129,22 +127,22 @@ void instruction_scanner::build_state() {
if (loc == location_map.end()) {
second_dest = { m_fragments->size(), SIZE_C(0) };
unvisited_fragments.push_front({ m_fragments->size(), second_ip });
m_fragments->push_back(nullptr);
m_fragments->push_back(std::nullopt);
} else {
second_dest = loc->second;
}

if (op == static_cast<int24_t>(THR_E) || op == static_cast<int24_t>(THR_W)) {
fragment->push_back(instruction::spawn_to({ first_dest, second_dest }));
fragment.push_back(instruction::spawn_to({ first_dest, second_dest }));
} else {
fragment->push_back(instruction::branch_to({ first_dest, second_dest }));
fragment.push_back(instruction::branch_to({ first_dest, second_dest }));
}
} else {
fragment->push_back(instruction::jump_to(loc->second));
fragment.push_back(instruction::jump_to(loc->second));
}
}

continue_outer:
m_fragments->at(index) = fragment;
m_fragments->at(index).emplace(std::move(fragment));
}
}
Loading
Loading