@samitouri / QOS-React-2 / commits / 6c3792e8d5

[rust] Initial translation of HIR and related types

This is an initial translation of HIR (minus the ReactiveFunction bits). It's mostly a straightforward translation. A few differences: * Instead of Effect having an Unknown variant, we type `Place.effect: Option<Effect>`. Maybe i'll revert that but it seems right. * Terminal is divided into `Terminal { id: InstructionId, value: TerminalValue }` and `enum TerminalValue { ... }`, so that we can reference the terminal's id without caring about which kind of terminal it is. * Each instruction value variant gets a named struct, whereas in JS we had lots of anonymous InstructionValue variants. * Rust generators are still an unstable nightly feature and likely to change syntax enough that it seems safer not to rely on them. So `eachTerminalSuccessor()` returns a `Vec<BlockId>`. If the perf of this is bad enough we can switch to something like SmallVec (a popular crate) which stores a few elements inline on the stack to optimize for small vecs (which our case should be). Perhaps more importantly, what's _not different_: I kept the existing approach of BasicBlocks having a Vec of owned instruction instances, with InstructionValue variant operands as Places rather than indices into a shared instruction array. If this works out it will make porting straightforward. However there's one aspect of this that is incorrect: right now each `Place` owns its `Identifier`, whereas in JS the Identifier instances are shared mutable references. I'll definitely need to refactor the data structures to allow sharing Identifiers in some form, at which point we may want to change other things too.

Joe Savona committed Jul 6, 2023 at 09:24 UTC 6c3792e8d5e79d94f0d5ee24a1c0b3262ac067fa
19 files changed +494 -3
compiler/forget/Cargo.lock
+41 -3
@@ -109,6 +109,13 @@ dependencies = [
109 "serde",
110 ]
111
112 +[[package]]
113 +name = "build-hir"
114 +version = "0.1.0"
115 +dependencies = [
116 + "hir",
117 +]
118 +
119 [[package]]
120 name = "bumpalo"
121 version = "3.13.0"
@@ -186,6 +193,12 @@ dependencies = [
193 "syn 2.0.18",
194 ]
195
196 +[[package]]
197 +name = "equivalent"
198 +version = "1.0.0"
199 +source = "registry+https://github.com/rust-lang/crates.io-index"
200 +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
201 +
202 [[package]]
203 name = "estree"
204 version = "0.1.0"
@@ -276,6 +289,12 @@ version = "0.12.3"
289 source = "registry+https://github.com/rust-lang/crates.io-index"
290 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
291
292 +[[package]]
293 +name = "hashbrown"
294 +version = "0.14.0"
295 +source = "registry+https://github.com/rust-lang/crates.io-index"
296 +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
297 +
298 [[package]]
299 name = "hermit-abi"
300 version = "0.1.19"
@@ -294,6 +313,15 @@ dependencies = [
313 "libc",
314 ]
315
316 +[[package]]
317 +name = "hir"
318 +version = "0.1.0"
319 +dependencies = [
320 + "bumpalo",
321 + "indexmap 2.0.0",
322 + "serde",
323 +]
324 +
325 [[package]]
326 name = "idna"
327 version = "0.4.0"
@@ -317,7 +345,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
345 checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
346 dependencies = [
347 "autocfg",
320 - "hashbrown",
348 + "hashbrown 0.12.3",
349 +]
350 +
351 +[[package]]
352 +name = "indexmap"
353 +version = "2.0.0"
354 +source = "registry+https://github.com/rust-lang/crates.io-index"
355 +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
356 +dependencies = [
357 + "equivalent",
358 + "hashbrown 0.14.0",
359 ]
360
361 [[package]]
@@ -1048,7 +1086,7 @@ checksum = "e659ba3b910dc0cc0deb8731293151f56054d603ee47f0042fda530b7d487517"
1086 dependencies = [
1087 "better_scoped_tls",
1088 "bitflags 2.3.2",
1051 - "indexmap",
1089 + "indexmap 1.9.3",
1090 "once_cell",
1091 "phf",
1092 "rustc-hash",
@@ -1069,7 +1107,7 @@ version = "0.119.4"
1107 source = "registry+https://github.com/rust-lang/crates.io-index"
1108 checksum = "266eaf9dc1ba58fca1fd34e97f9493e4df655eecde468104f32ab40d7147efac"
1109 dependencies = [
1072 - "indexmap",
1110 + "indexmap 1.9.3",
1111 "num_cpus",
1112 "once_cell",
1113 "rustc-hash",
compiler/forget/Cargo.toml
+2
@@ -1,6 +1,8 @@
1 [workspace]
2
3 members = [
4 + "crates/build-hir",
5 + "crates/hir",
6 "crates/swc-demo",
7 "crates/estree",
8 "crates/estree-swc",
compiler/forget/crates/build-hir/Cargo.toml new
+9
@@ -0,0 +1,9 @@
1 +[package]
2 +name = "build-hir"
3 +version = "0.1.0"
4 +edition = "2021"
5 +
6 +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 +
8 +[dependencies]
9 +hir = { path = "../hir" }
\ No newline at end of file
compiler/forget/crates/build-hir/README.md new
+3
@@ -0,0 +1,3 @@
1 +# Build-HIR
2 +
3 +This crate converts from ESTree into HIR format as the first phase of compilation.
\ No newline at end of file
compiler/forget/crates/build-hir/src/build.rs
compiler/forget/crates/build-hir/src/builder.rs new
+18
@@ -0,0 +1,18 @@
1 +use std::collections::HashMap;
2 +
3 +use hir::{BasicBlock, BlockId, Environment};
4 +
5 +/// Helper struct used when converting from ESTree to HIR. Includes:
6 +/// - Variable resolution
7 +/// - Label resolution (for labeled statements and break/continue)
8 +/// - Access to the environment
9 +///
10 +/// As well as representing the incomplete form of the HIR. Usage
11 +/// generally involves driving calls to enter/exit blocks, resolve
12 +/// labels and variables, and then calling `build()` when the HIR
13 +/// is complete.
14 +pub struct Builder<'a> {
15 + environment: &'a Environment<'a>,
16 + completed: HashMap<BlockId, BasicBlock<'a>>,
17 + entry: BlockId,
18 +}
compiler/forget/crates/build-hir/src/lib.rs new
+2
@@ -0,0 +1,2 @@
1 +mod build;
2 +mod builder;
compiler/forget/crates/hir/Cargo.toml new
+11
@@ -0,0 +1,11 @@
1 +[package]
2 +name = "hir"
3 +version = "0.1.0"
4 +edition = "2021"
5 +
6 +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 +
8 +[dependencies]
9 +bumpalo = { version = "3.13.0", features = ["boxed", "collections"] }
10 +indexmap = "2.0.0"
11 +serde = "1.0.164"
compiler/forget/crates/hir/README.md new
+3
@@ -0,0 +1,3 @@
1 +# HIR
2 +
3 +This crate defines the core data structures that Forget uses to represent and compile input programs.
compiler/forget/crates/hir/src/basic_block.rs new
+28
@@ -0,0 +1,28 @@
1 +use crate::{id_types::BlockId, Instruction};
2 +
3 +/// Represents a sequence of instructions that will always[1] execute
4 +/// consecutively. Concretely, a block may have zero or more instructions
5 +/// and ends with a terminal node that describes where control flow may
6 +/// continue.
7 +///
8 +/// [1] Assuming no exceptions are thrown.
9 +pub struct BasicBlock<'a> {
10 + /// The identifier for the block
11 + pub id: BlockId,
12 +
13 + /// What kind of block this is. Used to distinguish basic blocks that
14 + /// correspond to a block scope in the input from basic blocks that
15 + /// represent control flow within statements or expressions, such as
16 + /// loops, logicals, ternaries, optionals, or sequences.
17 + pub kind: BlockKind,
18 +
19 + /// The ordered instructions in this block
20 + pub instructions: bumpalo::collections::Vec<'a, Instruction<'a>>,
21 +}
22 +
23 +pub enum BlockKind {
24 + Block,
25 + Value,
26 + Loop,
27 + Sequence,
28 +}
compiler/forget/crates/hir/src/environment.rs new
+58
@@ -0,0 +1,58 @@
1 +use bumpalo::Bump;
2 +
3 +use crate::{BlockId, Features, IdentifierId, Registry};
4 +
5 +/// Stores all the contextual information about the top-level React function being
6 +/// compiled. Environments may not be reused between React functions, but *are*
7 +/// shared between each React function and all its nested function expressions.
8 +#[derive(Debug)]
9 +pub struct Environment<'a> {
10 + /// The set of enabled compiler features
11 + pub features: Features,
12 +
13 + /// Definitions for functions, hooks, and types which are used to compile more
14 + /// precisely
15 + #[allow(dead_code)]
16 + registry: Registry,
17 +
18 + /// Arena allocator so that data for compilation can be efficiently allocated
19 + /// and the memory reclaimed when compilation completes.
20 + allocator: &'a Bump,
21 +
22 + /// The next available block index
23 + next_block_id: BlockId,
24 +
25 + /// The next available identifier id
26 + next_identifier_id: IdentifierId,
27 +}
28 +
29 +impl<'a> Environment<'a> {
30 + pub fn new(allocator: &'a Bump, features: Features, registry: Registry) -> Self {
31 + Self {
32 + allocator,
33 + features,
34 + registry,
35 + next_block_id: BlockId(0),
36 + next_identifier_id: IdentifierId(0),
37 + }
38 + }
39 +
40 + /// Allocate a value into the environment's memory arena
41 + pub fn alloc<T>(&self, value: T) -> &'a mut T {
42 + self.allocator.alloc(value)
43 + }
44 +
45 + /// Get the next available block id
46 + pub fn next_block_id(&mut self) -> BlockId {
47 + let id = self.next_block_id;
48 + self.next_block_id = id.next();
49 + id
50 + }
51 +
52 + /// Get the next available identifier id
53 + pub fn next_identifier_id(&mut self) -> IdentifierId {
54 + let id = self.next_identifier_id;
55 + self.next_identifier_id = id.next();
56 + id
57 + }
58 +}
compiler/forget/crates/hir/src/features.rs new
+12
@@ -0,0 +1,12 @@
1 +use serde::{Deserialize, Serialize};
2 +
3 +/// Describes the feature flags available to control compilation and validation.
4 +/// This type is serializable in order to support parsing from config files or
5 +/// serialized values when invoked from other languages.
6 +#[derive(Serialize, Deserialize, Debug)]
7 +pub struct Features {
8 + /// Validate that mutable lambdas are not passed where a frozen value is expected,
9 + /// since mutable lambdas cannot be frozen. The only mutation allowed inside a
10 + /// frozen lambda is of ref values.
11 + pub validate_frozen_lambdas: bool,
12 +}
compiler/forget/crates/hir/src/function.rs new
+23
@@ -0,0 +1,23 @@
1 +use indexmap::IndexMap;
2 +
3 +use crate::{BasicBlock, BlockId};
4 +
5 +/// Represents either a React function or a function expression
6 +pub struct Function<'a> {
7 + pub body: HIR<'a>,
8 + pub is_async: bool,
9 + pub is_generator: bool,
10 +}
11 +
12 +/// Represents the body of a `Function` as a control-flow graph.
13 +/// Blocks are stored in reverse postorder (predecessors before successors)
14 +/// so that compiler passes can complete forward data flow analysis in a
15 +/// single pass over the CFG in the case where there are no loops.
16 +pub struct HIR<'a> {
17 + /// The id of the first block
18 + pub entry: BlockId,
19 +
20 + /// Blocks are stored in a map for easy retrieval by their id,
21 + /// but the blocks are in reverse postorder
22 + pub blocks: IndexMap<BlockId, BasicBlock<'a>>,
23 +}
compiler/forget/crates/hir/src/id_types.rs new
+42
@@ -0,0 +1,42 @@
1 +/// Unique identifier for a basic block. Values are unique only with respect to
2 +/// a single top-level function, and may be reused across different top-level
3 +/// functions. Notably, ids *are* unique across the basic blocks of a function
4 +/// and its inner function expressions. This makes it easy to inline the contents
5 +/// of a function expression without re-identifying blocks.
6 +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
7 +pub struct BlockId(pub(crate) u32);
8 +
9 +impl BlockId {
10 + pub(crate) fn next(self) -> Self {
11 + Self(self.0 + 1)
12 + }
13 +}
14 +
15 +/// Unique identifier for a variable within a program. This is used to distinguish
16 +/// different instances of a variable with the same name in different scopes, or
17 +/// even the same named identifier across reassignments (when in SSA form).
18 +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
19 +pub struct IdentifierId(pub(crate) u32);
20 +
21 +impl IdentifierId {
22 + pub(crate) fn next(self) -> Self {
23 + Self(self.0 + 1)
24 + }
25 +}
26 +
27 +/// Used to globally order the instructions and terminals within the scope
28 +/// of a given HIR value. Instructions and terminals are ordered using
29 +/// reverse postorder iteration of block instructions and their terminals.
30 +///
31 +/// TODO: rename to more clearly indicate that this is for sequencing
32 +/// and to reflect that it is applied to terminals as well
33 +pub struct InstructionId(pub(crate) u32);
34 +
35 +/// Uniquely identifies a reactive scope
36 +pub struct ScopeId(pub(crate) u32);
37 +
38 +/// Uniquely identifiers a builtin function type in the type registry
39 +pub struct FunctionId(pub(crate) u32);
40 +
41 +/// Uniquely identifiers a builtin object type in the type registry
42 +pub struct ObjectId(pub(crate) u32);
compiler/forget/crates/hir/src/instruction.rs new
+164
@@ -0,0 +1,164 @@
1 +use bumpalo::collections::{String, Vec};
2 +
3 +use crate::{IdentifierId, InstructionId, ScopeId, Type};
4 +
5 +pub struct Instruction<'a> {
6 + pub id: InstructionId,
7 + pub lvalue: Place<'a>,
8 + pub value: InstructionValue<'a>,
9 +}
10 +
11 +pub enum InstructionValue<'a> {
12 + Array(Array<'a>),
13 + // Await(Await<'a>),
14 + // Binary(Binary<'a>),
15 + // Call(Call<'a>),
16 + // ComputedDelete(ComputedDelete<'a>),
17 + // ComputedLoad(ComputedLoad<'a>),
18 + // ComputedStore(ComputedStore<'a>),
19 + // Debugger(Debugger<'a>),
20 + DeclareContext(DeclareContext<'a>),
21 + DeclareLocal(DeclareLocal<'a>),
22 + // Destructure(Destructure<'a>),
23 + // Expression(Expression<'a>),
24 + // Function(Function<'a>),
25 + // JsxFragment(JsxFragment<'a>),
26 + // JsxText(JsxText<'a>),
27 + LoadContext(LoadContext<'a>),
28 + // LoadGlobal(LoadGlobal<'a>),
29 + LoadLocal(LoadLocal<'a>),
30 + // MethodCall(MethodCall<'a>),
31 + // New(New<'a>),
32 + // NextIterable(NextIterable<'a>),
33 + // Object(Object<'a>),
34 + // Primitive(Primitive<'a>),
35 + // PropertyDelete(PropertyDelete<'a>),
36 + // PropertyLoad(PropertyLoad<'a>),
37 + // PropertyStore(PropertyStore<'a>),
38 + // RegExp(RegExp<'a>),
39 + // StoreContext(StoreContext<'a>),
40 + StoreLocal(StoreLocal<'a>),
41 + // TaggedTemplate(TaggedTemplate<'a>),
42 + // Template(Template<'a>),
43 + // TypeCast(TypeCast<'a>),
44 + // Unary(Unary<'a>),
45 + // Unsupported(Unsupported<'a>),
46 +}
47 +
48 +pub struct Array<'a> {
49 + pub elements: Vec<'a, ArrayElement<'a>>,
50 +}
51 +
52 +pub enum ArrayElement<'a> {
53 + Place(Place<'a>),
54 + Spread(Place<'a>),
55 +}
56 +
57 +pub struct LoadLocal<'a> {
58 + pub place: Place<'a>,
59 +}
60 +
61 +pub struct LoadContext<'a> {
62 + pub place: Place<'a>,
63 +}
64 +
65 +pub struct DeclareLocal<'a> {
66 + pub lvalue: LValue<'a>,
67 +}
68 +
69 +pub struct DeclareContext<'a> {
70 + pub lvalue: LValue<'a>, // note: kind must be InstructionKind::Let
71 +}
72 +
73 +pub struct StoreLocal<'a> {
74 + pub lvalue: LValue<'a>,
75 + pub value: Place<'a>,
76 +}
77 +
78 +pub struct Place<'a> {
79 + pub identifier: Identifier<'a>,
80 + pub effect: Option<Effect>,
81 +}
82 +
83 +pub struct LValue<'a> {
84 + pub place: Place<'a>,
85 + pub kind: InstructionKind,
86 +}
87 +
88 +pub enum InstructionKind {
89 + /// `const` declaration
90 + Const,
91 +
92 + /// `let` declaration
93 + Let,
94 +
95 + /// Reassignment from `=` or assignment-update (`+=` etc)
96 + Reassign,
97 +}
98 +
99 +#[derive(Clone, Copy, PartialEq, Eq, Debug)]
100 +pub enum Effect {
101 + /// This reference freezes the value (corresponds to a place where codegen should emit a freeze instruction)
102 + Freeze,
103 +
104 + /// This reference reads the value
105 + Read,
106 +
107 + /// This reference reads and stores the value
108 + Capture,
109 +
110 + /// This reference *may* write to (mutate) the value. This covers two similar cases:
111 + /// - The compiler is being conservative and assuming that a value *may* be mutated
112 + /// - The effect is polymorphic: mutable values may be mutated, non-mutable values
113 + /// will not be mutated.
114 + /// In both cases, we conservatively assume that mutable values will be mutated.
115 + /// But we do not error if the value is known to be immutable.
116 + ConditionallyMutate,
117 +
118 + /// This reference *does* write to (mutate) the value. It is an error (invalid input)
119 + /// if an immutable value flows into a location with this effect.
120 + Mutate,
121 +
122 + /// This reference may alias to (mutate) the value
123 + Store,
124 +}
125 +
126 +impl Effect {
127 + pub fn is_mutable(self) -> bool {
128 + match self {
129 + Self::Capture | Self::Store | Self::ConditionallyMutate | Self::Mutate => true,
130 + Self::Read | Self::Freeze => false,
131 + }
132 + }
133 +}
134 +
135 +pub struct Identifier<'a> {
136 + /// Uniquely identifiers this identifier
137 + pub id: IdentifierId,
138 +
139 + pub name: Option<String<'a>>,
140 +
141 + pub mutable_range: MutableRange,
142 +
143 + pub scope: Option<ReactiveScope>,
144 +
145 + pub type_: Type,
146 +}
147 +
148 +/// Describes a span of code, generally used to describe the range in which
149 +/// a particular value or set of values is mutable (hence the name).
150 +///
151 +/// Start is inclusive, end is exclusive (ie end is the "first" instruction
152 +/// for which the value is not mutable).
153 +pub struct MutableRange {
154 + /// start of the range, inclusive.
155 + pub start: InstructionId,
156 +
157 + /// end of the range, exclusive
158 + pub end: InstructionId,
159 +}
160 +
161 +pub struct ReactiveScope {
162 + pub id: ScopeId,
163 + pub range: MutableRange,
164 +}
compiler/forget/crates/hir/src/lib.rs new
+19
@@ -0,0 +1,19 @@
1 +mod basic_block;
2 +mod environment;
3 +mod features;
4 +mod function;
5 +mod id_types;
6 +mod instruction;
7 +mod registry;
8 +mod terminal;
9 +mod types;
10 +
11 +pub use basic_block::BasicBlock;
12 +pub use environment::Environment;
13 +pub use features::Features;
14 +pub use function::Function;
15 +pub use id_types::*;
16 +pub use instruction::Instruction;
17 +pub use registry::Registry;
18 +pub use terminal::*;
19 +pub use types::*;
compiler/forget/crates/hir/src/registry.rs new
+4
@@ -0,0 +1,4 @@
1 +/// TODO: merge GlobalRegistry and ShapeRegistry into a single
2 +/// data structure
3 +#[derive(Debug)]
4 +pub struct Registry;
compiler/forget/crates/hir/src/terminal.rs new
+40
@@ -0,0 +1,40 @@
1 +use crate::{instruction::Place, BlockId, InstructionId};
2 +
3 +/// Terminals represent statements or expressions that affect control flow,
4 +/// such as for-of, if-else, return, logical (??), ternaries (?:), etc.
5 +pub struct Terminal<'a> {
6 + pub id: InstructionId,
7 + pub value: TerminalValue<'a>,
8 +}
9 +
10 +pub enum TerminalValue<'a> {
11 + // BranchTerminal(BranchTerminal),
12 + // DoWhileTerminal(DoWhileTerminal),
13 + // ForOfTerminal(ForOfTerminal),
14 + // ForTerminal(ForTerminal),
15 + // GotoTerminal(GotoTerminal),
16 + IfTerminal(IfTerminal<'a>),
17 + // LabelTerminal(LabelTerminal),
18 + // LogicalTerminal(LogicalTerminal),
19 + // OptionalTerminal(OptionalTerminal),
20 + ReturnTerminal(ReturnTerminal<'a>),
21 + // SequenceTerminal(SequenceTerminal),
22 + // SwitchTerminal(SwitchTerminal),
23 + // TernaryTerminal(TernaryTerminal),
24 + // ThrowTerminal(ThrowTerminal),
25 + // UnsupportedTerminal(UnsupportedTerminal),
26 + // WhileTerminal(WhileTerminal),
27 +}
28 +
29 +pub struct IfTerminal<'a> {
30 + pub test: Place<'a>,
31 + pub consequent: BlockId,
32 + pub alternate: BlockId,
33 + pub fallthrough: Option<BlockId>,
34 + pub id: InstructionId,
35 +}
36 +
37 +pub struct ReturnTerminal<'a> {
38 + pub value: Place<'a>,
39 + pub id: InstructionId,
40 +}
compiler/forget/crates/hir/src/types.rs new
+15
@@ -0,0 +1,15 @@
1 +use crate::{FunctionId, ObjectId};
2 +
3 +pub enum Type {
4 + Builtin(Box<BuiltinType>),
5 + // Phi(Box<PhiType>),
6 + // Var(Box<TypeVar>),
7 + // Poly(Box<PolyType>),
8 + // Prop(Box<PropType>),
9 +}
10 +
11 +pub enum BuiltinType {
12 + Primitive,
13 + Function(Option<FunctionId>),
14 + Object(Option<ObjectId>),
15 +}