From d99da0169827ff01fc49a08c27223f1fc969e1a2 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 6 Jul 2023 09:24:50 +0900 Subject: [PATCH] [rust] basic VariableDeclaration support --- compiler/forget/Cargo.lock | 1 + compiler/forget/crates/build-hir/src/build.rs | 82 ++++++++++++++++++- .../forget/crates/build-hir/src/builder.rs | 50 ++++------- compiler/forget/crates/estree-swc/src/lib.rs | 53 +++++++----- compiler/forget/crates/estree/src/lib.rs | 32 ++++---- .../fixtures/tests/fixtures/identifiers.js | 4 + .../crates/fixtures/tests/fixtures_test.rs | 2 - ...ixtures_test__fixtures@identifiers.js.snap | 13 ++- compiler/forget/crates/hir/Cargo.toml | 1 + compiler/forget/crates/hir/src/environment.rs | 35 +++++++- compiler/forget/crates/hir/src/instruction.rs | 10 +++ compiler/forget/crates/hir/src/print.rs | 21 ++++- 12 files changed, 222 insertions(+), 82 deletions(-) diff --git a/compiler/forget/Cargo.lock b/compiler/forget/Cargo.lock index 2a5549340e..2a132d093c 100644 --- a/compiler/forget/Cargo.lock +++ b/compiler/forget/Cargo.lock @@ -587,6 +587,7 @@ name = "hir" version = "0.1.0" dependencies = [ "bumpalo", + "estree", "indexmap 2.0.0", "serde", ] diff --git a/compiler/forget/crates/build-hir/src/build.rs b/compiler/forget/crates/build-hir/src/build.rs index 541def2006..12c18a4e11 100644 --- a/compiler/forget/crates/build-hir/src/build.rs +++ b/compiler/forget/crates/build-hir/src/build.rs @@ -1,8 +1,11 @@ use bumpalo::collections::{CollectIn, String}; -use estree::{ExpressionLike, FunctionDeclaration, Literal, LiteralValue, Statement}; +use estree::{ + ExpressionLike, FunctionDeclaration, Literal, LiteralValue, Pattern, Statement, + VariableDeclarationKind, +}; use hir::{ - ArrayElement, BlockKind, Environment, Function, GotoKind, Identifier, InstructionValue, - LoadGlobal, LoadLocal, Place, PrimitiveValue, TerminalValue, + ArrayElement, BlockKind, Environment, Function, GotoKind, Identifier, InstructionKind, + InstructionValue, LValue, LoadGlobal, LoadLocal, Place, PrimitiveValue, TerminalValue, }; use crate::builder::{Binding, Builder}; @@ -102,6 +105,39 @@ fn lower_statement<'a>( Statement::EmptyStatement(_) => { // no-op } + Statement::VariableDeclaration(stmt) => { + let kind = match stmt.kind { + VariableDeclarationKind::Const => InstructionKind::Const, + VariableDeclarationKind::Let => InstructionKind::Let, + VariableDeclarationKind::Var => panic!("`var` declarations are not supported"), + }; + for declaration in stmt.declarations { + if let Some(init) = declaration.init { + let value = lower_expression_to_temporary(env, builder, init); + lower_assignment(env, builder, kind, declaration.id, value); + } else { + if let Pattern::Identifier(id) = declaration.id { + // TODO: handle unbound variables + let binding = builder.resolve_binding(&id).unwrap(); + let identifier = match binding { + Binding::Local(identifier) => identifier, + _ => panic!("Expected variable declaration to be a local binding"), + }; + let place = Place { + effect: None, + identifier, + }; + lower_value_to_temporary( + env, + builder, + InstructionValue::DeclareLocal(hir::DeclareLocal { + lvalue: LValue { place, kind }, + }), + ); + } + } + } + } _ => todo!("Lower {stmt:#?}"), } Ok(()) @@ -168,6 +204,46 @@ fn lower_expression<'a>( } } +fn lower_assignment<'a>( + env: &'a Environment<'a>, + builder: &mut Builder<'a>, + kind: InstructionKind, + lvalue: Pattern, + value: Place<'a>, +) -> InstructionValue<'a> { + match lvalue { + Pattern::Identifier(lvalue) => { + let place = lower_identifier_for_assignment(env, builder, kind, *lvalue).unwrap(); + let temporary = lower_value_to_temporary( + env, + builder, + InstructionValue::StoreLocal(hir::StoreLocal { + lvalue: LValue { place, kind }, + value, + }), + ); + InstructionValue::LoadLocal(LoadLocal { place: temporary }) + } + _ => todo!("lower assignment for {:#?}", lvalue), + } +} + +fn lower_identifier_for_assignment<'a>( + env: &'a Environment<'a>, + builder: &mut Builder<'a>, + kind: InstructionKind, + identifier: estree::Identifier, +) -> Option> { + let binding = builder.resolve_binding(&identifier)?; + match binding { + Binding::Module(..) | Binding::Global => panic!("Cannot reassign a global"), + Binding::Local(id) => Some(Place { + identifier: id, + effect: None, + }), + } +} + /// Given an already lowered InstructionValue: /// - if the instruction is a LoadLocal for a temporary location, avoid the indirection /// and return the place that the LoadLocal loads from diff --git a/compiler/forget/crates/build-hir/src/builder.rs b/compiler/forget/crates/build-hir/src/builder.rs index 36f54cd929..bdd9145e52 100644 --- a/compiler/forget/crates/build-hir/src/builder.rs +++ b/compiler/forget/crates/build-hir/src/builder.rs @@ -21,7 +21,7 @@ use indexmap::IndexMap; /// generally involves driving calls to enter/exit blocks, resolve /// labels and variables, and then calling `build()` when the HIR /// is complete. -pub struct Builder<'a> { +pub(crate) struct Builder<'a> { #[allow(dead_code)] environment: &'a Environment<'a>, @@ -32,8 +32,12 @@ pub struct Builder<'a> { wip: WipBlock<'a>, id_gen: InstructionIdGenerator, +} - bindings: HashMap<(bumpalo::collections::String<'a>, BindingId), Identifier<'a>>, +pub(crate) struct WipBlock<'a> { + pub id: BlockId, + pub kind: BlockKind, + pub instructions: Vec<'a, Instruction<'a>>, } impl<'a> Builder<'a> { @@ -50,7 +54,6 @@ impl<'a> Builder<'a> { entry, wip: current, id_gen: InstructionIdGenerator::new(), - bindings: Default::default(), } } @@ -148,35 +151,16 @@ impl<'a> Builder<'a> { ) -> Option> { identifier.binding.as_ref().map(|binding| match binding { estree::Binding::Global => Binding::Global, - estree::Binding::Local(id) => { - Binding::Local(self.resolve_binding_identifier(&identifier.name, *id)) - } - estree::Binding::Module(id) => { - Binding::Module(self.resolve_binding_identifier(&identifier.name, *id)) - } + estree::Binding::Local(id) => Binding::Local( + self.environment + .resolve_binding_identifier(&identifier.name, *id), + ), + estree::Binding::Module(id) => Binding::Module( + self.environment + .resolve_binding_identifier(&identifier.name, *id), + ), }) } - - fn resolve_binding_identifier(&mut self, name: &str, binding_id: BindingId) -> Identifier<'a> { - let key_name = bumpalo::collections::String::from_str_in(name, &self.environment.allocator); - if let Some(identifier) = self.bindings.get(&(key_name.clone(), binding_id)) { - identifier.clone() - } else { - let id = self.environment.next_identifier_id(); - let identifier = Identifier { - id, - name: Some(key_name.clone()), - data: Rc::new(RefCell::new(IdentifierData { - mutable_range: Default::default(), - scope: None, - type_: Type::Var(self.environment.next_type_var_id()), - })), - }; - self.bindings - .insert((key_name, binding_id), identifier.clone()); - identifier - } - } } pub(crate) enum Binding<'a> { @@ -231,12 +215,6 @@ fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) { hir.blocks = blocks; } -pub(crate) struct WipBlock<'a> { - pub id: BlockId, - pub kind: BlockKind, - pub instructions: Vec<'a, Instruction<'a>>, -} - /// Prunes ForTerminal.update values (sets to None) if they are unreachable fn remove_unreachable_for_updates<'a>(hir: &mut HIR<'a>) { let block_ids: HashSet = hir.blocks.keys().cloned().collect(); diff --git a/compiler/forget/crates/estree-swc/src/lib.rs b/compiler/forget/crates/estree-swc/src/lib.rs index 5802ace112..99de60e1dd 100644 --- a/compiler/forget/crates/estree-swc/src/lib.rs +++ b/compiler/forget/crates/estree-swc/src/lib.rs @@ -7,7 +7,7 @@ use swc_core::common::source_map::Pos; use swc_core::common::{FileName, FilePathMapping, Mark, SourceMap, Span, SyntaxContext, GLOBALS}; use swc_core::ecma::ast::{ AssignOp, BinaryOp, BlockStmt, Decl, EsVersion, Expr, Ident, Lit, ModuleItem, Pat, PatOrExpr, - Program, Stmt, UnaryOp, VarDeclKind, VarDeclOrExpr, + Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr, }; use swc_core::ecma::parser::Syntax; use swc_core::ecma::transforms::base::resolver; @@ -95,7 +95,7 @@ fn convert_module_item(cx: &Context, item: &ModuleItem) -> estree::ModuleItem { ModuleItem::Stmt(item) => { estree::ModuleItem::Statement(Box::new(convert_statement(cx, item))) } - _ => todo!("Convert {:#?}", item), + _ => todo!("translate module item {:#?}", item), } } @@ -152,6 +152,9 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement { range: convert_span(&item.function.span), })) } + Stmt::Decl(Decl::Var(item)) => { + estree::Statement::VariableDeclaration(Box::new(convert_variable_declaration(cx, item))) + } Stmt::Block(item) => { estree::Statement::BlockStatement(Box::new(convert_block_statement(cx, item))) } @@ -207,18 +210,9 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement { } VarDeclOrExpr::VarDecl(init) => { assert_eq!(init.decls.len(), 1); - let decl = &init.decls[0]; - estree::ForInit::VariableDeclaration(Box::new(estree::VariableDeclaration { - kind: convert_decl_kind(&init.kind), - declarations: vec![estree::VariableDeclarator { - id: convert_pattern(cx, &decl.name), - init: decl.init.as_ref().map(|init| convert_expression(cx, init)), - loc: None, - range: convert_span(&decl.span), - }], - loc: None, - range: convert_span(&init.span), - })) + estree::ForInit::VariableDeclaration(Box::new(convert_variable_declaration( + cx, init, + ))) } }), test: item.test.as_ref().map(|test| convert_expression(cx, test)), @@ -249,7 +243,28 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement { loc: None, range: convert_span(&item.span), })), - _ => todo!(), + _ => todo!("translate statement {:#?}", stmt), + } +} + +fn convert_variable_declaration(cx: &Context, decl: &VarDecl) -> estree::VariableDeclaration { + estree::VariableDeclaration { + kind: convert_decl_kind(&decl.kind), + declarations: decl + .decls + .iter() + .map(|declarator| estree::VariableDeclarator { + id: convert_pattern(cx, &declarator.name), + init: declarator + .init + .as_ref() + .map(|init| convert_expression(cx, init)), + loc: None, + range: convert_span(&decl.span), + }) + .collect(), + loc: None, + range: convert_span(&decl.span), } } @@ -351,12 +366,12 @@ fn convert_expression(cx: &Context, expr: &Expr) -> estree::ExpressionLike { range: convert_span(&expr.span), })) } - _ => todo!(), + _ => todo!("translate expression {:#?}", expr), } } fn convert_assignment_target(_target: &PatOrExpr) -> estree::AssignmentTarget { - todo!() + todo!("translate assignment target") } fn convert_unary_operator(op: UnaryOp) -> estree::UnaryOperator { @@ -374,7 +389,7 @@ fn convert_unary_operator(op: UnaryOp) -> estree::UnaryOperator { fn convert_assignment_operator(op: AssignOp) -> estree::AssignmentOperator { match op { AssignOp::AddAssign => estree::AssignmentOperator::PlusEquals, - _ => todo!(), + _ => todo!("translate assignment operator"), } } @@ -422,7 +437,7 @@ fn convert_pattern(cx: &Context, pat: &Pat) -> estree::Pattern { loc: None, range: convert_span(&pat.span), })), - _ => todo!(), + _ => todo!("translate pattern {:#?}", pat), } } diff --git a/compiler/forget/crates/estree/src/lib.rs b/compiler/forget/crates/estree/src/lib.rs index 3e8d59618c..22111383e3 100644 --- a/compiler/forget/crates/estree/src/lib.rs +++ b/compiler/forget/crates/estree/src/lib.rs @@ -1038,6 +1038,22 @@ pub enum Binding { Global, } +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)] +#[serde(transparent)] +pub struct BindingId(NonZeroU32); + +impl BindingId { + pub fn new(id: NonZeroU32) -> Self { + Self(id) + } +} + +impl From for u32 { + fn from(value: BindingId) -> Self { + value.0.into() + } +} + #[derive(Serialize, Deserialize, Debug)] pub struct MemberExpression { pub object: ExpressionLike, @@ -1253,22 +1269,6 @@ pub struct JSXText { pub range: Option, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)] -#[serde(transparent)] -pub struct BindingId(NonZeroU32); - -impl BindingId { - pub fn new(id: NonZeroU32) -> Self { - Self(id) - } -} - -impl From for u32 { - fn from(value: BindingId) -> Self { - value.0.into() - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/compiler/forget/crates/fixtures/tests/fixtures/identifiers.js b/compiler/forget/crates/fixtures/tests/fixtures/identifiers.js index 3d9c1c95be..5c1e3b02f9 100644 --- a/compiler/forget/crates/fixtures/tests/fixtures/identifiers.js +++ b/compiler/forget/crates/fixtures/tests/fixtures/identifiers.js @@ -7,5 +7,9 @@ function id(x) { // FOO; Math; id; + let y = true; + y; + let z; + z; return x; } diff --git a/compiler/forget/crates/fixtures/tests/fixtures_test.rs b/compiler/forget/crates/fixtures/tests/fixtures_test.rs index e4a399417d..68b1b4dd17 100644 --- a/compiler/forget/crates/fixtures/tests/fixtures_test.rs +++ b/compiler/forget/crates/fixtures/tests/fixtures_test.rs @@ -11,8 +11,6 @@ fn fixtures() { let input = std::fs::read_to_string(path).unwrap(); let ast = parse(&input, path.to_str().unwrap()).unwrap(); - println!("{:#?}", &ast); - let mut output = String::new(); for (ix, item) in ast.body.into_iter().enumerate() { diff --git a/compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap b/compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap index 636e552e63..c20e69fb58 100644 --- a/compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap +++ b/compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap @@ -13,6 +13,10 @@ function id(x) { // FOO; Math; id; + let y = true; + y; + let z; + z; return x; } @@ -22,6 +26,11 @@ entry bb0 bb0 [0] unknown $0 = LoadGlobal Math [1] unknown $2 = LoadGlobal id - [2] unknown $4 = LoadLocal unknown x$3 - [3] Return unknown $4 + [2] unknown $3 = true + [3] unknown $5 = StoreLocal Let unknown y$4 = unknown $3 + [4] unknown $6 = LoadLocal unknown y$4 + [5] unknown $8 = DeclareLocal Let unknown z$7 + [6] unknown $9 = LoadLocal unknown z$7 + [7] unknown $11 = LoadLocal unknown x$10 + [8] Return unknown $11 diff --git a/compiler/forget/crates/hir/Cargo.toml b/compiler/forget/crates/hir/Cargo.toml index 0ac6a3a199..834a8317a9 100644 --- a/compiler/forget/crates/hir/Cargo.toml +++ b/compiler/forget/crates/hir/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" [dependencies] bumpalo = { version = "3.13.0", features = ["boxed", "collections"] } +estree = { path = "../estree" } indexmap = "2.0.0" serde = "1.0.164" diff --git a/compiler/forget/crates/hir/src/environment.rs b/compiler/forget/crates/hir/src/environment.rs index 8452b20993..235ccc1fe1 100644 --- a/compiler/forget/crates/hir/src/environment.rs +++ b/compiler/forget/crates/hir/src/environment.rs @@ -1,8 +1,15 @@ -use std::cell::Cell; +use std::{ + cell::{Cell, RefCell}, + collections::HashMap, + rc::Rc, +}; use bumpalo::Bump; +use estree::BindingId; -use crate::{BlockId, Features, IdentifierId, Registry, TypeVarId}; +use crate::{ + BlockId, Features, Identifier, IdentifierData, IdentifierId, Registry, Type, TypeVarId, +}; /// Stores all the contextual information about the top-level React function being /// compiled. Environments may not be reused between React functions, but *are* @@ -28,6 +35,8 @@ pub struct Environment<'a> { next_identifier_id: Cell, next_type_var_id: Cell, + + bindings: Rc, BindingId), Identifier<'a>>>>, } impl<'a> Environment<'a> { @@ -39,6 +48,7 @@ impl<'a> Environment<'a> { next_block_id: Cell::new(BlockId(0)), next_identifier_id: Cell::new(IdentifierId(0)), next_type_var_id: Cell::new(TypeVarId(0)), + bindings: Default::default(), } } @@ -67,4 +77,25 @@ impl<'a> Environment<'a> { self.next_type_var_id.set(id.next()); id } + + pub fn resolve_binding_identifier(&self, name: &str, binding_id: BindingId) -> Identifier<'a> { + let key_name = bumpalo::collections::String::from_str_in(name, &self.allocator); + let mut bindings = self.bindings.borrow_mut(); + if let Some(identifier) = bindings.get(&(key_name.clone(), binding_id)) { + identifier.clone() + } else { + let id = self.next_identifier_id(); + let identifier = Identifier { + id, + name: Some(key_name.clone()), + data: Rc::new(RefCell::new(IdentifierData { + mutable_range: Default::default(), + scope: None, + type_: Type::Var(self.next_type_var_id()), + })), + }; + bindings.insert((key_name, binding_id), identifier.clone()); + identifier + } + } } diff --git a/compiler/forget/crates/hir/src/instruction.rs b/compiler/forget/crates/hir/src/instruction.rs index c34ebb5769..02e49d80e0 100644 --- a/compiler/forget/crates/hir/src/instruction.rs +++ b/compiler/forget/crates/hir/src/instruction.rs @@ -145,6 +145,16 @@ pub enum InstructionKind { Reassign, } +impl Display for InstructionKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Const => f.write_str("Const"), + Self::Let => f.write_str("Let"), + Self::Reassign => f.write_str("Reassign"), + } + } +} + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum Effect { /// This reference freezes the value (corresponds to a place where codegen should emit a freeze instruction) diff --git a/compiler/forget/crates/hir/src/print.rs b/compiler/forget/crates/hir/src/print.rs index 1205d8a785..cd0d96a178 100644 --- a/compiler/forget/crates/hir/src/print.rs +++ b/compiler/forget/crates/hir/src/print.rs @@ -1,8 +1,8 @@ use std::fmt::{Result, Write}; use crate::{ - ArrayElement, BasicBlock, Function, Instruction, InstructionValue, Place, PrimitiveValue, - Terminal, TerminalValue, + ArrayElement, BasicBlock, Function, Instruction, InstructionValue, LValue, Place, + PrimitiveValue, Terminal, TerminalValue, }; /// Trait for HIR types to describe how they print themselves. @@ -80,6 +80,16 @@ impl<'a> Print for InstructionValue<'a> { PrimitiveValue::Undefined => write!(out, "")?, }; } + InstructionValue::StoreLocal(value) => { + write!(out, "StoreLocal ")?; + value.lvalue.print(out)?; + write!(out, " = ")?; + value.value.print(out)?; + } + InstructionValue::DeclareLocal(value) => { + write!(out, "DeclareLocal ")?; + value.lvalue.print(out)?; + } _ => write!(out, "{:?}", self)?, } Ok(()) @@ -117,6 +127,13 @@ impl<'a> Print for Place<'a> { } } +impl<'a> Print for LValue<'a> { + fn print(&self, out: &mut impl Write) -> Result { + write!(out, "{} ", self.kind)?; + self.place.print(out) + } +} + impl<'a> Print for Terminal<'a> { fn print(&self, out: &mut impl Write) -> Result { write!(out, " {} ", self.id)?;