Skip to content

Commit

Permalink
Lower stack-sized module constants to be inlined
Browse files Browse the repository at this point in the history
Related to ethereum#192
  • Loading branch information
cburgdorf committed Oct 7, 2021
1 parent 6dedadf commit a02c900
Show file tree
Hide file tree
Showing 19 changed files with 289 additions and 13 deletions.
15 changes: 13 additions & 2 deletions crates/analyzer/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::context::{Analysis, FunctionBody};
use crate::errors::TypeError;
use crate::namespace::items::{
self, ContractFieldId, ContractId, EventId, FunctionId, ModuleId, StructFieldId, StructId,
TypeAliasId, TypeDefId,
self, ContractFieldId, ContractId, EventId, FunctionId, ModuleConstantId, ModuleId,
StructFieldId, StructId, TypeAliasId, TypeDefId,
};
use crate::namespace::types;
use indexmap::IndexMap;
Expand Down Expand Up @@ -31,6 +31,8 @@ pub trait AnalyzerDb {
#[salsa::interned]
fn intern_module(&self, data: Rc<items::Module>) -> ModuleId;
#[salsa::interned]
fn intern_module_const(&self, data: Rc<items::ModuleConstant>) -> ModuleConstantId;
#[salsa::interned]
fn intern_struct(&self, data: Rc<items::Struct>) -> StructId;
#[salsa::interned]
fn intern_struct_field(&self, data: Rc<items::StructField>) -> StructFieldId;
Expand Down Expand Up @@ -61,6 +63,15 @@ pub trait AnalyzerDb {
fn module_contracts(&self, module: ModuleId) -> Rc<Vec<ContractId>>;
#[salsa::invoke(queries::module::module_structs)]
fn module_structs(&self, module: ModuleId) -> Rc<Vec<StructId>>;
#[salsa::invoke(queries::module::module_constants)]
fn module_constants(&self, module: ModuleId) -> Rc<Vec<ModuleConstantId>>;

// Module Constant
#[salsa::invoke(queries::module::module_constant_type)]
fn module_constant_type(
&self,
id: ModuleConstantId,
) -> Analysis<Result<types::Type, TypeError>>;

// Contract
#[salsa::invoke(queries::contracts::contract_all_functions)]
Expand Down
35 changes: 31 additions & 4 deletions crates/analyzer/src/db/queries/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,20 +154,47 @@ pub fn function_body(db: &dyn AnalyzerDb, function: FunctionId) -> Analysis<Rc<F
}
}

let mut block_scope = BlockScope::new(&scope, BlockScopeType::Function);

add_module_constants_to_scope(db, &mut block_scope);

// If `traverse_statements` fails, we can be confident that a diagnostic
// has been emitted, either while analyzing this fn body or while analyzing
// a type or fn used in this fn body, because of the `DiagnosticVoucher`
// system. (See the definition of `FatalError`)
let _ = traverse_statements(
&mut BlockScope::new(&scope, BlockScopeType::Function),
&def.body,
);
let _ = traverse_statements(&mut block_scope, &def.body);
Analysis {
value: Rc::new(scope.body.into_inner()),
diagnostics: Rc::new(scope.diagnostics.into_inner()),
}
}

fn add_module_constants_to_scope(db: &dyn AnalyzerDb, scope: &mut BlockScope) {
for module_const in scope.root.function.module(db).all_constants(db).iter() {
let typ = match module_const.typ(db) {
Ok(typ) => typ,
Err(_) => {
// No need to report module_constant_type() already reports this
continue;
}
};

let const_type: FixedSize = match typ.try_into() {
Ok(typ) => typ,
Err(_) => {
// No need to report module_constant_type() already reports this
continue;
}
};

let const_name = module_const.name(db);
// add_var emits a msg on err; we can ignore the Result.
scope
.add_var(&const_name, const_type, module_const.span(db))
.ok();
}
}

