Last active
January 17, 2020 15:20
-
-
Save falgon/3e7b8dcb6083648efb8a296eb2ddacf7 to your computer and use it in GitHub Desktop.
Example implementation of monadic assembly language DSL using C++17 and etc (One of my graduation studies)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <boost/preprocessor/seq/for_each.hpp> | |
#include <boost/preprocessor/variadic/to_seq.hpp> | |
#include <functional> | |
#include <iostream> | |
#include <iterator> | |
#include <sstream> | |
#include <tuple> | |
#include <type_traits> | |
namespace dhu_grad { | |
namespace detail { | |
// Template for type checking | |
template <class...> | |
struct pack; | |
template <class T> | |
struct type_constant : std::enable_if<true, T> {}; | |
template <class, class> | |
struct cons; | |
template <class T, class... Ts> | |
struct cons<T, pack<Ts...>> : type_constant<pack<T, Ts...>> {}; | |
template <template <class...> class F, class... Ts> | |
struct bind { | |
template <class... Us> | |
struct result : type_constant<F<Ts..., Us ...>> {}; | |
}; | |
template <class... Ts> | |
struct more_bind { | |
template <class> | |
struct result; | |
template <template <class...> class F, class... Us> | |
struct result<bind<F, Us...>> | |
: type_constant<bind<F, Us..., Ts...>> {}; | |
}; | |
template <class> | |
struct bind_constantiation; | |
template <template <class...> class F, class... Ts> | |
struct bind_constantiation<bind<F, Ts...>> | |
: bind<F, Ts...>::template result<> {}; | |
template <template <class...> class, class...> | |
struct bind_map; | |
template <template <class...> class F, class T, class... Ts> | |
struct bind_map<F, T, Ts...> | |
: cons<bind<F, T>, typename bind_map<F, Ts...>::type> {}; | |
template <template <class...> class F, class... Ts, class... Us> | |
struct bind_map<F, pack<Ts...>, Us...> | |
: cons<bind<F, Ts...>, typename bind_map<F, Us...>::type> {}; | |
template <template <class...> class F> | |
struct bind_map<F> : type_constant<pack<>> {}; | |
template <template <class, class> class, class, class> | |
struct foldr; | |
template <template <class, class> class F, class A, class T, class... Ts> | |
struct foldr<F, A, pack<T, Ts...>> | |
: F<T, typename foldr<F, A, pack<Ts...>>::type> {}; | |
template <template <class, class> class F, class A> | |
struct foldr<F, A, pack<>> : type_constant<A> {}; | |
template <template <class...> class, class> | |
struct map_internal; | |
template <template <class...> class F, class... Ts> | |
struct map_internal<F, pack<Ts...>> { | |
template <class T, class U> | |
struct result : cons<F<T>, U> {}; | |
}; | |
template <template <class...> class F, class T> | |
struct map | |
: foldr<map_internal<F, T>::template result, pack<>, T> {}; | |
template <class... Ts> | |
struct disjunction : std::disjunction<Ts...> {}; | |
template <class... Ts> | |
struct disjunction<pack<Ts...>> | |
: std::disjunction<typename bind_constantiation<typename Ts::type>::type...> {}; | |
struct unit {}; | |
struct asm_ctx; | |
struct data_ctx; | |
struct data_lbl_ctx; | |
struct text_ctx; | |
struct text_lbl_ctx; | |
template <class T> | |
constexpr bool is_context_v = | |
disjunction< | |
typename map< | |
more_bind<T>::template result, | |
typename bind_map< | |
std::is_same, | |
asm_ctx, data_ctx, data_lbl_ctx, text_ctx, text_lbl_ctx | |
>::type | |
>::type | |
>::value; | |
template <class F, class R> | |
struct flat_result_traits { | |
private: | |
typedef std::invoke_result_t<F, R> other_asm_type; | |
public: | |
typedef typename other_asm_type::context_type context_type; | |
typedef decltype(std::declval<other_asm_type>().un_asm(bool())) result_type; | |
}; | |
// A type that constitutes a DSL and express monad. | |
// The μ corresponding to `action` and η to `pure`. | |
template <class Context, class R> | |
class assembler { | |
static_assert(is_context_v<Context>, "The type Context must be context type"); | |
public: | |
typedef Context context_type; | |
typedef R result_type; | |
template <class F, | |
std::enable_if_t< | |
std::conjunction_v< | |
std::is_invocable<F, bool>, | |
std::is_same<std::invoke_result_t<F, bool>, R>, | |
std::negation<std::is_same<std::decay_t<F>, assembler>> | |
>, | |
std::nullptr_t | |
> = nullptr | |
> | |
assembler(F&& f) | |
: un_asm(std::forward<F>(f)) {} | |
template <class F> | |
assembler< | |
typename flat_result_traits<F, R>::context_type, | |
typename flat_result_traits<F, R>::result_type | |
> action(F&& f) const | |
{ | |
return { | |
[this_un_asm = un_asm, g = std::forward<F>(f)](bool b) { | |
return std::invoke(std::invoke(g, std::invoke(this_un_asm, b)).un_asm, b); | |
} | |
}; | |
} | |
template <class T> | |
assembler<Context, std::decay_t<T>> | |
pure(T&& val) const | |
{ | |
return { [val = std::forward<T>(val)](bool) { return val; } }; | |
} | |
template <class Ctx, class RR> | |
assembler<Ctx, RR> | |
ap(const assembler<Ctx, RR>& asm_) const | |
{ | |
return { | |
([this_un_asm = un_asm, asm_](bool b) -> unit { | |
std::invoke(this_un_asm, b); | |
return std::invoke(asm_.un_asm, b); | |
}) | |
}; | |
} | |
assembler labeled() const | |
{ | |
return { [this_un_asm = un_asm](bool) { return std::invoke(this_un_asm, true); } }; | |
} | |
template <class NewContext> | |
assembler<NewContext, R> | |
un_context() const | |
{ | |
return { un_asm }; | |
} | |
const std::function<R(bool)> un_asm; | |
}; | |
// Specialization when context is `asm_ctx` | |
template <class R> | |
class assembler<asm_ctx, R> { | |
public: | |
typedef asm_ctx context_type; | |
typedef R result_type; | |
template <class F, | |
std::enable_if_t< | |
std::conjunction_v< | |
std::is_invocable<F, bool>, | |
std::is_same<std::invoke_result_t<F, bool>, R>, | |
std::negation<std::is_same<std::decay_t<F>, assembler>> | |
>, | |
std::nullptr_t | |
> = nullptr | |
> | |
assembler(F&& f) | |
: un_asm(std::forward<F>(f)) {} | |
template <class F> | |
assembler< | |
typename flat_result_traits<F, R>::context_type, | |
typename flat_result_traits<F, R>::result_type | |
> action(F&& f) const | |
{ | |
return { | |
[this_un_asm = un_asm, g = std::forward<F>(f)](bool b) { | |
return std::invoke(std::invoke(g, std::invoke(this_un_asm, b)).un_asm, b); | |
} | |
}; | |
} | |
template <class T> | |
assembler<asm_ctx, std::decay_t<T>> | |
pure(T&& val) const | |
{ | |
return { [val = std::forward<T>(val)](bool) { return val; } }; | |
} | |
assembler labeled() const | |
{ | |
return { [this_un_asm = un_asm](bool) { return std::invoke(this_un_asm, true); } }; | |
} | |
template <class NewContext> | |
assembler<NewContext, R> | |
un_context() const | |
{ | |
return { un_asm }; | |
} | |
// Pass arguments to the stored function object and execute | |
R run_asm() const | |
{ | |
std::cout << ".intel_syntax noprefix" << std::endl; | |
return std::invoke(un_asm, false); | |
} | |
const std::function<R(bool)> un_asm; | |
}; | |
template <class Context, class Range> | |
std::enable_if_t< | |
std::is_convertible_v<typename std::decay_t<Range>::value_type, char>, | |
assembler<Context, unit> | |
> | |
print(Range&& range) | |
{ | |
return { | |
[c = std::forward<Range>(range)](bool b) -> unit { | |
if (b) std::cout << "\t"; | |
std::copy( | |
std::begin(c), | |
std::end(c), | |
std::ostream_iterator<char>(std::cout, "")); | |
std::cout << std::endl; | |
return {}; | |
} | |
}; | |
} | |
template <class NewContext, class Context, class R> | |
assembler<NewContext, R> | |
section(std::string sec, const assembler<Context, R>& asm_) | |
{ | |
return print<Context>("." + std::move(sec)).ap(asm_.template un_context<NewContext>()); | |
} | |
} // namespace detail | |
template <class R> | |
detail::assembler<detail::asm_ctx, R> | |
data_section(const detail::assembler<detail::data_ctx, R>& asm_) | |
{ | |
return detail::section<detail::asm_ctx>("data", asm_); | |
} | |
template <class R> | |
detail::assembler<detail::data_ctx, detail::unit> | |
data_label(std::string s, const detail::assembler<detail::data_lbl_ctx, R>& asm_) | |
{ | |
s.push_back(':'); | |
return detail::print<detail::data_ctx>(std::move(s)).ap(asm_.labeled().template un_context<detail::data_ctx>()); | |
} | |
template <class Range> | |
detail::assembler<detail::data_lbl_ctx, detail::unit> | |
byte(Range&& s) | |
{ | |
std::ostringstream oss; | |
oss << ".byte "; | |
std::copy(std::begin(s), std::end(s), std::ostream_iterator<int>(oss, ", ")); | |
std::string bytes = std::move(oss.str()); | |
bytes.pop_back(); | |
bytes.pop_back(); | |
return detail::print<detail::data_lbl_ctx>(std::move(bytes)); | |
} | |
template <class R> | |
detail::assembler<detail::asm_ctx, R> | |
text_section(const detail::assembler<detail::text_ctx, R>& asm_) | |
{ | |
return detail::section<detail::asm_ctx>("text", asm_); | |
} | |
detail::assembler<detail::text_ctx, detail::unit> | |
global_directive(std::string s) | |
{ | |
return detail::print<detail::text_ctx>(".global " + s); | |
} | |
template <class R> | |
detail::assembler<detail::text_ctx, R> | |
text_label_fn(std::string s, const detail::assembler<detail::text_lbl_ctx, R>& asm_) | |
{ | |
s.push_back(':'); | |
return detail::print<detail::text_ctx>(std::move(s)).ap(asm_.labeled().template un_context<detail::text_ctx>()); | |
} | |
detail::assembler<detail::text_lbl_ctx, detail::unit> | |
text_label(std::string s) | |
{ | |
return { | |
[s = std::move(s)](bool) -> detail::unit { | |
std::cout << (".L." + s + ":") << std::endl; | |
return {}; | |
} | |
}; | |
} | |
detail::assembler<detail::text_lbl_ctx, detail::unit> | |
noarg_instruction(std::string mnemonic) | |
{ | |
return detail::print<detail::text_lbl_ctx>(mnemonic); | |
} | |
detail::assembler<detail::text_lbl_ctx, detail::unit> | |
unary_instrunction(std::string mnemonic, std::string operand) | |
{ | |
return detail::print<detail::text_lbl_ctx>(mnemonic + " " + operand); | |
} | |
detail::assembler<detail::text_lbl_ctx, detail::unit> | |
binary_instruction(std::string mnemonic, std::string lhs, std::string rhs) | |
{ | |
return detail::print<detail::text_lbl_ctx>(mnemonic + " " + lhs + ", " + rhs); | |
} | |
#define DEF_NOARG_INST(MNEMONIC)\ | |
detail::assembler<detail::text_lbl_ctx, detail::unit>\ | |
MNEMONIC()\ | |
{\ | |
return noarg_instruction(#MNEMONIC);\ | |
} | |
#define DEF_UNARY_INST(MNEMONIC)\ | |
detail::assembler<detail::text_lbl_ctx, detail::unit>\ | |
MNEMONIC(std::string operand)\ | |
{\ | |
return unary_instrunction(#MNEMONIC, std::move(operand));\ | |
} | |
#define DEF_BINARY_INST(MNEMONIC)\ | |
detail::assembler<detail::text_lbl_ctx, detail::unit>\ | |
MNEMONIC(std::string lhs, std::string rhs)\ | |
{\ | |
return binary_instruction(#MNEMONIC, std::move(lhs), std::move(rhs));\ | |
} | |
#define NOARG_OP(D, STATE, X) DEF_NOARG_INST(X) | |
#define DEF_NOARG_INSTS(...)\ | |
BOOST_PP_SEQ_FOR_EACH(NOARG_OP, , BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) | |
#define UNARY_OP(D, STATE, X) DEF_UNARY_INST(X) | |
#define DEF_UNARY_INSTS(...)\ | |
BOOST_PP_SEQ_FOR_EACH(UNARY_OP, , BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) | |
#define BINARY_OP(D, STATE, X) DEF_BINARY_INST(X) | |
#define DEF_BINARY_INSTS(...)\ | |
BOOST_PP_SEQ_FOR_EACH(BINARY_OP, , BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) | |
DEF_NOARG_INSTS(leave, ret) | |
DEF_UNARY_INSTS(push, jmp) | |
DEF_BINARY_INSTS(mov, movsx, add, sub, imul, lea) | |
#define CONST(X) .action([](auto&&) { return X; }) | |
template <class F, class Context, class R> | |
detail::assembler<Context, R> | |
fmap(F&& f, const detail::assembler<Context, R>& asm_) | |
{ | |
return { | |
[f = std::forward<F>(f), this_un_asm = asm_.un_asm](bool b) { | |
return f(this_un_asm(b)); | |
} | |
}; | |
} | |
} // namespace dhu_grad | |
/* | |
* An example of usage. | |
* This will output the following assembly code: | |
* | |
*.intel_syntax noprefix | |
* .data | |
* .L.data.0: | |
* .byte 104, 111, 103, 101, 0 | |
* .text | |
* .global main | |
* main: | |
* push rbp | |
* mov rbp, rsp | |
* sub rsp, 8 | |
* lea rax, [rbp-8] | |
* mov rdi, offset .L.data.0 | |
* mov [rax], rdi | |
* add rsp, 8 | |
* lea rax, [rbp-8] | |
* mov rax, [rax] | |
* mov rdi, 0 | |
* imul rdi, 1 | |
* add rax, rdi | |
* movsx rax, byte ptr [rax] | |
* jmp .L.return.main | |
* .L.return.main: | |
* leave | |
* ret | |
* | |
* This is roughly equivalent to the following C code: | |
* | |
* int main() { char* s = "hoge"; return s[0]; } | |
*/ | |
int main() | |
{ | |
using namespace dhu_grad; | |
const auto dsl = | |
data_section( | |
data_label(".L.data.0", byte("hoge")) | |
) | |
CONST( | |
text_section( | |
global_directive("main") | |
CONST( | |
text_label_fn("main", | |
push("rbp") | |
CONST(mov("rbp", "rsp")) | |
CONST(sub("rsp", "8")) | |
CONST(lea("rax", "[rbp-8]")) | |
CONST(mov("rdi", "offset .L.data.0")) | |
CONST(mov("[rax]", "rdi")) | |
CONST(add("rsp", "8")) | |
CONST(lea("rax", "[rbp-8]")) | |
CONST(mov("rax", "[rax]")) | |
CONST(mov("rdi", "0")) | |
CONST(imul("rdi", "1")) | |
CONST(add("rax", "rdi")) | |
CONST(movsx("rax", "byte ptr [rax]")) | |
CONST(jmp(".L.return.main")) | |
CONST(text_label("return.main")) | |
CONST(leave()) | |
CONST(ret()) | |
) | |
) | |
) | |
); | |
dsl.run_asm(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <tuple> | |
#include <type_traits> | |
template <class F, class... Fs, std::size_t... Is> | |
constexpr std::tuple<Fs...> | |
tail_impl(const std::tuple<F, Fs...>& ts, std::index_sequence<Is...>) | |
{ | |
return std::make_tuple(std::get<Is + 1>(ts)...); | |
} | |
template <class F, class... Fs> | |
constexpr std::tuple<Fs...> | |
tail(const std::tuple<F, Fs...>& ts) | |
{ | |
return tail_impl(ts, std::make_index_sequence<sizeof...(Fs)>{}); | |
} | |
template <class F, class T, | |
std::enable_if_t< | |
std::is_invocable_v<F, T>, | |
std::nullptr_t | |
> = nullptr> | |
constexpr auto | |
make_composition(const std::tuple<F>& f, const std::tuple<T>& val) | |
{ | |
return std::get<0>(f)(std::get<0>(val)); | |
} | |
template <class F, | |
std::enable_if_t<std::is_invocable_v<F>, std::nullptr_t> = nullptr> | |
constexpr auto | |
make_composition(const std::tuple<F>& f, const std::tuple<>&) | |
{ | |
return std::get<0>(f)(); | |
} | |
template <class F, class... Fs, class... Ts> | |
constexpr auto | |
make_composition(const std::tuple<F, Fs...>& fs, const std::tuple<Ts...>& val) | |
{ | |
return std::get<0>(fs)(make_composition(tail(fs), val)); | |
} | |
template <class... Fs> | |
constexpr auto | |
composition(Fs&&... fs) | |
{ | |
return [fs = std::make_tuple(std::forward<Fs>(fs)...)](auto&&... xs) { | |
return make_composition(fs, std::forward_as_tuple( | |
std::forward<decltype(xs)>(xs)... | |
)); | |
}; | |
} | |
int main() | |
{ | |
constexpr auto f = [](int x) constexpr -> int { return x; }; | |
constexpr auto g = [](int x) constexpr -> char { return x; }; | |
constexpr auto h = [](char x) constexpr -> bool { return x == 'h'; }; | |
constexpr auto i = []() constexpr -> int { return 42; }; | |
static_assert(composition(h, g, f)(104) == true); | |
static_assert(composition(h, g, i)() == false); | |
constexpr auto id = [](auto&& val) constexpr { return std::forward<decltype(val)>(val); }; | |
static_assert(composition(h, id)(104) == true); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment