lean4-htt/src/library/compiler/emit_cpp.cpp
2019-05-10 11:26:49 -07:00

1202 lines
42 KiB
C++

/*
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
*/
#include <iostream>
#include <limits>
#include <string>
#include "runtime/utf8.h"
#include "runtime/apply.h"
#include "kernel/find_fn.h"
#include "kernel/instantiate.h"
#include "library/module.h"
#include "library/compiler/llnf.h"
#include "library/compiler/name_mangling.h"
#include "library/compiler/emit_cpp.h"
#include "library/compiler/llnf_code.h"
#include "library/compiler/export_attribute.h"
#include "library/compiler/extern_attribute.h"
#include "library/compiler/init_attribute.h"
namespace lean {
static std::string to_cpp_type(expr const & e) {
if (is_constant(e)) {
if (e == mk_enf_object_type() || e == mk_enf_neutral_type()) {
return "obj*";
} else if (const_name(e) == get_uint8_name()) {
return "uint8";
} else if (const_name(e) == get_uint16_name()) {
return "uint16";
} else if (const_name(e) == get_uint32_name()) {
return "uint32";
} else if (const_name(e) == get_uint64_name()) {
return "uint64";
} else if (const_name(e) == get_usize_name()) {
return "usize";
}
} else if (is_pi(e)) {
return "obj*";
}
throw exception("unknown type");
}
static void open_namespaces_core(std::ostream & out, name const & p) {
if (p.is_anonymous()) return;
open_namespaces_core(out, p.get_prefix());
lean_assert(p.is_string());
out << "namespace " << p.get_string().to_std_string() << " {\n";
}
static void open_namespaces(std::ostream & out, name const & n) {
open_namespaces_core(out, n.get_prefix());
}
/* If `n` has the attribute [export], and the "export" is hierarchical, then
we must put `n` code inside of a namespace. */
static void open_namespaces_for(std::ostream & out, environment const & env, name const & n) {
optional<name> c = get_export_name_for(env, n);
if (!c || c->is_atomic()) return;
open_namespaces(out, *c);
}
static void close_namespaces(std::ostream & out, name const & n) {
if (n.is_atomic()) return;
name p = n.get_prefix();
while (!p.is_anonymous()) {
out << "}";
p = p.get_prefix();
}
out << "\n";
}
static void close_namespaces_for(std::ostream & out, environment const & env, name const & n) {
optional<name> c = get_export_name_for(env, n);
if (!c || c->is_atomic()) return;
return close_namespaces(out, *c);
}
static char const * g_lean_main = "_lean_main";
static std::string to_base_cpp_name(environment const & env, name const & n) {
if (optional<name> c = get_export_name_for(env, n)) {
lean_assert(c->is_string());
if (*c == "main")
return g_lean_main;
else
return c->get_string().to_std_string();
} else if (n == "main") {
return g_lean_main;
} else {
return mangle(n);
}
}
static std::string to_cpp_name(environment const & env, name const & n) {
if (optional<name> c = get_export_name_for(env, n)) {
lean_assert(c->is_string());
if (*c == "main")
return g_lean_main;
else
return c->to_string("::");
} else if (n == "main") {
return g_lean_main;
} else {
return mangle(n);
}
}
static std::string to_cpp_init_name(environment const & env, name const & n) {
if (optional<name> c = get_export_name_for(env, n)) {
name init_c(c->get_prefix(), (std::string("_init_") + c->get_string().to_std_string()).c_str());
return init_c.to_string("::");
} else {
return std::string("_init_") + mangle(n);
}
}
static expr get_result_type(expr type) {
while (is_pi(type)) {
type = binding_body(type);
}
return type;
}
static void emit_fn_decl_core(std::ostream & out, environment const & env, name const & n, name const & cpp_base_name, bool mod_decl) {
expr type = get_constant_ll_type(env, n);
if (!mod_decl && !is_pi(type)) {
/* We should add extern for constants coming from other modules. */
out << "extern ";
}
out << to_cpp_type(get_result_type(type)) << " " << cpp_base_name;
if (is_pi(type)) {
out << "(";
bool first = true;
while (is_pi(type)) {
if (first) first = false; else out << ", ";
out << to_cpp_type(binding_domain(type));
type = binding_body(type);
}
out << ")";
}
out << ";\n";
}
static void emit_fn_decl(std::ostream & out, environment const & env, name const & n, bool mod_decl) {
open_namespaces_for(out, env, n);
emit_fn_decl_core(out, env, n, to_base_cpp_name(env, n), mod_decl);
close_namespaces_for(out, env, n);
}
static name cpp_qualified_name_to_name(std::string const & s) {
size_t pos = s.find("::");
if (pos == std::string::npos) {
return name(s.c_str());
} else {
name prefix(s.substr(0, pos).c_str());
name rest = cpp_qualified_name_to_name(s.substr(pos+2, s.size()-pos-2));
return prefix + rest;
}
}
static void emit_extern_decl(std::ostream & out, environment const & env, name const & n, std::string const & cpp_name) {
name q_cpp_name = cpp_qualified_name_to_name(cpp_name);
open_namespaces(out, q_cpp_name);
if (is_extern_c(env, n)) out << "extern \"C\" ";
emit_fn_decl_core(out, env, n, q_cpp_name.get_string(), false);
close_namespaces(out, q_cpp_name);
}
/* Auxiliary function for `collect_dependencies`. */
static void collect_constant(expr const & e, name_set & deps) {
lean_assert(is_constant(e));
if (!is_llnf_op(e) && !is_enf_neutral(e) && !is_enf_unreachable(e)) {
deps.insert(const_name(e));
}
}
/* Collect declarations used by `e` in `deps`. */
static void collect_dependencies(environment const & env, expr e, name_set & deps) {
while (true) {
switch (e.kind()) {
case expr_kind::Lambda:
e = binding_body(e);
break;
case expr_kind::Let:
collect_dependencies(env, let_value(e), deps);
e = let_body(e);
break;
case expr_kind::App:
if (is_cases_on_app(env, e)) {
buffer<expr> args;
get_app_args(e, args);
for (expr const & arg : args)
collect_dependencies(env, arg, deps);
} else if (is_llnf_closure(get_app_fn(e))) {
buffer<expr> args;
get_app_args(e, args);
collect_constant(args[0], deps);
} else {
collect_constant(get_app_fn(e), deps);
}
return;
case expr_kind::Const:
collect_constant(e, deps);
return;
default:
return;
}
}
}
static name * g_cpp = nullptr;
/* Emit C++ function declaration for all functions/constants declared in the current module,
and their direct dependencies. */
static void emit_fn_decls(std::ostream & out, environment const & env) {
comp_decls ds = get_llnf_code(env);
name_set mod_decls;
name_set all_decls;
for (comp_decl const & d : ds) {
mod_decls.insert(d.fst());
all_decls.insert(d.fst());
collect_dependencies(env, d.snd(), all_decls);
}
all_decls.for_each([&](name const & n) {
if (is_extern_constant(env, n)) {
if (optional<std::string> fn = get_extern_name_for(env, *g_cpp, n))
emit_extern_decl(out, env, n, *fn);
} else {
emit_fn_decl(out, env, n, mod_decls.contains(n));
}
});
}
static optional<comp_decl> has_main_fn(environment const & env) {
comp_decls ds = get_llnf_code(env);
for (comp_decl const & d : ds) {
name const & n = d.fst();
if (n == "main") return optional<comp_decl>(d);
if (optional<name> e = get_export_name_for(env, n)) {
if (*e == "main") return optional<comp_decl>(d);
}
}
return optional<comp_decl>();
}
static void emit_file_header(std::ostream & out, environment const & env, module_name const & m, list<module_name> const & deps) {
out << "// Lean compiler output\n";
out << "// Module: " << m << "\n";
out << "// Imports:"; for (module_name const & d : deps) out << " " << d; out << "\n";
out << "#include \"runtime/object.h\"\n";
out << "#include \"runtime/apply.h\"\n";
if (has_main_fn(env))
out << "#include \"runtime/init_module.h\"\n";
out << "typedef lean::object obj; typedef lean::usize usize;\n";
out << "typedef lean::uint8 uint8; typedef lean::uint16 uint16;\n";
out << "typedef lean::uint32 uint32; typedef lean::uint64 uint64;\n";
out << "#if defined(__clang__)\n";
out << "#pragma clang diagnostic ignored \"-Wunused-parameter\"\n";
out << "#pragma clang diagnostic ignored \"-Wunused-label\"\n";
out << "#elif defined(__GNUC__) && !defined(__CLANG__)\n";
out << "#pragma GCC diagnostic ignored \"-Wunused-parameter\"\n";
out << "#pragma GCC diagnostic ignored \"-Wunused-label\"\n";
out << "#pragma GCC diagnostic ignored \"-Wunused-but-set-variable\"\n";
out << "#endif\n";
}
static void emit_hexdigit(std::ostream & out, unsigned char c) {
lean_assert(c < 16);
if (c < 10) {
out << static_cast<char>('0' + c);
} else {
out << static_cast<char>('a' + (c - 10));
}
}
static void emit_quoted_string(std::ostream & out, std::string const & s) {
for (unsigned i = 0; i < s.size(); i++) {
unsigned char c = s[i];
if (c == '\n') {
out << "\\n";
} else if (c == '\t') {
out << "\\t";
} else if (c == '\\') {
out << "\\\\";
} else if (c == '\"') {
out << "\\\"";
} else if (c <= 31 || c >= 0x7f) {
out << "\\x"; emit_hexdigit(out, c / 16); emit_hexdigit(out, c % 16);
} else {
out << c;
}
}
}
static char const * get_scalar_type_from_size(unsigned i) {
switch (i) {
case 1: return "uint8";
case 2: return "uint16";
case 4: return "uint32";
case 8: return "uint64";
default: throw exception("C++ code generation failed, invalid scalar size");
}
}
struct emit_fn_fn {
std::ostream & m_out;
name_generator m_ngen;
environment m_env;
local_ctx m_lctx;
name_map<exprs> m_jp_vars;
name m_fn_name;
buffer<expr> m_fn_args;
static bool is_jmp(expr const & e) {
return is_llnf_jmp(get_app_fn(e));
}
bool is_obj(expr const & x) {
lean_assert(is_fvar(x));
return m_lctx.get_local_decl(x).get_type() == mk_enf_object_type();
}
void emit_unit(std::ostream & out) {
out << "lean::box(0)";
}
void emit_unit() {
emit_unit(m_out);
}
void emit_constant(expr const & c) {
lean_assert(is_constant(c));
lean_assert(!is_enf_unreachable(c));
if (is_enf_neutral(c))
emit_unit();
else
m_out << to_cpp_name(m_env, const_name(c));
}
void emit_fvar(std::ostream & out, expr const & x) {
lean_assert(is_fvar(x));
name const & id = fvar_name(x);
lean_assert(id.is_numeral());
out << "x_" << id.get_numeral();
}
void emit_fvar(expr const & x) {
emit_fvar(m_out, x);
}
void emit_lbl(expr const & jp) {
lean_assert(is_fvar(jp));
name const & id = fvar_name(jp);
lean_assert(id.is_numeral());
m_out << "lbl_" << id.get_numeral();
}
/* Emit instructions that return void. */
void emit_instr(expr const & e) {
expr const & f = get_app_fn(e);
lean_assert(is_llnf_inc(f) || is_llnf_dec(f));
if (is_llnf_inc(f)) {
m_out << "lean::inc(";
} else {
m_out << "lean::dec(";
}
emit_fvar(app_arg(e));
m_out << ");\n";
}
void emit_lhs(expr const & x) {
emit_fvar(x); m_out << " = ";
}
void emit_num_lit_core(expr const & x, nat const & v) {
if (is_obj(x)) {
if (v <= std::numeric_limits<unsigned long>::max()) {
m_out << "lean::mk_nat_obj(" << v << "ul)";
} else {
m_out << "lean::mk_nat_obj(lean::mpz(\"" << v << "\"))";
}
} else {
m_out << v;
}
}
void emit_num_lit(expr const & x, nat const & v) {
emit_lhs(x);
emit_num_lit_core(x, v);
m_out << ";\n";
}
void emit_lit(expr const & x, expr const & v) {
lean_assert(is_lit(v));
emit_lhs(x);
literal const & l = lit_value(v);
switch (l.kind()) {
case literal_kind::Nat:
emit_num_lit_core(x, l.get_nat());
break;
case literal_kind::String:
m_out << "lean::mk_string(\"";
emit_quoted_string(m_out, l.get_string().to_std_string());
m_out << "\")";
break;
}
m_out << ";\n";
}
void emit_arg(std::ostream & out, expr const & arg) {
if (is_fvar(arg))
emit_fvar(out, arg);
else
emit_unit(out);
}
void emit_arg(expr const & arg) {
emit_arg(m_out, arg);
}
void emit_args(unsigned sz, expr const * args) {
for (unsigned i = 0; i < sz; i++) {
if (i > 0) m_out << ", ";
emit_arg(args[i]);
}
}
string_ref arg_to_string_ref(expr const & arg) {
std::ostringstream out;
emit_arg(out, arg);
return string_ref(out.str());
}
void emit_apply(expr const & x, buffer<expr> const & args) {
lean_assert(args.size() > 0);
expr const & f = args[0];
unsigned nargs = args.size() - 1;
if (nargs > LEAN_CLOSURE_MAX_ARGS) {
m_out << "{ obj* _aargs[] = {";
emit_args(nargs, args.data()+1);
m_out << "}; ";
emit_lhs(x);
m_out << "lean::apply_m("; emit_fvar(f); m_out << ", " << nargs << ", _aargs); }\n";
} else {
emit_lhs(x);
m_out << "lean::apply_" << nargs << "(";
emit_fvar(f);
m_out << ", ";
emit_args(nargs, args.data()+1);
m_out << ");\n";
}
}
void emit_closure(expr const & x, buffer<expr> const & args) {
lean_assert(!args.empty());
expr const & fn = args[0];
lean_assert(is_constant(fn));
unsigned arity = get_llnf_arity(m_env, const_name(fn));
emit_lhs(x);
m_out << "lean::alloc_closure(reinterpret_cast<void*>("; emit_constant(fn); m_out << "), " << arity << ", " << (args.size()-1) << ");\n";
for (unsigned i = 1; i < args.size(); i++) {
m_out << "lean::closure_set("; emit_fvar(x); m_out << ", " << (i-1) << ", "; emit_arg(args[i]); m_out << ");\n";
}
}
void emit_cnstr_scalar_size(unsigned num_usizes, unsigned num_bytes) {
if (num_usizes == 0)
m_out << num_bytes;
else if (num_bytes == 0)
m_out << "sizeof(size_t)*" << num_usizes;
else
m_out << "sizeof(size_t)*" << num_usizes << " + " << num_bytes;
}
void emit_alloc_cnstr(expr const & x, unsigned cidx, unsigned num_objs, unsigned num_usizes, unsigned num_bytes) {
emit_lhs(x);
m_out << "lean::alloc_cnstr(" << cidx << ", " << num_objs << ", ";
emit_cnstr_scalar_size(num_usizes, num_bytes); m_out << ");\n";
}
void emit_cnstr_sets(expr const & x, unsigned sz, expr const * args) {
for (unsigned i = 0; i < sz; i++) {
m_out << "lean::cnstr_set("; emit_fvar(x); m_out << ", " << i << ", "; emit_arg(args[i]); m_out << ");\n";
}
}
void emit_cnstr(expr const & x, expr const & fn, buffer<expr> const & args) {
unsigned cidx, num_usizes, num_bytes;
lean_verify(is_llnf_cnstr(fn, cidx, num_usizes, num_bytes));
if (num_usizes == 0 && num_bytes == 0 && args.size() == 0) {
emit_lhs(x); m_out << "lean::box(" << cidx << ");\n";
} else {
emit_alloc_cnstr(x, cidx, args.size(), num_usizes, num_bytes);
emit_cnstr_sets(x, args.size(), args.data());
}
}
void emit_reset(expr const & x, expr const & fn, expr const & o) {
unsigned n;
lean_verify(is_llnf_reset(fn, n));
m_out << "if (lean::is_exclusive("; emit_fvar(o); m_out <<")) {\n";
for (unsigned i = 0; i < n; i++) {
m_out << " lean::cnstr_release("; emit_fvar(o); m_out << ", " << i << ");\n";
}
m_out << " "; emit_lhs(x); emit_fvar(o); m_out << ";\n";
m_out << "} else {\n";
m_out << " lean::dec("; emit_fvar(o); m_out << ");\n ";
emit_lhs(x); emit_unit(); m_out << ";\n";
m_out << "}\n";
}
void emit_reuse(expr const & x, expr const & fn, buffer<expr> const & args) {
lean_assert(!args.empty());
unsigned cidx, num_usizes, num_bytes;
bool updt_cidx;
lean_verify(is_llnf_reuse(fn, cidx, num_usizes, num_bytes, updt_cidx));
expr const & o = args[0];
m_out << "if (lean::is_scalar("; emit_fvar(o); m_out <<")) {\n";
m_out << " "; emit_alloc_cnstr(x, cidx, args.size()-1, num_usizes, num_bytes);
m_out << "} else {\n";
m_out << " "; emit_lhs(x); emit_fvar(o); m_out << ";\n";
if (updt_cidx) {
m_out << " lean::cnstr_set_tag("; emit_fvar(o); m_out << ", " << cidx << ");\n";
}
m_out << "}\n";
emit_cnstr_sets(x, args.size()-1, args.data()+1);
}
void emit_offset(unsigned n, unsigned offset) {
if (n > 0) {
m_out << "sizeof(void*)*" << n;
if (offset > 0)
m_out << " + " << offset;
} else {
m_out << offset;
}
}
void emit_sset(expr const & x, expr const & fn, buffer<expr> const & args) {
lean_assert(args.size() == 2);
unsigned sz, n, offset;
lean_verify(is_llnf_sset(fn, sz, n, offset));
m_out << "lean::cnstr_set_scalar("; emit_fvar(args[0]); m_out << ", "; emit_offset(n, offset); m_out << ", "; emit_fvar(args[1]); m_out << ");\n";
emit_lhs(x); emit_fvar(args[0]); m_out << ";\n";
}
void emit_uset(expr const & x, expr const & fn, buffer<expr> const & args) {
unsigned n;
lean_verify(is_llnf_uset(fn, n));
m_out << "lean::cnstr_set_scalar("; emit_fvar(args[0]); m_out << ", "; emit_offset(n, 0); m_out << ", "; emit_fvar(args[1]); m_out << ");\n";
emit_lhs(x); emit_fvar(args[0]); m_out << ";\n";
}
void emit_proj(expr const & x, expr const & fn, expr const & o) {
unsigned i;
lean_verify(is_llnf_proj(fn, i));
emit_lhs(x);
m_out << "lean::cnstr_get("; emit_fvar(o); m_out << ", " << i << ");\n";
}
void emit_sproj(expr const & x, expr const & fn, expr const & o) {
unsigned sz, n, offset;
lean_verify(is_llnf_sproj(fn, sz, n, offset));
emit_lhs(x);
m_out << "lean::cnstr_get_scalar<" << get_scalar_type_from_size(sz) << ">("; emit_fvar(o); m_out << ", "; emit_offset(n, offset); m_out << ");\n";
}
void emit_uproj(expr const & x, expr const & fn, expr const & o) {
unsigned n;
lean_verify(is_llnf_uproj(fn, n));
emit_lhs(x);
m_out << "lean::cnstr_get_scalar<usize>("; emit_fvar(o); m_out << ", sizeof(void*)*" << n << ");\n";
}
void emit_unbox(expr const & x, expr const & fn, expr const & arg) {
unsigned n;
lean_verify(is_llnf_unbox(fn, n));
emit_lhs(x);
switch (n) {
case 0: m_out << "lean::unbox_size_t("; break;
case 4: m_out << "lean::unbox_uint32("; break;
case 8: m_out << "lean::unbox_uint64("; break;
default: m_out << "lean::unbox("; break; // default case for scalars that fit in tagged pointers in all platforms
}
emit_fvar(arg);
m_out << ");\n";
}
void emit_box(expr const & x, expr const & fn, expr const & arg) {
unsigned n;
lean_verify(is_llnf_box(fn, n));
emit_lhs(x);
switch (n) {
case 0: m_out << "lean::box_size_t("; break;
case 4: m_out << "lean::box_uint32("; break;
case 8: m_out << "lean::box_uint64("; break;
default: m_out << "lean::box("; break; // default case for scalars that fit in tagged pointers in all platforms
}
emit_fvar(arg);
m_out << ");\n";
}
void emit_extern_constant(expr const & x, expr const & fn, buffer<expr> const & args) {
emit_lhs(x);
string_refs arg_strs;
unsigned i = args.size();
while (i > 0) {
--i;
arg_strs = string_refs(arg_to_string_ref(args[i]), arg_strs);
}
emit_extern_call(m_out, m_env, *g_cpp, const_name(fn), arg_strs);
m_out << ";\n";
}
void emit_instr(local_decl const & d) {
expr x = d.mk_ref();
expr val = *d.get_value();
if (is_lit(val)) {
return emit_lit(x, val);
} else if (optional<nat> const & v = get_num_lit_ext(val)) {
emit_num_lit(x, *v);
} else if (is_constant(val)) {
if (is_llnf_cnstr(val)) {
buffer<expr> args;
emit_cnstr(x, val, args);
} else if (is_enf_unreachable(val)) {
m_out << "lean_unreachable();\n";
emit_lhs(x); emit_unit(); m_out << ";\n";
} else {
emit_lhs(x); emit_constant(val); m_out << ";\n";
}
} else if (is_app(val)) {
buffer<expr> args;
expr const & fn = get_app_args(val, args);
lean_assert(is_constant(fn));
if (is_llnf_cnstr(fn)) {
emit_cnstr(x, fn, args);
} else if (is_llnf_apply(fn)) {
emit_apply(x, args);
} else if (is_llnf_closure(fn)) {
emit_closure(x, args);
} else if (is_llnf_reuse(fn)) {
emit_reuse(x, fn, args);
} else if (is_llnf_reset(fn)) {
emit_reset(x, fn, args[0]);
} else if (is_llnf_sset(fn)) {
emit_sset(x, fn, args);
} else if (is_llnf_uset(fn)) {
emit_uset(x, fn, args);
} else if (is_llnf_proj(fn)) {
emit_proj(x, fn, args[0]);
} else if (is_llnf_sproj(fn)) {
emit_sproj(x, fn, args[0]);
} else if (is_llnf_uproj(fn)) {
emit_uproj(x, fn, args[0]);
} else if (is_llnf_unbox(fn)) {
emit_unbox(x, fn, args[0]);
} else if (is_llnf_box(fn)) {
emit_box(x, fn, args[0]);
} else if (is_extern_constant(m_env, const_name(fn))) {
emit_extern_constant(x, fn, args);
} else {
/* Regular function application. */
emit_lhs(x);
emit_constant(fn);
m_out << "(";
emit_args(args.size(), args.data());
m_out << ");\n";
}
} else {
lean_assert(!is_fvar(val));
lean_unreachable();
}
}
expr find_max_occs(unsigned sz, expr const * es) {
lean_assert(sz > 0);
expr r = es[sz-1];
unsigned r_noccs = 1;
for (unsigned i = 0; i < sz; i++) {
expr curr = es[i];
unsigned curr_noccs = 1;
for (unsigned j = i+1; j < sz; j++) {
if (es[j] == curr)
curr_noccs++;
}
if (curr_noccs > r_noccs) {
r = curr;
r_noccs = curr_noccs;
}
}
return r;
}
void emit_cases(expr const & e) {
lean_assert(is_cases_on_app(m_env, e));
buffer<expr> args;
get_app_args(e, args);
expr const & x = args[0];
if (args.size() == 3) {
// use if-statement
if (is_obj(x)) {
m_out << "if (lean::obj_tag("; emit_fvar(x); m_out << ") == 0)\n";
} else {
m_out << "if ("; emit_fvar(x); m_out << " == 0)\n";
}
emit(args[1]);
m_out << "else\n";
emit(args[2]);
} else {
if (is_obj(x)) {
m_out << "switch (lean::obj_tag("; emit_fvar(x); m_out << ")) {\n";
} else {
m_out << "switch ("; emit_fvar(x); m_out << ") {\n";
}
expr def = find_max_occs(args.size() - 1, args.data() + 1);
for (unsigned i = 1; i < args.size(); i++) {
if (args[i] != def) {
m_out << "case " << (i-1) << ":\n";
emit(args[i]);
}
}
m_out << "default:\n";
emit(def);
m_out << "}\n";
}
}
void emit_jmp(expr const & e) {
lean_assert(is_jmp(e));
buffer<expr> args;
get_app_args(e, args);
expr const & jp = args[0];
lean_assert(is_fvar(jp));
lean_assert(m_jp_vars.contains(fvar_name(jp)));
exprs params = *m_jp_vars.find(fvar_name(jp));
lean_assert(length(params) == args.size() - 1);
for (unsigned i = 1; i < args.size(); i++) {
emit_fvar(head(params)); m_out << " = "; emit_arg(args[i]); m_out << ";\n";
params = tail(params);
}
m_out << "goto "; emit_lbl(jp); m_out << ";\n";
}
optional<expr> is_self_call(expr const & val) {
expr fn = get_app_fn(val);
if (is_constant(fn) && const_name(fn) == m_fn_name)
return some_expr(val);
else
return none_expr();
}
void emit_tail_call(expr const & e) {
buffer<expr> args;
expr fn = get_app_args(e, args);
lean_assert(is_constant(fn) && const_name(fn) == m_fn_name);
lean_assert(args.size() == m_fn_args.size());
for (unsigned i = 0; i < args.size(); i++) {
if (args[i] != m_fn_args[i]) {
emit_fvar(m_fn_args[i]); m_out << " = "; emit_arg(args[i]); m_out << ";\n";
}
}
m_out << "goto _start;\n";
}
void emit_terminal(expr const & e, bool tail_call) {
if (is_cases_on_app(m_env, e)) {
emit_cases(e);
} else if (is_jmp(e)) {
emit_jmp(e);
} else if (tail_call) {
emit_tail_call(*m_lctx.get_local_decl(e).get_value());
} else if (is_fvar(e)) {
m_out << "return "; emit_fvar(e); m_out << ";\n";
} else if (is_enf_unreachable(e)) {
m_out << "lean_unreachable();\n";
} else {
lean_unreachable();
}
}
expr get_instr_rhs(expr const & x) {
local_decl d = m_lctx.get_local_decl(x);
return *d.get_value();
}
optional<pair<unsigned, expr>> is_proj(expr const & x) {
expr rhs = get_instr_rhs(x);
unsigned idx;
if (is_llnf_proj(get_app_fn(rhs), idx))
return optional<pair<unsigned, expr>>(mk_pair(idx, app_arg(rhs)));
else
return optional<pair<unsigned, expr>>();
}
bool is_scalar_proj(expr const & x) {
expr rhs = get_instr_rhs(x);
return is_app(rhs) && (is_llnf_sproj(app_fn(rhs)) || is_llnf_uproj(app_fn(rhs)));
}
optional<expr> is_inc(expr const & x) {
expr rhs = get_instr_rhs(x);
if (is_llnf_inc(get_app_fn(rhs)))
return some_expr(app_arg(rhs));
else
return none_expr();
}
optional<pair<unsigned, expr>> is_reset(expr const & x) {
expr rhs = get_instr_rhs(x);
unsigned n;
if (is_llnf_reset(get_app_fn(rhs), n))
return optional<pair<unsigned, expr>>(mk_pair(n, app_arg(rhs)));
else
return optional<pair<unsigned, expr>>();
}
/* Return true if the sub-sequence of instructions starting at `i`
is a sequence of projections and incs followed by a reset.
Examples:
1-
```
_x_2 : _obj := _proj.0 _x_1,
_inc _x_2,
_x_3 : _obj := _proj.1 _x_1,
_inc _x_3,
_x_4 : _obj := _reset.2 _x_1,
```
2-
```
_x_3 : _obj := _proj.0 _x_1,
_inc _x_3,
_x_4 : uint32 := _sproj.4.1.4 _x_1,
_x_5 : _obj := _reset.1 _x_1,
```
If the result is true and a `_reset.n` instruction was found,
then `inc_projs` will have size `n` and `inc_projs[i] = some(x)` if
projection `_proj.i` was accessed and its RC incremented.
*/
bool is_proj_inc_reset_seq(unsigned i, buffer<expr> const & instrs, buffer<optional<expr>> & inc_projs) {
optional<pair<unsigned, expr>> p = is_proj(instrs[i]);
if (!p) return false;
expr x = p->second;
unsigned j = i;
unsigned n = 0;
while (j < instrs.size()) {
expr x_j = instrs[j];
if (is_scalar_proj(x_j)) {
j++;
} else if (optional<pair<unsigned, expr>> c = is_proj(x_j)) {
/* x_j := proj.i x
_ := inc x_j */
if (c->second != x) return false;
j++;
if (j == instrs.size()) return false;
optional<expr> y = is_inc(instrs[j]);
if (!y || *y != x_j) return false;
j++;
} else if (optional<pair<unsigned, expr>> c = is_reset(instrs[j])) {
n = c->first;
/* y := reset.n x */
if (c->second != x) return false;
break; // found matching reset
} else {
return false;
}
}
if (j == instrs.size()) return false;
inc_projs.clear();
inc_projs.resize(n, none_expr());
for (; i < j; i++) {
if (optional<pair<unsigned, expr>> c = is_proj(instrs[i]))
inc_projs[c->first] = some_expr(instrs[i]);
}
return true;
}
/* Return true iff `x` is reused in `instrs[i], ...,instrs[instrs.size()-1]`.
That is, we are checking whether `x` is always reused. If this is the case,
we can avoid `lean::cnstr_set(o, k, lean::box(0))` operations at `emit_proj_inc_reset_seq`.
We can extend this check to `cases` terminal expressions. In this case, we would
need to check whether `x` is reused in **all** branches or not. */
bool is_always_reused(expr const & x, unsigned i, buffer<expr> const & instrs) {
for (unsigned j = i; j < instrs.size(); j++) {
expr val = get_instr_rhs(instrs[j]);
if (is_llnf_reuse(get_app_fn(val))) {
buffer<expr> args;
get_app_args(val, args);
if (args[0] == x)
return true;
}
}
return false;
}
/* Emit the a sub-sequence starting at `i` that was detected by is_proj_inc_reset_seq */
unsigned emit_proj_inc_reset_seq(unsigned i, buffer<expr> const & instrs, buffer<optional<expr>> & inc_projs) {
/* Emit projections, but skip incs */
unsigned j = i;
while (!is_reset(instrs[j])) {
expr x_j = instrs[j];
if (is_scalar_proj(x_j) || is_proj(x_j)) {
emit_instr(m_lctx.get_local_decl(x_j));
}
j++;
}
/* Emit reset if-statement with decs when shared and missing incs when exclusive */
lean_assert(is_reset(instrs[j]));
local_decl d = m_lctx.get_local_decl(instrs[j]);
expr x = instrs[j];
expr o = app_arg(*d.get_value());
bool reused = is_always_reused(x, j, instrs);
m_out << "if (lean::is_exclusive("; emit_fvar(o); m_out <<")) {\n";
for (unsigned k = 0; k < inc_projs.size(); k++) {
if (inc_projs[k]) {
if (!reused) {
/* projection RC was consumed, so we just need to set field to 0 */
m_out << " lean::cnstr_set("; emit_fvar(o); m_out << ", " << k << ", lean::box(0));\n";
}
} else {
m_out << " lean::cnstr_release("; emit_fvar(o); m_out << ", " << k << ");\n";
}
}
m_out << " "; emit_lhs(x); emit_fvar(o); m_out << ";\n";
m_out << "} else {\n";
for (unsigned k = 0; k < inc_projs.size(); k++) {
if (optional<expr> y = inc_projs[k]) {
m_out << " lean::inc("; emit_fvar(*y); m_out << ");\n";
}
}
m_out << " lean::dec("; emit_fvar(o); m_out << ");\n ";
emit_lhs(x); emit_unit(); m_out << ";\n";
m_out << "}\n";
return j+1;
}
void emit(expr e) {
m_out << "{\n";
buffer<expr> jps;
buffer<expr> locals;
buffer<expr> instrs;
bool declared_vars = false;
bool tail_call = false;
while (is_let(e)) {
expr v = instantiate_rev(let_value(e), locals.size(), locals.data());
if (is_join_point_name(let_name(e))) {
buffer<expr> jp_vars;
while (is_lambda(v)) {
expr y = m_lctx.mk_local_decl(m_ngen, binding_name(v), binding_domain(v));
jp_vars.push_back(y);
/* Declare join point parameter, we need them to implement join point calls in this block. */
m_out << to_cpp_type(binding_domain(v)) << " "; emit_fvar(y); m_out << "; ";
declared_vars = true;
v = binding_body(v);
}
v = instantiate_rev(v, jp_vars.size(), jp_vars.data());
expr x = m_lctx.mk_local_decl(m_ngen, let_name(e), let_type(e), v);
locals.push_back(x);
jps.push_back(x);
m_jp_vars.insert(fvar_name(x), to_list_ref(jp_vars));
} else {
expr x = m_lctx.mk_local_decl(m_ngen, let_name(e), let_type(e), v);
locals.push_back(x);
if (is_bvar(let_body(e), 0) && is_self_call(v)) {
/* Ignore tail call, we will emit it at emit_terminal as a `goto`. */
tail_call = true;
} else {
if (!is_llnf_void_type(let_type(e))) {
/* Declare local variable.
Remark: variables of type `_void` are used to store instructions that do
not return any value. */
m_out << to_cpp_type(let_type(e)) << " "; emit_fvar(x); m_out << "; ";
declared_vars = true;
}
instrs.push_back(x);
}
}
e = let_body(e);
}
e = instantiate_rev(e, locals.size(), locals.data());
if (declared_vars)
m_out << "\n";
/* emit instructions */
buffer<optional<expr>> inc_projs;
unsigned i = 0;
while (i < instrs.size()) {
expr const & x = instrs[i];
if (is_proj_inc_reset_seq(i, instrs, inc_projs)) {
i = emit_proj_inc_reset_seq(i, instrs, inc_projs);
} else {
i++;
local_decl d = m_lctx.get_local_decl(x);
if (is_llnf_void_type(d.get_type())) {
emit_instr(*d.get_value());
} else {
emit_instr(d);
}
}
}
emit_terminal(e, tail_call);
for (expr const & jp : jps) {
emit_lbl(jp); m_out << ":\n";
emit(*m_lctx.get_local_decl(jp).get_value());
}
m_out << "}\n";
}
public:
emit_fn_fn(std::ostream & out, environment const & env):
m_out(out), m_env(env) {
}
void operator()(comp_decl const & d) {
name n = d.fst();
expr e = d.snd();
if (!is_lambda(e) && has_init_attribute(m_env, n)) {
/* We do not need to generate the `_init_*` function since
`n` has its own initialization function. */
return;
}
m_fn_name = n;
expr type = get_constant_ll_type(m_env, n);
m_out << to_cpp_type(get_result_type(type)) << " ";
if (is_lambda(e)) {
m_out << to_base_cpp_name(m_env, n);
m_out << "(";
bool first = true;
while (is_lambda(e)) {
if (first) first = false; else m_out << ", ";
expr x = m_lctx.mk_local_decl(m_ngen, binding_name(e), binding_domain(e));
m_fn_args.push_back(x);
m_out << to_cpp_type(binding_domain(e));
m_out << " ";
emit_fvar(x);
e = binding_body(e);
}
m_out << ")";
e = instantiate_rev(e, m_fn_args.size(), m_fn_args.data());
} else {
m_out << "_init_" << to_base_cpp_name(m_env, n) << "()";
}
m_out << " {\n";
m_out << "_start:\n";
emit(e);
m_out << "}\n";
}
};
static void emit_fn(std::ostream & out, environment const & env, comp_decl const & d) {
name const & n = d.fst();
expr code = d.snd();
open_namespaces_for(out, env, n);
emit_fn_fn(out, env)(d);
close_namespaces_for(out, env, n);
}
static void emit_fns(std::ostream & out, environment const & env) {
comp_decls ds = get_llnf_code(env);
for (comp_decl const & d : ds) {
emit_fn(out, env, d);
}
}
static void emit_initialize(std::ostream & out, environment const & env, module_name const & m, list<module_name> const & deps) {
for (module_name const & d : deps) {
out << "obj* initialize_" << mangle(d, false) << "(obj*);\n";
}
out << "static bool _G_initialized = false;\n";
out << "obj* initialize_" << mangle(m, false) << "(obj* w) {\n";
out << " if (_G_initialized) return w;\n";
out << " _G_initialized = true;\n";
out << "if (io_result_is_error(w)) return w;\n";
for (module_name const & d : deps) {
out << "w = initialize_" << mangle(d, false) << "(w);\n";
out << "if (io_result_is_error(w)) return w;\n";
}
comp_decls ds = get_llnf_code(env);
for (comp_decl const & d : ds) {
name const & n = d.fst();
expr const & code = d.snd();
if (is_io_unit_init_fn(env, n)) {
out << "w = " << to_cpp_name(env, n) << "(w);\n";
out << "if (io_result_is_error(w)) return w;\n";
} else if (!is_lambda(code)) {
if (optional<name> init_fn = get_init_fn_name_for(env, d.fst())) {
out << "w = " << to_cpp_name(env, *init_fn) << "(w);\n";
out << "if (io_result_is_error(w)) return w;\n";
out << " " << to_cpp_name(env, n) << " = io_result_get_value(w);\n";
} else {
out << " " << to_cpp_name(env, n) << " = " << to_cpp_init_name(env, n) << "();\n";
}
expr type = get_constant_ll_type(env, n);
if (is_pi(type) || is_enf_object_type(type)) {
out << "lean::mark_persistent(" << to_cpp_name(env, n) << ");\n";
}
}
}
out << "return w;\n";
out << "}\n";
}
static name * g_lean = nullptr;
static bool uses_lean_namespace(environment const & env, expr const & e, name_set & visited) {
return static_cast<bool>(find(e, [&](expr const & e, unsigned) {
if (is_constant(e)) {
if (is_prefix_of(*g_lean, const_name(e)))
return true;
if (!visited.contains(const_name(e))) {
visited.insert(const_name(e));
if (optional<constant_info> info = env.find(mk_cstage2_name(const_name(e)))) {
if (info->is_definition() && uses_lean_namespace(env, info->get_value(), visited)) {
return true;
}
}
}
}
return false;
}));
}
/* Return true iff the main/current module depends (directly or indirectly) on `lean.` declarations. */
static bool uses_lean_namespace(environment const & env) {
comp_decls ds = get_llnf_code(env);
name_set visited;
for (comp_decl const & d : ds) {
if (!visited.contains(d.fst())) {
visited.insert(d.fst());
if (uses_lean_namespace(env, d.snd(), visited))
return true;
}
}
return false;
}
static void emit_main_fn(std::ostream & out, environment const & env, module_name const & m, comp_decl const & d) {
unsigned arity = get_num_nested_lambdas(d.snd());
if (arity != 2 && arity != 1) {
throw exception("invalid main function, incorrect arity when generating code");
}
bool uses_lean_api = uses_lean_namespace(env);
if (uses_lean_api)
out << "namespace lean { void initialize(); }\n";
out << "int main(int argc, char ** argv) {\n";
if (uses_lean_api)
out << "lean::initialize();\n";
else
out << "lean::initialize_runtime_module();\n";
out << "obj * w = lean::io_mk_world();\n";
out << "w = initialize_" << mangle(m, false) << "(w);\n";
out << "lean::io_mark_end_initialization();\n";
out << "if (io_result_is_ok(w)) {\n";
out << "lean::scoped_task_manager tmanager(lean::hardware_concurrency());\n";
if (arity == 2) {
out << "obj* in = lean::box(0);\n";
out << "int i = argc;\n";
out << "while (i > 1) {\n i--;\n";
out << " obj* n = lean::alloc_cnstr(1,2,0); lean::cnstr_set(n, 0, lean::mk_string(argv[i])); lean::cnstr_set(n, 1, in);\n";
out << " in = n;\n";
out << "}\n";
out << "w = " << g_lean_main << "(in, w);\n";
} else {
out << "w = " << g_lean_main << "(w);\n";
}
out << "}\n";
out <<
"if (io_result_is_ok(w)) {\n"
" int ret = lean::unbox(io_result_get_value(w));\n"
" lean::dec_ref(w);\n"
" return ret;\n"
"} else {\n"
" lean::io_result_show_error(w);\n"
" lean::dec_ref(w);\n"
" return 1;\n"
"}\n"
"}\n";
}
void emit_cpp(std::ostream & out, environment const & env, module_name const & m, list<module_name> const & deps) {
emit_file_header(out, env, m, deps);
emit_fn_decls(out, env);
emit_fns(out, env);
emit_initialize(out, env, m, deps);
if (optional<comp_decl> d = has_main_fn(env)) emit_main_fn(out, env, m, *d);
}
void initialize_emit_cpp() {
g_cpp = new name("cpp");
g_lean = new name("Lean");
}
void finalize_emit_cpp() {
delete g_cpp;
delete g_lean;
}
}