From 24049a12c15aa6e0d104cd80eed4d4447b0f1da8 Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Tue, 28 Dec 2021 20:39:51 +0100 Subject: [PATCH] separated synthesis from fragment tempaltes. --- src/main/java/dk/camelot64/kickc/KickC.java | 10 +- .../kickc/fragment/AsmFragmentTemplate.java | 72 +- .../fragment/AsmFragmentTemplateCache.java | 4 +- .../fragment/AsmFragmentTemplateUsages.java | 73 +- .../synthesis/AsmFragmentSynthesis.java | 169 ++++ .../synthesis/AsmFragmentSynthesisOption.java | 64 ++ .../synthesis/AsmFragmentSynthesisResult.java | 98 ++ ...AsmFragmentTemplateSynthesisRuleRegex.java | 4 +- .../AsmFragmentTemplateSynthesizer.java | 890 +++++++----------- .../camelot64/kickc/test/TestFragments.java | 14 +- 10 files changed, 738 insertions(+), 660 deletions(-) create mode 100644 src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesis.java create mode 100644 src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesisOption.java create mode 100644 src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesisResult.java diff --git a/src/main/java/dk/camelot64/kickc/KickC.java b/src/main/java/dk/camelot64/kickc/KickC.java index 2c130b68f..9dddab518 100644 --- a/src/main/java/dk/camelot64/kickc/KickC.java +++ b/src/main/java/dk/camelot64/kickc/KickC.java @@ -1,10 +1,10 @@ package dk.camelot64.kickc; import dk.camelot64.kickc.asm.AsmProgram; -import dk.camelot64.kickc.fragment.AsmFragmentTemplate; +import dk.camelot64.kickc.fragment.AsmFragmentTemplateUsages; +import dk.camelot64.kickc.fragment.synthesis.AsmFragmentSynthesisResult; import dk.camelot64.kickc.fragment.synthesis.AsmFragmentTemplateMasterSynthesizer; import dk.camelot64.kickc.fragment.synthesis.AsmFragmentTemplateSynthesizer; -import dk.camelot64.kickc.fragment.AsmFragmentTemplateUsages; import dk.camelot64.kickc.model.*; import dk.camelot64.kickc.model.statements.StatementSource; import dk.camelot64.kickc.model.symbols.Procedure; @@ -275,8 +275,8 @@ public class KickC implements Callable { try { final AsmFragmentTemplateMasterSynthesizer masterSynthesizer = compiler.getAsmFragmentMasterSynthesizer(); final AsmFragmentTemplateSynthesizer cpuSynthesizer = masterSynthesizer.getSynthesizer(program.getTargetCpu()); - Collection fragmentTemplates = cpuSynthesizer.getBestTemplates(fragment, compiler.getLog()); - for(AsmFragmentTemplate fragmentTemplate : fragmentTemplates) { + Collection fragmentTemplates = cpuSynthesizer.getBestTemplates(fragment, compiler.getLog()); + for(AsmFragmentSynthesisResult fragmentTemplate : fragmentTemplates) { AsmFragmentTemplateUsages.logTemplate(compiler.getLog(), fragmentTemplate, ""); } if(fragmentTemplates.size() == 0) { @@ -359,7 +359,7 @@ public class KickC implements Callable { } StringBuilder CFileNames = new StringBuilder(); - cFiles.stream().forEach(path -> CFileNames.append(path.toString()).append(" ")); + cFiles.forEach(path -> CFileNames.append(path.toString()).append(" ")); Map effectiveDefines = new LinkedHashMap<>(); effectiveDefines.put("__KICKC__", "1"); diff --git a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplate.java b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplate.java index ca819b0cc..9600809cf 100644 --- a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplate.java +++ b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplate.java @@ -2,7 +2,6 @@ package dk.camelot64.kickc.fragment; import dk.camelot64.cpufamily6502.CpuClobber; import dk.camelot64.kickc.asm.AsmProgram; -import dk.camelot64.kickc.fragment.synthesis.AsmFragmentTemplateSynthesisRule; import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.Registers; import dk.camelot64.kickc.model.TargetCpu; @@ -19,6 +18,7 @@ import dk.camelot64.kickc.parser.AsmParser; import dk.camelot64.kickc.parser.KickCParser; import java.util.LinkedHashMap; +import java.util.Objects; /** * An ASM fragment template usable for generating KickAssembler code for different bindings. @@ -26,43 +26,24 @@ import java.util.LinkedHashMap; */ public class AsmFragmentTemplate { - /** true if the fragment was loaded from disk. */ - private boolean file; - /** true if the fragment was loaded from the disk cache. */ - private boolean cache; /** The fragment template signature name. */ private final String signature; - /** The fragment template body */ - private String body; - /** The synthesis that created the fragment. null if the fragment template was loaded. */ - private AsmFragmentTemplateSynthesisRule synthesis; - /** The sub fragment template that the synthesis modified to create this. null if the fragment template was loaded. */ - private AsmFragmentTemplate subFragment; /** The target CPU. */ private final TargetCpu targetCpu; - /** The parsed ASM lines. Initially null. Will be non-null, is the template is ever used to generate ASM code. */ + /** The fragment template body */ + private String body; + + /** The parsed ASM lines. Initially null. Non-null after the template is used to generate ASM code. */ private KickCParser.AsmLinesContext bodyAsm; - /** The ASM clobber of the fragment. */ + /** The ASM clobber of the fragment. Initially null. Non-null after the template is used to generate ASM code.*/ private AsmFragmentClobber clobber; - /** The cycles consumed by the ASM of the fragment. */ + /** The cycles consumed by the ASM of the fragment. Initially null. Non-null after the template is used to generate ASM code.*/ private Double cycles; - public AsmFragmentTemplate(String signature, String body, TargetCpu targetCpu, boolean cache) { + public AsmFragmentTemplate(String signature, String body, TargetCpu targetCpu) { this.signature = signature; this.body = body; this.targetCpu = targetCpu; - this.file = true; - this.cache = cache; - } - - public AsmFragmentTemplate(String signature, String body, AsmFragmentTemplateSynthesisRule synthesis, AsmFragmentTemplate subFragment) { - this.signature = signature; - this.body = body; - this.synthesis = synthesis; - this.subFragment = subFragment; - this.targetCpu = subFragment.targetCpu; - this.file = false; - this.cache = false; } /** @@ -173,7 +154,7 @@ public class AsmFragmentTemplate { this.cycles = asm.getCycles(); } - String getSignature() { + public String getSignature() { return signature; } @@ -206,43 +187,16 @@ public class AsmFragmentTemplate { return cycles; } - public boolean isFile() { - return file; - } - - public boolean isCache() { - return cache; - } - - AsmFragmentTemplateSynthesisRule getSynthesis() { - return synthesis; - } - - AsmFragmentTemplate getSubFragment() { - return subFragment; - } - - public String getName() { - StringBuilder name = new StringBuilder(); - name.append(signature); - if(synthesis != null) { - name.append(" < "); - name.append(subFragment.getName()); - } - return name.toString(); - } - @Override public boolean equals(Object o) { - if(this == o) return true; - if(o == null || getClass() != o.getClass()) return false; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; AsmFragmentTemplate that = (AsmFragmentTemplate) o; - return getName().equals(that.getName()); + return Objects.equals(signature, that.signature) && targetCpu == that.targetCpu && Objects.equals(body, that.body); } @Override public int hashCode() { - return getName().hashCode(); + return Objects.hash(signature, targetCpu, body); } - } diff --git a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateCache.java b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateCache.java index e95cad9af..ef52c1f68 100644 --- a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateCache.java +++ b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateCache.java @@ -48,7 +48,7 @@ public class AsmFragmentTemplateCache { } /** Special singleton representing that the fragment can not be synthesized or loaded. */ - public static AsmFragmentTemplate NO_SYNTHESIS = new AsmFragmentTemplate("NO_SYNTHESIS", "NO_SYNTHESIS", null, false); + public static AsmFragmentTemplate NO_SYNTHESIS = new AsmFragmentTemplate("NO_SYNTHESIS", "NO_SYNTHESIS", null); /** Prefix for the fragment hash file header. */ public static final String HASH_HEADER = "//KICKC FRAGMENT CACHE "; @@ -189,7 +189,7 @@ public class AsmFragmentTemplateCache { cache.put(signature, NO_SYNTHESIS); } else { CharStream fragmentCharStream = CharStreams.fromString(bodyString); - AsmFragmentTemplate template = new AsmFragmentTemplate(signature, AsmFragmentTemplateSynthesizer.fixNewlines(fragmentCharStream.toString()), targetCpu, true); + AsmFragmentTemplate template = new AsmFragmentTemplate(signature, AsmFragmentTemplateSynthesizer.fixNewlines(fragmentCharStream.toString()), targetCpu); cache.put(signature, template); } } diff --git a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateUsages.java b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateUsages.java index bb516dd3b..f23d3948d 100644 --- a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateUsages.java +++ b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateUsages.java @@ -1,9 +1,7 @@ package dk.camelot64.kickc.fragment; import dk.camelot64.kickc.CompileLog; -import dk.camelot64.kickc.fragment.synthesis.AsmFragmentTemplateSynthesisRule; -import dk.camelot64.kickc.fragment.synthesis.AsmFragmentTemplateSynthesisRuleManager; -import dk.camelot64.kickc.fragment.synthesis.AsmFragmentTemplateSynthesizer; +import dk.camelot64.kickc.fragment.synthesis.*; import java.io.File; import java.util.*; @@ -12,22 +10,23 @@ import java.util.*; public class AsmFragmentTemplateUsages { /** Usage Statistics for fragment templates. */ - private static Map fragmentTemplateUsage = new HashMap<>(); + private static Map fragmentTemplateUsage = new HashMap<>(); /** * Count one usage of ASM fragment templates - directly or through synthesis * * @param fragmentTemplate The template to increment usage of */ - public static void incUsage(AsmFragmentTemplate fragmentTemplate) { + public static void incUsage(AsmFragmentSynthesisResult fragmentTemplate) { Integer usage = fragmentTemplateUsage.get(fragmentTemplate); if(usage == null) { usage = 0; } fragmentTemplateUsage.put(fragmentTemplate, usage + 1); - AsmFragmentTemplate subFragment = fragmentTemplate.getSubFragment(); - if(subFragment != null) { - incUsage(subFragment); + for (AsmFragmentSynthesisResult subFragment : fragmentTemplate.getSubFragments()) { + if (subFragment != null) { + incUsage(subFragment); + } } } @@ -38,7 +37,7 @@ public class AsmFragmentTemplateUsages { */ public static void logUsages(AsmFragmentTemplateSynthesizer synthesizer, CompileLog log, boolean logRedundantFiles, boolean logUnusedFiles, boolean logUnusedRules, boolean logFileDetails, boolean logAllDetails, boolean logDetailsBody) { - Map synthesisGraph = + Map synthesisGraph = synthesizer.getSynthesisGraph(); ArrayList signatures = new ArrayList<>(synthesisGraph.keySet()); Collections.sort(signatures); @@ -51,29 +50,21 @@ public class AsmFragmentTemplateUsages { String fileName = file.getName(); String signature = fileName.substring(0, fileName.length() - 4); // Synthesize the fragment - and check if the synthesis is as good as the file body - Collection templates = synthesizer.getBestTemplates(signature, log); + Collection templates = synthesizer.getBestTemplates(signature, log); boolean isFile = false; - for(AsmFragmentTemplate template : templates) { + for(AsmFragmentSynthesisResult template : templates) { isFile |= template.isFile(); } if(!isFile) { StringBuilder templateNames = new StringBuilder(); boolean first = true; - for(AsmFragmentTemplate template : templates) { + for(AsmFragmentSynthesisResult template : templates) { templateNames.append(template.getName()); if(first) { first = false; } else { templateNames.append(" / "); } - // Check if the synthesis uses a file marked as redundant - AsmFragmentTemplate sourceFileTemplate = template; - while(!sourceFileTemplate.isFile()) { - sourceFileTemplate = sourceFileTemplate.getSubFragment(); - } - if(redundantSignatures.contains(sourceFileTemplate.getSignature())) { - throw new RuntimeException("Problem in redundancy analysis! " + sourceFileTemplate.getSignature() + ".asm seems redundant but is needed for synthesis of " + signature); - } } log.append("rm " + signature + ".asm #synthesized better ASM by " + templateNames); redundantSignatures.add(signature); @@ -86,13 +77,13 @@ public class AsmFragmentTemplateUsages { for(File file : files) { String fileName = file.getName(); String signature = fileName.substring(0, fileName.length() - 4); - AsmFragmentTemplateSynthesizer.AsmFragmentSynthesis asmFragmentSynthesis = synthesisGraph.get(signature); - Collection templates = asmFragmentSynthesis.getBestTemplates(); + AsmFragmentSynthesis asmFragmentSynthesis = synthesisGraph.get(signature); + Collection templates = asmFragmentSynthesis.getBestTemplates(); if(templates != null && templates.size() > 0) { // The template has been loaded / synthesized - is the usage count zero? boolean allZero = true; Integer fileUsage = null; - for(AsmFragmentTemplate template : templates) { + for(AsmFragmentSynthesisResult template : templates) { Integer usage = fragmentTemplateUsage.get(template); if(usage == null) usage = 0; if(usage > 0) { @@ -118,16 +109,16 @@ public class AsmFragmentTemplateUsages { if(logUnusedRules) { log.append("\nUNUSED ASM FRAGMENT SYNTHESIS RULE ANALYSIS (if found consider removing them)"); final Collection allRules = AsmFragmentTemplateSynthesisRuleManager.getAllSynthesisRules(); + for(String signature : signatures) { - Collection templates = - synthesizer.getBestTemplates(signature, log); - for(AsmFragmentTemplate template : templates) { - while(template.getSynthesis()!=null) { - allRules.remove(template.getSynthesis()); - template = template.getSubFragment(); - } + LinkedList templates = new LinkedList<>(synthesizer.getBestTemplates(signature, log)); + while(!templates.isEmpty()) { + final AsmFragmentSynthesisResult template = templates.pop(); + allRules.remove(template.getSynthesis()); + templates.addAll(template.getSubFragments()); } } + for(AsmFragmentTemplateSynthesisRule rule : allRules) { log.append("Synthesis Rule Unused: - "+rule.toString()); } @@ -136,10 +127,10 @@ public class AsmFragmentTemplateUsages { if(logFileDetails) { log.append("\nDETAILED ASM FILE USAGES"); // Find all file templates - List fileTemplates = new ArrayList<>(); + List fileTemplates = new ArrayList<>(); for(String signature : signatures) { - Collection templates = synthesisGraph.get(signature).getBestTemplates(); - for(AsmFragmentTemplate template : templates) { + Collection templates = synthesisGraph.get(signature).getBestTemplates(); + for(AsmFragmentSynthesisResult template : templates) { if(template.isFile()) { fileTemplates.add(template); } @@ -151,9 +142,9 @@ public class AsmFragmentTemplateUsages { if(logAllDetails) { log.append("\nDETAILED ASM FRAGMENT USAGES"); - List allTemplates = new ArrayList<>(); + List allTemplates = new ArrayList<>(); for(String signature : signatures) { - Collection templates = synthesisGraph.get(signature).getBestTemplates(); + Collection templates = synthesisGraph.get(signature).getBestTemplates(); allTemplates.addAll(templates); } logTemplatesByUsage(synthesizer, log, allTemplates, logDetailsBody); @@ -162,7 +153,7 @@ public class AsmFragmentTemplateUsages { } - private static void logTemplatesByUsage(AsmFragmentTemplateSynthesizer synthesizer, CompileLog log, List templates, boolean logBody) { + private static void logTemplatesByUsage(AsmFragmentTemplateSynthesizer synthesizer, CompileLog log, List templates, boolean logBody) { // Sort by usage Collections.sort(templates, (o1, o2) -> { Integer u1 = fragmentTemplateUsage.get(o1); @@ -173,14 +164,14 @@ public class AsmFragmentTemplateUsages { } ); // Output - for(AsmFragmentTemplate template : templates) { + for(AsmFragmentSynthesisResult template : templates) { Integer usage = fragmentTemplateUsage.get(template); if(usage == null) usage = 0; - AsmFragmentTemplateSynthesizer.AsmFragmentSynthesis synthesis = synthesizer.getOrCreateSynthesis(template.getSignature(), log); - Collection bestTemplates = synthesis.getBestTemplates(); + AsmFragmentSynthesis synthesis = synthesizer.getOrCreateSynthesis(template.getSignature(), log); + Collection bestTemplates = synthesis.getBestTemplates(); log.append(String.format("%8d", usage) + " " + template.getSignature()+" - templates: " + bestTemplates.size()); if(logBody) { - for(AsmFragmentTemplate bestTemplate : bestTemplates) { + for(AsmFragmentSynthesisResult bestTemplate : bestTemplates) { logTemplate(log, bestTemplate, " "); } @@ -188,7 +179,7 @@ public class AsmFragmentTemplateUsages { } } - public static void logTemplate(CompileLog log, AsmFragmentTemplate template, String indent) { + public static void logTemplate(CompileLog log, AsmFragmentSynthesisResult template, String indent) { String prefix; if(template.isCache()) { prefix = "cached "; diff --git a/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesis.java b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesis.java new file mode 100644 index 000000000..5e2cb22a0 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesis.java @@ -0,0 +1,169 @@ +package dk.camelot64.kickc.fragment.synthesis; + +import dk.camelot64.kickc.fragment.AsmFragmentClobber; + +import java.util.*; + +/** + * The synthesis is capable of loading the fragment from disk or synthesizing it from other fragments using synthesis rules. + * The synthesis caches the best fragments for each clobber profile (loaded or synthesized). + * A node in the synthesis graph. + */ +public class AsmFragmentSynthesis { + + /** + * The signature of the fragment template being synthesized. + */ + private final String signature; + + /** + * The best template loaded/synthesized so far for each clobber profile + */ + private final Map bestTemplates; + + /** + * Options for synthesizing the template from sub-fragments using a specific synthesis rule. Forward edges in the synthesis graph. + */ + private final Set synthesisOptions; + + /** + * Options for synthesizing the other templates from this template using a specific synthesis rule. Backward edges in the synthesis graph. + */ + private final Set parentOptions; + + /** + * The templates loaded from a file. Empty if no file exists for the signature. + */ + private final List fileTemplates; + + /** + * Create a new synthesis + * + * @param signature The signature of the fragment template to load/synthesize + */ + AsmFragmentSynthesis(String signature) { + this.signature = signature; + this.bestTemplates = new LinkedHashMap<>(); + this.synthesisOptions = new LinkedHashSet<>(); + this.parentOptions = new LinkedHashSet<>(); + this.fileTemplates = new ArrayList<>(); + } + + /** + * Add a synthesis option to the template synthesis. + * The synthesis options can be used for synthesizing the template from sub-fragments using a specific synthesis rule. + * + * @param synthesisOption The option to add + */ + void addSynthesisOption(AsmFragmentSynthesisOption synthesisOption) { + this.synthesisOptions.add(synthesisOption); + } + + /** + * Get the options for synthesizing the template from sub-fragments using a specific synthesis rule. Forward edges in the synthesis graph. + * + * @return The options + */ + Collection getSynthesisOptions() { + return synthesisOptions; + } + + /** + * Add a parent. The parent is an option for synthesizing another fragment template from this one using a specific synthesis rule. + * + * @param synthesisOption Thew parent option to add + */ + void addParentOption(AsmFragmentSynthesisOption synthesisOption) { + this.parentOptions.add(synthesisOption); + } + + void addFileTemplate(AsmFragmentSynthesisResult fileTemplate) { + this.fileTemplates.add(fileTemplate); + } + + List getFileTemplates() { + return fileTemplates; + } + + /** + * Handle a candidate for best template. + * If the candidate is better than the current best for its clobber profile update the best template + * + * @param candidate The template candidate to examine + * @return true if the best template was updated + */ + boolean bestTemplateCandidate(AsmFragmentSynthesisResult candidate) { + AsmFragmentClobber candidateClobber = candidate.getClobber(); + double candidateCycles = candidate.getCycles(); + + // Check if any current best templates are better + Set bestClobbers = new LinkedHashSet<>(bestTemplates.keySet()); + for (AsmFragmentClobber bestClobber : bestClobbers) { + AsmFragmentSynthesisResult bestTemplate = bestTemplates.get(bestClobber); + double bestCycles = bestTemplate.getCycles(); + if (bestClobber.isTrueSubset(candidateClobber) && bestCycles <= candidateCycles) { + // A better template already found - don't update + return false; + } + if (bestClobber.isSubset(candidateClobber) && bestCycles < candidateCycles) { + // A better template already found - don't update + return false; + } + if (bestClobber.equals(candidateClobber) && bestCycles == candidateCycles && bestTemplate.getBody().compareTo(candidate.getBody()) <= 0) { + // A better template already found - don't update + return false; + } + + } + + // The candidate is better than some of the current best! + + // Remove any current templates that are worse + for (AsmFragmentClobber bestClobber : bestClobbers) { + AsmFragmentSynthesisResult bestTemplate = bestTemplates.get(bestClobber); + double bestCycles = bestTemplate.getCycles(); + + if (candidateClobber.isTrueSubset(bestClobber) && candidateCycles <= bestCycles) { + // The candidate is better - remove the current template + bestTemplates.remove(bestClobber); + } + if (candidateClobber.isSubset(bestClobber) && candidateCycles < bestCycles) { + // The candidate is better - remove the current template + bestTemplates.remove(bestClobber); + } + if (candidateClobber.equals(bestClobber) && candidateCycles == bestCycles && candidate.getBody().compareTo(bestTemplate.getBody()) <= 0) { + // The candidate is better - remove the current template + bestTemplates.remove(bestClobber); + } + + } + // Update the current best + bestTemplates.put(candidateClobber, candidate); + return true; + } + + /** + * Get the parent options. + * These are options for synthesizing other templates + * from this template using a specific synthesis rule. Backward edges in the synthesis graph. + * + * @return The parent options. + */ + Set getParentOptions() { + return parentOptions; + } + + /** + * Get the best fragment templates of the synthesis. + * Multiple templates are returned if templates with different clobber profiles exist. + * + * @return The best templates of the synthesis. + */ + public Collection getBestTemplates() { + return bestTemplates.values(); + } + + public String getSignature() { + return signature; + } +} diff --git a/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesisOption.java b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesisOption.java new file mode 100644 index 000000000..b1c849eb6 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesisOption.java @@ -0,0 +1,64 @@ +package dk.camelot64.kickc.fragment.synthesis; + +import java.util.List; +import java.util.Objects; + +/** + * An option for synthesizing a fragment template from a sub-template using a specific synthesis rule. An edge in the synthesis graph. + */ +public class AsmFragmentSynthesisOption { + + /** + * The signature of the fragment template being synthesized. The from-node in the graph. + */ + private final String signature; + + /** + * The signatures of the sub-fragment templates to synthesize from. The to-nodes in the graph. + */ + private final List subSignatures; + + /** + * The synthesis rule capable of synthesizing this template from the sub-fragments. + */ + private final AsmFragmentTemplateSynthesisRule rule; + + /** + * Create a synthesis option + * + * @param signature the signature of the fragment template being synthesized. + * @param rule The synthesis rule capable of synthesizing this template from the sub-fragment. + */ + AsmFragmentSynthesisOption(String signature, AsmFragmentTemplateSynthesisRule rule) { + this.signature = signature; + this.rule = rule; + this.subSignatures = rule.getSubSignatures(signature); + } + + public String getSignature() { + return signature; + } + + List getSubSignatures() { + return subSignatures; + } + + AsmFragmentTemplateSynthesisRule getRule() { + return rule; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AsmFragmentSynthesisOption that = (AsmFragmentSynthesisOption) o; + return Objects.equals(signature, that.signature) && + Objects.equals(subSignatures, that.subSignatures) && + Objects.equals(rule, that.rule); + } + + @Override + public int hashCode() { + return Objects.hash(signature, subSignatures, rule); + } +} diff --git a/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesisResult.java b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesisResult.java new file mode 100644 index 000000000..a529724a7 --- /dev/null +++ b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentSynthesisResult.java @@ -0,0 +1,98 @@ +package dk.camelot64.kickc.fragment.synthesis; + +import dk.camelot64.kickc.fragment.AsmFragmentClobber; +import dk.camelot64.kickc.fragment.AsmFragmentTemplate; + +import java.util.List; + +/** + * A successfully synthesized or loaded fragment. + */ +public class AsmFragmentSynthesisResult { + + /** + * The ASM fragment template. + */ + final AsmFragmentTemplate fragment; + + /** + * true if the fragment was loaded from disk. + */ + private boolean file; + /** + * true if the fragment was loaded from the disk cache. + */ + private boolean cache; + + /** + * The synthesis that created the fragment. null if the fragment template was loaded. + */ + private AsmFragmentTemplateSynthesisRule synthesis; + /** + * The sub fragment template that the synthesis used to create this. + */ + private List subFragments; + + public AsmFragmentSynthesisResult(AsmFragmentTemplate fragment, boolean file, boolean cache, AsmFragmentTemplateSynthesisRule synthesis, List subFragments) { + this.fragment = fragment; + this.file = file; + this.cache = cache; + this.synthesis = synthesis; + this.subFragments = subFragments; + } + + public boolean isFile() { + return file; + } + + public boolean isCache() { + return cache; + } + + public AsmFragmentTemplateSynthesisRule getSynthesis() { + return synthesis; + } + + public AsmFragmentTemplate getFragment() { + return fragment; + } + + public List getSubFragments() { + return subFragments; + } + + public String getName() { + StringBuilder name = new StringBuilder(); + name.append(fragment.getSignature()); + if (synthesis != null) { + name.append(" < "); + if (subFragments.size() == 1) { + name.append(subFragments.get(0).getName()); + } else { + for (AsmFragmentSynthesisResult subFragment : subFragments) { + name.append("("); + name.append(subFragment.getName()); + name.append(")"); + } + } + } + return name.toString(); + } + + + public AsmFragmentClobber getClobber() { + return fragment.getClobber(); + } + + public double getCycles() { + return fragment.getCycles(); + } + + public String getBody() { + return fragment.getBody(); + } + + public String getSignature() { + return fragment.getSignature(); + } +} diff --git a/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentTemplateSynthesisRuleRegex.java b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentTemplateSynthesisRuleRegex.java index c9dcc3122..958e51432 100644 --- a/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentTemplateSynthesisRuleRegex.java +++ b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentTemplateSynthesisRuleRegex.java @@ -97,7 +97,7 @@ class AsmFragmentTemplateSynthesisRuleRegex implements AsmFragmentTemplateSynthe subSignature = subSignature.replace(bound, bindMappings.get(bound)); } } - return Arrays.asList(subSignature); + return Collections.singletonList(subSignature); } else { return null; } @@ -148,7 +148,7 @@ class AsmFragmentTemplateSynthesisRuleRegex implements AsmFragmentTemplateSynthe if(newFragment.length() > 0 && newFragment.charAt(newFragment.length() - 1) == '\n') { newFragment = new StringBuilder(newFragment.substring(0, newFragment.length() - 1)); } - return new AsmFragmentTemplate(signature, newFragment.toString(), this, subTemplate); + return new AsmFragmentTemplate(signature, newFragment.toString(), subTemplate.getTargetCpu()); } static String regexpRewriteSignature(String signature, String match, String replace) { diff --git a/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentTemplateSynthesizer.java b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentTemplateSynthesizer.java index 9b592f314..08596367b 100644 --- a/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentTemplateSynthesizer.java +++ b/src/main/java/dk/camelot64/kickc/fragment/synthesis/AsmFragmentTemplateSynthesizer.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; /** * Provides fragment templates from their signature. @@ -27,593 +28,394 @@ import java.util.*; public class AsmFragmentTemplateSynthesizer { /** Create synthesizer. */ - public AsmFragmentTemplateSynthesizer(TargetCpu targetCpu, Path baseFragmentFolder, boolean useFragmentCache, CompileLog log) { - this.baseFragmentFolder = baseFragmentFolder; - this.targetCpu = targetCpu; - this.synthesisGraph = new LinkedHashMap<>(); - this.bestTemplateUpdate = new ArrayDeque<>(); - if(useFragmentCache) - this.fragmentCache = AsmFragmentTemplateCache.load(targetCpu, baseFragmentFolder, log); - else - this.fragmentCache = AsmFragmentTemplateCache.memoryCache(targetCpu); - } + public AsmFragmentTemplateSynthesizer(TargetCpu targetCpu, Path baseFragmentFolder, boolean useFragmentCache, CompileLog log) { + this.baseFragmentFolder = baseFragmentFolder; + this.targetCpu = targetCpu; + this.synthesisGraph = new LinkedHashMap<>(); + this.bestTemplateUpdate = new ArrayDeque<>(); + if (useFragmentCache) + this.fragmentCache = AsmFragmentTemplateCache.load(targetCpu, baseFragmentFolder, log); + else + this.fragmentCache = AsmFragmentTemplateCache.memoryCache(targetCpu); + } /** The folder containing fragment files. */ - private final Path baseFragmentFolder; + private final Path baseFragmentFolder; /** The Target CPU - used for obtaining CPU-specific fragment files. */ - private final TargetCpu targetCpu; + private final TargetCpu targetCpu; /** Cache for the best fragment templates. Maps signature to the best fragment template for the signature. */ - private final AsmFragmentTemplateCache fragmentCache; + private final AsmFragmentTemplateCache fragmentCache; - /** - * Contains the synthesis for each fragment template signature. - * The synthesis is capable of loading the fragment from disk or synthesizing it from other fragments using synthesis rules. - * The synthesis caches the best fragments for each clobber profile (loaded or synthesized). - * This map is effectively a full-graph where the nodes are synthesis for signatures and edges are the - * synthesis rules capable of synthesizing one fragment temple from another. - */ - private final Map synthesisGraph; + /** + * Contains the synthesis for each fragment template signature. + * The synthesis is capable of loading the fragment from disk or synthesizing it from other fragments using synthesis rules. + * The synthesis caches the best fragments for each clobber profile (loaded or synthesized). + * This map is effectively a full-graph where the nodes are synthesis for signatures and edges are the + * synthesis rules capable of synthesizing one fragment temple from another. + */ + private final Map synthesisGraph; /** Finalize the fragment template synthesizer. */ - void finalize(CompileLog log) { - if(fragmentCache != null) - fragmentCache.save(baseFragmentFolder, log); - } + void finalize(CompileLog log) { + if (fragmentCache != null) + fragmentCache.save(baseFragmentFolder, log); + } - /** - * Get information about the size of the synthesizer data structures - * - * @return the size - */ - public int getSize() { - return synthesisGraph.size(); - } + /** + * Get information about the size of the synthesizer data structures + * + * @return the size + */ + public int getSize() { + return synthesisGraph.size(); + } - public AsmFragmentInstance getFragmentInstance(AsmFragmentInstanceSpec instanceSpec, CompileLog log) { - String signature = instanceSpec.getSignature(); - AsmFragmentTemplate fragmentTemplate = getFragmentTemplate(signature, log); - // Return the resulting fragment instance - return new AsmFragmentInstance( - instanceSpec.getProgram(), - signature, - instanceSpec.getCodeScope(), - fragmentTemplate, - instanceSpec.getBindings()); - } + public AsmFragmentInstance getFragmentInstance(AsmFragmentInstanceSpec instanceSpec, CompileLog log) { + String signature = instanceSpec.getSignature(); + AsmFragmentTemplate fragmentTemplate = getFragmentTemplate(signature, log); + // Return the resulting fragment instance + return new AsmFragmentInstance( + instanceSpec.getProgram(), + signature, + instanceSpec.getCodeScope(), + fragmentTemplate, + instanceSpec.getBindings()); + } - /** - * Get the best fragment templates for a signature - * - * @param signature The signature - * @param log The log - * @return The best templates (with different clobber profiles) for the signature - */ - private AsmFragmentTemplate getFragmentTemplate(String signature, CompileLog log) { - // Attempt to find in memory/disk cache - AsmFragmentTemplate bestTemplate = fragmentCache.get(signature); - if(bestTemplate == AsmFragmentTemplateCache.NO_SYNTHESIS) { - if(log.isVerboseFragmentLog()) { - log.append("Unknown fragment " + signature); - } - throw new UnknownFragmentException(signature); - } - if(bestTemplate == null) { - // Attempt to synthesize or load in main fragment folder - Collection candidates = getBestTemplates(signature, log); - if(candidates.size() == 0) { - if(log.isVerboseFragmentLog()) { - log.append("Unknown fragment " + signature); + /** + * Get the best fragment templates for a signature + * + * @param signature The signature + * @param log The log + * @return The best templates (with different clobber profiles) for the signature + */ + private AsmFragmentTemplate getFragmentTemplate(String signature, CompileLog log) { + // Attempt to find in memory/disk cache + AsmFragmentTemplate bestTemplate = fragmentCache.get(signature); + if (bestTemplate == AsmFragmentTemplateCache.NO_SYNTHESIS) { + if (log.isVerboseFragmentLog()) { + log.append("Unknown fragment " + signature); + } + throw new UnknownFragmentException(signature); + } + if (bestTemplate != null) { + AsmFragmentTemplateUsages.incUsage(new AsmFragmentSynthesisResult(bestTemplate, false, true, null, new ArrayList<>())); + return bestTemplate; + } + + AsmFragmentSynthesisResult bestResult = null; + // Attempt to synthesize or load in main fragment folder + Collection candidates = getBestTemplates(signature, log); + if (candidates.size() == 0) { + if (log.isVerboseFragmentLog()) { + log.append("Unknown fragment " + signature); } fragmentCache.put(signature, AsmFragmentTemplateCache.NO_SYNTHESIS); throw new UnknownFragmentException(signature); - } - double minScore = Double.MAX_VALUE; - for(AsmFragmentTemplate candidateTemplate : candidates) { + } + double minScore = Double.MAX_VALUE; + for (AsmFragmentSynthesisResult candidateTemplate : candidates) { double score = candidateTemplate.getCycles(); - if(candidateTemplate.getClobber().isClobberZ()) score += 0.25; - if(candidateTemplate.getClobber().isClobberA()) score += 0.5; - if(candidateTemplate.getClobber().isClobberY()) score += 1.0; - if(candidateTemplate.getClobber().isClobberX()) score += 1.5; - if(score < minScore) { - minScore = score; - bestTemplate = candidateTemplate; + if (candidateTemplate.getClobber().isClobberZ()) score += 0.25; + if (candidateTemplate.getClobber().isClobberA()) score += 0.5; + if (candidateTemplate.getClobber().isClobberY()) score += 1.0; + if (candidateTemplate.getClobber().isClobberX()) score += 1.5; + if (score < minScore) { + minScore = score; + bestResult = candidateTemplate; } - } - if(log.isVerboseFragmentLog()) { - log.append("Found best fragment " + bestTemplate.getName() + " score: " + minScore); - } - fragmentCache.put(signature, bestTemplate); - } - // Count usages - AsmFragmentTemplateUsages.incUsage(bestTemplate); - return bestTemplate; - } + } + if (log.isVerboseFragmentLog()) { + log.append("Found best fragment " + bestResult.getName() + " score: " + minScore); + } + fragmentCache.put(signature, bestResult.getFragment()); + AsmFragmentTemplateUsages.incUsage(bestResult); + return bestResult.getFragment(); + } - /** - * Get the best fragment templates for a signature. - * The templates are either loaded or synthesized from other templates. - * - * @param signature The signature of the fragment template to get - * @param log The compile log - * @return The best templates for the passed signature - */ - public Collection getBestTemplates(String signature, CompileLog log) { - getOrCreateSynthesis(signature, log); - updateBestTemplates(log); - AsmFragmentSynthesis synthesis = getSynthesis(signature); - if(synthesis == null) { - throw new RuntimeException("Synthesis Graph Error! synthesis not found in graph " + signature); - } - return synthesis.getBestTemplates(); - } + /** + * Get the best fragment templates for a signature. + * The templates are either loaded or synthesized from other templates. + * + * @param signature The signature of the fragment template to get + * @param log The compile log + * @return The best templates for the passed signature + */ + public Collection getBestTemplates(String signature, CompileLog log) { + getOrCreateSynthesis(signature, log); + updateBestTemplates(log); + AsmFragmentSynthesis synthesis = getSynthesis(signature); + if (synthesis == null) { + throw new RuntimeException("Synthesis Graph Error! synthesis not found in graph " + signature); + } + return synthesis.getBestTemplates(); + } - /** - * The synthesis is capable of loading the fragment from disk or synthesizing it from other fragments using synthesis rules. - * The synthesis caches the best fragments for each clobber profile (loaded or synthesized). - * A node in the synthesis graph. - */ - public static class AsmFragmentSynthesis { + /** + * Get the entire synthesis graph. Called by the usage statistics. + * + * @return The entire synthesis graph + */ + public Map getSynthesisGraph() { + return synthesisGraph; + } - /** The signature of the fragment template being synthesized. */ - private final String signature; + /** + * Get the synthesis used to synthesize a fragment template. + * If the synthesis does not exist null is returned. + * + * @param signature The signature of the template to synthesize + * @return The synthesis used to synthesize the fragment template. + * null if the synthesis graph does not contain the synthesis. + */ + private AsmFragmentSynthesis getSynthesis(String signature) { + return synthesisGraph.get(signature); + } - /** The best template loaded/synthesized so far for each clobber profile */ - private final Map bestTemplates; - - /** Options for synthesizing the template from sub-fragments using a specific synthesis rule. Forward edges in the synthesis graph. */ - private final Set synthesisOptions; - - /** Options for synthesizing the other templates from this template using a specific synthesis rule. Backward edges in the synthesis graph. */ - private final Set parentOptions; - - /** The templates loaded from a file. Empty if no file exists for the signature. */ - private final List fileTemplates; - - /** - * Create a new synthesis - * - * @param signature The signature of the fragment template to load/synthesize - */ - AsmFragmentSynthesis(String signature) { - this.signature = signature; - this.bestTemplates = new LinkedHashMap<>(); - this.synthesisOptions = new LinkedHashSet<>(); - this.parentOptions = new LinkedHashSet<>(); - this.fileTemplates = new ArrayList<>(); - } - - /** - * Add a synthesis option to the template synthesis. - * The synthesis options can be used for synthesizing the template from sub-fragments using a specific synthesis rule. - * - * @param synthesisOption The option to add - */ - void addSynthesisOption(AsmFragmentSynthesisOption synthesisOption) { - this.synthesisOptions.add(synthesisOption); - } - - /** - * Get the options for synthesizing the template from sub-fragments using a specific synthesis rule. Forward edges in the synthesis graph. - * - * @return The options - */ - Collection getSynthesisOptions() { - return synthesisOptions; - } - - /** - * Add a parent. The parent is an option for synthesizing another fragment template from this one using a specific synthesis rule. - * - * @param synthesisOption Thew parent option to add - */ - void addParentOption(AsmFragmentSynthesisOption synthesisOption) { - this.parentOptions.add(synthesisOption); - } - - void addFileTemplate(AsmFragmentTemplate fileTemplate) { - this.fileTemplates.add(fileTemplate); - } - - List getFileTemplates() { - return fileTemplates; - } - - /** - * Handle a candidate for best template. - * If the candidate is better than the current best for its clobber profile update the best template - * - * @param candidate The template candidate to examine - * @return true if the best template was updated - */ - boolean bestTemplateCandidate(AsmFragmentTemplate candidate) { - AsmFragmentClobber candidateClobber = candidate.getClobber(); - double candidateCycles = candidate.getCycles(); - - // Check if any current best templates are better - Set bestClobbers = new LinkedHashSet<>(bestTemplates.keySet()); - for(AsmFragmentClobber bestClobber : bestClobbers) { - AsmFragmentTemplate bestTemplate = bestTemplates.get(bestClobber); - double bestCycles = bestTemplate.getCycles(); - if(bestClobber.isTrueSubset(candidateClobber) && bestCycles <= candidateCycles) { - // A better template already found - don't update - return false; + /** + * Get (or create) the synthesis used to synthesize a fragment template. + * If the synthesis does not already exist in the synthesis graph it is created - along with any sub-synthesis recursively usable for creating it. + * If any synthesis are created they are added to the {@link #bestTemplateUpdate} queue. + * + * @param signature The signature of the template to synthesize + * @param log The compile log + * @return The synthesis that is used to load/synthesize the best template + */ + public AsmFragmentSynthesis getOrCreateSynthesis(String signature, CompileLog log) { + AsmFragmentSynthesis synthesis = getSynthesis(signature); + if (synthesis != null) { + return synthesis; + } + // Synthesis not found - create it + if (log.isVerboseFragmentLog()) { + log.append("New fragment synthesis " + signature); + } + synthesis = new AsmFragmentSynthesis(signature); + synthesisGraph.put(signature, synthesis); + queueUpdateBestTemplate(synthesis); + // Load the template from file - if it exists + List fileTemplates = loadFragmentTemplates(signature); + for (AsmFragmentSynthesisResult fileTemplate : fileTemplates) { + synthesis.addFileTemplate(fileTemplate); + if (log.isVerboseFragmentLog()) { + log.append("New fragment synthesis " + signature + " - Successfully loaded " + signature + ".asm"); } - if(bestClobber.isSubset(candidateClobber) && bestCycles < candidateCycles) { - // A better template already found - don't update - return false; + } + // Populate with synthesis options + for (AsmFragmentTemplateSynthesisRule rule : AsmFragmentTemplateSynthesisRuleManager.getSynthesisRules(targetCpu)) { + if (rule.matches(signature)) { + AsmFragmentSynthesisOption synthesisOption = new AsmFragmentSynthesisOption(signature, rule); + synthesis.addSynthesisOption(synthesisOption); + if (log.isVerboseFragmentLog()) { + log.append("New fragment synthesis " + signature + " - sub-option " + String.join(",", synthesisOption.getSubSignatures())); + } } - if(bestClobber.equals(candidateClobber) && bestCycles == candidateCycles && bestTemplate.getBody().compareTo(candidate.getBody()) <= 0) { - // A better template already found - don't update - return false; + } + // Ensure that all sub-synthesis exist (recursively) - and set their parent options + for (AsmFragmentSynthesisOption synthesisOption : synthesis.getSynthesisOptions()) { + for (String subSignature : synthesisOption.getSubSignatures()) { + AsmFragmentSynthesis subSynthesis = getOrCreateSynthesis(subSignature, log); + subSynthesis.addParentOption(synthesisOption); } - - } - - // The candidate is better than some of the current best! - - // Remove any current templates that are worse - for(AsmFragmentClobber bestClobber : bestClobbers) { - AsmFragmentTemplate bestTemplate = bestTemplates.get(bestClobber); - double bestCycles = bestTemplate.getCycles(); - - if(candidateClobber.isTrueSubset(bestClobber) && candidateCycles <= bestCycles) { - // The candidate is better - remove the current template - bestTemplates.remove(bestClobber); - } - if(candidateClobber.isSubset(bestClobber) && candidateCycles < bestCycles) { - // The candidate is better - remove the current template - bestTemplates.remove(bestClobber); - } - if(candidateClobber.equals(bestClobber) && candidateCycles == bestCycles && candidate.getBody().compareTo(bestTemplate.getBody()) < 0) { - // The candidate is better - remove the current template - bestTemplates.remove(bestClobber); - } - - } - // Update the current best - bestTemplates.put(candidateClobber, candidate); - return true; - } - - /** - * Get the parent options. - * These are options for synthesizing other templates - * from this template using a specific synthesis rule. Backward edges in the synthesis graph. - * - * @return The parent options. - */ - Set getParentOptions() { - return parentOptions; - } - - /** - * Get the best fragment templates of the synthesis. - * Multiple templates are returned if templates with different clobber profiles exist. - * - * @return The best templates of the synthesis. - */ - public Collection getBestTemplates() { - return bestTemplates.values(); - } - - public String getSignature() { - return signature; - } - } - - /** An option for synthesizing a fragment template from a sub-template using a specific synthesis rule. An edge in the synthesis graph. */ - public static class AsmFragmentSynthesisOption { - - /** The signature of the fragment template being synthesized. The from-node in the graph. */ - private final String signature; - - /** The signatures of the sub-fragment templates to synthesize from. The to-nodes in the graph. */ - private final List subSignatures; - - /** The synthesis rule capable of synthesizing this template from the sub-fragments. */ - private final AsmFragmentTemplateSynthesisRule rule; - - /** - * Create a synthesis option - * - * @param signature he signature of the fragment template being synthesized. - * @param rule The synthesis rule capable of synthesizing this template from the sub-fragment. - */ - AsmFragmentSynthesisOption(String signature, AsmFragmentTemplateSynthesisRule rule) { - this.signature = signature; - this.rule = rule; - this.subSignatures = rule.getSubSignatures(signature); - } - - public String getSignature() { - return signature; - } - - List getSubSignatures() { - return subSignatures; - } - - AsmFragmentTemplateSynthesisRule getRule() { - return rule; - } - - @Override - public boolean equals(Object o) { - if(this == o) return true; - if(o == null || getClass() != o.getClass()) return false; - AsmFragmentSynthesisOption that = (AsmFragmentSynthesisOption) o; - return Objects.equals(signature, that.signature) && - Objects.equals(subSignatures, that.subSignatures) && - Objects.equals(rule, that.rule); - } - - @Override - public int hashCode() { - return Objects.hash(signature, subSignatures, rule); - } - } - - /** - * Get the entire synthesis graph. Called by the usage statistics. - * - * @return The entire synthesis graph - */ - public Map getSynthesisGraph() { - return synthesisGraph; - } - - /** - * Get the synthesis used to synthesize a fragment template. - * If the synthesis does not exist null is returned. - * - * @param signature The signature of the template to synthesize - * @return The synthesis used to synthesize the fragment template. - * null if the synthesis graph does not contain the synthesis. - */ - private AsmFragmentSynthesis getSynthesis(String signature) { - return synthesisGraph.get(signature); - } - - /** - * Get (or create) the synthesis used to synthesize a fragment template. - * If the synthesis does not already exist in the synthesis graph it is created - along with any sub-synthesis recursively usable for creating it. - * If any synthesis are created they are added to the {@link #bestTemplateUpdate} queue. - * - * @param signature The signature of the template to synthesize - * @param log The compile log - * @return The synthesis that is used to load/synthesize the best template - */ - public AsmFragmentSynthesis getOrCreateSynthesis(String signature, CompileLog log) { - AsmFragmentSynthesis synthesis = getSynthesis(signature); - if(synthesis != null) { - return synthesis; - } - // Synthesis not found - create it - if(log.isVerboseFragmentLog()) { - log.append("New fragment synthesis " + signature); - } - synthesis = new AsmFragmentSynthesis(signature); - synthesisGraph.put(signature, synthesis); - queueUpdateBestTemplate(synthesis); - // Load the template from file - if it exists - List fileTemplates = loadFragmentTemplates(signature); - for(AsmFragmentTemplate fileTemplate : fileTemplates) { - synthesis.addFileTemplate(fileTemplate); - if(log.isVerboseFragmentLog()) { - log.append("New fragment synthesis " + signature + " - Successfully loaded " + signature + ".asm"); - } - } - // Populate with synthesis options - for(AsmFragmentTemplateSynthesisRule rule : AsmFragmentTemplateSynthesisRuleManager.getSynthesisRules(targetCpu)) { - if(rule.matches(signature)) { - AsmFragmentSynthesisOption synthesisOption = new AsmFragmentSynthesisOption(signature, rule); - synthesis.addSynthesisOption(synthesisOption); - if(log.isVerboseFragmentLog()) { - log.append("New fragment synthesis " + signature + " - sub-option " + String.join(",", synthesisOption.getSubSignatures())); - } - } - } - // Ensure that all sub-synthesis exist (recursively) - and set their parent options - for(AsmFragmentSynthesisOption synthesisOption : synthesis.getSynthesisOptions()) { - for (String subSignature : synthesisOption.getSubSignatures()) { - AsmFragmentSynthesis subSynthesis = getOrCreateSynthesis(subSignature, log); - subSynthesis.addParentOption(synthesisOption); - } - } - return synthesis; - } + } + return synthesis; + } /** Work queue with synthesis that need to be recalculated to find the best templates. */ - private final Deque bestTemplateUpdate; + private final Deque bestTemplateUpdate; - /** - * Queue an update of the best templates for a synthesis to the work queue - * - * @param synthesis The synthesis to add to the work queue - */ - private void queueUpdateBestTemplate(AsmFragmentSynthesis synthesis) { - if(!bestTemplateUpdate.contains(synthesis)) { - bestTemplateUpdate.push(synthesis); - } - } + /** + * Queue an update of the best templates for a synthesis to the work queue + * + * @param synthesis The synthesis to add to the work queue + */ + private void queueUpdateBestTemplate(AsmFragmentSynthesis synthesis) { + if (!bestTemplateUpdate.contains(synthesis)) { + bestTemplateUpdate.push(synthesis); + } + } - /** - * Find the best fragment template for all synthesis in the work queue {@link #bestTemplateUpdate} queue. - * During the update more items can be added to the work queue. - * Returns when the work queue is empty. - */ - private void updateBestTemplates(CompileLog log) { - while(!bestTemplateUpdate.isEmpty()) { - AsmFragmentSynthesis synthesis = bestTemplateUpdate.pop(); - boolean modified = false; - // Check if the file template is best in class - List fileTemplates = synthesis.getFileTemplates(); - for(AsmFragmentTemplate fileTemplate : fileTemplates) { - modified |= synthesis.bestTemplateCandidate(fileTemplate); - } - Collection synthesisOptions = synthesis.getSynthesisOptions(); - for(AsmFragmentSynthesisOption synthesisOption : synthesisOptions) { - // for each sub-signature find the best templates - List> subSignatureTemplates = new ArrayList<>(); - for (String subSignature : synthesisOption.getSubSignatures()) { - AsmFragmentSynthesis subSynthesis = getSynthesis(subSignature); - if (subSynthesis == null) { - throw new RuntimeException("Synthesis Graph Error! Sub-synthesis not found in graph " + subSignature); - } - Collection subTemplates = subSynthesis.getBestTemplates(); - subSignatureTemplates.add(subTemplates); + /** + * Find the best fragment template for all synthesis in the work queue {@link #bestTemplateUpdate} queue. + * During the update more items can be added to the work queue. + * Returns when the work queue is empty. + */ + private void updateBestTemplates(CompileLog log) { + while (!bestTemplateUpdate.isEmpty()) { + AsmFragmentSynthesis synthesis = bestTemplateUpdate.pop(); + boolean modified = false; + // Check if the file template is best in class + List fileTemplates = synthesis.getFileTemplates(); + for (AsmFragmentSynthesisResult fileTemplate : fileTemplates) { + modified |= synthesis.bestTemplateCandidate(fileTemplate); } - AsmFragmentTemplateSynthesisRule rule = synthesisOption.getRule(); - // Create all combinations of the best sub-templates - List> combinations = combinations(subSignatureTemplates); - for (List subTemplateCombination : combinations) { - AsmFragmentTemplate synthesized = rule.synthesize(synthesis.getSignature(), subTemplateCombination); - if (synthesized != null) { - if (log.isVerboseFragmentLog()) { - log.append("Fragment synthesis " + synthesis.getSignature() + " - Successfully synthesized from " + String.join(",", synthesisOption.subSignatures)); - } - modified |= synthesis.bestTemplateCandidate(synthesized); - } else { - if (log.isVerboseFragmentLog()) { - log.append("Fragment synthesis " + synthesis.getSignature() + " - Sub clobber prevents synthesis from " + String.join(",", synthesisOption.subSignatures)); - } - } + Collection synthesisOptions = synthesis.getSynthesisOptions(); + for (AsmFragmentSynthesisOption synthesisOption : synthesisOptions) { + // for each sub-signature find the best templates + List> subSignatureTemplates = new ArrayList<>(); + for (String subSignature : synthesisOption.getSubSignatures()) { + AsmFragmentSynthesis subSynthesis = getSynthesis(subSignature); + if (subSynthesis == null) { + throw new RuntimeException("Synthesis Graph Error! Sub-synthesis not found in graph " + subSignature); + } + Collection subTemplates = subSynthesis.getBestTemplates(); + subSignatureTemplates.add(subTemplates); + } + AsmFragmentTemplateSynthesisRule rule = synthesisOption.getRule(); + // Create all combinations of the best sub-templates + List> combinations = combinations(subSignatureTemplates); + for (List subFragmentCombination : combinations) { + final List subFragments = subFragmentCombination.stream().map(AsmFragmentSynthesisResult::getFragment).collect(Collectors.toList()); + final AsmFragmentTemplate synthesize = rule.synthesize(synthesis.getSignature(), subFragments); + if (synthesize != null) { + if (log.isVerboseFragmentLog()) { + log.append("Fragment synthesis " + synthesis.getSignature() + " - Successfully synthesized from " + String.join(",", synthesisOption.getSubSignatures())); + } + AsmFragmentSynthesisResult synthesisResult = new AsmFragmentSynthesisResult(synthesize, false, false, rule, subFragmentCombination); + modified |= synthesis.bestTemplateCandidate(synthesisResult); + } else { + if (log.isVerboseFragmentLog()) { + log.append("Fragment synthesis " + synthesis.getSignature() + " - Sub clobber prevents synthesis from " + String.join(",", synthesisOption.getSubSignatures())); + } + } + } } - } - if(modified) { - // The synthesis was modified - update all parents later - for(AsmFragmentSynthesisOption parentOption : synthesis.getParentOptions()) { - String parentSignature = parentOption.getSignature(); - AsmFragmentSynthesis parentSynthesis = getSynthesis(parentSignature); - if(parentSynthesis == null) { - throw new RuntimeException("Synthesis Graph Error! Parent synthesis not found in graph " + parentSignature); - } - queueUpdateBestTemplate(parentSynthesis); - if(log.isVerboseFragmentLog()) { - log.append("Fragment synthesis " + synthesis.getSignature() + " - New best, scheduling parent " + parentSignature); - } + if (modified) { + // The synthesis was modified - update all parents later + for (AsmFragmentSynthesisOption parentOption : synthesis.getParentOptions()) { + String parentSignature = parentOption.getSignature(); + AsmFragmentSynthesis parentSynthesis = getSynthesis(parentSignature); + if (parentSynthesis == null) { + throw new RuntimeException("Synthesis Graph Error! Parent synthesis not found in graph " + parentSignature); + } + queueUpdateBestTemplate(parentSynthesis); + if (log.isVerboseFragmentLog()) { + log.append("Fragment synthesis " + synthesis.getSignature() + " - New best, scheduling parent " + parentSignature); + } + } } - } - if(synthesis.getBestTemplates().isEmpty() && log.isVerboseFragmentLog()) { - log.append("Fragment synthesis " + synthesis.getSignature() + " - No file or synthesis results!"); - } - } - } - - /** - * Generate all possible combinations of a number of options. - * @param options A list of the options for each position. - * @param The top of an option - * @return A list with all combinations of the passed options on each position. - */ - private static List> combinations(List> options) { - if(options.isEmpty()) { - // the empty list has one single combination - final ArrayList> combinations = new ArrayList<>(); - combinations.add(new ArrayList<>()); - return combinations; - } else { - // Calculate recursively - final List> tail = options.subList(1, options.size()); - List> tailCombinations = combinations(tail); - final ArrayList> combinations = new ArrayList<>(); - final Collection head = options.get(0); - for (T headOption : head) { - for (List tailCombination : tailCombinations) { - final ArrayList combination = new ArrayList<>(); - combination.add(headOption); - combination.addAll(tailCombination); - combinations.add(combination); + if (synthesis.getBestTemplates().isEmpty() && log.isVerboseFragmentLog()) { + log.append("Fragment synthesis " + synthesis.getSignature() + " - No file or synthesis results!"); } - } - return combinations; - } - } + } + } - /** - * Attempt to load a fragment template from disk. Also searches relevant fragment sub-folders specified by CPU and other options. - * - * @param signature The signature - * @return The fragment template from a file. null if the template is not found as a file. - */ - private List loadFragmentTemplates(String signature) { - ArrayList fileTemplates = new ArrayList<>(); - List cpuFeatures = targetCpu.getFeatures(); - for(TargetCpu.Feature cpuFeature : cpuFeatures) { - AsmFragmentTemplate fileFragment = loadFragmentTemplate(signature, baseFragmentFolder.resolve(cpuFeature.getName())); - if(fileFragment != null) - fileTemplates.add(fileFragment); - } - return fileTemplates; - } + /** + * Generate all possible combinations of a number of options. + * + * @param options A list of the options for each position. + * @param The top of an option + * @return A list with all combinations of the passed options on each position. + */ + private static List> combinations(List> options) { + if (options.isEmpty()) { + // the empty list has one single combination + final ArrayList> combinations = new ArrayList<>(); + combinations.add(new ArrayList<>()); + return combinations; + } else { + // Calculate recursively + final List> tail = options.subList(1, options.size()); + List> tailCombinations = combinations(tail); + final ArrayList> combinations = new ArrayList<>(); + final Collection head = options.get(0); + for (T headOption : head) { + for (List tailCombination : tailCombinations) { + final ArrayList combination = new ArrayList<>(); + combination.add(headOption); + combination.addAll(tailCombination); + combinations.add(combination); + } + } + return combinations; + } + } - /** - * Attempt to load a fragment template from a folder on disk - * - * @param signature The signature to search for - * @param fragmentFolder The folder to look in - * @return any fragment with the gicen signature found in the folder. null if not found. - */ - private AsmFragmentTemplate loadFragmentTemplate(String signature, Path fragmentFolder) { - try { - File fragmentFile = fragmentFolder.resolve(signature + ".asm").toFile(); - if(!fragmentFile.exists()) { - return null; - } - InputStream fragmentStream = new FileInputStream(fragmentFile); - String body; - if(fragmentStream.available() == 0) { - body = ""; - } else { - CharStream fragmentCharStream = CharStreams.fromStream(fragmentStream); - body = fixNewlines(fragmentCharStream.toString()); - } - return new AsmFragmentTemplate(signature, body, targetCpu, false); - } catch(IOException e) { - throw new RuntimeException("Error loading fragment file " + signature, e); - } catch(StringIndexOutOfBoundsException e) { - throw new RuntimeException("Problem reading fragment file " + signature, e); - } - } + /** + * Attempt to load a fragment template from disk. Also searches relevant fragment sub-folders specified by CPU and other options. + * + * @param signature The signature + * @return The fragment template from a file. null if the template is not found as a file. + */ + private List loadFragmentTemplates(String signature) { + ArrayList fileTemplates = new ArrayList<>(); + List cpuFeatures = targetCpu.getFeatures(); + for (TargetCpu.Feature cpuFeature : cpuFeatures) { + AsmFragmentSynthesisResult fileFragment = loadFragmentTemplate(signature, baseFragmentFolder.resolve(cpuFeature.getName())); + if (fileFragment != null) + fileTemplates.add(fileFragment); + } + return fileTemplates; + } - /** - * Fix all newlines in the body. - * - Removes all '\r' - * - Removes all trailing newlines - * - * @param body The body - * @return The body with fixed newlines - */ - public static String fixNewlines(String body) { - body = body.replace("\r", ""); - while(body.length() > 0 && body.charAt(body.length() - 1) == '\n') { - body = body.substring(0, body.length() - 1); - } - return body; - } + /** + * Attempt to load a fragment template from a folder on disk + * + * @param signature The signature to search for + * @param fragmentFolder The folder to look in + * @return any fragment with the given signature found in the folder. null if not found. + */ + private AsmFragmentSynthesisResult loadFragmentTemplate(String signature, Path fragmentFolder) { + try { + File fragmentFile = fragmentFolder.resolve(signature + ".asm").toFile(); + if (!fragmentFile.exists()) { + return null; + } + InputStream fragmentStream = new FileInputStream(fragmentFile); + String body; + if (fragmentStream.available() == 0) { + body = ""; + } else { + CharStream fragmentCharStream = CharStreams.fromStream(fragmentStream); + body = fixNewlines(fragmentCharStream.toString()); + } + final AsmFragmentTemplate fragment = new AsmFragmentTemplate(signature, body, targetCpu); + return new AsmFragmentSynthesisResult(fragment, true, false, null, new ArrayList<>()); + } catch (IOException e) { + throw new RuntimeException("Error loading fragment file " + signature, e); + } catch (StringIndexOutOfBoundsException e) { + throw new RuntimeException("Problem reading fragment file " + signature, e); + } + } - public File[] allFragmentFiles() { - return baseFragmentFolder.toFile().listFiles((dir, name) -> name.endsWith(".asm")); + /** + * Fix all newlines in the body. + * - Removes all '\r' + * - Removes all trailing newlines + * + * @param body The body + * @return The body with fixed newlines + */ + public static String fixNewlines(String body) { + body = body.replace("\r", ""); + while (body.length() > 0 && body.charAt(body.length() - 1) == '\n') { + body = body.substring(0, body.length() - 1); + } + return body; + } - } + public File[] allFragmentFiles() { + return baseFragmentFolder.toFile().listFiles((dir, name) -> name.endsWith(".asm")); - public static class UnknownFragmentException extends RuntimeException { + } - private final String signature; + public static class UnknownFragmentException extends RuntimeException { - public UnknownFragmentException(String signature) { - super("Fragment not found " + signature); - this.signature = signature; - } + private final String signature; - public String getFragmentSignature() { - return signature; - } + public UnknownFragmentException(String signature) { + super("Fragment not found " + signature); + this.signature = signature; + } - } + public String getFragmentSignature() { + return signature; + } + + } } diff --git a/src/test/java/dk/camelot64/kickc/test/TestFragments.java b/src/test/java/dk/camelot64/kickc/test/TestFragments.java index 308d645d8..373e3c6c6 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestFragments.java +++ b/src/test/java/dk/camelot64/kickc/test/TestFragments.java @@ -1,9 +1,9 @@ package dk.camelot64.kickc.test; import dk.camelot64.kickc.CompileLog; -import dk.camelot64.kickc.fragment.AsmFragmentTemplate; -import dk.camelot64.kickc.fragment.synthesis.AsmFragmentTemplateSynthesizer; import dk.camelot64.kickc.fragment.AsmFragmentTemplateUsages; +import dk.camelot64.kickc.fragment.synthesis.AsmFragmentSynthesisResult; +import dk.camelot64.kickc.fragment.synthesis.AsmFragmentTemplateSynthesizer; import dk.camelot64.kickc.model.TargetCpu; import dk.camelot64.kickc.model.operators.Operators; import org.junit.jupiter.api.AfterAll; @@ -196,11 +196,11 @@ public class TestFragments { asmFragmentTemplateSynthesizer = new AsmFragmentTemplateSynthesizer(TargetCpu.MOS6502X, new File("src/main/fragment/").toPath(), false, new CompileLog()); log.setSysOut(true); //log.setVerboseFragmentLog(true); - List templates = + List templates = new ArrayList<>(asmFragmentTemplateSynthesizer.getBestTemplates(signature, log)); if(templates.size() > 0) { log.append(""); - for(AsmFragmentTemplate template : templates) { + for(AsmFragmentSynthesisResult template : templates) { AsmFragmentTemplateUsages.logTemplate(log, template, ""); } log.append(""); @@ -227,14 +227,14 @@ public class TestFragments { String signature = sigs.get(testIdx); - List templates = + List templates = new ArrayList<>(asmFragmentTemplateSynthesizer.getBestTemplates(signature, log)); - Collections.sort(templates, Comparator.comparing(AsmFragmentTemplate::getClobber)); + Collections.sort(templates, Comparator.comparing(AsmFragmentSynthesisResult::getClobber)); if(templates.size() == 0) { log.append("CANNOT SYNTHESIZE " + signature); } - for(AsmFragmentTemplate template : templates) { + for(AsmFragmentSynthesisResult template : templates) { String prefix = ""; if(template.isCache()) { prefix = "cached ";