mirror of
https://gitlab.com/camelot/kickc.git
synced 2024-06-04 14:30:04 +00:00
20f3529a2d
- Fixed bug for hasExportAsmLibrary to use the LibraryName always and optimized the code in AsmLibrary.java. - Fixed bug for hasExportAsmLibrary to use the LibraryName always and optimized the code in Pass4CodeGeneration.java. - Optimized naming of asmLibrary labels and names. Labels are to be used in assembler generation only. Names are used for all other purposes.
372 lines
15 KiB
Java
372 lines
15 KiB
Java
package dk.camelot64.kickc.asm;
|
|
|
|
import dk.camelot64.kickc.model.CallGraph;
|
|
import dk.camelot64.kickc.model.Program;
|
|
import dk.camelot64.kickc.model.Registers;
|
|
import dk.camelot64.kickc.model.symbols.Procedure;
|
|
import dk.camelot64.kickc.model.symbols.Scope;
|
|
import dk.camelot64.kickc.model.symbols.Symbol;
|
|
import dk.camelot64.kickc.model.symbols.Variable;
|
|
import dk.camelot64.kickc.model.types.SymbolType;
|
|
import dk.camelot64.kickc.model.types.SymbolTypeStruct;
|
|
import dk.camelot64.kickc.model.values.*;
|
|
|
|
import java.nio.file.Path;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* A Kick assembler "library" helper class used to import and export assembler from a C source.
|
|
*/
|
|
public class AsmLibrary extends AsmLine {
|
|
|
|
public String getAsmLibraryName() {
|
|
return this.asmLibraryName.toString();
|
|
}
|
|
|
|
public String getAsmLibraryIdentifier() {
|
|
return this.asmLibraryName.getAsm();
|
|
}
|
|
|
|
private final AsmIdentifier asmLibraryName;
|
|
|
|
private final HashMap<String, Procedure.CallingConvention> symbols;
|
|
|
|
public AsmLibrary(String asmLibraryName, Path resource, HashMap<String, Procedure.CallingConvention> symbols) {
|
|
if(asmLibraryName!=null) {
|
|
this.asmLibraryName = new AsmIdentifier(asmLibraryName);
|
|
} else {
|
|
this.asmLibraryName = null;
|
|
}
|
|
this.symbols = symbols;
|
|
}
|
|
|
|
public AsmLibrary(String asmLibraryName, Path resource) {
|
|
this(asmLibraryName, resource, new LinkedHashMap<>());
|
|
}
|
|
|
|
public Set<String> getSymbols() {
|
|
return this.symbols.keySet();
|
|
}
|
|
|
|
public void addSymbol(String symbolName, Procedure.CallingConvention callingConvention) {
|
|
symbols.put(symbolName, callingConvention);
|
|
}
|
|
|
|
public Procedure.CallingConvention getProcedureCallingConvention(String procedureName) {
|
|
return symbols.get(procedureName);
|
|
}
|
|
|
|
public boolean hasSymbol(String symbolName) {
|
|
return symbols.containsKey(symbolName);
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate the zeropages used
|
|
*
|
|
* @param param The variable to investigate
|
|
* @param scope The scope (procedure)
|
|
* @param zp The zero pages to append
|
|
*/
|
|
void generateZPVar(Set<String> zp, Variable param, Scope scope) {
|
|
if (param == null) return;
|
|
if (param.isKindPhiMaster()) {
|
|
List<Variable> versions = new ArrayList<>(scope.getVersions(param));
|
|
if (!versions.isEmpty()) if (param.getLocalName().equals("return")) {
|
|
// Choose the last version for return values
|
|
param = versions.get(versions.size() - 1);
|
|
} else {
|
|
// Choose the first version for parameters
|
|
param = versions.get(0);
|
|
}
|
|
else
|
|
// Parameter optimized away to a constant or unused
|
|
return;
|
|
}
|
|
|
|
Registers.Register allocation = param.getAllocation();
|
|
if (allocation instanceof Registers.RegisterZpMem registerZp) {
|
|
for(byte b=0; b<allocation.getBytes(); b++) {
|
|
zp.add(Integer.toString(registerZp.getZp()+b));
|
|
}
|
|
}
|
|
}
|
|
|
|
void generatePrototypeVar(Variable var, StringBuilder signature) {
|
|
Registers.Register allocation = var.getAllocation();
|
|
if (allocation instanceof Registers.RegisterZpMem registerZp) {
|
|
signature.append("__zp(").append(AsmFormat.getAsmNumber(registerZp.getZp())).append(") ");
|
|
} else if (allocation instanceof Registers.RegisterMainMem registerMainMem) {
|
|
signature.append("__mem(").append(registerMainMem.getAddress() == null ? "" : AsmFormat.getAsmNumber(registerMainMem.getAddress())).append(") ");
|
|
} else if (allocation instanceof Registers.RegisterAByte) {
|
|
signature.append("__register(A) ");
|
|
} else if (allocation instanceof Registers.RegisterXByte) {
|
|
signature.append("__register(X) ");
|
|
} else if (allocation instanceof Registers.RegisterYByte) {
|
|
signature.append("__register(Y) ");
|
|
} else if (allocation instanceof Registers.RegisterZByte) {
|
|
signature.append("__register(Z) ");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate part of a comment that describes a returnvalue/parameter
|
|
*
|
|
* @param param The variable to describe
|
|
* @param scope The scope (procedure)
|
|
* @param signature The signature to append to
|
|
* @param isStackCall true if the signature is for stack call.
|
|
* @return The version of the variable chosen
|
|
*/
|
|
Variable generateSignatureVar(Variable param, Scope scope, StringBuilder signature, boolean isStackCall) {
|
|
if (param == null) return param;
|
|
if (param.isKindPhiMaster()) {
|
|
List<Variable> versions = new ArrayList<>(scope.getVersions(param));
|
|
if (versions.size() > 0) if (param.getLocalName().equals("return")) {
|
|
// Choose the last version for return values
|
|
param = versions.get(versions.size() - 1);
|
|
} else {
|
|
// Choose the first version for parameters
|
|
param = versions.get(0);
|
|
}
|
|
else
|
|
// Parameter optimized away to a constant or unused
|
|
return param;
|
|
}
|
|
|
|
generatePrototypeVar(param, signature);
|
|
return param;
|
|
}
|
|
|
|
/**
|
|
* #820/40 - Reserve used zero-page registers usage by the procedure, if any.
|
|
* If there are nested calls within the procedure to other exported procedures,
|
|
* then the zero-page reserve must include all the zero-pages
|
|
* of those nested functions!
|
|
*
|
|
* @param zp The set of consolidated zero-page used.
|
|
* @param procedureRef The reference to the procedure to generate the nested zero-page usage for.
|
|
* @param program The program.
|
|
* @return The set of reserved zero-page collected in String type format.
|
|
*/
|
|
private Set<String> generateZP(Set<String> zp, ProcedureRef procedureRef, Program program) {
|
|
CallGraph callGraph = program.getCallGraph();
|
|
Collection<ScopeRef> calls =
|
|
callGraph.getRecursiveCalls(procedureRef);
|
|
for(ScopeRef called : calls) {
|
|
if(called instanceof ProcedureRef calledRef) {
|
|
generateZP(zp, calledRef, program);
|
|
}
|
|
}
|
|
Procedure procedure = program.getScope().getProcedure(procedureRef);
|
|
HashMap<String, Symbol> procedureSymbols = procedure.getSymbols();
|
|
for (Symbol symbol : procedureSymbols.values()) {
|
|
if (symbol instanceof Variable variable) {
|
|
generateZPVar(zp, variable, procedure);
|
|
}
|
|
}
|
|
return zp;
|
|
}
|
|
|
|
/**
|
|
* Generate a header for a C function prototype for .asm library import.
|
|
* It defines the signature of each procedure in terms of the registers used,
|
|
* which can be __mem, __zp, __register for the procedure parameters and return value.
|
|
* Local variables, when allocated to __zp within the pre-compiled .asm library,
|
|
* are reserved using __zp_reserve directive.
|
|
* __stackcall procedures don't produce __mem, __zp, __register directives.
|
|
* @param procedure The procedure to generate the header for.
|
|
* @return A String with the generated function prototype.
|
|
*/
|
|
private String generateProcedureHeader(Procedure procedure, Program program) {
|
|
StringBuilder signature = new StringBuilder();
|
|
Procedure.CallingConvention callingConvention = procedure.getCallingConvention();
|
|
boolean isStackCall = callingConvention == Procedure.CallingConvention.STACK_CALL;
|
|
signature.append("extern ");
|
|
signature.append(callingConvention.getName()).append(" ");
|
|
signature.append("__asm_import(\"").append(this.getAsmLibraryName()).append("\") ");
|
|
|
|
Set<String> zpSet = generateZP(new HashSet<String>(), procedure.getRef(), program);
|
|
if(!zpSet.isEmpty()) {
|
|
String zpDelimited = String.join(",", zpSet);
|
|
signature.append("__zp_reserve( ");
|
|
signature.append(zpDelimited);
|
|
signature.append(" ) ");
|
|
}
|
|
|
|
generateSignatureVar(procedure.getLocalVar("return"), procedure, signature, isStackCall);
|
|
signature.append(procedure.getReturnType().toCDecl());
|
|
signature.append(" ").append(procedure.getLocalName()).append("(");
|
|
int i = 0;
|
|
for (Variable parameter : procedure.getParameters()) {
|
|
if (i++ > 0) signature.append(", ");
|
|
Variable param = generateSignatureVar(parameter, procedure, signature, isStackCall);
|
|
signature.append(param.getType().toCDecl(parameter.getLocalName()));
|
|
}
|
|
signature.append(");");
|
|
signature.append("\n");
|
|
// Always add the signature comments...
|
|
return signature.toString();
|
|
}
|
|
|
|
/**
|
|
* Generate struct forward declarations for C function prototypes for .asm library import,
|
|
* that use structs in the parameters or return values.
|
|
* @param procedure The procedure to generate the struct forward for.
|
|
* @return A String with the generated function prototype.
|
|
*/
|
|
private String generateProcedureStructForwards(Procedure procedure, Program program) {
|
|
StringBuilder signature = new StringBuilder();
|
|
HashSet<String> structForwards = new HashSet<>();
|
|
|
|
Variable returnVariable = procedure.getLocalVar("return");
|
|
if(returnVariable != null && returnVariable.isStruct()) {
|
|
String structReturn = generateVariableStructForward(returnVariable, program);
|
|
structForwards.add(structReturn);
|
|
}
|
|
|
|
for (Variable parameter : procedure.getParameters()) {
|
|
if(parameter.isStruct()) {
|
|
String paramStruct = generateVariableStructForward(parameter, program);
|
|
structForwards.add(paramStruct);
|
|
}
|
|
}
|
|
if(!structForwards.isEmpty())
|
|
signature.append(String.join(";\n", structForwards)).append(";\n");
|
|
return signature.toString();
|
|
}
|
|
|
|
private boolean hasData(Variable constantVar) {
|
|
ConstantValue constantValue = constantVar.getInitValue();
|
|
if (constantValue instanceof ConstantArray) return true;
|
|
else if (constantValue instanceof ConstantStructValue) return true;
|
|
else if (constantValue instanceof ConstantString) return true;
|
|
else return false;
|
|
}
|
|
|
|
private boolean hasExportAsmLibrary(Variable constantVar, String exportAsmLibraryName) {
|
|
if(exportAsmLibraryName != null) {
|
|
String varExportAsmLibraryName = constantVar.getExportAsmLibraryName();
|
|
if(varExportAsmLibraryName != null) {
|
|
return varExportAsmLibraryName.equals(exportAsmLibraryName);
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private String generateVariableStructForward(Variable variable, Program program) {
|
|
StringBuilder signature = new StringBuilder();
|
|
// #820/50 - Handle struct definitions correctly for proper declaration.
|
|
if(variable.isStruct()) {
|
|
SymbolType symbolType = variable.getType();
|
|
if(symbolType instanceof SymbolTypeStruct typeStruct) {
|
|
if(typeStruct.getTypeDefName() == null) {
|
|
String structName = variable.getType().toCDecl();
|
|
signature.append(structName).append(";\n");
|
|
}
|
|
}
|
|
}
|
|
return signature.toString();
|
|
|
|
}
|
|
|
|
private String generateVariableHeader(Variable variable, Program program) {
|
|
StringBuilder signature = new StringBuilder();
|
|
signature.append("extern ");
|
|
signature.append("__asm_import(\"").append(this.getAsmLibraryName()).append("\") ");
|
|
generatePrototypeVar(variable, signature);
|
|
// signature.append(variable.getType().toCDecl());
|
|
// signature.append(variable.getLocalName()).append(" ");
|
|
signature.append(variable.toCDecl());
|
|
// if(variable.isKindConstant()) {
|
|
// signature.append(" = ").append(variable.getInitValue());
|
|
// }
|
|
signature.append(";");
|
|
signature.append("\n");
|
|
// Always add the signature comments...
|
|
return signature.toString();
|
|
|
|
}
|
|
/**
|
|
* Generate a header file for an .asm library import.
|
|
* This is to be used by the main program to import an .asm library.
|
|
* It defines for each the registers used, which can be `__mem`, `__zp`, `__register`:
|
|
* - the extern declaration of each exported global variable in the .asm library.
|
|
* - the extern declaration of each exported procedure in the .asm library,
|
|
* in terms of the registers used, for the procedure parameters and return value.
|
|
* Local variables, when allocated to `__zp` within the pre-compiled .asm library,
|
|
* are reserved using `__zp_reserve` directive.
|
|
* @param program The program
|
|
* @return A string with the generated function prototypes for import.
|
|
*/
|
|
public String generateHeaders(Program program) {
|
|
StringBuilder headers = new StringBuilder();
|
|
StringBuilder structs = new StringBuilder();
|
|
|
|
// Generate the global variables headers.
|
|
Scope scope = program.getScope().getScope(ScopeRef.ROOT);
|
|
Collection<Variable> globalVariables = scope.getAllVars(false);
|
|
Set<String> zp = new HashSet<>();
|
|
// Add all constants arrays incl. strings with data
|
|
for (Variable globalVariable : globalVariables) {
|
|
if (hasExportAsmLibrary(globalVariable, this.getAsmLibraryName())) {
|
|
if(globalVariable.isMemoryAreaZP()) {
|
|
Registers.RegisterZpMem registerZpMem = (Registers.RegisterZpMem)globalVariable.getAllocation();
|
|
if(registerZpMem != null) {
|
|
for(byte b=0; b<registerZpMem.getBytes(); b++) {
|
|
zp.add(Integer.toString(registerZpMem.getZp()+b));
|
|
}
|
|
}
|
|
}
|
|
// TODO: #820/50 - Declaration of global variables only when they are exported using asm_export pragma.
|
|
// This requires rework. It is a challenge to generate only those global var
|
|
// By default at this moment, all global variables in libraries are encapsulated in the asm namespace.
|
|
// This prevents any conflict of global variables.
|
|
headers.append(generateVariableHeader(globalVariable, program));
|
|
structs.append(generateVariableStructForward(globalVariable, program));
|
|
}
|
|
}
|
|
|
|
if(!zp.isEmpty())
|
|
headers.insert(0, "#pragma zp_reserve(" + String.join(",", zp) + ")\n");
|
|
|
|
// Generate the procedure headers.
|
|
for(String procedureName: this.getSymbols()) {
|
|
Procedure procedure = program.getScope().getProcedure(new ProcedureRef(procedureName));
|
|
if(procedure != null) {
|
|
if(procedure.isAsmExportLibrary())
|
|
headers.append(generateProcedureHeader(procedure, program));
|
|
structs.append(generateProcedureStructForwards(procedure, program));
|
|
}
|
|
}
|
|
return structs.toString() + headers.toString();
|
|
}
|
|
|
|
@Override
|
|
public int getLineBytes() {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public double getLineCycles() {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public String getAsm() {
|
|
StringBuilder asm = new StringBuilder();
|
|
asm.append(".export ").append(" [ ");
|
|
asm.append("name=\"").append(asmLibraryName).append("\"");
|
|
for(String procedureName : symbols.keySet()) {
|
|
asm.append(",");
|
|
asm.append(procedureName);
|
|
}
|
|
asm.append(" ]");
|
|
return asm.toString();
|
|
}
|
|
|
|
}
|