feat: refactoring project

This commit is contained in:
Carlos
2024-11-23 14:56:07 -05:00
parent f0c2a50c18
commit 1c6db5818d
2351 changed files with 39323 additions and 60326 deletions

View File

@@ -94,8 +94,7 @@ import {
walk,
walk_body,
TreeWalker,
walk_parent,
} from "../ast.js";
import { HOP, make_node, noop } from "../utils/index.js";
@@ -496,28 +495,50 @@ function mark_lambda(tw, descend, compressor) {
* // use defined_after
* }
*
* This function is called on the parent to handle this issue.
* Or even indirectly:
*
* B();
* var defined_after = true;
* function A() {
* // use defined_after
* }
* function B() {
* A();
* }
*
* Access a variable before declaration will either throw a ReferenceError
* (if the variable is declared with `let` or `const`),
* or get an `undefined` (if the variable is declared with `var`).
*
* If the variable is inlined into the function, the behavior will change.
*
* This function is called on the parent to disallow inlining of such variables,
*/
function handle_defined_after_hoist(parent) {
const defuns = [];
walk(parent, node => {
if (node === parent) return;
if (node instanceof AST_Defun) defuns.push(node);
if (node instanceof AST_Defun) {
defuns.push(node);
return true;
}
if (
node instanceof AST_Scope
|| node instanceof AST_SimpleStatement
) return true;
});
// `defun` id to array of `defun` it uses
const defun_dependencies_map = new Map();
// `defun` id to array of enclosing `def` that are used by the function
const dependencies_map = new Map();
// all symbol ids that will be tracked for read/write
const symbols_of_interest = new Set();
const defuns_of_interest = new Set();
const potential_conflicts = [];
for (const defun of defuns) {
const fname_def = defun.name.definition();
const found_self_ref_in_other_defuns = defuns.some(
d => d !== defun && d.enclosed.indexOf(fname_def) !== -1
);
const enclosing_defs = [];
for (const def of defun.enclosed) {
if (
@@ -528,93 +549,107 @@ function handle_defined_after_hoist(parent) {
continue;
}
// defun is hoisted, so always safe
symbols_of_interest.add(def.id);
// found a reference to another function
if (
def.assignments === 0
&& def.orig.length === 1
&& def.orig[0] instanceof AST_SymbolDefun
) {
defuns_of_interest.add(def.id);
symbols_of_interest.add(def.id);
defuns_of_interest.add(fname_def.id);
symbols_of_interest.add(fname_def.id);
if (!defun_dependencies_map.has(fname_def.id)) {
defun_dependencies_map.set(fname_def.id, []);
}
defun_dependencies_map.get(fname_def.id).push(def.id);
continue;
}
if (found_self_ref_in_other_defuns) {
def.fixed = false;
continue;
}
enclosing_defs.push(def);
}
// for the slower checks below this loop
potential_conflicts.push({ defun, def, fname_def });
symbols_of_interest.add(def.id);
if (enclosing_defs.length) {
dependencies_map.set(fname_def.id, enclosing_defs);
defuns_of_interest.add(fname_def.id);
symbols_of_interest.add(fname_def.id);
defuns_of_interest.add(defun);
}
}
// linearize all symbols, and locate defs that are read after the defun
if (potential_conflicts.length) {
// All "symbols of interest", that is, defuns or defs, that we found.
// These are placed in order so we can check which is after which.
const found_symbols = [];
// Indices of `found_symbols` which are writes
const found_symbol_writes = new Set();
// Defun ranges are recorded because we don't care if a function uses the def internally
const defun_ranges = new Map();
// No defuns use outside constants
if (!dependencies_map.size) {
return;
}
let tw;
parent.walk((tw = new TreeWalker((node, descend) => {
if (node instanceof AST_Defun && defuns_of_interest.has(node)) {
const start = found_symbols.length;
descend();
const end = found_symbols.length;
// Increment to count "symbols of interest" (defuns or defs) that we found.
// These are tracked in AST order so we can check which is after which.
let symbol_index = 1;
// Map a defun ID to its first read (a `symbol_index`)
const defun_first_read_map = new Map();
// Map a symbol ID to its last write (a `symbol_index`)
const symbol_last_write_map = new Map();
defun_ranges.set(node, { start, end });
return true;
}
// if we found a defun on the list, mark IN_DEFUN=id and descend
walk_parent(parent, (node, walk_info) => {
if (node instanceof AST_Symbol && node.thedef) {
const id = node.definition().id;
if (node instanceof AST_Symbol && node.thedef) {
const id = node.definition().id;
if (symbols_of_interest.has(id)) {
if (node instanceof AST_SymbolDeclaration || is_lhs(node, tw)) {
found_symbol_writes.add(found_symbols.length);
}
found_symbols.push(id);
symbol_index++;
// Track last-writes to symbols
if (symbols_of_interest.has(id)) {
if (node instanceof AST_SymbolDeclaration || is_lhs(node, walk_info.parent())) {
symbol_last_write_map.set(id, symbol_index);
}
}
})));
for (const { def, defun, fname_def } of potential_conflicts) {
const defun_range = defun_ranges.get(defun);
// find the index in `found_symbols`, with some special rules:
const find = (sym_id, starting_at = 0, must_be_write = false) => {
let index = starting_at;
for (;;) {
index = found_symbols.indexOf(sym_id, index);
if (index === -1) {
break;
} else if (index >= defun_range.start && index < defun_range.end) {
index = defun_range.end;
continue;
} else if (must_be_write && !found_symbol_writes.has(index)) {
index++;
continue;
} else {
break;
}
// Track first-reads of defuns (refined later)
if (defuns_of_interest.has(id)) {
if (!defun_first_read_map.has(id) && !is_recursive_ref(walk_info, id)) {
defun_first_read_map.set(id, symbol_index);
}
}
}
});
return index;
};
// Refine `defun_first_read_map` to be as high as possible
for (const [defun, defun_first_read] of defun_first_read_map) {
// Update all depdencies of `defun`
const queue = new Set(defun_dependencies_map.get(defun));
for (const enclosed_defun of queue) {
let enclosed_defun_first_read = defun_first_read_map.get(enclosed_defun);
if (enclosed_defun_first_read != null && enclosed_defun_first_read < defun_first_read) {
continue;
}
const read_defun_at = find(fname_def.id);
const wrote_def_at = find(def.id, read_defun_at + 1, true);
defun_first_read_map.set(enclosed_defun, defun_first_read);
const wrote_def_after_reading_defun = read_defun_at != -1 && wrote_def_at != -1 && wrote_def_at > read_defun_at;
for (const enclosed_enclosed_defun of defun_dependencies_map.get(enclosed_defun) || []) {
queue.add(enclosed_enclosed_defun);
}
}
}
if (wrote_def_after_reading_defun) {
// ensure write-then-read order, otherwise clear `fixed`
// This is safe because last-writes (found_symbol_writes) are assumed to be as late as possible, and first-reads (defun_first_read_map) are assumed to be as early as possible.
for (const [defun, defs] of dependencies_map) {
const defun_first_read = defun_first_read_map.get(defun);
if (defun_first_read === undefined) {
continue;
}
for (const def of defs) {
if (def.fixed === false) {
continue;
}
let def_last_write = symbol_last_write_map.get(def.id) || 0;
if (defun_first_read < def_last_write) {
def.fixed = false;
}
}