Skip to content

Instantly share code, notes, and snippets.

@falgon
Last active January 17, 2020 15:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save falgon/3e7b8dcb6083648efb8a296eb2ddacf7 to your computer and use it in GitHub Desktop.
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)
#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();
}
#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