820 lines
29 KiB
C++
820 lines
29 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 "runtime/utf8.h"
|
|
#include "runtime/apply.h"
|
|
#include "kernel/instantiate.h"
|
|
#include "library/attribute_manager.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/builtin.h"
|
|
|
|
#include "library/trace.h"
|
|
|
|
namespace lean {
|
|
struct cppname_attr_data : public attr_data {
|
|
name m_id;
|
|
cppname_attr_data(name const & id): m_id(id) {}
|
|
cppname_attr_data() {}
|
|
|
|
virtual unsigned hash() const override { return m_id.hash(); }
|
|
virtual void parse(abstract_parser & p) override { m_id = p.parse_name(); }
|
|
virtual void print(std::ostream & out) override { out << " " << m_id; }
|
|
void write(serializer & s) const { s << m_id; }
|
|
void read(deserializer & d) { m_id = read_name(d); }
|
|
};
|
|
|
|
typedef typed_attribute<cppname_attr_data> cppname_attr;
|
|
|
|
static cppname_attr const & get_cppname_attr() {
|
|
return static_cast<cppname_attr const &>(get_system_attribute("cppname"));
|
|
}
|
|
|
|
optional<name> get_cppname_for(environment const & env, name const & n) {
|
|
if (auto const & data = get_cppname_attr().get(env, n)) {
|
|
return optional<name>(data->m_id);
|
|
} else {
|
|
return optional<name>();
|
|
}
|
|
}
|
|
|
|
struct emit_cpp_ext : public environment_extension {
|
|
comp_decls m_code;
|
|
};
|
|
|
|
struct emit_cpp_ext_reg {
|
|
unsigned m_ext_id;
|
|
emit_cpp_ext_reg() { m_ext_id = environment::register_extension(std::make_shared<emit_cpp_ext>()); }
|
|
};
|
|
|
|
static emit_cpp_ext_reg * g_ext = nullptr;
|
|
static emit_cpp_ext const & get_extension(environment const & env) {
|
|
return static_cast<emit_cpp_ext const &>(env.get_extension(g_ext->m_ext_id));
|
|
}
|
|
static environment update(environment const & env, emit_cpp_ext const & ext) {
|
|
return env.update(g_ext->m_ext_id, std::make_shared<emit_cpp_ext>(ext));
|
|
}
|
|
|
|
environment emit_cpp(environment const & env, comp_decls const & ds) {
|
|
/* Remark: we don't generate C++ code here, but at `print_cpp_code`. In this function
|
|
we simply save the LLNF expression in the emit_cpp_ext.
|
|
Reason: the attributes for `ds` are only applied after the compiler is executed,
|
|
and we need to be able to access the `[cppname]` attribute. */
|
|
emit_cpp_ext ext = get_extension(env);
|
|
ext.m_code = append(ext.m_code, ds);
|
|
return update(env, ext);
|
|
}
|
|
|
|
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 "unsigned char";
|
|
} else if (const_name(e) == get_uint16_name()) {
|
|
return "unsigned short";
|
|
} else if (const_name(e) == get_uint32_name()) {
|
|
return "unsigned";
|
|
} else if (const_name(e) == get_uint64_name()) {
|
|
return "unsigned long long";
|
|
} else if (const_name(e) == get_usize_name()) {
|
|
return "size_t";
|
|
}
|
|
} 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";
|
|
}
|
|
|
|
/* If `n` has the attribute [cppname], and the "cppname" 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_cppname_for(env, n);
|
|
if (!c || c->is_atomic()) return;
|
|
open_namespaces_core(out, c->get_prefix());
|
|
}
|
|
|
|
static void close_namespaces_for(std::ostream & out, environment const & env, name const & n) {
|
|
optional<name> c = get_cppname_for(env, n);
|
|
if (!c || c->is_atomic()) return;
|
|
name p = c->get_prefix();
|
|
while (!p.is_anonymous()) {
|
|
out << "}";
|
|
p = p.get_prefix();
|
|
}
|
|
out << "\n";
|
|
}
|
|
|
|
static std::string to_base_cpp_name(environment const & env, name const & n) {
|
|
if (optional<name> c = get_cppname_for(env, n)) {
|
|
lean_assert(c->is_string());
|
|
return c->get_string().to_std_string();
|
|
} else {
|
|
return mangle(n);
|
|
}
|
|
}
|
|
|
|
static std::string to_cpp_name(environment const & env, name const & n) {
|
|
if (optional<name> c = get_cppname_for(env, n)) {
|
|
lean_assert(c->is_string());
|
|
return c->to_string("::");
|
|
} else {
|
|
return mangle(n);
|
|
}
|
|
}
|
|
|
|
static std::string to_cpp_init_name(environment const & env, name const & n) {
|
|
if (optional<name> c = get_cppname_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(std::ostream & out, environment const & env, name const & n) {
|
|
open_namespaces_for(out, env, n);
|
|
expr type = get_constant_ll_type(env, n);
|
|
out << to_cpp_type(get_result_type(type)) << " " << to_base_cpp_name(env, n);
|
|
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";
|
|
close_namespaces_for(out, env, n);
|
|
}
|
|
|
|
/* 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_builtin_constant(const_name(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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_extension(env).m_code;
|
|
name_set todo;
|
|
for (comp_decl const & d : ds) {
|
|
todo.insert(d.fst());
|
|
collect_dependencies(env, d.snd(), todo);
|
|
}
|
|
todo.for_each([&](name const & n) { emit_fn_decl(out, env, n); });
|
|
}
|
|
|
|
static void emit_file_header(std::ostream & out, 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";
|
|
out << "typedef lean::object obj;\n";
|
|
}
|
|
|
|
static void emit_hexdigit(std::ostream & out, unsigned char c) {
|
|
lean_assert(c < 16);
|
|
if (c < 10) {
|
|
out << c;
|
|
} else {
|
|
out << '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 <= 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 "unsigned char";
|
|
case 2: return "unsigned short";
|
|
case 4: return "unsigned";
|
|
case 8: return "unsigned long long";
|
|
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;
|
|
|
|
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() {
|
|
m_out << "lean::box(0)";
|
|
}
|
|
|
|
void emit_constant(expr const & c) {
|
|
lean_assert(is_constant(c));
|
|
lean_assert(!is_enf_unreachable(c));
|
|
if (optional<name> n = get_builtin_cname(const_name(c)))
|
|
m_out << *n;
|
|
else if (is_enf_neutral(c))
|
|
emit_unit();
|
|
else
|
|
m_out << to_cpp_name(m_env, const_name(c));
|
|
}
|
|
|
|
void emit_fvar(expr const & x) {
|
|
lean_assert(is_fvar(x));
|
|
name const & id = fvar_name(x);
|
|
lean_assert(id.is_numeral());
|
|
m_out << "x_" << id.get_numeral();
|
|
}
|
|
|
|
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_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:
|
|
if (is_obj(x)) {
|
|
if (l.get_nat() < std::numeric_limits<unsigned>::max()) {
|
|
m_out << "lean::mk_nat_obj(" << l.get_nat() << "u)";
|
|
} else {
|
|
m_out << "lean::mk_nat_obj(lean::mpz(\"" << l.get_nat() << "\"))";
|
|
}
|
|
} else {
|
|
m_out << 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(expr const & arg) {
|
|
if (is_fvar(arg))
|
|
emit_fvar(arg);
|
|
else
|
|
emit_unit();
|
|
}
|
|
|
|
void emit_args(unsigned sz, expr const * args) {
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
if (i > 0) m_out << ", ";
|
|
emit_arg(args[i]);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
lean_assert(!args.empty());
|
|
unsigned cidx, num_usizes, num_bytes;
|
|
lean_verify(is_llnf_cnstr(fn, cidx, num_usizes, num_bytes));
|
|
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_shared("; emit_fvar(o); m_out <<")) {\n";
|
|
m_out << " lean::dec("; emit_fvar(o); m_out << ");\n ";
|
|
emit_lhs(x); emit_unit(); m_out << ";\n";
|
|
m_out << "} else {\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 << "}\n";
|
|
}
|
|
|
|
void emit_reuse(expr const & x, expr const & fn, buffer<expr> const & args) {
|
|
lean_assert(!args.empty());
|
|
unsigned cidx, num_usizes, num_bytes;
|
|
lean_verify(is_llnf_reuse(fn, cidx, num_usizes, num_bytes));
|
|
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";
|
|
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));
|
|
emit_lhs(x);
|
|
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<size_t>("; 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_instr(local_decl const & d) {
|
|
expr x = d.mk_ref();
|
|
expr val = *d.get_value();
|
|
if (is_lit(val)) {
|
|
emit_lit(x, val);
|
|
} else if (is_constant(val)) {
|
|
unsigned cidx, d1, d2;
|
|
if (is_llnf_cnstr(val, cidx, d1, d2)) {
|
|
emit_lhs(x);
|
|
if (is_obj(x))
|
|
m_out << "lean::box(" << cidx << ")";
|
|
else
|
|
m_out << cidx;
|
|
} else if (is_enf_unreachable(val)) {
|
|
/* We have commented `m_out << "lean_unreachable();\n` because it
|
|
may break initialization. The main issue is that `reduce_arity`
|
|
creates an auxiliary 0-ary definition for
|
|
```
|
|
def false.elim {C : Sort u} (h : false) : C := ..
|
|
```
|
|
and this auxiliary definition is executed at initialization time.
|
|
We should fix the problem by to preventing `reduce_arity` from
|
|
converting a n-ary function into a 0-ary one.
|
|
Another option is to use lazy initialization, but lazy initialization
|
|
would require thread local values.
|
|
*/
|
|
// m_out << "lean_unreachable();\n";
|
|
emit_lhs(x); emit_unit();
|
|
} 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 {
|
|
/* 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();
|
|
}
|
|
}
|
|
|
|
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";
|
|
}
|
|
for (unsigned i = 1; i < args.size(); i++) {
|
|
if (i == args.size() - 1)
|
|
m_out << "default:\n";
|
|
else
|
|
m_out << "case " << (i-1) << ":\n";
|
|
emit(args[i]);
|
|
}
|
|
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_fvar(args[i]); m_out << ";\n";
|
|
params = tail(params);
|
|
}
|
|
m_out << "goto "; emit_lbl(jp); m_out << ";\n";
|
|
}
|
|
|
|
void emit_terminal(expr const & e) {
|
|
if (is_cases_on_app(m_env, e)) {
|
|
emit_cases(e);
|
|
} else if (is_jmp(e)) {
|
|
emit_jmp(e);
|
|
} else if (is_fvar(e)) {
|
|
m_out << "return "; emit_fvar(e); m_out << ";\n";
|
|
} else {
|
|
lean_unreachable();
|
|
}
|
|
}
|
|
|
|
void emit(expr e) {
|
|
m_out << "{\n";
|
|
buffer<expr> jps;
|
|
buffer<expr> locals;
|
|
buffer<expr> instrs;
|
|
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 << ";\n";
|
|
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_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 << ";\n";
|
|
}
|
|
instrs.push_back(x);
|
|
}
|
|
e = let_body(e);
|
|
}
|
|
/* emit instructions */
|
|
for (expr const & x : instrs) {
|
|
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);
|
|
}
|
|
}
|
|
e = instantiate_rev(e, locals.size(), locals.data());
|
|
emit_terminal(e);
|
|
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();
|
|
expr type = get_constant_ll_type(m_env, n);
|
|
m_out << to_cpp_type(get_result_type(type)) << " ";
|
|
if (is_lambda(e)) {
|
|
buffer<expr> locals;
|
|
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));
|
|
locals.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, locals.size(), locals.data());
|
|
} else {
|
|
m_out << "_init_" << to_base_cpp_name(m_env, n) << "()";
|
|
}
|
|
m_out << " {\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_extension(env).m_code;
|
|
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 << "void _l_initialize_" << mangle(d) << "();\n";
|
|
}
|
|
out << "static bool _G_initialized = false;\n";
|
|
out << "void _l_initialize_" << mangle(m) << "() {\n";
|
|
out << " if (_G_initialized) return;\n";
|
|
out << " _G_initialized = true;\n";
|
|
for (module_name const & d : deps) {
|
|
out << " _l_initialize_" << mangle(d) << "();\n";
|
|
}
|
|
comp_decls ds = get_extension(env).m_code;
|
|
for (comp_decl const & d : ds) {
|
|
name const & n = d.fst();
|
|
expr const & code = d.snd();
|
|
if (!is_lambda(code)) {
|
|
out << " " << to_cpp_name(env, n) << " = " << to_cpp_init_name(env, n) << "();\n";
|
|
}
|
|
}
|
|
out << "}\n";
|
|
}
|
|
|
|
void print_cpp_code(std::ostream & out, environment const & env, module_name const & m, list<module_name> const & deps) {
|
|
emit_file_header(out, m, deps);
|
|
emit_fn_decls(out, env);
|
|
emit_fns(out, env);
|
|
emit_initialize(out, env, m, deps);
|
|
}
|
|
|
|
void initialize_emit_cpp() {
|
|
g_ext = new emit_cpp_ext_reg();
|
|
register_system_attribute(cppname_attr("cppname", "name to be used by C++ code generator",
|
|
[](environment const & env, io_state const &, name const & n, unsigned, bool persistent) {
|
|
if (!persistent) throw exception("invalid [cppname] attribute, must be persistent");
|
|
if (n.is_anonymous()) throw exception("invalid [cppname] attribute, argument is missing");
|
|
name it = n;
|
|
while (!it.is_anonymous()) {
|
|
if (!it.is_string()) throw exception("invalid [cppname] attribute, identifier cannot be numeric");
|
|
it = it.get_prefix();
|
|
}
|
|
return env;
|
|
}));
|
|
}
|
|
|
|
void finalize_emit_cpp() {
|
|
delete g_ext;
|
|
}
|
|
}
|