From 7cb47fea7960c5ed9eb87bf0bc7845f1a032a3f0 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 31 Jul 2026 12:55:23 +0200 Subject: [PATCH 01/13] Prepare separate main branch for performance improvements --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index fdaa54a44ec..c090e3b690a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,6 +9,7 @@ on: branches: - main - 'feat/*' + - performance-improvements-main env: MAVEN_OPTS: "-Xmx4G -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true" IS_OWN_PR: "${{ secrets.MAVEN_MIRROR_URL }}" From 95c668e96bbeb909d243e0b9f267a5d5e56813a5 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Mon, 3 Aug 2026 10:31:24 +0200 Subject: [PATCH 02/13] Remove `-ea` from "Launch `RascalShell`" configuration (to be reverted) --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fa9529bef36..fa0c556435d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,7 +37,7 @@ "request": "launch", "mainClass": "org.rascalmpl.shell.RascalShell", "projectName": "rascal", - "vmArgs": "-Xss80m -Xmx2g -ea", + "vmArgs": "-Xss80m -Xmx2g", "console": "integratedTerminal" }, { From ed8f781acad13f028c306b3fd3be1afd13a1389d Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Mon, 3 Aug 2026 10:37:30 +0200 Subject: [PATCH 03/13] Add flame graph generator (to be reverted) --- .vscode/launch.json | 2 +- .../rascalmpl/interpreter/utils/Profiler.java | 68 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fa0c556435d..d6d60dc0909 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,7 +37,7 @@ "request": "launch", "mainClass": "org.rascalmpl.shell.RascalShell", "projectName": "rascal", - "vmArgs": "-Xss80m -Xmx2g", + "vmArgs": "-Xss80m -Xmx2g -Dorg.rascalmpl.profiling.flameGraph.script=C:/Users/sung-/Desktop/FlameGraph-1.0/flamegraph.pl", "console": "integratedTerminal" }, { diff --git a/src/org/rascalmpl/interpreter/utils/Profiler.java b/src/org/rascalmpl/interpreter/utils/Profiler.java index f7671310dfe..c54df894aaf 100644 --- a/src/org/rascalmpl/interpreter/utils/Profiler.java +++ b/src/org/rascalmpl/interpreter/utils/Profiler.java @@ -13,15 +13,21 @@ *******************************************************************************/ package org.rascalmpl.interpreter.utils; +import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; +import java.util.stream.Collectors; import org.rascalmpl.ast.AbstractAST; +import org.rascalmpl.debug.IRascalFrame; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.Environment; import io.usethesource.vallang.IList; @@ -72,6 +78,64 @@ public boolean equals(Object obj) { } } +class FlameGraph { + private final Map counts = new HashMap<>(); + + void sample(Evaluator eval) { + var frames = eval.getCallStack().stream(); + var folded = frames.map(FlameGraph::getFrameTitle).collect(Collectors.joining(";")); + var count = counts.computeIfAbsent(folded, k -> new Count()); + count.increment(); + } + + private static String getFrameTitle(IRascalFrame frame) { + var title = frame.getName(); + var callerLocation = frame.getCallerLocation(); + if (callerLocation != null) { + title += " at " + callerLocation; + } + return title; + } + + void write() { + var name = "flameGraph"; + var out = Path.of(name + ".out"); + var err = Path.of(name + ".err"); + var svg = Path.of(name + ".svg"); + + try { + Files.writeString(out, ""); + for (var e : counts.entrySet()) { + // Newlines must be `\n` for `flamegraph.pl` to work + var csq = String.format("%s %d\n", e.getKey(), e.getValue().getTicks()); + Files.writeString(out, csq, StandardOpenOption.APPEND); + } + + var scriptKey = "org.rascalmpl.profiling.flameGraph.script"; + var scriptValue = System.getProperty(scriptKey); + if (scriptValue != null) { + var script = Path.of(scriptValue); + if (Files.exists(script)) { + + ProcessBuilder processBuilder = new ProcessBuilder("perl", script.toString(), out.toString()); + processBuilder.redirectOutput(svg.toFile()); + processBuilder.redirectError(err.toFile()); + + Process process = processBuilder.start(); + try { + process.waitFor(); + } catch (InterruptedException e) { + // Ignore; doesn't matter + } + } + } + + } catch (IOException e) { + e.printStackTrace(); + } + } +} + public class Profiler extends Thread { private Evaluator eval; private volatile boolean running; @@ -79,6 +143,7 @@ public class Profiler extends Thread { private final Map ast; private final Map frame; private final Map names; + private final FlameGraph flameGraph = new FlameGraph(); public Profiler(Evaluator ev){ super("Rascal-Sampling-Profiler"); @@ -95,6 +160,8 @@ public void run(){ AbstractAST current = eval.getCurrentAST(); Environment env = eval.getCurrentEnvt(); String name = env.getName(); + + flameGraph.sample(eval); if (current != null) { ISourceLocation stat = current.getLocation(); @@ -162,6 +229,7 @@ public void report() { report("FRAMES", frame); eval.getOutPrinter().println(); report("ASTS", ast); + flameGraph.write(); } private void report(String title, Map data) { From 9ed9f11c7c57fadd2d81e8829c9c7a52045f1b7f Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Tue, 4 Aug 2026 11:41:28 +0200 Subject: [PATCH 04/13] Update flame graph generator to write data to disk asap --- src/org/rascalmpl/interpreter/utils/Profiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/interpreter/utils/Profiler.java b/src/org/rascalmpl/interpreter/utils/Profiler.java index c54df894aaf..a612907ff10 100644 --- a/src/org/rascalmpl/interpreter/utils/Profiler.java +++ b/src/org/rascalmpl/interpreter/utils/Profiler.java @@ -226,10 +226,10 @@ public IList getProfileData(){ } public void report() { + flameGraph.write(); report("FRAMES", frame); eval.getOutPrinter().println(); report("ASTS", ast); - flameGraph.write(); } private void report(String title, Map data) { From b723a5d33daed983cc3cf7df6e5d5b675aa156bb Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 31 Jul 2026 15:36:02 +0200 Subject: [PATCH 05/13] Update `doSaveModule` to use map lookups instead of linear searches --- .../compiler/lang/rascalcore/check/Import.rsc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc index 6b39268e7ef..f08e7535863 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc @@ -421,16 +421,20 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, if(isEmpty(component)) return ms; //println("doSaveModule: , , , "); - component_scopes = component; //{ getModuleScope(mid, moduleScopes, pcfg) | MODID mid <- component }; + set[MODID] componentScopes = component; //{ getModuleScope(mid, moduleScopes, pcfg) | MODID mid <- component }; + map[str, MODID] componentScopesByUri = (); set[MODID] filteredModuleScopes = {}; + map[str, MODID] filteredModuleScopesByUri = (); loc2moduleName = invertUnique(ms.moduleLocs); bool isContainedInComponentScopes(loc inner, map[loc,loc] m){ - return any(cs <- component_scopes, isContainedIn(inner, cs, m)); + inner = m[inner] ? inner; + return inner.uri in componentScopesByUri ? isContainedIn(inner, componentScopesByUri[inner.uri]) : false; }; bool isContainedInFilteredModuleScopes(loc inner, map[loc,loc] m){ - return any(cs <- filteredModuleScopes, isContainedIn(inner, cs, m)); + inner = m[inner] ? inner; + return inner.uri in filteredModuleScopesByUri ? isContainedIn(inner, filteredModuleScopesByUri[inner.uri]) : false; }; for(currentModule <- component){ @@ -443,9 +447,12 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, bom = makeBom(currentModule, ms); + componentScopesByUri = (s.uri: s | loc s <- componentScopes, loc s := tm.logical2physical[s] ? s); + extendedModuleScopes = {m | MODID m <- extends, hasProperty(m, ms, checked())}; extendedModuleScopes += {*tm.paths[ems,importPath()] | MODID ems <- extendedModuleScopes}; // add imports of extended modules filteredModuleScopes = {m | MODID m <- (currentModule + imports), hasProperty(m, ms, checked())} + extendedModuleScopes; + filteredModuleScopesByUri = (m.uri: m | loc m <- filteredModuleScopes, loc m := tm.logical2physical[m] ? m); TModel m1 = tmodel(); m1.version = getCurrentTplVersion(); @@ -455,7 +462,7 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, m1.facts = (key : tm.facts[key] | key <- tm.facts, isContainedInFilteredModuleScopes(key, tm.logical2physical)); - m1.specializedFacts = (key : tm.specializedFacts[key] | key <- tm.specializedFacts, isContainedInComponentScopes(key, tm.logical2physical), any(fms <- filteredModuleScopes, isContainedIn(key, fms))); + m1.specializedFacts = (key : tm.specializedFacts[key] | key <- tm.specializedFacts, isContainedInComponentScopes(key, tm.logical2physical), isContainedInFilteredModuleScopes(key, tm.logical2physical)); m1.facts += m1.specializedFacts; m1.messages = [msg | msg <- tm.messages, isContainedIn(msg.at, currentModule, tm.logical2physical)]; From 498ef0a57eef17b4177b07cf03bfb883a49670cb Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Tue, 4 Aug 2026 13:44:09 +0200 Subject: [PATCH 06/13] Update `doSaveModule` to reduce the number of field lookups --- .../compiler/lang/rascalcore/check/Import.rsc | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc index f08e7535863..f4658794402 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc @@ -447,12 +447,20 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, bom = makeBom(currentModule, ms); - componentScopesByUri = (s.uri: s | loc s <- componentScopes, loc s := tm.logical2physical[s] ? s); + // Lookup fields only once to save interpreter time (significant) + paths = tm.paths; + facts = tm.facts; + specializedFacts = tm.specializedFacts; + useDef = tm.useDef; + logical2physical = tm.logical2physical; + definitions = tm.definitions; + + componentScopesByUri = (s.uri: s | loc s <- componentScopes, loc s := logical2physical[s] ? s); extendedModuleScopes = {m | MODID m <- extends, hasProperty(m, ms, checked())}; - extendedModuleScopes += {*tm.paths[ems,importPath()] | MODID ems <- extendedModuleScopes}; // add imports of extended modules + extendedModuleScopes += {*paths[ems,importPath()] | MODID ems <- extendedModuleScopes}; // add imports of extended modules filteredModuleScopes = {m | MODID m <- (currentModule + imports), hasProperty(m, ms, checked())} + extendedModuleScopes; - filteredModuleScopesByUri = (m.uri: m | loc m <- filteredModuleScopes, loc m := tm.logical2physical[m] ? m); + filteredModuleScopesByUri = (m.uri: m | loc m <- filteredModuleScopes, loc m := logical2physical[m] ? m); TModel m1 = tmodel(); m1.version = getCurrentTplVersion(); @@ -460,15 +468,15 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, m1.modelName = moduleId2moduleName(currentModule); m1.moduleLocs = (m1.modelName : currentModule); - m1.facts = (key : tm.facts[key] | key <- tm.facts, isContainedInFilteredModuleScopes(key, tm.logical2physical)); + m1.facts = (key : facts[key] | key <- facts, isContainedInFilteredModuleScopes(key, logical2physical)); - m1.specializedFacts = (key : tm.specializedFacts[key] | key <- tm.specializedFacts, isContainedInComponentScopes(key, tm.logical2physical), isContainedInFilteredModuleScopes(key, tm.logical2physical)); + m1.specializedFacts = (key : specializedFacts[key] | key <- specializedFacts, isContainedInComponentScopes(key, logical2physical), isContainedInFilteredModuleScopes(key, logical2physical)); m1.facts += m1.specializedFacts; - m1.messages = [msg | msg <- tm.messages, isContainedIn(msg.at, currentModule, tm.logical2physical)]; + m1.messages = [msg | msg <- tm.messages, isContainedIn(msg.at, currentModule, logical2physical)]; ms.messages[currentModule] = toSet(m1.messages); - filteredModuleScopePaths = {ml.path |loc ml <- filteredModuleScopes}; + // filteredModuleScopePaths = {ml.path |loc ml <- filteredModuleScopes}; m1.scopes = tm.scopes; // m1.scopes // = ( inner : tm.scopes[inner] @@ -487,23 +495,23 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, m1.store[key_common_keyword_fields] = tm.store[key_common_keyword_fields] ? []; - m1.paths = { tup | tuple[MODID from, PathRole pathRole, MODID to] tup <- tm.paths, tup.from == currentModule || tup.from in filteredModuleScopes /*|| tup.from in filteredModuleScopePaths*/ }; + m1.paths = { tup | tuple[MODID from, PathRole pathRole, MODID to] tup <- paths, tup.from == currentModule || tup.from in filteredModuleScopes /*|| tup.from in filteredModuleScopePaths*/ }; keepRoles = variableRoles + keepInTModelRoles; m1.useDef = { - | <- tm.useDef, - isContainedIn(u, currentModule, tm.logical2physical) - || (tm.definitions[d]? && tm.definitions[d].idRole in keepRoles) + | <- useDef, + isContainedIn(u, currentModule, logical2physical) + || (definitions[d]? && definitions[d].idRole in keepRoles) }; // Filter model for current module and replace functions in defType by their defined type defs = for(tup: <- tm.defines){ - if( ( idRole in variableRoles ? ( isContainedInComponentScopes(defined, tm.logical2physical) + if( ( idRole in variableRoles ? ( isContainedInComponentScopes(defined, logical2physical) ) : ( idRole in keepInTModelRoles - && ( isContainedInComponentScopes(defined, tm.logical2physical) - || isContainedInFilteredModuleScopes(defined, tm.logical2physical) + && ( isContainedInComponentScopes(defined, logical2physical) + || isContainedInFilteredModuleScopes(defined, logical2physical) ) ) ) @@ -518,11 +526,12 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, m1.define2id = tm.define2id; // Remove default expressions and fragments + // (Relatively expensive: can take >50% of the execution time of this function) m1 = visit(m1) { case kwField(AType atype, str fieldName, str definingModule, Expression _defaultExp) => kwField(atype, fieldName, definingModule) case loc l : if(!isEmpty(l.fragment)) insert l[fragment=""]; }; - m1.logical2physical = tm.logical2physical; + m1.logical2physical = logical2physical; ms = deleteProperty(currentModule, ms, tpl_saved()); ms = addTModel(currentModule, m1, ms); // println("TModel for :"); iprintln(m1); From b628e7c186c6dc95ecc503cb699c4029b174aef9 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Mon, 17 Aug 2026 15:23:17 +0200 Subject: [PATCH 07/13] Replace ternary conditional with binary conjunction --- src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc index f4658794402..8085f073b5a 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc @@ -429,12 +429,12 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, bool isContainedInComponentScopes(loc inner, map[loc,loc] m){ inner = m[inner] ? inner; - return inner.uri in componentScopesByUri ? isContainedIn(inner, componentScopesByUri[inner.uri]) : false; + return inner.uri in componentScopesByUri && isContainedIn(inner, componentScopesByUri[inner.uri]); }; bool isContainedInFilteredModuleScopes(loc inner, map[loc,loc] m){ inner = m[inner] ? inner; - return inner.uri in filteredModuleScopesByUri ? isContainedIn(inner, filteredModuleScopesByUri[inner.uri]) : false; + return inner.uri in filteredModuleScopesByUri && isContainedIn(inner, filteredModuleScopesByUri[inner.uri]); }; for(currentModule <- component){ From 470a0921b743d0bc2497f4ea6a7ba420f34d227d Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Wed, 19 Aug 2026 13:40:37 +0200 Subject: [PATCH 08/13] Remove commented code --- .../compiler/lang/rascalcore/check/Import.rsc | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc index 8085f073b5a..881a6cf846b 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc @@ -420,8 +420,7 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, component = { m | m <- component, hasNotProperty(m, ms, ModuleProperty::ignored()) }; if(isEmpty(component)) return ms; - //println("doSaveModule: , , , "); - set[MODID] componentScopes = component; //{ getModuleScope(mid, moduleScopes, pcfg) | MODID mid <- component }; + set[MODID] componentScopes = component; map[str, MODID] componentScopesByUri = (); set[MODID] filteredModuleScopes = {}; map[str, MODID] filteredModuleScopesByUri = (); @@ -476,14 +475,7 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, m1.messages = [msg | msg <- tm.messages, isContainedIn(msg.at, currentModule, logical2physical)]; ms.messages[currentModule] = toSet(m1.messages); - // filteredModuleScopePaths = {ml.path |loc ml <- filteredModuleScopes}; m1.scopes = tm.scopes; - // m1.scopes - // = ( inner : tm.scopes[inner] - // | loc inner <- tm.scopes, - // inner.path in filteredModuleScopePaths, - // (tm.scopes[inner] == |global-scope:///| || isContainedInComponentScopes(inner, tm.logical2physical)) - // ); m1.store = (key_bom : bom); @@ -495,7 +487,7 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, m1.store[key_common_keyword_fields] = tm.store[key_common_keyword_fields] ? []; - m1.paths = { tup | tuple[MODID from, PathRole pathRole, MODID to] tup <- paths, tup.from == currentModule || tup.from in filteredModuleScopes /*|| tup.from in filteredModuleScopePaths*/ }; + m1.paths = { tup | tuple[MODID from, PathRole pathRole, MODID to] tup <- paths, tup.from == currentModule || tup.from in filteredModuleScopes }; keepRoles = variableRoles + keepInTModelRoles; m1.useDef = { @@ -534,7 +526,6 @@ ModuleStatus doSaveModule(set[MODID] component, map[MODID,set[MODID]] m_imports, m1.logical2physical = logical2physical; ms = deleteProperty(currentModule, ms, tpl_saved()); ms = addTModel(currentModule, m1, ms); - // println("TModel for :"); iprintln(m1); } return ms; } \ No newline at end of file From d1c7dc75a305b46efed2f736b783e206778ac9ab Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Tue, 18 Aug 2026 15:33:48 +0200 Subject: [PATCH 09/13] Add `rascalFilterUnused` as a faster alternative to `rascalReportUnused` --- .../lang/rascalcore/check/RascalConfig.rsc | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/RascalConfig.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/RascalConfig.rsc index e64fb92c351..e02a5287979 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/RascalConfig.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/RascalConfig.rsc @@ -323,23 +323,25 @@ bool isOverloadedFunction(loc fun, map[loc,Define] definitions, map[loc, AType] } bool rascalReportUnused(loc def, TModel tm){ + return rascalFilterUnused([def], tm) == [def]; +} + +list[loc] rascalFilterUnused(list[loc] defs, TModel tm) { config = tm.config; - if(!config.warnUnused) return false; + if(!config.warnUnused) return []; + // Lookup fields only once to save interpreter time (significant) + warnUnusedFormals = config.warnUnusedFormals; + moduleLocs = tm.moduleLocs; + modelName = tm.modelName; + logical2physical = tm.logical2physical; definitions = tm.definitions; - - if(!definitions[def]? || !tm.moduleLocs[tm.modelName]?) return false; - - if(!isContainedIn(definitions[def].defined, tm.moduleLocs[tm.modelName], tm.logical2physical)){ - return false; - } - scopes = tm.scopes; facts = tm.facts; bool reportFormal(Define define){ - if(!config.warnUnusedFormals || isWildCard(define.id[0])) return false; - container = tm.definitions[findContainer(def, definitions, scopes)]; + if(!warnUnusedFormals || isWildCard(define.id[0])) return false; + container = definitions[findContainer(define.defined, definitions, scopes)]; if(container.idRole == functionId()){ if(isOverloadedFunction(container.defined, definitions, facts)) return false; return "java" notin container.defInfo.modifiers; @@ -347,8 +349,8 @@ bool rascalReportUnused(loc def, TModel tm){ return false; } - define = definitions[def]; - try { + bool filterFormal(loc def) { + define = definitions[def]; switch(define.idRole){ case moduleId(): return false; case dataId(): return false; @@ -383,9 +385,25 @@ bool rascalReportUnused(loc def, TModel tm){ case layoutId(): return false; case keywordId(): return false; } - } catch NoSuchKey(_): return false; + return true; + } + + bool tryFilterFormal(loc def) { + try { + return filterFormal(def); + } catch NoSuchKey(_): { + return false; + } + } - return true; + if (modelName in moduleLocs) { + moduleLoc = moduleLocs[modelName]; + moduleLoc = logical2physical[moduleLoc] ? moduleLoc; + // Assumption: `logical2physical` has already been applied to each `def` + return [def | loc def <- defs, isContainedIn(def, moduleLoc), tryFilterFormal(def)]; + } else { + return []; + } } // Extend the path relation by @@ -663,6 +681,7 @@ RascalCompilerConfig rascalCompilerConfig(PathConfig pcfg, preSolver = rascalPreSolver, postSolver = rascalPostSolver, reportUnused = rascalReportUnused, + filterUnused = rascalFilterUnused, createLogicalLoc = rascalCreateLogicalLoc, similarNames = rascalSimilarNames ); From d2ce976663a23941305fbd95ef65a2078c88a0b1 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Tue, 4 Aug 2026 15:31:06 +0200 Subject: [PATCH 10/13] Add pre-conversion of `facts` and `defines` to a more efficient representation for later use in `implicitlyUsesParseTree`, `implicitlyUsesLayoutOrLexical`, and `usesOrExtendsADT` --- .../lang/rascalcore/check/Checker.rsc | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc index a6363b66e7c..9e2d5b2b603 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc @@ -299,6 +299,14 @@ ModuleStatus rascalTModelForLocs( // } = rascalTModelComponent(component, ms); + + // Convert `tm.facts` and `tm.defines` to a more efficient + // representation for later use + map[loc, AType] facts = tm.facts; + rel[str, AType] factsByPath = { | loc l <- facts}; + Defines defines = tm.defines; + rel[str, Define] definesByPath = { | Define d <- defines}; + // moduleScopes += getModuleScopes(tm); map[str,TModel] tmodels_for_component = (); map[MODID,set[MODID]] m_imports = (); @@ -328,13 +336,13 @@ ModuleStatus rascalTModelForLocs( imsgs += error("Rascal TPL version error for ``, no source found", imod.src); } if(inameId notin usedModules){ - if(iname == "ParseTree" && implicitlyUsesParseTree(ms.moduleLocs[m].path, tm)){ + if(iname == "ParseTree" && implicitlyUsesParseTree(ms.moduleLocs[m].path, factsByPath)){ continue check_imports; } - if(ms.moduleLocs[inameId]? && ms.moduleLocs[m]? && implicitlyUsesLayoutOrLexical(ms.moduleLocs[m].path, ms.moduleLocs[inameId].path, tm)){ + if(ms.moduleLocs[inameId]? && ms.moduleLocs[m]? && implicitlyUsesLayoutOrLexical(ms.moduleLocs[m].path, ms.moduleLocs[inameId].path, factsByPath)){ continue check_imports; } - if(ms.moduleLocs[inameId]? && ms.moduleLocs[m]? && usesOrExtendsADT(ms.moduleLocs[m].path, ms.moduleLocs[inameId].path, tm)){ + if(ms.moduleLocs[inameId]? && ms.moduleLocs[m]? && usesOrExtendsADT(ms.moduleLocs[m].path, ms.moduleLocs[inameId].path, factsByPath, definesByPath)){ continue check_imports; } if((inameId in component || hasProperty(inameId, ms, checked())) && hasNotProperty(inameId, ms, rsc_not_found())){ @@ -401,20 +409,20 @@ ModuleStatus rascalTModelForLocs( return clearTModelCache(ms); } -bool implicitlyUsesParseTree(str modulePath, TModel tm){ - return any(loc l <- tm.facts, l.path == modulePath, areified(_) <- tm.facts[l]); +bool implicitlyUsesParseTree(str modulePath, rel[str, AType] factsByPath){ + return any(areified(_) <- factsByPath[modulePath]); } -bool implicitlyUsesLayoutOrLexical(str modulePath, str importPath, TModel tm){ - return any(loc l <- tm.facts, l.path == importPath, aadt(_,_,sr) := tm.facts[l], sr in {layoutSyntax(), lexicalSyntax()}) - && any(loc l <- tm.facts, l.path == modulePath, aadt(_,_,contextFreeSyntax()) := tm.facts[l]); +bool implicitlyUsesLayoutOrLexical(str modulePath, str importPath, rel[str, AType] factsByPath){ + return any(aadt(_,_,sr) <- factsByPath[importPath], sr in {layoutSyntax(), lexicalSyntax()}) + && any(aadt(_,_,contextFreeSyntax()) <- factsByPath[modulePath]); } -bool usesOrExtendsADT(str modulePath, str importPath, TModel tm){ - usedADTs = { unset(tm.facts[l], "alabel") | loc l <- tm.facts, l.path == modulePath, aadt(_,_,_) := tm.facts[l] }; - definedADTs = { unset(the_adt, "alabel") | Define d <- tm.defines, d.defined.path == modulePath, defType(the_adt:aadt(_,_,_)) := d.defInfo }; +bool usesOrExtendsADT(str modulePath, str importPath, rel[str, AType] factsByPath, rel[str, Define] definesByPath){ + usedADTs = { unset(the_adt, "alabel") | the_adt:aadt(_,_,_) <- factsByPath[modulePath] }; + definedADTs = { unset(the_adt, "alabel") | Define d <- definesByPath[modulePath], defType(the_adt:aadt(_,_,_)) := d.defInfo }; usedOrDefinedADTs = usedADTs + definedADTs; - res = any(loc l <- tm.facts, l.path == importPath, the_adt:aadt(_,_,_) := tm.facts[l], unset(the_adt, "alabel") in usedOrDefinedADTs); + res = any(the_adt:aadt(_,_,_) <- factsByPath[importPath], unset(the_adt, "alabel") in usedOrDefinedADTs); return res; } From 6e95fee5e7d32b2e1ee79d093f7c32e2f92245a1 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Tue, 18 Aug 2026 15:55:56 +0200 Subject: [PATCH 11/13] Add cache to `importedModulesResolved` --- .../interpreter/env/ModuleEnvironment.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java b/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java index 1cbca8745ae..8aeceda7faa 100644 --- a/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java +++ b/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java @@ -90,6 +90,7 @@ public class ModuleEnvironment extends Environment { private Map resourceImporters; private Map> cachedGeneralKeywordParameters; private Map> cachedPublicFunctions; + private List cachedImportedModulesResolved; private static final TypeFactory TF = TypeFactory.getInstance(); @@ -109,6 +110,7 @@ public ModuleEnvironment(String name, GlobalEnvironment heap) { this.resourceImporters = new HashMap(); this.cachedGeneralKeywordParameters = null; this.cachedPublicFunctions = null; + this.cachedImportedModulesResolved = null; } @Override @@ -127,12 +129,14 @@ public void reset() { this.generalKeywordParameters = new HashMap<>(); this.cachedGeneralKeywordParameters = null; this.cachedPublicFunctions = null; + this.cachedImportedModulesResolved = null; } public void clearLookupCaches() { importedModules.replaceAll((k, v) -> Optional.empty()); cachedGeneralKeywordParameters = null; cachedPublicFunctions = null; + cachedImportedModulesResolved = null; } /** @@ -382,12 +386,14 @@ public void addImport(String name, ModuleEnvironment env) { typeStore.importStore(env.typeStore); this.cachedGeneralKeywordParameters = null; this.cachedPublicFunctions = null; + this.cachedImportedModulesResolved = null; } void removeModule(String name) { importedModules.computeIfPresent(name, (k, v) -> Optional.empty()); this.cachedGeneralKeywordParameters = null; this.cachedPublicFunctions = null; + this.cachedImportedModulesResolved = null; } public void addExtend(String name) { @@ -397,6 +403,7 @@ public void addExtend(String name) { extended.add(name); this.cachedGeneralKeywordParameters = null; this.cachedPublicFunctions = null; + this.cachedImportedModulesResolved = null; } public List getTests() { @@ -453,6 +460,7 @@ public void unImport(String moduleName) { } cachedGeneralKeywordParameters = null; cachedPublicFunctions = null; + cachedImportedModulesResolved = null; } public void unExtend(String moduleName) { @@ -885,6 +893,17 @@ public ModuleEnvironment getImport(String moduleName) { } private Iterable importedModulesResolved = + () -> getImportedModulesResolved().iterator(); + + private List getImportedModulesResolved() { + if (cachedImportedModulesResolved == null) { + cachedImportedModulesResolved = new ArrayList<>(); + importedModulesResolver.forEach(cachedImportedModulesResolved::add); + } + return cachedImportedModulesResolved; + } + + private Iterable importedModulesResolver = () -> new Iterator() { Iterator>> iterator = importedModules.entrySet().iterator(); @Override From b963bcb289dc64d79720d382cefb96e10adb1d31 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Tue, 18 Aug 2026 16:05:00 +0200 Subject: [PATCH 12/13] Simplify `getImportedModulesResolved` --- .../interpreter/env/ModuleEnvironment.java | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java b/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java index 8aeceda7faa..be5f4fb0bd0 100644 --- a/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java +++ b/src/org/rascalmpl/interpreter/env/ModuleEnvironment.java @@ -530,7 +530,7 @@ public void storeVariable(String name, Result value) { super.storeVariable(name, value); } else { - for (ModuleEnvironment module : importedModulesResolved) { + for (ModuleEnvironment module : getImportedModulesResolved()) { result = module.getLocalPublicVariable(name); if (result != null) { @@ -551,7 +551,7 @@ public org.rascalmpl.interpreter.result.Result getSimpleVariable(String return var; } - for (ModuleEnvironment mod : importedModulesResolved) { + for (ModuleEnvironment mod : getImportedModulesResolved()) { if (mod != null) { var = mod.getLocalPublicVariable(name); @@ -578,7 +578,7 @@ protected Map> getVariableDefiningEnvironment(String name) } } - for (ModuleEnvironment mod : importedModulesResolved) { + for (ModuleEnvironment mod : getImportedModulesResolved()) { Result r = null; if (mod != null && mod.variableEnvironment != null) r = mod.variableEnvironment.get(name); @@ -600,7 +600,7 @@ private List lookupFunctionsNoCache(String name) { var result = new ArrayList(); super.getAllFunctions(name, result); - for (ModuleEnvironment mod : importedModulesResolved) { + for (ModuleEnvironment mod : getImportedModulesResolved()) { if (mod != null) { mod.getLocalPublicFunctions(name, result); } @@ -791,7 +791,7 @@ public Set lookupGenericKeywordParameters(Type adt) { result.add(new GenericKeywordParameters(this, list, getStore().getKeywordParameters(adt))); } - for (ModuleEnvironment mod : importedModulesResolved) { + for (ModuleEnvironment mod : getImportedModulesResolved()) { list = mod.generalKeywordParameters.get(adt); if (list != null) { @@ -892,35 +892,20 @@ public ModuleEnvironment getImport(String moduleName) { return result.get(); } - private Iterable importedModulesResolved = - () -> getImportedModulesResolved().iterator(); - private List getImportedModulesResolved() { if (cachedImportedModulesResolved == null) { cachedImportedModulesResolved = new ArrayList<>(); - importedModulesResolver.forEach(cachedImportedModulesResolved::add); - } - return cachedImportedModulesResolved; - } - - private Iterable importedModulesResolver = - () -> new Iterator() { - Iterator>> iterator = importedModules.entrySet().iterator(); - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - @Override - public ModuleEnvironment next() { - var entry = iterator.next(); + for (var entry : importedModules.entrySet()) { var result = entry.getValue(); if (result.isEmpty()) { result = Optional.ofNullable(heap.getModule(entry.getKey())); entry.setValue(result); } - return result.orElse(null); + cachedImportedModulesResolved.add(result.orElse(null)); } - }; + } + return cachedImportedModulesResolved; + } @Override public void storeVariable(QualifiedName name, Result result) { @@ -959,7 +944,7 @@ public Type lookupConcreteSyntaxType(String name) { Type type = concreteSyntaxTypes.get(name); if (type == null) { - for (ModuleEnvironment mod : importedModulesResolved) { + for (ModuleEnvironment mod : getImportedModulesResolved()) { if (mod == null) { continue; @@ -1119,7 +1104,7 @@ protected Environment getVariableFlagsEnvironment(String name) { return env; } - for (ModuleEnvironment mod : importedModulesResolved) { + for (ModuleEnvironment mod : getImportedModulesResolved()) { if(mod == null) { throw new RuntimeException("getFlagsEnvironment"); } @@ -1141,7 +1126,7 @@ protected Environment getFunctionFlagsEnvironment(String name) { return env; } - for (ModuleEnvironment mod : importedModulesResolved) { + for (ModuleEnvironment mod : getImportedModulesResolved()) { if(mod == null) { throw new RuntimeException("getFlagsEnvironment"); } From 8dc85446c625eeae7b83c155999c3e12aa53e4f1 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Wed, 19 Aug 2026 22:06:29 +0200 Subject: [PATCH 13/13] Remove performance improvements scaffolding --- .github/workflows/build.yaml | 1 - .vscode/launch.json | 2 +- .../rascalmpl/interpreter/utils/Profiler.java | 68 ------------------- 3 files changed, 1 insertion(+), 70 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c090e3b690a..fdaa54a44ec 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,7 +9,6 @@ on: branches: - main - 'feat/*' - - performance-improvements-main env: MAVEN_OPTS: "-Xmx4G -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true" IS_OWN_PR: "${{ secrets.MAVEN_MIRROR_URL }}" diff --git a/.vscode/launch.json b/.vscode/launch.json index d6d60dc0909..fa9529bef36 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,7 +37,7 @@ "request": "launch", "mainClass": "org.rascalmpl.shell.RascalShell", "projectName": "rascal", - "vmArgs": "-Xss80m -Xmx2g -Dorg.rascalmpl.profiling.flameGraph.script=C:/Users/sung-/Desktop/FlameGraph-1.0/flamegraph.pl", + "vmArgs": "-Xss80m -Xmx2g -ea", "console": "integratedTerminal" }, { diff --git a/src/org/rascalmpl/interpreter/utils/Profiler.java b/src/org/rascalmpl/interpreter/utils/Profiler.java index a612907ff10..f7671310dfe 100644 --- a/src/org/rascalmpl/interpreter/utils/Profiler.java +++ b/src/org/rascalmpl/interpreter/utils/Profiler.java @@ -13,21 +13,15 @@ *******************************************************************************/ package org.rascalmpl.interpreter.utils; -import java.io.IOException; import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; -import java.util.stream.Collectors; import org.rascalmpl.ast.AbstractAST; -import org.rascalmpl.debug.IRascalFrame; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.Environment; import io.usethesource.vallang.IList; @@ -78,64 +72,6 @@ public boolean equals(Object obj) { } } -class FlameGraph { - private final Map counts = new HashMap<>(); - - void sample(Evaluator eval) { - var frames = eval.getCallStack().stream(); - var folded = frames.map(FlameGraph::getFrameTitle).collect(Collectors.joining(";")); - var count = counts.computeIfAbsent(folded, k -> new Count()); - count.increment(); - } - - private static String getFrameTitle(IRascalFrame frame) { - var title = frame.getName(); - var callerLocation = frame.getCallerLocation(); - if (callerLocation != null) { - title += " at " + callerLocation; - } - return title; - } - - void write() { - var name = "flameGraph"; - var out = Path.of(name + ".out"); - var err = Path.of(name + ".err"); - var svg = Path.of(name + ".svg"); - - try { - Files.writeString(out, ""); - for (var e : counts.entrySet()) { - // Newlines must be `\n` for `flamegraph.pl` to work - var csq = String.format("%s %d\n", e.getKey(), e.getValue().getTicks()); - Files.writeString(out, csq, StandardOpenOption.APPEND); - } - - var scriptKey = "org.rascalmpl.profiling.flameGraph.script"; - var scriptValue = System.getProperty(scriptKey); - if (scriptValue != null) { - var script = Path.of(scriptValue); - if (Files.exists(script)) { - - ProcessBuilder processBuilder = new ProcessBuilder("perl", script.toString(), out.toString()); - processBuilder.redirectOutput(svg.toFile()); - processBuilder.redirectError(err.toFile()); - - Process process = processBuilder.start(); - try { - process.waitFor(); - } catch (InterruptedException e) { - // Ignore; doesn't matter - } - } - } - - } catch (IOException e) { - e.printStackTrace(); - } - } -} - public class Profiler extends Thread { private Evaluator eval; private volatile boolean running; @@ -143,7 +79,6 @@ public class Profiler extends Thread { private final Map ast; private final Map frame; private final Map names; - private final FlameGraph flameGraph = new FlameGraph(); public Profiler(Evaluator ev){ super("Rascal-Sampling-Profiler"); @@ -160,8 +95,6 @@ public void run(){ AbstractAST current = eval.getCurrentAST(); Environment env = eval.getCurrentEnvt(); String name = env.getName(); - - flameGraph.sample(eval); if (current != null) { ISourceLocation stat = current.getLocation(); @@ -226,7 +159,6 @@ public IList getProfileData(){ } public void report() { - flameGraph.write(); report("FRAMES", frame); eval.getOutPrinter().println(); report("ASTS", ast);