fn all_paths_return_or_revert(block: &[Node<ast::FuncStmt>]) -> bool {
for statement in block.iter().rev() {
match &statement.kind {
Expand Down
52 changes: 48 additions & 4 deletions crates/analyzer/src/db/queries/module.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::builtins;
use crate::context::Analysis;
use crate::context::{Analysis, AnalyzerContext};
use crate::db::AnalyzerDb;
use crate::errors;
use crate::errors::{self, TypeError};
use crate::namespace::items::{
Contract, ContractId, ModuleId, Struct, StructId, TypeAlias, TypeDefId,
Contract, ContractId, ModuleConstant, ModuleConstantId, ModuleId, Struct, StructId, TypeAlias,
TypeDefId,
};
use crate::namespace::types;
use crate::namespace::scopes::ItemScope;
use crate::namespace::types::{self, Type};
use crate::traversal::types::type_desc;
use fe_common::diagnostics::Label;
use fe_parser::ast;
use indexmap::map::{Entry, IndexMap};
Expand Down Expand Up @@ -131,3 +134,44 @@ pub fn module_structs(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<Vec<StructId>
.collect(),
)
}

pub fn module_constants(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<Vec<ModuleConstantId>> {
let ast::Module { body } = &module.data(db).ast;
let ids = body
.iter()
.filter_map(|stmt| match stmt {
ast::ModuleStmt::Constant(node) => {
Some(db.intern_module_const(Rc::new(ModuleConstant {
ast: node.clone(),
module,
})))
}
_ => None,
})
.collect();
Rc::new(ids)
}

pub fn module_constant_type(
db: &dyn AnalyzerDb,
constant: ModuleConstantId,
) -> Analysis<Result<types::Type, TypeError>> {
let mut scope = ItemScope::new(db, constant.data(db).module);
let typ = type_desc(&mut scope, &constant.data(db).ast.kind.typ);

match &typ {
Ok(typ) if !matches!(typ, Type::Base(_)) => {
scope.error(
"Non-base types not yet supported for constants",
constant.data(db).ast.kind.typ.span,
&format!("this has type `{}`; expected a primitive type", typ),
);
}
_ => {}
}

Analysis {
value: typ,
diagnostics: Rc::new(scope.diagnostics),
}
}
70 changes: 70 additions & 0 deletions crates/analyzer/src/namespace/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::traversal::pragma::check_pragma_version;
use crate::AnalyzerDb;
use fe_common::diagnostics::Diagnostic;
use fe_parser::ast;
use fe_parser::ast::Expr;
use fe_parser::node::{Node, Span};
use indexmap::IndexMap;
use std::rc::Rc;
Expand Down Expand Up @@ -71,6 +72,19 @@ impl ModuleId {
db.module_structs(*self)
}

/// All constants, including duplicates
pub fn all_constants(&self, db: &dyn AnalyzerDb) -> Rc<Vec<ModuleConstantId>> {
db.module_constants(*self)
}

/// Get constant by name
pub fn lookup_constant(&self, db: &dyn AnalyzerDb, name: &str) -> Option<ModuleConstantId> {
self.all_constants(db)
.iter()
.cloned()
.find(|item| item.name(db) == name)
}

pub fn diagnostics(&self, db: &dyn AnalyzerDb) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
self.sink_diagnostics(db, &mut diagnostics);
Expand All @@ -89,6 +103,10 @@ impl ModuleId {
ast::ModuleStmt::Use(inner) => {
sink.push(&errors::not_yet_implemented("use", inner.span));
}
ast::ModuleStmt::Constant(_) => self
.all_constants(db)
.iter()
.for_each(|id| id.sink_diagnostics(db, sink)),
_ => {} // everything else is a type def, handled below.
}
}
Expand All @@ -103,6 +121,58 @@ impl ModuleId {
}
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ModuleConstant {
pub ast: Node<ast::ConstantDecl>,
pub module: ModuleId,
}

#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub struct ModuleConstantId(pub(crate) u32);
impl_intern_key!(ModuleConstantId);

impl ModuleConstantId {
pub fn data(&self, db: &dyn AnalyzerDb) -> Rc<ModuleConstant> {
db.lookup_intern_module_const(*self)
}
pub fn span(&self, db: &dyn AnalyzerDb) -> Span {
self.data(db).ast.span
}
pub fn typ(&self, db: &dyn AnalyzerDb) -> Result<types::Type, TypeError> {
db.module_constant_type(*self).value
}

pub fn is_base_type(&self, db: &dyn AnalyzerDb) -> bool {
matches!(self.typ(db), Ok(types::Type::Base(_)))
}

pub fn name(&self, db: &dyn AnalyzerDb) -> String {
self.data(db).ast.kind.name.kind.clone()
}

pub fn value(&self, db: &dyn AnalyzerDb) -> fe_parser::ast::Expr {
self.data(db).ast.kind.value.kind.clone()
}

pub fn sink_diagnostics(&self, db: &dyn AnalyzerDb, sink: &mut impl DiagnosticSink) {
db.module_constant_type(*self)
.diagnostics
.iter()
.for_each(|d| sink.push(d));

if !matches!(
self.value(db),
Expr::Bool(_) | Expr::Num(_) | Expr::Str(_) | Expr::Unit
) {
sink.push(&errors::error(
"non-literal expressions not yet supported for constants",
self.data(db).ast.kind.value.span,
"not a literal",
))
}
}
}

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum TypeDefId {
Alias(TypeAliasId),
Expand Down
3 changes: 3 additions & 0 deletions crates/analyzer/tests/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ test_file! { mismatch_return_type }
test_file! { missing_return }
test_file! { missing_return_in_else }
test_file! { missing_return_after_if }
test_file! { module_const_unknown_type }
test_file! { module_const_non_base_type }
test_file! { module_const_not_literal }
test_file! { needs_mem_copy }
test_file! { not_callable }
test_file! { not_in_scope }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/analyzer/tests/errors.rs
expression: "error_string(&path, &src)"

---
error: Non-base types not yet supported for constants
┌─ compile_errors/module_const_non_base_type.fe:1:12
1const FOO: String<3> = "foo"
^^^^^^^^^ this has type `String<3>`; expected a primitive type


Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/analyzer/tests/errors.rs
expression: "error_string(&path, &src)"

---
error: non-literal expressions not yet supported for constants
┌─ compile_errors/module_const_not_literal.fe:1:19
1const FOO: u256 = 1 + 2
^^^^^ not a literal


Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/analyzer/tests/errors.rs
expression: "error_string(&path, &src)"

---
error: undefined type
┌─ compile_errors/module_const_unknown_type.fe:1:12
1const FOO: Bar = 1
^^^ this type name has not been defined


9 changes: 8 additions & 1 deletion crates/lowering/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use fe_analyzer::context::{ExpressionAttributes, FunctionBody};
use fe_analyzer::namespace::items::FunctionId;
use fe_analyzer::namespace::types::{Array, FixedSize, Tuple};
use fe_analyzer::AnalyzerDb;
use fe_parser::ast;
Expand Down Expand Up @@ -52,16 +53,22 @@ impl<'a, 'db> ContractContext<'a, 'db> {
pub struct FnContext<'a, 'b, 'db> {
pub contract: &'a mut ContractContext<'b, 'db>,
pub body: Rc<FunctionBody>,
pub id: FunctionId,

/// Holds fresh id for [`FnContext::make_unique_name`]
fresh_id: u64,
}

impl<'a, 'b, 'db> FnContext<'a, 'b, 'db> {
pub fn new(contract: &'a mut ContractContext<'b, 'db>, body: Rc<FunctionBody>) -> Self {
pub fn new(
id: FunctionId,
contract: &'a mut ContractContext<'b, 'db>,
body: Rc<FunctionBody>,
) -> Self {
Self {
contract,
body,
id,
fresh_id: 0,
}
}
Expand Down
23 changes: 22 additions & 1 deletion crates/lowering/src/mappers/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub fn expr(context: &mut FnContext, exp: Node<fe::Expr>) -> Node<fe::Expr> {
let span = exp.span;

let lowered_kind = match exp.kind {
fe::Expr::Name(_) => exp.kind,
fe::Expr::Name(_) => expr_name(context, exp),
fe::Expr::Num(_) => exp.kind,
fe::Expr::Bool(_) => exp.kind,
fe::Expr::Subscript { value, index } => fe::Expr::Subscript {
Expand Down Expand Up @@ -103,6 +103,27 @@ pub fn call_args(
Node::new(lowered_args, args.span)
}

fn expr_name(context: &mut FnContext, exp: Node<fe::Expr>) -> fe::Expr {
let name = match &exp.kind {
fe::Expr::Name(name) => name,
_ => unreachable!(),
};

let db = context.db();

let module_const = context.id.module(db).lookup_constant(db, name);

// If the name maps to a base type constant we lower the code to inline
// the name expression to the value expression of the constant.
match module_const {
Some(val) if val.is_base_type(db) => val.value(db),
Some(_) => {
panic!("Should have been rejected at first analyzer pass")
}
None => exp.kind,
}
}

fn expr_tuple(context: &mut FnContext, exp: Node<fe::Expr>) -> fe::Expr {
let typ = context
.expression_attributes(&exp)
Expand Down
2 changes: 1 addition & 1 deletion crates/lowering/src/mappers/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use fe_parser::node::Node;

/// Lowers a function definition.
pub fn func_def(contract: &mut ContractContext, function: FunctionId) -> Node<fe::Function> {
let mut context = FnContext::new(contract, function.body(contract.db()));
let mut context = FnContext::new(function, contract, function.body(contract.db()));
let node = &function.data(context.db()).ast;
let fe::Function {
is_pub,
Expand Down
3 changes: 3 additions & 0 deletions crates/lowering/src/mappers/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub fn module(db: &dyn AnalyzerDb, module: ModuleId) -> ast::Module {
.filter_map(|stmt| match stmt {
ast::ModuleStmt::Pragma(_) => Some(stmt.clone()),
ast::ModuleStmt::Use(_) => Some(stmt.clone()),
// All name expressions referring to constants are handled at the time of lowering,
// which causes the constants to no longer serve a purpose.
ast::ModuleStmt::Constant(_) => None,
_ => None,
})
.collect::<Vec<_>>();
Expand Down
1 change: 1 addition & 0 deletions crates/lowering/tests/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,6 @@ test_file! { nested_tuple, "lowering/nested_tuple.fe" }
test_file! { map_tuple, "lowering/map_tuple.fe" }
test_file! { type_alias_tuple, "lowering/type_alias_tuple.fe" }
test_file! { tuple_destruct, "lowering/tuple_destruct.fe" }
test_file! { module_const, "lowering/module_const.fe" }
// TODO: the analyzer rejects lowered nested tuples.
// test_file!(array_tuple, "lowering/array_tuple.fe");
Loading

0 comments on commit a02c900

Please sign in to comment.