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

Compile Trilangle to C #26

Merged
merged 18 commits into from
Sep 28, 2023
Merged
2 changes: 1 addition & 1 deletion .github/workflows/lint-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Lint Builds
paths:
# It would be nice if I could specify this per-job, but I'm not making a whole new file just to avoid running
# clang for web-only changes.
- src/*
- src/**
- wasm/*
- .clang-format
- .github/workflows/lint-build.yml
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Run Tests
branches:
- '**'
paths:
- src/*
- src/**
- tests/**
- .github/workflows/test.yml
- .gitmodules
Expand Down Expand Up @@ -38,11 +38,13 @@ jobs:
run: |
g++ -Og -std=gnu++17 src/*.cpp -o trilangle
export PATH="${PATH}:${PWD}"
for script in tests/cli/*/index.sh
export CC="${CC:-gcc}"
for script in tests/cli/*/index.sh tests/compiler/*/index.sh
do
echo "Running ${script}..."
"$script"
done
echo 'Done.'
cli-tests-windows:
runs-on: windows-latest
name: Run CLI Tests for Windows
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ wasm/*.css
*.obj
trilangle
*.nativecodeanalysis.xml
out.c
149 changes: 149 additions & 0 deletions src/compiler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// This file is not in the compiler folder because the MSVC build script requires this folder to contain all the .cpp
// files.
#include "compiler.hh"
#include <iostream>
#include "compiler/strings.hh"

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

os << header;

for (size_t i = 0; i < m_fragments->size(); ++i) {
const std::vector<instruction>& frag = *m_fragments->at(i);
for (size_t j = 0; j < frag.size(); ++j) {
os << "\nlbl" << i << '_' << j << ": ";
get_c_code(frag[j], os);
}
}

os << footer << std::flush;
}

void compiler::get_c_code(const instruction& i, std::ostream& os) {
using op = instruction::operation;

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;
os << dest2.first << '_' << dest2.second << "; goto lbl" << dest1.first << '_' << dest1.second << ';';
return;
}
case op::JMP: {
os << "goto lbl";
const auto& dest = i.m_arg.next;
os << dest.first << '_' << dest.second << ';';
return;
}
case op::TKL:
case op::EXT:
os << "lws_deinit(stack); return 0;";
return;
case op::TJN:
case op::TSP:
std::cerr << "Threading is not supported for compiled programs." << std::endl;
exit(EXIT_FAILURE);
case op::NOP:
break;
case op::ADD:
os << "lws_push(stack, lws_pop(stack) + lws_pop(stack));";
return;
case op::SUB:
// The calls need to be well-ordered
os << "{ int32_t tmp = lws_pop(stack); lws_push(stack, lws_pop(stack) - tmp); }";
return;
case op::MUL:
os << "lws_push(stack, lws_pop(stack) * lws_pop(stack));";
return;
case op::DIV:
os << "{ int32_t tmp = lws_pop(stack); lws_push(stack, lws_pop(stack) / tmp); }";
return;
case op::MOD:
os << "{ int32_t tmp = lws_pop(stack); lws_push(stack, lws_pop(stack) % tmp); }";
return;
case op::PSC: {
os << "lws_push(stack,";
const auto value = i.m_arg.number;
os << static_cast<int32_t>(value) << ");";
return;
}
case op::PSI: {
os << "lws_push(stack,";
const auto value = i.m_arg.number;
os << static_cast<int32_t>(value - (int24_t)'0') << ");";
return;
}
case op::POP:
os << "(void)lws_pop(stack);";
return;
case op::INC:
os << "lws_push(stack, (lws_pop(stack) + 1) << 8 >> 8);";
return;
case op::DEC:
os << "lws_push(stack, (lws_pop(stack) - 1) << 8 >> 8);";
return;
case op::AND:
os << "lws_push(stack, lws_pop(stack) & lws_pop(stack));";
return;
case op::IOR:
os << "lws_push(stack, lws_pop(stack) | lws_pop(stack));";
return;
case op::XOR:
os << "lws_push(stack, lws_pop(stack) ^ lws_pop(stack));";
return;
case op::NOT:
os << "lws_push(stack, ~lws_pop(stack));";
return;
case op::GTC:
// FIXME: reports (some?) high unicode characters as WEOF (on some systems?)
bbrk24 marked this conversation as resolved.
Show resolved Hide resolved
os << "{ wint_t temp = getwchar(); lws_push(stack, temp == WEOF ? -1 : temp); }";
return;
case op::PTC:
os << "putwchar(lws_top(stack));";
return;
case op::GTI:
os << "{"
"int32_t i = -1;"
"while (!(feof(stdin) || scanf(\"%\" SCNi32, &i))) (void)getchar();"
"lws_push(stack, i);"
"}";
return;
case op::PTI:
os << R"( printf("%" PRId32 "\n", lws_top(stack)); )";
return;
case op::IDX:
os << "lws_push(stack, lws_index(stack, lws_pop(stack)));";
return;
case op::DUP:
os << "lws_push(stack, lws_top(stack));";
return;
case op::DP2: {
const char* str = "lws_push(stack, lws_index(stack, 1));";
os << str << str;
return;
}
case op::RND:
case op::GDT:
case op::GTM:
std::cerr << "Nondeterministic instructions are not yet supported for compiled programs." << std::endl;
exit(EXIT_FAILURE);
case op::EXP:
os << "lws_push(stack, 1 << lws_pop(stack));";
return;
case op::SWP:
os << "{"
"int32_t temp1 = lws_pop(stack);"
"int32_t temp2 = lws_pop(stack);"
"lws_push(stack, temp1);"
"lws_push(stack, temp2);"
"}";
return;
default:
std::cerr << "Unknown opcode." << std::endl;
exit(EXIT_FAILURE);
}
}
11 changes: 11 additions & 0 deletions src/compiler.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include "disassembler.hh"

class compiler : public disassembler {
public:
CONSTEXPR_VECTOR compiler(NONNULL_PTR(const program) p, flags f) : disassembler(p, f) {}
void write_state(std::ostream& os) override;
private:
static void get_c_code(const instruction& i, std::ostream& os);
};
54 changes: 54 additions & 0 deletions src/compiler/lightweight_stack.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This entire file is a string. Comment out the opening quote marker to get syntax highlighting while editing.
R"(
#include <stdlib.h>
#include <stdint.h>

#ifndef __GNUC__
#define __builtin_expect(exp, c) exp
#endif

struct lightweight_stack_s {
int32_t* base;
size_t length;
size_t capacity;
};

typedef struct lightweight_stack_s* lws;

static inline int32_t lws_pop(lws stack) {
return stack->base[--stack->length];
}

static inline int lws_push(lws stack, int32_t value) {
if (__builtin_expect(stack->length == stack->capacity, 0L)) {
bbrk24 marked this conversation as resolved.
Show resolved Hide resolved
size_t new_cap = stack->capacity * 7 / 4;
int32_t* new_ptr = (int32_t*)realloc(stack->base, new_cap * sizeof(int32_t));
if (new_ptr == NULL) {
return 0;
}
stack->base = new_ptr;
stack->capacity = new_cap;
}
stack->base[stack->length++] = value;
return 1;
}

static inline int32_t lws_index(const lws stack, int32_t index) {
return stack->base[stack->length - index - 1];
}

static inline int32_t lws_top(const lws stack) {
return lws_index(stack, 0);
}

static inline void lws_init(lws stack) {
stack->length = 0;
stack->base = (int32_t*)calloc(4, sizeof(int32_t));
// Note: actually handling errors prevents the compiler from doing optimizations we want
stack->capacity = 4;
}

static inline void lws_deinit(const lws stack) {
free(stack->base);
}
//)"
16 changes: 16 additions & 0 deletions src/compiler/strings.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

constexpr const char* header =
#include "lightweight_stack.h"
R"#(
#include <stdio.h>
#include <inttypes.h>
#include <wchar.h>

int main(int argc, const char** argv) {
struct lightweight_stack_s stack_storage;
lws stack = &stack_storage;
lws_init(stack);
)#";

constexpr const char* footer = "}\n";
2 changes: 1 addition & 1 deletion src/disassembler.hh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public:
}

// Write the state to the specified output stream.
void write_state(std::ostream& os);
virtual void write_state(std::ostream& os);
protected:
void build_state();

Expand Down
7 changes: 5 additions & 2 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,19 @@ static constexpr const char* FLAGS_HELP =
"\t \tcode. Incompatible with --debug, --warnings, and\n"
"\t \t--pipekill.\n"
"\t--hide-nops, -n \tDon't include NOPs in the disassembly. Requires\n"
"\t \t--disassemble.\n\n"
"\t \t--disassemble.\n"
"\t--compile, -c \tOutput C code for the program. Doesn't support all\n"
"\t \toperations. Incompatible with all other flags.\n\n"
"\t--expand, -e \tSpace the program out to fit the triangle.\n"
"\t \tIncompatible with all other flags.\n\n"
"\t \tIncompatible with all other flags.\n"
"\t--null, -z \tRead the program until null terminator instead of EOF.";

namespace flag_container {
static CONSTINIT_LAMBDA std::tuple<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; } },
{ "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; } },
Expand Down
5 changes: 4 additions & 1 deletion src/input.hh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct flags {
bool hide_nops : 1;
bool expand : 1;
bool null_terminated : 1;
bool compile : 1;

constexpr flags() noexcept :
debug(false),
Expand All @@ -20,12 +21,14 @@ struct flags {
disassemble(false),
hide_nops(false),
expand(false),
null_terminated(false) {}
null_terminated(false),
compile(false) {}

constexpr bool is_valid() const noexcept {
return !(
(show_stack && !debug) || ((debug || warnings || pipekill) && disassemble) || (hide_nops && !disassemble)
|| (expand && (debug || warnings || pipekill || disassemble))
|| (compile && (debug || warnings || pipekill || disassemble || expand))
bbrk24 marked this conversation as resolved.
Show resolved Hide resolved
);
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/instruction.hh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ constexpr bool is_branch(int24_t op, direction dir) noexcept {

// A variant-like type.
class instruction {
friend class compiler;
bbrk24 marked this conversation as resolved.
Show resolved Hide resolved

using instruction_pointer = program_walker::instruction_pointer;

template<typename T>
Expand Down
4 changes: 4 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <iostream>
#include "compiler.hh"
#include "disassembler.hh"
#include "interpreter.hh"

Expand All @@ -13,6 +14,9 @@ inline void execute(const std::string& prg, flags f) {
d.write_state(std::cout);
} else if (f.expand) {
std::cout << p << std::flush;
} else if (f.compile) {
compiler c(&p, f);
c.write_state(std::cout);
} else {
interpreter i(&p, f);
i.run();
Expand Down
5 changes: 5 additions & 0 deletions src/thread.hh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ protected:
NO_UNIQUE_ADDRESS instruction_pointer m_ip;
status m_status;
flags m_flags;

static_assert(
sizeof(instruction_pointer) % sizeof(size_t) + sizeof(status) + sizeof(flags) <= sizeof(size_t),
"flags, status, or instruction_pointer got too big, adding an entire extra word of padding"
);
private:
unsigned long m_number;
};
Expand Down
4 changes: 4 additions & 0 deletions tests/compiler/cat/cat.trg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<
> i
, @ #
# o . .
16 changes: 16 additions & 0 deletions tests/compiler/cat/index.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

set -euo pipefail

folder=$(dirname "$0")

trilangle -c "${folder}/cat.trg" | $CC -o ./trgcat.out -xc -

text='
/\_/\ ___
= O_O =_______ \ \
__^ __( \.__) )
(@)<_____>__(_____)____/'
output=$(./trgcat.out <<<"$text")

test "$text" = "$output"
Loading