Skip to content

Commit

Permalink
[cpp] linking WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
harrand committed May 21, 2024
1 parent 5fb89ef commit 7439f7d
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 16 deletions.
2 changes: 2 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ add_library(libpsyc
src/error.hpp
src/lex.cpp
src/lex.hpp
src/link.cpp
src/link.hpp
src/parse.cpp
src/parse.hpp
src/semal.cpp
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ namespace code
return this->module_name + ".o";
}

void output::write_to_object_file(std::filesystem::path output_dir)
void output::write_to_object_file(std::filesystem::path output_dir) const
{
std::string object_filename = (output_dir / this->get_output_filename()).string();

Expand Down
2 changes: 1 addition & 1 deletion cpp/src/codegen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace code
std::string module_name;
std::string dump_ir() const;
std::string get_output_filename() const;
void write_to_object_file(std::filesystem::path output_dir);
void write_to_object_file(std::filesystem::path output_dir) const;
};

// initialise and terminate *once*, at the beginning and end of the compiler program respectively.
Expand Down
4 changes: 3 additions & 1 deletion cpp/src/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum class error_code
type,
semal,
codegen,
link,
_count
};

Expand All @@ -26,7 +27,8 @@ constexpr std::array<const char*, (int)error_code::_count> error_names
"parse",
"type",
"semantic analysis",
"code generation"
"code generation",
"linker"
};

#endif // PSYC_ERROR_HPP
226 changes: 226 additions & 0 deletions cpp/src/link.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#include "link.hpp"
#include "diag.hpp"

namespace link
{
// implementation details...
enum class linker_type
{
gnu_like,
msvc,
};

linker_type divine_linker_type(std::string_view linker_name)
{
if(linker_name == "lld-link" // lld windows
|| linker_name == "link") // msvc link.exe
{
return linker_type::msvc;
}

if(linker_name == "ld" // gnu linker
|| linker_name == "lld" // lld generic
|| linker_name == "ld.lld" // lld unix
|| linker_name == "ld64.lld" // lld macos
|| linker_name == "gold") // another gnu linker for elf
{
return linker_type::gnu_like;
}

#ifdef _WIN32
return linker_type::msvc;
#else
return linker_type::gnu_like;
#endif
}

linker_type divine_archiver_type(std::string_view archiver_name)
{
if(archiver_name == "ar"
|| archiver_name == "llvm-ar")
{
return linker_type::gnu_like;
}
if(archiver_name == "lib")
{
return linker_type::msvc;
}

#ifdef _WIN32
return linker_type::msvc;
#else
return linker_type::gnu_like;
#endif
}

std::string guess_a_linker()
{
// guess a linker name to use depending on operating system. complete stab in the dark.
#ifdef _WIN32
return "link";
#elif defined(__linux__)
return "ld";
#else
diag::fatal_error("unable to automatically discern linker for your platform. please explicitly specify a linker via -l");
return "";
#endif
}

std::string guess_a_archiver()
{
#ifdef _WIN32
return "lib";
#elif defined(__linux__)
return "ar";
#else
diag::fatal_error("unable to automatically discern archiver for your platform. please explicitly specify an archiver via -l");
return "";
#endif
}

std::string exec_windows(std::string_view cmd)
{
#ifdef _WIN32
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&_pclose)> pipe(_popen(cmd.data(), "r"), _pclose);
if (!pipe) {
return "";
}
while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;

#else

return "";
#endif
}

std::string replace_all(std::string str, const std::string& from, const std::string& to) {
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
}
return str;
}

std::string get_linker_prefix()
{
// if we're on win32, we will need to invoke vcvars64.bat first.
// let's get that.
#ifdef _WIN32

std::string root_path = exec_windows("\"\"C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe\" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 | findstr \"installationPath\"");
if(root_path.empty())
{
return "";
}

root_path.erase(0, sizeof("installationPath:"));
std::string vcvarsall_path = replace_all(root_path + "\\VC\\Auxiliary\\Build\\vcvars64.bat", "\n", "");
vcvarsall_path = "\"\"" + vcvarsall_path + "\" && ";
return vcvarsall_path;
#else
return "";
#endif
}

