1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "boxingForLocals.h"
17
18 #include "varbinder/ETSBinder.h"
19 #include "checker/ETSchecker.h"
20
21 namespace ark::es2panda::compiler {
22
23 static constexpr std::string_view LOWERING_NAME = "boxing-for-locals";
24
FindCaptured(public_lib::Context *ctx, ir::ScriptFunction *func)25 static ArenaSet<varbinder::Variable *> FindCaptured(public_lib::Context *ctx, ir::ScriptFunction *func)
26 {
27 auto *allocator = ctx->allocator;
28 auto captured = ArenaSet<varbinder::Variable *>(allocator->Adapter());
29 bool withinLambda = false;
30
31 auto innermostArrowScopes = ArenaSet<varbinder::Scope *>(allocator->Adapter());
32 innermostArrowScopes.insert(func->Scope());
33 innermostArrowScopes.insert(func->Scope()->ParamScope());
34
35 std::function<void(ir::AstNode *)> walker = [&](ir::AstNode *ast) {
36 if (ast->IsArrowFunctionExpression() || ast->IsClassDeclaration()) {
37 auto savedWL = withinLambda;
38 auto savedScopes = ArenaSet<varbinder::Scope *>(allocator->Adapter());
39 std::swap(innermostArrowScopes, savedScopes);
40 withinLambda = true;
41
42 ast->Iterate(walker);
43
44 withinLambda = savedWL;
45 std::swap(innermostArrowScopes, savedScopes);
46
47 return;
48 }
49
50 if (withinLambda && ast->IsScopeBearer()) {
51 innermostArrowScopes.insert(ast->Scope());
52 if (ast->Scope()->IsFunctionScope()) {
53 innermostArrowScopes.insert(ast->Scope()->AsFunctionScope()->ParamScope());
54 }
55 if (ast->Scope()->IsCatchScope()) {
56 innermostArrowScopes.insert(ast->Scope()->AsCatchScope()->ParamScope());
57 }
58 if (ast->Scope()->IsLoopScope()) {
59 innermostArrowScopes.insert(ast->Scope()->AsLoopScope()->DeclScope());
60 }
61 } else if (withinLambda && ast->IsIdentifier()) {
62 auto *var = ast->AsIdentifier()->Variable();
63 if (var == nullptr) {
64 return;
65 }
66 auto *scope = var->GetScope();
67 if (scope != nullptr && !scope->IsClassScope() && !scope->IsGlobalScope() &&
68 innermostArrowScopes.count(scope) == 0) {
69 captured.insert(var);
70 }
71 }
72
73 ast->Iterate(walker);
74 };
75
76 func->Iterate(walker);
77
78 auto varsToBox = ArenaSet<varbinder::Variable *>(allocator->Adapter());
79 return captured;
80 }
81
FindModified(public_lib::Context *ctx, ir::ScriptFunction *func)82 static ArenaSet<varbinder::Variable *> FindModified(public_lib::Context *ctx, ir::ScriptFunction *func)
83 {
84 auto *allocator = ctx->allocator;
85 auto modified = ArenaSet<varbinder::Variable *>(allocator->Adapter());
86
87 std::function<void(ir::AstNode *)> walker = [&](ir::AstNode *ast) {
88 if (ast->IsAssignmentExpression()) {
89 auto assignment = ast->AsAssignmentExpression();
90 if (assignment->Left()->IsIdentifier()) {
91 ASSERT(assignment->Left()->Variable() != nullptr);
92 auto *var = assignment->Left()->Variable();
93 var->AddFlag(varbinder::VariableFlags::INITIALIZED);
94 modified.insert(var);
95 }
96 }
97 };
98
99 func->IterateRecursively(walker);
100 return modified;
101 }
102
FindVariablesToBox(public_lib::Context *ctx, ir::ScriptFunction *func)103 static ArenaSet<varbinder::Variable *> FindVariablesToBox(public_lib::Context *ctx, ir::ScriptFunction *func)
104 {
105 auto *allocator = ctx->allocator;
106 auto captured = FindCaptured(ctx, func);
107 auto modified = FindModified(ctx, func);
108
109 auto varsToBox = ArenaSet<varbinder::Variable *>(allocator->Adapter());
110 std::set_intersection(captured.cbegin(), captured.cend(), modified.cbegin(), modified.cend(),
111 std::inserter(varsToBox, varsToBox.begin()));
112
113 return varsToBox;
114 }
115
HandleFunctionParam(public_lib::Context *ctx, ir::ETSParameterExpression *param, ArenaMap<varbinder::Variable *, varbinder::Variable *> *varsMap)116 static void HandleFunctionParam(public_lib::Context *ctx, ir::ETSParameterExpression *param,
117 ArenaMap<varbinder::Variable *, varbinder::Variable *> *varsMap)
118 {
119 auto *allocator = ctx->allocator;
120 auto *checker = ctx->checker->AsETSChecker();
121 auto *varBinder = checker->VarBinder();
122
123 auto *id = param->Ident()->AsIdentifier();
124 auto *oldVar = id->Variable();
125 auto *oldType = oldVar->TsType();
126 auto *func = param->Parent()->AsScriptFunction();
127 ASSERT(func->Body()->IsBlockStatement()); // guaranteed after expressionLambdaLowering
128 auto *body = func->Body()->AsBlockStatement();
129 auto &bodyStmts = body->Statements();
130 auto *scope = body->Scope();
131
132 auto *initId = allocator->New<ir::Identifier>(id->Name(), allocator);
133 initId->SetVariable(id->Variable());
134 initId->SetTsType(oldType);
135
136 // The new variable will have the same name as the parameter. This is not representable in source code.
137 auto *boxedType = checker->GlobalBuiltinBoxType(oldType);
138 ArenaVector<ir::Expression *> newInitArgs {allocator->Adapter()};
139 newInitArgs.push_back(initId);
140 auto *newInit = util::NodeAllocator::ForceSetParent<ir::ETSNewClassInstanceExpression>(
141 allocator, allocator->New<ir::OpaqueTypeNode>(boxedType), std::move(newInitArgs), nullptr);
142 auto *newDeclarator = util::NodeAllocator::ForceSetParent<ir::VariableDeclarator>(
143 allocator, ir::VariableDeclaratorFlag::CONST, allocator->New<ir::Identifier>(id->Name(), allocator), newInit);
144 ArenaVector<ir::VariableDeclarator *> declVec {allocator->Adapter()};
145 declVec.push_back(newDeclarator);
146
147 auto *newDecl = allocator->New<varbinder::ConstDecl>(id->Name(), newDeclarator);
148 auto *newVar = allocator->New<varbinder::LocalVariable>(newDecl, oldVar->Flags());
149 newVar->SetTsType(boxedType);
150
151 newDeclarator->Id()->AsIdentifier()->SetVariable(newVar);
152 newVar->AddFlag(varbinder::VariableFlags::INITIALIZED);
153 newVar->SetScope(scope);
154 scope->EraseBinding(newVar->Name());
155 scope->InsertBinding(newVar->Name(), newVar);
156
157 auto *newDeclaration = util::NodeAllocator::ForceSetParent<ir::VariableDeclaration>(
158 allocator, ir::VariableDeclaration::VariableDeclarationKind::CONST, allocator, std::move(declVec), false);
159 newDeclaration->SetParent(body);
160 bodyStmts.insert(bodyStmts.begin(), newDeclaration);
161
162 auto lexScope = varbinder::LexicalScope<varbinder::Scope>::Enter(varBinder, scope);
163 auto savedContext = checker::SavedCheckerContext(checker, checker::CheckerStatus::NO_OPTS);
164 auto scopeContext = checker::ScopeContext(checker, scope);
165
166 newDeclaration->Check(checker);
167
168 varsMap->emplace(oldVar, newVar);
169 }
170
HandleVariableDeclarator(public_lib::Context *ctx, ir::VariableDeclarator *declarator, ArenaMap<varbinder::Variable *, varbinder::Variable *> *varsMap)171 static ir::AstNode *HandleVariableDeclarator(public_lib::Context *ctx, ir::VariableDeclarator *declarator,
172 ArenaMap<varbinder::Variable *, varbinder::Variable *> *varsMap)
173 {
174 auto *allocator = ctx->allocator;
175 auto *checker = ctx->checker->AsETSChecker();
176 auto *varBinder = checker->VarBinder();
177
178 auto *id = declarator->Id()->AsIdentifier();
179 auto *oldVar = id->Variable();
180 auto *scope = oldVar->GetScope();
181 auto *type = oldVar->TsType();
182 auto *boxedType = checker->GlobalBuiltinBoxType(type);
183
184 auto initArgs = ArenaVector<ir::Expression *>(allocator->Adapter());
185 if (declarator->Init() != nullptr) {
186 auto *arg = declarator->Init();
187 if (arg->TsType() != type) {
188 arg = util::NodeAllocator::ForceSetParent<ir::TSAsExpression>(
189 allocator, arg, allocator->New<ir::OpaqueTypeNode>(type), false);
190 }
191 initArgs.push_back(arg);
192 }
193 auto *newInit = util::NodeAllocator::ForceSetParent<ir::ETSNewClassInstanceExpression>(
194 allocator, allocator->New<ir::OpaqueTypeNode>(boxedType), std::move(initArgs), nullptr);
195 auto *newDeclarator = util::NodeAllocator::ForceSetParent<ir::VariableDeclarator>(
196 allocator, declarator->Flag(), allocator->New<ir::Identifier>(id->Name(), allocator), newInit);
197 newDeclarator->SetParent(declarator->Parent());
198
199 auto *newDecl = allocator->New<varbinder::ConstDecl>(oldVar->Name(), newDeclarator);
200 auto *newVar = allocator->New<varbinder::LocalVariable>(newDecl, oldVar->Flags());
201 newDeclarator->Id()->AsIdentifier()->SetVariable(newVar);
202 newVar->AddFlag(varbinder::VariableFlags::INITIALIZED);
203 newVar->SetScope(scope);
204
205 scope->EraseBinding(oldVar->Name());
206 scope->InsertBinding(newVar->Name(), newVar);
207
208 auto lexScope = varbinder::LexicalScope<varbinder::Scope>::Enter(varBinder, scope);
209 auto savedContext = checker::SavedCheckerContext(checker, checker::CheckerStatus::NO_OPTS);
210 auto scopeContext = checker::ScopeContext(checker, scope);
211
212 newDeclarator->Check(checker);
213
214 varsMap->emplace(oldVar, newVar);
215
216 return newDeclarator;
217 }
218
IsBeingDeclared(ir::AstNode *ast)219 static bool IsBeingDeclared(ir::AstNode *ast)
220 {
221 ASSERT(ast->IsIdentifier());
222 return (ast->Parent()->IsVariableDeclarator() && ast == ast->Parent()->AsVariableDeclarator()->Id()) ||
223 (ast->Parent()->IsETSParameterExpression() && ast == ast->Parent()->AsETSParameterExpression()->Ident());
224 }
225
IsPartOfBoxInitializer(public_lib::Context *ctx, ir::AstNode *ast)226 static bool IsPartOfBoxInitializer(public_lib::Context *ctx, ir::AstNode *ast)
227 {
228 ASSERT(ast->IsIdentifier());
229 auto *checker = ctx->checker->AsETSChecker();
230 auto *id = ast->AsIdentifier();
231
232 // NOTE(gogabr): rely on caching for type instantiations, so we can use pointer comparison.
233 return id->Parent()->IsETSNewClassInstanceExpression() &&
234 id->Parent()->AsETSNewClassInstanceExpression()->GetTypeRef()->TsType() ==
235 checker->GlobalBuiltinBoxType(id->TsType());
236 }
237
OnLeftSideOfAssignment(ir::AstNode *ast)238 static bool OnLeftSideOfAssignment(ir::AstNode *ast)
239 {
240 return ast->Parent()->IsAssignmentExpression() && ast->Parent()->AsAssignmentExpression()->Left() == ast;
241 }
242
HandleReference(public_lib::Context *ctx, ir::Identifier *id, varbinder::Variable *var)243 static ir::AstNode *HandleReference(public_lib::Context *ctx, ir::Identifier *id, varbinder::Variable *var)
244 {
245 auto *parser = ctx->parser->AsETSParser();
246 auto *checker = ctx->checker->AsETSChecker();
247
248 // `as` is needed to account for smart types
249 auto *res = parser->CreateFormattedExpression("@@I1.get() as @@T2", var->Name(), id->TsType());
250 res->SetParent(id->Parent());
251 res->AsTSAsExpression()
252 ->Expr()
253 ->AsCallExpression()
254 ->Callee()
255 ->AsMemberExpression()
256 ->Object()
257 ->AsIdentifier()
258 ->SetVariable(var);
259
260 // NOTE(gogabr) -- The `get` property remains without variable; this is OK for the current checker, but may need
261 // adjustment later.
262 res->Check(checker);
263
264 ASSERT(res->TsType() == id->TsType());
265 res->SetBoxingUnboxingFlags(id->GetBoxingUnboxingFlags());
266
267 return res;
268 }
269
HandleAssignment(public_lib::Context *ctx, ir::AssignmentExpression *ass, ArenaMap<varbinder::Variable *, varbinder::Variable *> const &varsMap)270 static ir::AstNode *HandleAssignment(public_lib::Context *ctx, ir::AssignmentExpression *ass,
271 ArenaMap<varbinder::Variable *, varbinder::Variable *> const &varsMap)
272 {
273 // Should be true after opAssignment lowering
274 ASSERT(ass->OperatorType() == lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
275
276 auto *parser = ctx->parser->AsETSParser();
277 auto *varBinder = ctx->checker->VarBinder()->AsETSBinder();
278 auto *checker = ctx->checker->AsETSChecker();
279
280 auto *oldVar = ass->Left()->AsIdentifier()->Variable();
281 auto *newVar = varsMap.find(oldVar)->second;
282 auto *scope = newVar->GetScope();
283 newVar->AddFlag(varbinder::VariableFlags::INITIALIZED);
284
285 auto *res = parser->CreateFormattedExpression("@@I1.set(@@E2 as @@T3) as @@T4", newVar->Name(), ass->Right(),
286 oldVar->TsType(), ass->TsType());
287 res->SetParent(ass->Parent());
288
289 // NOTE(gogabr) -- The `get` and `set` properties remain without variable; this is OK for the current checker, but
290 // may need adjustment later.
291 auto lexScope = varbinder::LexicalScope<varbinder::Scope>::Enter(varBinder, scope);
292 auto savedContext = checker::SavedCheckerContext(checker, checker::CheckerStatus::NO_OPTS);
293 auto scopeContext = checker::ScopeContext(checker, scope);
294
295 varBinder->ResolveReferencesForScopeWithContext(res, scope);
296 res->Check(checker);
297
298 ASSERT(res->TsType() == ass->TsType());
299 res->SetBoxingUnboxingFlags(ass->GetBoxingUnboxingFlags());
300
301 return res;
302 }
303
HandleScriptFunction(public_lib::Context *ctx, ir::ScriptFunction *func)304 static void HandleScriptFunction(public_lib::Context *ctx, ir::ScriptFunction *func)
305 {
306 auto *allocator = ctx->allocator;
307 auto varsToBox = FindVariablesToBox(ctx, func);
308 if (varsToBox.empty()) {
309 return;
310 }
311 auto varsMap = ArenaMap<varbinder::Variable *, varbinder::Variable *>(allocator->Adapter());
312
313 /*
314 The function relies on the following facts:
315 - TransformChildrenRecursively handles children in order
316 - local variables are never used before declaration.
317 This ensures that varsToMap has the appropriate record by the time the variable reference is processed.
318 */
319 auto handleNode = [ctx, &varsToBox, &varsMap](ir::AstNode *ast) {
320 if (ast->IsETSParameterExpression() && varsToBox.count(ast->AsETSParameterExpression()->Variable()) > 0) {
321 HandleFunctionParam(ctx, ast->AsETSParameterExpression(), &varsMap);
322 return ast; // modifications occur in the containing function
323 }
324 if (ast->IsVariableDeclarator() && ast->AsVariableDeclarator()->Id()->IsIdentifier() &&
325 varsToBox.count(ast->AsVariableDeclarator()->Id()->AsIdentifier()->Variable()) > 0) {
326 return HandleVariableDeclarator(ctx, ast->AsVariableDeclarator(), &varsMap);
327 }
328 if (ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->Left()->IsIdentifier() &&
329 varsToBox.count(ast->AsAssignmentExpression()->Left()->AsIdentifier()->Variable()) > 0) {
330 return HandleAssignment(ctx, ast->AsAssignmentExpression(), varsMap);
331 }
332 if (ast->IsIdentifier() && !IsBeingDeclared(ast) && !IsPartOfBoxInitializer(ctx, ast) &&
333 !OnLeftSideOfAssignment(ast) && varsToBox.count(ast->AsIdentifier()->Variable()) > 0) {
334 return HandleReference(ctx, ast->AsIdentifier(), varsMap.find(ast->AsIdentifier()->Variable())->second);
335 }
336
337 return ast;
338 };
339
340 func->TransformChildrenRecursivelyPostorder(handleNode, LOWERING_NAME);
341 }
342
Perform(public_lib::Context *ctx, parser::Program *program)343 bool BoxingForLocals::Perform(public_lib::Context *ctx, parser::Program *program)
344 {
345 parser::SavedFormattingFileName savedFormattingName(ctx->parser->AsETSParser(), "boxing-for-lambdas");
346
347 if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
348 for (auto &[_, ext_programs] : program->ExternalSources()) {
349 (void)_;
350 for (auto *extProg : ext_programs) {
351 Perform(ctx, extProg);
352 }
353 }
354 }
355
356 std::function<void(ir::AstNode *)> searchForFunctions = [&](ir::AstNode *ast) {
357 if (ast->IsScriptFunction()) {
358 HandleScriptFunction(ctx, ast->AsScriptFunction()); // no recursion
359 } else {
360 ast->Iterate(searchForFunctions);
361 }
362 };
363 program->Ast()->Iterate(searchForFunctions);
364 return true;
365 }
366
Postcondition([[maybe_unused]] public_lib::Context *ctx, parser::Program const *program)367 bool BoxingForLocals::Postcondition([[maybe_unused]] public_lib::Context *ctx, parser::Program const *program)
368 {
369 for (auto &[_, ext_programs] : program->ExternalSources()) {
370 (void)_;
371 for (auto *extProg : ext_programs) {
372 if (!Postcondition(ctx, extProg)) {
373 return false;
374 }
375 }
376 }
377
378 return !program->Ast()->IsAnyChild([](const ir::AstNode *node) {
379 if (node->IsAssignmentExpression() && node->AsAssignmentExpression()->Left()->IsIdentifier()) {
380 auto asExpr = node->AsAssignmentExpression();
381 auto var = asExpr->Left()->AsIdentifier()->Variable();
382 if (var != nullptr && var->IsLocalVariable() && !var->HasFlag(varbinder::VariableFlags::INITIALIZED)) {
383 return true;
384 }
385 }
386 return false;
387 });
388 }
389
390 } // namespace ark::es2panda::compiler
391