1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-06-02 00:41:42 +00:00

- Correct calculation of live ranges for exported procedures parameters, resulting in better ZP coalesce accuracy.

- Working on the coalesce benchmark tests (comparing the compiles of the same procedures with library functions and without library functions). WIP.
- Added documentation.
This commit is contained in:
Sven Van de Velde 2023-12-06 18:33:44 +01:00
parent 5e6622b2a8
commit 4bfb6d5d1d
8 changed files with 250 additions and 56 deletions

View File

@ -108,8 +108,19 @@ public class OutputFileManager {
return getOutputFile(getOutputDirectory(), getAsmExtension());
}
/**
* #820/8
* When creating an .asm export library, a C header file is created with the C function
* parameter and return prototypes and library import directives.
* The name of the header file is the name of the library + `_asm.h`.
* The header file is allocated to be written in the output directory of the KICKC
* compiler setting `-odir` to ensure that as a result of compilation, the .asm file and the .h
* file can be found and used at the same output location.
* @return The path to the header file that is generated when creating an .asm export library.
*/
public Path getAsmExportHeaderFile() {
return getOutputFile(getOutputDirectory(), "h");
return getOutputFile(getOutputDirectory(), "_asm", "h");
}
/**
@ -121,15 +132,37 @@ public class OutputFileManager {
return getOutputFile(getOutputDirectory(), extension);
}
/**
* Get the output file name in a specific directory with a suffix added and a specific extension
*
* @return The output file with the specified directory, suffix added and extension
*/
public Path getOutputFile(Path outputDir, String suffix, String extension) {
String fileName = getOutputBaseName() + suffix;
if(extension.length() > 0) fileName += "." + extension;
final Path outputFile = outputDir.resolve(fileName);
return outputFile.normalize();
}
/**
* Get the output file name in a specific directory with a specific extension
*
* @return The output file with the specified directory and extension
*/
public Path getOutputFile(Path outputDir, String extension) {
String fileName = getOutputBaseName();
return getOutputFile(outputDir, "", extension);
}
/**
* Get a file name in a specific directory with a specific extension.
* This has been added to facilitate the creation of files other than the base file name
* of the compilation result. Mainly used for .asm export libraries for the moment.
*
* @return The file with the specified directory and extension
*/
public Path getFile(Path dir, String fileName, String extension) {
if(extension.length() > 0) fileName += "." + extension;
final Path outputFile = outputDir.resolve(fileName);
final Path outputFile = dir.resolve(fileName);
return outputFile.normalize();
}

View File