// actual linking...
void clear_output_files(const state& state)
{
for(const auto& [unused_input, output] : state.input_output_files)
{
(void)unused_input;
std::filesystem::remove(output);
}
}

void state::build(build::info binfo)
{
// todo: link.
switch(binfo.link)
{
case build::linkage_type::executable:
{
if(binfo.compiler_args.linker_name.empty())
{
binfo.compiler_args.linker_name = guess_a_linker();
}
std::string cmd = binfo.compiler_args.linker_name;
for(const auto& [input, output] : this->input_output_files)
{
cmd += std::format(" \"{}\"", output.string());

}
#ifdef _WIN32
cmd += " libcmt.lib";
#endif

auto lt = divine_linker_type(binfo.compiler_args.linker_name);
std::string full_output = binfo.get_output_path().string();
switch(lt)
{
case linker_type::gnu_like:
cmd += std::format(" -o \"{}\"", full_output);
break;
case linker_type::msvc:
cmd += std::format( " /OUT:\"{}\"", full_output);
cmd = get_linker_prefix() + cmd;
break;
}

int ret = std::system(cmd.c_str());
diag::assert_that(ret == 0, error_code::link, "detected that linker failed. unlucky, dickface!");
}
break;
case build::linkage_type::library:
{
if(binfo.compiler_args.linker_name.empty())
{
binfo.compiler_args.linker_name = guess_a_archiver();
}
auto lt = divine_linker_type(binfo.compiler_args.linker_name);
std::string full_output = binfo.get_output_path().string();

std::string cmd = binfo.compiler_args.linker_name;

switch(lt)
{
case linker_type::gnu_like:
cmd += std::format(" rcs \"{}\"", full_output);
break;
case linker_type::msvc:
cmd += std::format( " /OUT:\"{}\"", full_output);
cmd = get_linker_prefix() + cmd;
break;
}

for(const auto& [input, output] : this->input_output_files)
{
cmd += std::format(" \"{}\"", output.string());
}

#ifdef _WIN32
cmd += " libcmt.lib";
#endif

int ret = std::system(cmd.c_str());
diag::assert_that(ret == 0, error_code::link, "detected that archiver failed. unlucky, dickface!");
}
break;
case build::linkage_type::none: break;
default:
diag::error(error_code::ice, "unknown or corrupted linkage type specified: {}", static_cast<int>(binfo.link));
break;
}

// if we've just linked into an output, delete object files as they're not needed anymore.
if(binfo.link != build::linkage_type::none)
{
clear_output_files(*this);
}
}
}
18 changes: 18 additions & 0 deletions cpp/src/link.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef PSYC_LINK_HPP
#define PSYC_LINK_HPP
#include "build.hpp"
#include <filesystem>
#include <unordered_map>

namespace link
{
struct state
{
std::unordered_map<std::filesystem::path, std::filesystem::path> input_output_files = {};

void build(build::info binfo);
};

}

#endif // PSYC_LINK_HPP
20 changes: 7 additions & 13 deletions cpp/src/psyc_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "parse.hpp"
#include "semal.hpp"
#include "codegen.hpp"
#include "link.hpp"
#include "timer.hpp"
#include "diag.hpp"
#include <filesystem>
Expand Down Expand Up @@ -125,21 +126,14 @@ int main(int argc, char** argv)
t.codegen = timer::elapsed_millis();
// link
timer::start();
switch(binfo.link)
link::state link;
for(const auto& [input_file, codegen_output] : codegen.codegend_input_files)
{
case build::linkage_type::executable:

break;
case build::linkage_type::library:

break;
case build::linkage_type::none:

break;
default:
diag::error(error_code::ice, "unknown or corrupted linkage type specified: {}", static_cast<int>(binfo.link));
break;
codegen_output.write_to_object_file(binfo.compiler_args.output_dir);
link.input_output_files[input_file] = codegen_output.get_output_filename();
}
link.build(binfo);

t.link = timer::elapsed_millis();
t.print();

Expand Down

0 comments on commit 7439f7d

Please sign in to comment.