Skip to content

Implement proper intrinsic functions handling #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions integration_tests/test_builtin_abs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ def test_abs():
assert abs(x) == 5.5
x = -5.5
assert abs(x) == 5.5
assert abs(5.5) == 5.5
assert abs(-5.5) == 5.5


test_abs()
12 changes: 12 additions & 0 deletions src/libasr/asr_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,18 @@ static inline bool is_intrinsic_function(const ASR::Function_t *fn) {
return false;
}

// Returns true if the Function is intrinsic, otherwise false
// This version uses the `intrinsic` member of `Module`, so it
// should be used instead of is_intrinsic_function
static inline bool is_intrinsic_function2(const ASR::Function_t *fn) {
ASR::symbol_t *sym = (ASR::symbol_t*)fn;
ASR::Module_t *m = get_sym_module0(sym);
if (m != nullptr) {
if (m->m_intrinsic) return true;
}
return false;
}

// Returns true if all arguments have a `value`
static inline bool all_args_have_value(const Vec<ASR::expr_t*> &args) {
for (auto &a : args) {
Expand Down
93 changes: 88 additions & 5 deletions src/lpython/semantics/python_ast_to_asr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <lpython/utils.h>
#include <lpython/semantics/semantic_exception.h>
#include <lpython/python_serialization.h>
#include <lpython/semantics/python_comptime_eval.h>


namespace LFortran::Python {
Expand Down Expand Up @@ -147,11 +148,14 @@ class CommonVisitor : public AST::BaseVisitor<Derived> {
ASR::asr_t *tmp;
Allocator &al;
SymbolTable *current_scope;
// The current_module contains the current module that is being visited;
// this is used to append to the module dependencies if needed
ASR::Module_t *current_module = nullptr;
Vec<char *> current_module_dependencies;
// True for the main module, false for every other one
// The main module is stored directly in TranslationUnit, other modules are Modules
bool main_module;
PythonIntrinsicProcedures intrinsic_procedures;

CommonVisitor(Allocator &al, SymbolTable *symbol_table,
diag::Diagnostics &diagnostics, bool main_module)
Expand All @@ -178,6 +182,72 @@ class CommonVisitor : public AST::BaseVisitor<Derived> {
return ASR::make_Var_t(al, loc, v);
}

ASR::symbol_t* resolve_intrinsic_function(const Location &loc, const std::string &remote_sym) {
LFORTRAN_ASSERT(intrinsic_procedures.is_intrinsic(remote_sym))
std::string module_name = intrinsic_procedures.get_module(remote_sym, loc);

SymbolTable *tu_symtab = ASRUtils::get_tu_symtab(current_scope);
std::string rl_path = get_runtime_library_dir();
bool ltypes;
ASR::Module_t *m = load_module(al, tu_symtab, module_name,
loc, true, rl_path,
ltypes,
[&](const std::string &msg, const Location &loc) { throw SemanticError(msg, loc); }
);
LFORTRAN_ASSERT(!ltypes)

ASR::symbol_t *t = m->m_symtab->resolve_symbol(remote_sym);
if (!t) {
throw SemanticError("The symbol '" + remote_sym
+ "' not found in the module '" + module_name + "'",
loc);
} else if (! (ASR::is_a<ASR::GenericProcedure_t>(*t)
|| ASR::is_a<ASR::Function_t>(*t)
|| ASR::is_a<ASR::Subroutine_t>(*t))) {
throw SemanticError("The symbol '" + remote_sym
+ "' found in the module '" + module_name + "', "
+ "but it is not a function, subroutine or a generic procedure.",
loc);
}
char *fn_name = ASRUtils::symbol_name(t);
ASR::asr_t *fn = ASR::make_ExternalSymbol_t(
al, t->base.loc,
/* a_symtab */ current_scope,
/* a_name */ fn_name,
t,
m->m_name, nullptr, 0, fn_name,
ASR::accessType::Private
);
std::string sym = fn_name;

current_scope->scope[sym] = ASR::down_cast<ASR::symbol_t>(fn);
ASR::symbol_t *v = ASR::down_cast<ASR::symbol_t>(fn);

// Now we need to add the module `m` with the intrinsic function
// into the current module dependencies
if (current_module) {
// We are in body visitor, the module is already constructed
// and available as current_module.
// Add the module `m` to current module dependencies
Vec<char*> vec;
vec.from_pointer_n_copy(al, current_module->m_dependencies,
current_module->n_dependencies);
if (!present(vec, m->m_name)) {
vec.push_back(al, m->m_name);
current_module->m_dependencies = vec.p;
current_module->n_dependencies = vec.size();
}
} else {
// We are in the symtab visitor or body visitor and we are
// constructing a module, so current_module is not available yet
// (the current_module_dependencies is not used in body visitor)
if (!present(current_module_dependencies, m->m_name)) {
current_module_dependencies.push_back(al, m->m_name);
}
}
return v;
}

// Convert Python AST type annotation to an ASR type
// Examples:
// i32, i64, f32, f64
Expand Down Expand Up @@ -1981,6 +2051,18 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {


if (!s) {
if (intrinsic_procedures.is_intrinsic(call_name)) {
s = resolve_intrinsic_function(x.base.base.loc, call_name);
} else {
// TODO: We need to port all functions below to the intrinsic functions file
// Then we can uncomment this error message:
/*
throw SemanticError("The function '" + call_name + "' is not declared and not intrinsic",
x.base.base.loc);
}
if (false) {
*/
// This will all be removed once we port it to intrinsic functions
// Intrinsic functions
if (call_name == "size") {
// TODO: size should be part of ASR. That way
Expand Down Expand Up @@ -2481,21 +2563,22 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
throw SemanticError("Function '" + call_name + "' is not declared and not intrinsic",
x.base.base.loc);
}
} // end of "comment"
}

if (!s) {
throw SemanticError("Function '" + call_name + "' is not declared",
x.base.base.loc);
}
// handling ExternalSymbol
ASR::symbol_t *stemp = s;
s = ASRUtils::symbol_get_past_external(s);

if(ASR::is_a<ASR::Function_t>(*s)) {
ASR::Function_t *func = ASR::down_cast<ASR::Function_t>(s);
ASR::ttype_t *a_type = ASRUtils::expr_type(func->m_return_var);
ASR::expr_t *value = nullptr;
if (ASRUtils::is_intrinsic_function2(func)) {
value = intrinsic_procedures.comptime_eval(call_name, al, x.base.base.loc, args);
}
tmp = ASR::make_FunctionCall_t(al, x.base.base.loc, stemp,
nullptr, args.p, args.size(), nullptr, 0, a_type, nullptr, nullptr);
nullptr, args.p, args.size(), nullptr, 0, a_type, value, nullptr);
} else if(ASR::is_a<ASR::Subroutine_t>(*s)) {
tmp = ASR::make_SubroutineCall_t(al, x.base.base.loc, stemp,
nullptr, args.p, args.size(), nullptr);
Expand Down
107 changes: 107 additions & 0 deletions src/lpython/semantics/python_comptime_eval.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#ifndef LPYTHON_SEMANTICS_COMPTIME_EVAL_H
#define LPYTHON_SEMANTICS_COMPTIME_EVAL_H

#include <complex>

#include <libasr/asr.h>
#include <lpython/ast.h>
#include <lpython/bigint.h>
#include <libasr/string_utils.h>
#include <lpython/utils.h>
#include <lpython/semantics/semantic_exception.h>

namespace LFortran {

struct PythonIntrinsicProcedures {

const std::string m_builtin = "lpython_builtin";

typedef ASR::expr_t* (*comptime_eval_callback)(Allocator &, const Location &, Vec<ASR::expr_t*> &);
// Table of intrinsics
// The callback is only called if all arguments have compile time `value`
// which is always one of the `Constant*` expression ASR nodes, so inside
// the callback one can assume that.
std::map<std::string, std::tuple<std::string, comptime_eval_callback>> comptime_eval_map;

PythonIntrinsicProcedures() {
comptime_eval_map = {
{"abs", {m_builtin, &eval_abs}},
};
}

// Return `true` if `name` is in the table of intrinsics
bool is_intrinsic(std::string name) const {
auto search = comptime_eval_map.find(name);
if (search != comptime_eval_map.end()) {
return true;
} else {
return false;
}
}

// Looks up `name` in the table of intrinsics and returns the corresponding
// module name; Otherwise rises an exception
std::string get_module(std::string name, const Location &loc) const {
auto search = comptime_eval_map.find(name);
if (search != comptime_eval_map.end()) {
std::string module_name = std::get<0>(search->second);
return module_name;
} else {
throw SemanticError("Function '" + name
+ "' not found among intrinsic procedures",
loc);
}
}

// Evaluates the intrinsic function `name` at compile time
ASR::expr_t *comptime_eval(std::string name, Allocator &al, const Location &loc, Vec<ASR::expr_t*> &args) const {
auto search = comptime_eval_map.find(name);
if (search != comptime_eval_map.end()) {
comptime_eval_callback cb = std::get<1>(search->second);
Vec<ASR::expr_t*> arg_values = ASRUtils::get_arg_values(al, args);
if (arg_values.size() != args.size()) {
// Not all arguments have compile time values; we do not call the callback
return nullptr;
}
return cb(al, loc, arg_values);
} else {
throw SemanticError("Intrinsic function '" + name
+ "' compile time evaluation is not implemented yet",
loc);
}
}


static ASR::expr_t *eval_abs(Allocator &al, const Location &loc,
Vec<ASR::expr_t*> &args
) {
LFORTRAN_ASSERT(ASRUtils::all_args_evaluated(args));
if (args.size() != 1) {
throw SemanticError("Intrinsic abs function accepts exactly 1 argument", loc);
}
ASR::expr_t* trig_arg = args[0];
ASR::ttype_t* t = ASRUtils::expr_type(args[0]);
if (ASR::is_a<ASR::Real_t>(*t)) {
double rv = ASR::down_cast<ASR::ConstantReal_t>(trig_arg)->m_r;
double val = std::abs(rv);
return ASR::down_cast<ASR::expr_t>(ASR::make_ConstantReal_t(al, loc, val, t));
} else if (ASR::is_a<ASR::Integer_t>(*t)) {
int64_t rv = ASR::down_cast<ASR::ConstantInteger_t>(trig_arg)->m_n;
int64_t val = std::abs(rv);
return ASR::down_cast<ASR::expr_t>(ASR::make_ConstantInteger_t(al, loc, val, t));
} else if (ASR::is_a<ASR::Complex_t>(*t)) {
double re = ASR::down_cast<ASR::ConstantComplex_t>(trig_arg)->m_re;
double im = ASR::down_cast<ASR::ConstantComplex_t>(trig_arg)->m_im;
std::complex<double> x(re, im);
double result = std::abs(x);
return ASR::down_cast<ASR::expr_t>(ASR::make_ConstantReal_t(al, loc, result, t));
} else {
throw SemanticError("Argument of the abs function must be Integer, Real or Complex", loc);
}
}

}; // ComptimeEval

} // namespace LFortran

#endif /* LPYTHON_SEMANTICS_COMPTIME_EVAL_H */
2 changes: 1 addition & 1 deletion tests/reference/asr-constants1-5828e8a.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"outfile": null,
"outfile_hash": null,
"stdout": "asr-constants1-5828e8a.stdout",
"stdout_hash": "82599c6a0114feef56f5e9198dd5cfbd06d87638efc14126af92a89e",
"stdout_hash": "0a0d5acbd9b14c908f856d9d95d36e49e4cb2f5db735109a008af200",
"stderr": null,
"stderr_hash": null,
"returncode": 0
Expand Down
Loading