@ -642,18 +642,28 @@ public class Program {
}
/**
* 820/20 - Add a new library in the asm import list.
* #820/20 - Add a new library in the asm import list.
* When adding an asm import library, it is important for the compiler to handle these input
* .asm source files correctly. KICKC in pass5 does optimizations using kickasm (yes!).
* For this, kickc copies in pass5 (in @link Pass5FixLongBranches) the .asm source files
* to the temporary directory where kickasm will compile and assemble. For this, the
* resource must first be copied into this temporary directory.
* So the asmImportResource ensures that the asm import library file gets declared in kickc
* as a resource, and copied into the temp directory when kickass needs it.
* The asmImportResource gets the OUTPUT directory of KICKC as the input source directory
* from where the .asm import library file will be copied, for kickasm to assemble.
*/
public AsmLibrary addAsmImportLibrary(String asmImportLibraryName) {
Path asmImportResource = Paths.get(asmImportLibraryName + ".asm").toAbsolutePath();
Path asmImportResource = outputFileManager.getFile(outputFileManager.getOutputDirectory(),asmImportLibraryName, "asm");;
AsmLibrary asmLibrary = new AsmLibrary(asmImportLibraryName, asmImportResource);
this.asmImports.putIfAbsent(asmImportLibraryName, asmLibrary);
// this.addAsmResourceFile(asmImportResource);
this.asmImports.putIfAbsent(asmImportLibraryName, asmLibrary);
if(!this.getAsmResourceFiles().contains(asmImportResource))
this.addAsmResourceFile(asmImportResource); // #820/20 here ...
return asmLibrary;
}
/**
* 820/20 - Add the procedure to a library.
* 820/20 - Add the procedure to an .asm import library.
*/
public void addAsmImportProcedures(AsmLibrary asmImportLibrary, Procedure.CallingConvention callingConvention, List<String> procedures) {
@ -663,21 +673,7 @@ public class Program {
}
/**
* 820/20 - Check if the procedure is in the asm import list.
*/
public boolean isProcedureAsmImport(String procedureName) {
Map<String, AsmLibrary> asmImports = this.getAsmImports();
for (String asmImportKey : asmImports.keySet()) {
AsmLibrary library = asmImports.get(asmImportKey);
if (library.hasProcedure(procedureName)) {
return true;
}
}
return false;
}
/**
* 820/20 - Get the asm import library of the procedure.
* 820/20 - Get the .asm import library of the procedure.
*/
public AsmLibrary getProcedureAsmImportLibrary(String procedureName) {
Map<String, AsmLibrary> asmImports = this.getAsmImports();
@ -690,6 +686,14 @@ public class Program {
return null;
}
/**
* #820/20 - Configure the procedure as an .asm import library, and set the relevant properties!
*
* @param procedure The procedure to be configured.
* @param asmImport The .asm import library.
*
* TODO: Inlined and Intrinsic procedures!
*/
public void setProcedureAsAsmImport(Procedure procedure, AsmLibrary asmImport) {
// Here we check if the procedure declaration is already included in the asm import.
// If yes, the procedure should:,
@ -702,24 +706,50 @@ public class Program {
procedure.setDeclaredExtern(true);
procedure.setDeclaredInline(false);
}
/**
* #820/20 - Retrieve all registered .asm exported libraries!
*
* @return All .asm export libraries!
*/
public Map<String, AsmLibrary> getAsmExports() {
return asmExports;
}
/**
* #820/20 - Creates a new AsmLibrary from a given .asm library name.
*
* @param asmExportLibraryName The library name of the .asm export library.
* @return The new object of the .asm export library.
*/
public AsmLibrary addAsmExportLibrary(String asmExportLibraryName) {
Path asmExportResource = Paths.get(asmExportLibraryName + ".asm").toAbsolutePath();
AsmLibrary asmLibrary = new AsmLibrary(asmExportLibraryName, asmExportResource);
this.asmExports.putIfAbsent(asmExportLibraryName, asmLibrary);
// this.addAsmResourceFile(asmImportResource);
// this.addAsmResourceFile(asmExportResource);
return asmLibrary;
}
/**
* #820/20 - Add to an .asm export library (AsmLibrary) object
* a list of procedures with a specified calling convention.
*
* @param asmExportLibrary The AsmLibrary object of the .asm export library.
* @param callingConvention The CallingConvention, which is either __stackcall or __varcall.
* @param procedures The list of procedures to be added.
*/
public void addAsmExportProcedures(AsmLibrary asmExportLibrary, Procedure.CallingConvention callingConvention, List<String> procedures) {
for(String procedureName : procedures) {
this.asmExports.get(asmExportLibrary.getFullName()).addProcedure(procedureName, callingConvention);
}
}
/**
* #820/20 - Evaluate if the procedure is an .asm exported library.
*
* @param procedureName The name of the procedure to be evaluated.
* @return true if the procedure is part of an .asm export library.
*/
public boolean isProcedureAsmExport(String procedureName) {
Map<String, AsmLibrary> asmExports = this.getAsmExports();
for (String asmExportKey : asmExports.keySet()) {
@ -731,6 +761,12 @@ public class Program {
return false;
}
/**
* 820/20 - Retrieve the .asm export library (AsmLibrary) object from a given Procedure object.
*
* @param procedure The Procedure object.
* @return The AsmLibrary object that is an .asm export library.
*/
public AsmLibrary getAsmExportLibrary(Procedure procedure) {
Map<String, AsmLibrary> asmExports = this.getAsmExports();
for (String asmExportKey : asmExports.keySet()) {
@ -742,15 +778,24 @@ public class Program {
return null;
}
public void setProcedureAsAsmExport(Procedure procedure, AsmLibrary asmLibrary) {
/**
* #820/20 - Configure the Procedure object as an .asm import library,
* and set the relevant properties!
*
* @param procedure The Procedure object to be configured.
* @param asmExportLibrary The .asm export library.
*
* TODO: Inlined and Intrinsic procedures!
*/
public void setProcedureAsAsmExport(Procedure procedure, AsmLibrary asmExportLibrary) {
// Here we check if the procedure declaration is already included in the asm export.
// If yes, the procedure should:,
// - have a stackcall or varcall calling convention
// - be external
// - not inlined
// - have a library name tagged for further processing of the namespace and asm inclusion etc.
procedure.setCallingConvention(asmLibrary.getProcedureCallingConvention(procedure.getFullName()));
procedure.setAsmExportLibrary(asmLibrary.getFullName());
procedure.setCallingConvention(asmExportLibrary.getProcedureCallingConvention(procedure.getFullName()));
procedure.setAsmExportLibrary(asmExportLibrary.getFullName());
procedure.setDeclaredExtern(false);
procedure.setDeclaredInline(false);
}

View File

@ -56,11 +56,23 @@ public class Procedure extends Scope {
return asmLibrary;
}
/**
* Attaches the .asm library that is imported to a procedure.
* Note that the boolean this.asmImportLibrary of the procedure keeps the identity of the
* library for later enquiries!
*
* @param asmLibrary The .asm library that is imported.
*/
public void setAsmImportLibrary(String asmLibrary) {
this.asmLibrary = asmLibrary;
this.asmImportLibrary = true;
}
/**
* #820/19 - Enquire if the procedure is an .asm import library procedure.
* @return true if the procedure is an .asm imported library procedure.
* Through #pragma asm_import or using the __asm_import() procedure directive.
*/
public boolean isAsmImportLibrary() {
return this.asmLibrary != null && this.asmImportLibrary;
}
@ -73,11 +85,27 @@ public class Procedure extends Scope {
}
}
/**
* #820/19 - Attaches the .asm library that is exported to a procedure.
* Note that the boolean this.asmExportLibrary of the procedure keeps the identity of the
* library for later enquiries!
*
* @param asmLibrary The .asm library that is exported.
*/
public void setAsmExportLibrary(String asmLibrary) {
this.asmLibrary = asmLibrary;
this.asmExportLibrary = true;
}
/**
* #820/19 - Enquire if the procedure is an .asm export library procedure.
* @return true if the procedure is an .asm exported library procedure.
* Through #pragma asm_export or using the __asm_export() procedure directive.
*/
public boolean isAsmExportLibrary() {
return this.asmLibrary != null && this.asmExportLibrary;
}
public boolean isAsmLibrary() {
return this.asmLibrary != null;
}

View File

@ -92,6 +92,9 @@ public abstract class Pass4MemoryCoalesce extends Pass2Base {
* Determines if two live range equivalence classes can be coalesced without cross-thread clobber.
* This is possible if they are both only called from the same thread head.
*
* Added #820/27 - Ensure that .asm exported libraries are coalesced together, even if their threads differ.
* Variables are coalesced if both first functions of the equivalent classes are an .asm exported procedure.
*
* @param ec1 One equivalence class
* @param ec2 Another equivalence class
* @param threadHeads The heads (in the call graph) from each thread in the program
@ -110,43 +113,21 @@ public abstract class Pass4MemoryCoalesce extends Pass2Base {
}
/* #820/27 - We allow the coalesce if the first procedure in the thread is an .asm export library
for the equivalence class1 and class2.
We want to coalesce the parameters of all exported procedures in the same
zero-page allocations as much as possible.
*/
Procedure proc1 = program.getScope().getProcedure((ProcedureRef)threads1.toArray()[0]);
Procedure proc2 = program.getScope().getProcedure((ProcedureRef)threads2.toArray()[0]);
if(proc1.isAsmLibrary() || proc2.isAsmLibrary()) {
if(proc1.isAsmExportLibrary() && proc2.isAsmExportLibrary()) {
return true;
} else {
// Otherwise it is a normal equivalence test.
return threads1.equals(threads2);
}
}
private static boolean isAsmExportParameter(VariableRef varRef, Program program) {
Variable variable = program.getScope().getVariable(varRef);
return variable.isAsmLibraryParameter();
}
/**
* Determines if two live range equivalence classes can be coalesced when exported.
* This is possible if they are both exported and input parameters.
*
* @param ec1 One equivalence class
* @param ec2 Another equivalence class
* @param threadHeads The heads (in the call graph) from each thread in the program
* @param program The program
* @return True if the two equivalence classes can be coalesced into one without problems.
*/
private static boolean canCoalesceExports(LiveRangeEquivalenceClass ec1, LiveRangeEquivalenceClass ec2, Program program) {
for(VariableRef varRef : ec1.getVariables()) {
if(isAsmExportParameter(varRef, program)) {
for(VariableRef varRef2 : ec2.getVariables()) {
if(isAsmExportParameter(varRef2, program))
return false;
}
}
}
return true;
}
/**
* Find the threads for the variables in an equivalence class.
*

View File

@ -2,9 +2,12 @@ package dk.camelot64.kickc.passes.calcs;
import dk.camelot64.kickc.model.*;
import dk.camelot64.kickc.model.statements.Statement;
import dk.camelot64.kickc.model.statements.StatementAssignment;
import dk.camelot64.kickc.model.statements.StatementCalling;
import dk.camelot64.kickc.model.statements.StatementPhiBlock;
import dk.camelot64.kickc.model.symbols.Procedure;
import dk.camelot64.kickc.model.symbols.Scope;
import dk.camelot64.kickc.model.symbols.Variable;
import dk.camelot64.kickc.model.values.LabelRef;
import dk.camelot64.kickc.model.values.ProcedureRef;
import dk.camelot64.kickc.model.values.VariableRef;
@ -75,6 +78,12 @@ public class PassNCalcLiveRangeVariables extends PassNCalcBase<LiveRangeVariable
* Calls to methods are also given special consideration.
* Variables used inside the method must be propagated back to all callers while variables not used inside the method must skip the method entirely back to the statement before the call.
*
* #820/26 - Ensure that in pass3, .asm export library procedure parameters are registered as "alive",
* so that during further pass4 calculations, the coalesce or uplift of these parameters are
* considered as interfering when there are multiple input parameters within the procedure to be
* coalesced. This to ensure that these input parameters are not simply allocated to one ZP!
* I tested this thoroughly.
*
* @param liveRanges The live ranges to propagate.
* @return true if any live ranges was modified. false if no modification was performed (and the propagation is complete)
*/
@ -86,8 +95,27 @@ public class PassNCalcLiveRangeVariables extends PassNCalcBase<LiveRangeVariable
List<VariableRef> aliveNextStmt = liveRangeVariablesByStatement.getAlive(
nextStmt.getIndex());
Collection<VariableRef> definedNextStmt = referenceInfo.getDefinedVars(nextStmt);
Collection<VariableRef> usedNextStmt = referenceInfo.getUsedVars(nextStmt);
initLiveRange(liveRanges, definedNextStmt);
Collection<PreviousStatement> previousStmts = getPreviousStatements(nextStmt);
// #820/26 - For .asm exported procedures, we need to consider for the first statement of a block
// the input parameters ...
if(previousStmts.isEmpty()) {
SymbolInfos scope = getProgram().getSymbolInfos();
for(VariableRef parameterRef : usedNextStmt) {
Variable var = scope.getVariable(parameterRef);
if(var.isAsmLibraryParameter()) {
boolean addAlive = liveRanges.addAlive(parameterRef,
nextStmt.getIndex());
modified |= addAlive;
if (addAlive && getLog().isVerboseLiveRanges()) {
getLog().append("Propagated alive parameter " + var + " to "
+ nextStmt.getIndex());
}
}
}
}
for (PreviousStatement previousStmt : previousStmts) {
if (PreviousStatement.Type.NORMAL.equals(previousStmt.getType())) {
// Add all vars alive in the next statement

View File

@ -0,0 +1,48 @@
/* Testing an export library calc.asm file generation.
The result of this compile should result in:
- A new calc.asm file created in the compiler output directory.
- A new calc_asm.h file created in the compiler output directory.
The verbose of the compile should show the following ZP configuration:
---
zp[1]:002 (0x02) [ plus::$0 plus::return min::$0 min::return ]
zp[1]:003 (0x03) [ min::a plus::a ]
zp[1]:004 (0x04) [ min::b plus::b ]
Maximum zeropage register consumed: 4 (0x4)
---
The compile ensures that both plus and min are procedures are coalesced.
But the parameters of plus and min are nicely allocated to other ZP.
As plus and min contain atomic functionality, there is no nesting possible,
so coalescing the ZP of these two functions make absolute sense.
*/
// A link file is still needed, but this must be eliminated.
#pragma link("calc.ld")
#pragma encoding(screencode_mixed)
#pragma var_model(zp)
// Register the compilation as an calc.asm export library output artefact.
// There is no main() function!
#pragma asm_library("calc")
// Register the exported procedures that should be included in the calc.asm export library.
#pragma asm_export("calc", __varcall, plus, min)
// The plus function.
char plus(char a, char b) {
return a+b;
}
// The min function.
char min(char a, char b) {
return a-b;
}
// Note that both functions stand on its own in this source, there is no main!
// Parameters a and b are taken into consideration when allocating the ZP!

View File

@ -0,0 +1,6 @@
#if __asm_import__
#else
.segmentdef Code [start=%P]
.segmentdef Data [startAfter="Code"]
#endif

View File

@ -0,0 +1,25 @@
/* Compile with C64
Here we use the calc.asm library functions.
We also use the printf to nicely display the result and to document how the
imported calc.asm library works and how it compiles with the main source.
*/
#pragma encoding(screencode_mixed)
#pragma var_model(zp)
// We include the calc_asm.h generated C function prototype file.
// This file contains the function prototypes for plus and min, which are
// contained in the calc.asm file in assembler.
#include "calc_asm.h"
// We include the C64 and printf stuff.
#include <c64.h>
#include <stdio.h>
#include <printf.h>
void main() {
clrscr();
printf("5 + 4 = %u\n", plus(5,4));
printf("5 - 4 = %u\n", min(5,4));
}