Commit 857c6c2c authored by Roman Alifanov's avatar Roman Alifanov

Add DCE for unused class methods

parent cb16dd5f
...@@ -38,6 +38,14 @@ class ClassMixin: ...@@ -38,6 +38,14 @@ class ClassMixin:
self._generate_construct_method(cls) self._generate_construct_method(cls)
for method in cls.methods: for method in cls.methods:
if self.used_methods is not None:
method_used = (
cls.name in self.used_methods and
method.name in self.used_methods[cls.name]
)
if not method_used:
continue
has_awk = any(dec.name == "awk" for dec in method.decorators) has_awk = any(dec.name == "awk" for dec in method.decorators)
if has_awk: if has_awk:
self._generate_awk_method(cls, method) self._generate_awk_method(cls, method)
...@@ -51,6 +59,13 @@ class ClassMixin: ...@@ -51,6 +59,13 @@ class ClassMixin:
own_method_names = {m.name for m in cls.methods} own_method_names = {m.name for m in cls.methods}
for parent_method in parent_cls.methods: for parent_method in parent_cls.methods:
if parent_method.name not in own_method_names: if parent_method.name not in own_method_names:
if self.used_methods is not None:
method_used = (
cls.name in self.used_methods and
parent_method.name in self.used_methods[cls.name]
)
if not method_used:
continue
self._generate_inherited_method(cls, parent_cls, parent_method) self._generate_inherited_method(cls, parent_cls, parent_method)
self.current_class = None self.current_class = None
......
...@@ -19,6 +19,10 @@ class UsageAnalyzer: ...@@ -19,6 +19,10 @@ class UsageAnalyzer:
self.used_classes: set = set() self.used_classes: set = set()
self.used_methods: dict = {} self.used_methods: dict = {}
self.class_fields: dict = {} self.class_fields: dict = {}
self.variable_types: dict = {}
self.current_class_name: str = None
self.current_method_name: str = None
self.method_calls: dict = {}
def analyze(self, programs: list) -> set: def analyze(self, programs: list) -> set:
self.used = {'core'} self.used = {'core'}
...@@ -34,6 +38,7 @@ class UsageAnalyzer: ...@@ -34,6 +38,7 @@ class UsageAnalyzer:
self._analyze_stmt(stmt) self._analyze_stmt(stmt)
self._resolve_transitive_classes() self._resolve_transitive_classes()
self._resolve_transitive_methods()
if self.has_classes: if self.has_classes:
self.used.add('object') self.used.add('object')
...@@ -59,6 +64,12 @@ class UsageAnalyzer: ...@@ -59,6 +64,12 @@ class UsageAnalyzer:
while changed: while changed:
changed = False changed = False
for cls_name in list(self.used_classes): for cls_name in list(self.used_classes):
if cls_name in self.defined_classes:
cls_decl = self.defined_classes[cls_name]
if cls_decl.parent and cls_decl.parent not in self.used_classes:
if cls_decl.parent in self.defined_classes:
self.used_classes.add(cls_decl.parent)
changed = True
if cls_name in self.class_fields: if cls_name in self.class_fields:
for field_name, field_class in self.class_fields[cls_name].items(): for field_name, field_class in self.class_fields[cls_name].items():
if field_class and field_class not in self.used_classes: if field_class and field_class not in self.used_classes:
...@@ -66,6 +77,36 @@ class UsageAnalyzer: ...@@ -66,6 +77,36 @@ class UsageAnalyzer:
self.used_classes.add(field_class) self.used_classes.add(field_class)
changed = True changed = True
def _resolve_transitive_methods(self):
changed = True
while changed:
changed = False
for cls_name, methods in list(self.used_methods.items()):
for method_name in list(methods):
key = (cls_name, method_name)
if key in self.method_calls:
for called_cls, called_method in self.method_calls[key]:
if called_cls not in self.used_methods:
self.used_methods[called_cls] = set()
if called_method not in self.used_methods[called_cls]:
self.used_methods[called_cls].add(called_method)
changed = True
for cls_name in list(self.used_classes):
if cls_name in self.defined_classes:
cls_decl = self.defined_classes[cls_name]
if cls_decl.parent and cls_decl.parent in self.defined_classes:
parent_cls = self.defined_classes[cls_decl.parent]
if cls_name in self.used_methods:
child_methods = {m.name for m in cls_decl.methods}
for method in list(self.used_methods[cls_name]):
if method not in child_methods:
if cls_decl.parent not in self.used_methods:
self.used_methods[cls_decl.parent] = set()
if method not in self.used_methods[cls_decl.parent]:
self.used_methods[cls_decl.parent].add(method)
changed = True
def get_used_classes(self) -> set: def get_used_classes(self) -> set:
return self.used_classes return self.used_classes
...@@ -75,14 +116,18 @@ class UsageAnalyzer: ...@@ -75,14 +116,18 @@ class UsageAnalyzer:
def _analyze_stmt(self, stmt): def _analyze_stmt(self, stmt):
if isinstance(stmt, ClassDecl): if isinstance(stmt, ClassDecl):
self.has_classes = True self.has_classes = True
self.current_class_name = stmt.name
for method in stmt.methods: for method in stmt.methods:
self.current_method_name = method.name
if method.decorators: if method.decorators:
for dec in method.decorators: for dec in method.decorators:
if dec.name == 'awk': if dec.name == 'awk':
self.has_awk = True self.has_awk = True
self._analyze_body(method.body) self._analyze_body(method.body)
self.current_method_name = None
if stmt.constructor: if stmt.constructor:
self._analyze_body(stmt.constructor.body) self._analyze_body(stmt.constructor.body)
self.current_class_name = None
elif isinstance(stmt, FunctionDecl): elif isinstance(stmt, FunctionDecl):
if stmt.decorators: if stmt.decorators:
...@@ -93,6 +138,14 @@ class UsageAnalyzer: ...@@ -93,6 +138,14 @@ class UsageAnalyzer:
elif isinstance(stmt, Assignment): elif isinstance(stmt, Assignment):
self._analyze_expr(stmt.value) self._analyze_expr(stmt.value)
if isinstance(stmt.target, Identifier):
var_name = stmt.target.name
if isinstance(stmt.value, NewExpr):
self.variable_types[var_name] = stmt.value.class_name
elif isinstance(stmt.value, CallExpr) and isinstance(stmt.value.callee, Identifier):
callee_name = stmt.value.callee.name
if callee_name in self.defined_classes:
self.variable_types[var_name] = callee_name
elif isinstance(stmt, ExpressionStmt): elif isinstance(stmt, ExpressionStmt):
self._analyze_expr(stmt.expression) self._analyze_expr(stmt.expression)
...@@ -212,7 +265,17 @@ class UsageAnalyzer: ...@@ -212,7 +265,17 @@ class UsageAnalyzer:
if isinstance(callee, MemberAccess): if isinstance(callee, MemberAccess):
if isinstance(callee.object, ThisExpr): if isinstance(callee.object, ThisExpr):
pass method = callee.member
if self.current_class_name:
if self.current_class_name not in self.used_methods:
self.used_methods[self.current_class_name] = set()
self.used_methods[self.current_class_name].add(method)
if self.current_method_name:
caller = (self.current_class_name, self.current_method_name)
callee_key = (self.current_class_name, method)
if caller not in self.method_calls:
self.method_calls[caller] = set()
self.method_calls[caller].add(callee_key)
elif isinstance(callee.object, MemberAccess) and isinstance(callee.object.object, ThisExpr): elif isinstance(callee.object, MemberAccess) and isinstance(callee.object.object, ThisExpr):
field_name = callee.object.member field_name = callee.object.member
...@@ -226,7 +289,13 @@ class UsageAnalyzer: ...@@ -226,7 +289,13 @@ class UsageAnalyzer:
elif isinstance(callee.object, Identifier): elif isinstance(callee.object, Identifier):
ns = callee.object.name ns = callee.object.name
if ns == 'http': method = callee.member
if ns in self.variable_types:
obj_class = self.variable_types[ns]
if obj_class not in self.used_methods:
self.used_methods[obj_class] = set()
self.used_methods[obj_class].add(method)
elif ns == 'http':
self.used.add('http') self.used.add('http')
elif ns == 'fs': elif ns == 'fs':
self.used.add('fs') self.used.add('fs')
...@@ -245,8 +314,18 @@ class UsageAnalyzer: ...@@ -245,8 +314,18 @@ class UsageAnalyzer:
elif ns == 'shell': elif ns == 'shell':
pass pass
else: else:
method = callee.member # Check if this could be a class method (conservative approach)
self._check_method(method) # Include method in all classes that define it
found_in_class = False
for cls_name, cls_decl in self.defined_classes.items():
for m in cls_decl.methods:
if m.name == method:
if cls_name not in self.used_methods:
self.used_methods[cls_name] = set()
self.used_methods[cls_name].add(method)
found_in_class = True
if not found_in_class:
self._check_method(method)
def _analyze_member_access(self, expr: MemberAccess): def _analyze_member_access(self, expr: MemberAccess):
if isinstance(expr.object, Identifier): if isinstance(expr.object, Identifier):
......
...@@ -101,7 +101,7 @@ class ExprMixin: ...@@ -101,7 +101,7 @@ class ExprMixin:
if parts[0] == 'this': if parts[0] == 'this':
return f'\\$${{__CT_OBJ["$this.{parts[1]}"]}}' return f'\\$${{__CT_OBJ["$this.{parts[1]}"]}}'
else: else:
return f'\\$${{__CT_OBJ["${parts[0]}.{parts[1]}"]}}' return f'\\$${{__CT_OBJ["${{{parts[0]}}}.{parts[1]}"]:-}}'
return f'\\$${{{content}}}' return f'\\$${{{content}}}'
def replace_interpolation(match): def replace_interpolation(match):
...@@ -115,7 +115,7 @@ class ExprMixin: ...@@ -115,7 +115,7 @@ class ExprMixin:
if parts[0] == 'this': if parts[0] == 'this':
return f'${{__CT_OBJ["$this.{parts[1]}"]}}' return f'${{__CT_OBJ["$this.{parts[1]}"]}}'
else: else:
return f'${{__CT_OBJ["${parts[0]}.{parts[1]}"]}}' return f'${{__CT_OBJ["${{{parts[0]}}}.{parts[1]}"]:-}}'
return f'${{{content}}}' return f'${{{content}}}'
value = value.replace('\x00DOLLAR\x00{', '\x01ESCAPED_DOLLAR_BRACE\x01') value = value.replace('\x00DOLLAR\x00{', '\x01ESCAPED_DOLLAR_BRACE\x01')
...@@ -336,8 +336,9 @@ class ExprMixin: ...@@ -336,8 +336,9 @@ class ExprMixin:
return f'${{__CT_OBJ["$this.{expr.member}"]}}' return f'${{__CT_OBJ["$this.{expr.member}"]}}'
obj = self.generate_expr(expr.object) obj = self.generate_expr(expr.object)
if obj.startswith('${') and obj.endswith('}'): if obj.startswith('${') and obj.endswith('}'):
obj = '$' + obj[2:-1] var_name = obj[2:-1]
return f'${{__CT_OBJ["{obj}.{expr.member}"]}}' return f'${{__CT_OBJ["${{{var_name}}}.{expr.member}"]:-}}'
return f'${{__CT_OBJ["{obj}.{expr.member}"]:-}}'
def _generate_index_access(self, expr: IndexAccess) -> str: def _generate_index_access(self, expr: IndexAccess) -> str:
obj = self.generate_expr(expr.object) obj = self.generate_expr(expr.object)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment