Skip to content

Commit

Permalink
Implement if let expressions and associated fixes (FuelLabs#815)
Browse files Browse the repository at this point in the history
* Sketch out parsing for if lets

* wip

* wip

* if lets work for types of one=word size

* wip

* working codegen for types under one word

* TDD: lots of type issues

* wip

* wip

* bugfix: return type checking from within if expressions

* wip

* remove owned typed enum variant

* remove owned typed struct fields, now that we have static spans

* improve dead code analysis for enums

* remove impl self test case

* wip

* use MCPI

* fix cargo warnings

* fix clippy warnings

* fix tests for IR

* resolve some todos

* resolve more todos

* resolve todos

* add chained if let tests

* TDD: chained if let

* chained if let expressions

* update tests for new stdlib

* improve dead code analysis; fix large type if let codegen issue

* wip

* fix tests

* fix bug where large types are destructured incorrectly

* clippy

* update toml files to use tag

* fmt

* update lockfiles

* refresh more lockfiles

* PR feedback

* update lockfiles
  • Loading branch information
sezna authored Mar 10, 2022
1 parent 3268092 commit cfeeecc
Show file tree
Hide file tree
Showing 76 changed files with 1,595 additions and 589 deletions.
4 changes: 2 additions & 2 deletions sway-core/src/asm_generation/declaration/reassignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
},
asm_lang::{VirtualImmediate12, VirtualOp},
constants::VM_WORD_SIZE,
semantic_analysis::ast_node::{OwnedTypedStructField, ReassignmentLhs, TypedReassignment},
semantic_analysis::ast_node::{ReassignmentLhs, TypedReassignment, TypedStructField},
type_engine::*,
type_engine::{resolve_type, TypeInfo},
};
Expand Down Expand Up @@ -124,7 +124,7 @@ pub(crate) fn convert_reassignment_to_asm(
};
let fields_for_layout = fields
.iter()
.map(|OwnedTypedStructField { name, r#type, .. }| {
.map(|TypedStructField { name, r#type, .. }| {
(*r#type, span.clone(), name.clone())
})
.collect::<Vec<_>>();
Expand Down
10 changes: 6 additions & 4 deletions sway-core/src/asm_generation/expression/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@ use crate::asm_lang::{
};
use crate::{
error::*,
semantic_analysis::{ast_node::TypedEnumDeclaration, TypedExpression},
semantic_analysis::ast_node::{TypedEnumDeclaration, TypedExpression},
type_engine::resolve_type,
CompileResult, Ident, Literal,
CompileResult, Literal,
};
use sway_types::Span;

pub(crate) fn convert_enum_instantiation_to_asm(
decl: &TypedEnumDeclaration,
variant_name: &Ident,
tag: usize,
contents: &Option<Box<TypedExpression>>,
return_register: &VirtualRegister,
namespace: &mut AsmNamespace,
register_sequencer: &mut RegisterSequencer,
instantiation_span: &Span,
) -> CompileResult<Vec<Op>> {
let mut warnings = vec![];
let mut errors = vec![];
Expand Down Expand Up @@ -50,13 +51,14 @@ pub(crate) fn convert_enum_instantiation_to_asm(
return err(warnings, errors);
}
};
let size_of_enum: u64 = 1 /* tag */ + match ty.size_in_words(variant_name.span()) {
let size_of_enum: u64 = 1 /* tag */ + match ty.size_in_words(instantiation_span) {
Ok(o) => o,
Err(e) => {
errors.push(e);
return err(warnings, errors);
}
};

if size_of_enum > EIGHTEEN_BITS {
errors.push(CompileError::Unimplemented(
"Stack variables which exceed 2^18 words in size are not supported yet.",
Expand Down
220 changes: 167 additions & 53 deletions sway-core/src/asm_generation/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ use crate::{
parse_tree::{CallPath, Literal},
semantic_analysis::{
ast_node::{
SizeOfVariant, TypedAsmRegisterDeclaration, TypedCodeBlock, TypedExpressionVariant,
SizeOfVariant, TypedAsmRegisterDeclaration, TypedCodeBlock, TypedEnumVariant,
TypedExpressionVariant,
},
TypedExpression,
},
type_engine::look_up_type_id,
type_engine::*,
};
use sway_types::span::Span;

Expand Down Expand Up @@ -148,24 +149,6 @@ pub(crate) fn convert_expression_to_asm(
// For each opcode in the asm expression, attempt to parse it into an opcode and
// replace references to the above registers with the newly allocated ones.
for op in body {
/*
errors.append(
&mut op
.op_args
.iter()
.filter_map(|Ident { primary_name, span }| {
if mapping_of_real_registers_to_declared_names
.get(primary_name)
.is_none() &&
{
Some(todo!("error! {:?}", primary_name))
} else {
None
}
})
.collect::<Vec<_>>(),
);
*/
let replaced_registers = op.op_args.iter().map(|x| -> Result<_, CompileError> {
match realize_register(x.as_str(), &mapping_of_real_registers_to_declared_names)
{
Expand Down Expand Up @@ -260,12 +243,10 @@ pub(crate) fn convert_expression_to_asm(
resolved_type_of_parent,
prefix,
field_to_access,
field_to_access_span,
} => convert_subfield_expression_to_asm(
&exp.span,
prefix,
&field_to_access.name,
field_to_access_span.clone(),
field_to_access.name.clone(),
*resolved_type_of_parent,
namespace,
register_sequencer,
Expand All @@ -276,48 +257,36 @@ pub(crate) fn convert_expression_to_asm(
TypedExpressionVariant::TupleElemAccess {
resolved_type_of_parent,
prefix,
elem_to_access_num,
elem_to_access_span,
} => convert_subfield_expression_to_asm(
&exp.span,
prefix,
&format!("{}", elem_to_access_num),
elem_to_access_span.clone(),
*resolved_type_of_parent,
namespace,
register_sequencer,
return_register,
),
/*
TypedExpressionVariant::EnumArgAccess {
prefix,
variant_to_access,
arg_num_to_access,
resolved_type_of_parent,
} => convert_enum_arg_expression_to_asm(
&exp.span,
prefix,
variant_to_access,
arg_num_to_access.to_owned(),
*resolved_type_of_parent,
namespace,
register_sequencer,
return_register,
),
*/
elem_to_access_num,
} => {
// sorry
let leaked_ix: &'static str = Box::leak(Box::new(elem_to_access_num.to_string()));
let access_ident = Ident::new_with_override(leaked_ix, elem_to_access_span.clone());
convert_subfield_expression_to_asm(
&exp.span,
prefix,
access_ident,
*resolved_type_of_parent,
namespace,
register_sequencer,
return_register,
)
}
TypedExpressionVariant::EnumInstantiation {
enum_decl,
variant_name,
tag,
contents,
instantiation_span,
..
} => convert_enum_instantiation_to_asm(
enum_decl,
variant_name,
*tag,
contents,
return_register,
namespace,
register_sequencer,
instantiation_span,
),
TypedExpressionVariant::IfExp {
condition,
Expand Down Expand Up @@ -353,6 +322,24 @@ pub(crate) fn convert_expression_to_asm(
}
// ABI casts are purely compile-time constructs and generate no corresponding bytecode
TypedExpressionVariant::AbiCast { .. } => ok(vec![], warnings, errors),
TypedExpressionVariant::IfLet {
enum_type,
variant,
then,
r#else,
variable_to_assign,
expr,
} => convert_if_let_to_asm(
expr,
*enum_type,
variant,
then,
r#else,
variable_to_assign,
return_register,
namespace,
register_sequencer,
),
TypedExpressionVariant::SizeOf { variant } => convert_size_of_expression_to_asm(
variant,
namespace,
Expand Down Expand Up @@ -594,3 +581,130 @@ pub(crate) fn convert_abi_fn_to_asm(
// is done
ok(asm_buf, warnings, errors)
}

#[allow(clippy::too_many_arguments)]
fn convert_if_let_to_asm(
expr: &TypedExpression,
_enum_type: TypeId,
variant: &TypedEnumVariant,
then: &TypedCodeBlock,
r#else: &Option<Box<TypedExpression>>,
variable_to_assign: &Ident,
return_register: &VirtualRegister,
namespace: &mut AsmNamespace,
register_sequencer: &mut RegisterSequencer,
) -> CompileResult<Vec<Op>> {
// 1. evaluate the expression
// 2. load the expected tag into a register ($rA)
// 3. compare the tag to the first word of the expression's returned value
// 4. grab a register for `variable_to_assign`, insert it into the asm namespace
// 5. if the tags are equal, load the returned value from byte 1..end into `variable_to_assign`
// 5.5 if they are not equal, jump to the label in 7
// 6. evaluate the then branch with that variable in scope
// 7. insert a jump label for the else branch
// 8. evaluate the else branch, if any
let mut warnings = vec![];
let mut errors = vec![];
let mut buf = vec![];
// 1.
let expr_return_register = register_sequencer.next();
let mut expr_buf = check!(
convert_expression_to_asm(&*expr, namespace, &expr_return_register, register_sequencer),
vec![],
warnings,
errors
);
buf.append(&mut expr_buf);
// load the tag from the evaluated value
// as this is an enum we know the value in the register is a pointer
// we can therefore read a word from the register and move it into another register
let received_tag_register = register_sequencer.next();
buf.push(Op {
opcode: Either::Left(VirtualOp::LW(
received_tag_register.clone(),
expr_return_register.clone(),
VirtualImmediate12::new_unchecked(0, "infallible"),
)),
comment: "load received enum tag".into(),
owning_span: Some(expr.span.clone()),
});
// 2.
let expected_tag_register = register_sequencer.next();
let expected_tag_label = namespace.insert_data_value(&Literal::U64(variant.tag as u64));
buf.push(Op {
opcode: either::Either::Left(VirtualOp::LWDataId(
expected_tag_register.clone(),
expected_tag_label,
)),
comment: "load enum tag for if let".into(),
owning_span: Some(expr.span.clone()),
});
let label_for_else_branch = register_sequencer.get_label();
// 3 - 5
buf.push(Op {
opcode: Either::Right(OrganizationalOp::JumpIfNotEq(
expected_tag_register,
received_tag_register,
label_for_else_branch.clone(),
)),
comment: "jump to if let's else branch".into(),
owning_span: Some(expr.span.clone()),
});
// 6.
// put the destructured variable into the namespace for the then branch, but not otherwise
let mut then_branch_asm_namespace = namespace.clone();
let variable_to_assign_register = register_sequencer.next();
then_branch_asm_namespace.insert_variable(
variable_to_assign.clone(),
variable_to_assign_register.clone(),
);
// load the word that is at the expr return register + 1 word
// + 1 word is to account for the enum tag
buf.push(Op {
opcode: Either::Left(VirtualOp::LW(
variable_to_assign_register,
expr_return_register,
VirtualImmediate12::new_unchecked(1, "infallible"),
)),
owning_span: Some(then.span().clone()),
comment: "Load destructured value into register".into(),
});

// 6
buf.append(&mut check!(
convert_code_block_to_asm(
then,
&mut then_branch_asm_namespace,
register_sequencer,
Some(return_register)
),
return err(warnings, errors),
warnings,
errors
));

// add the data section from the then branch back to the main one

namespace.overwrite_data_section(then_branch_asm_namespace);

let label_for_after_else_branch = register_sequencer.get_label();
if let Some(r#else) = r#else {
buf.push(Op::jump_to_label_comment(
label_for_after_else_branch.clone(),
"jump to after the else branch",
));

buf.push(Op::unowned_jump_label(label_for_else_branch));

buf.append(&mut check!(
convert_expression_to_asm(r#else, namespace, return_register, register_sequencer),
return err(warnings, errors),
warnings,
errors
));

buf.push(Op::unowned_jump_label(label_for_after_else_branch));
}

ok(buf, warnings, errors)
}
7 changes: 3 additions & 4 deletions sway-core/src/asm_generation/expression/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ pub(crate) struct FieldMemoryLayoutDescriptor<N> {
size: u64,
}

// TODO(static span) this String should be an Ident
impl ContiguousMemoryLayoutDescriptor<String> {
impl ContiguousMemoryLayoutDescriptor<Ident> {
/// Calculates the offset in words from the start of a struct to a specific field.
pub(crate) fn offset_to_field_name(&self, name: &str, span: Span) -> CompileResult<u64> {
let field_ix = if let Some(ix) =
Expand Down Expand Up @@ -89,11 +88,11 @@ fn test_struct_memory_layout() {
let numbers = ContiguousMemoryLayoutDescriptor {
fields: vec![
FieldMemoryLayoutDescriptor {
name_of_field: first_field_name.as_str().to_string(),
name_of_field: first_field_name.clone(),
size: 1,
},
FieldMemoryLayoutDescriptor {
name_of_field: second_field_name.as_str().to_string(),
name_of_field: second_field_name.clone(),
size: 1,
},
],
Expand Down
Loading

0 comments on commit cfeeecc

Please sign in to comment.