1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-10-21 17:24:39 +00:00

Working on #pragma target handling in preprocessor - to fix the embedded #defines.

This commit is contained in:
jespergravgaard 2020-05-12 00:08:11 +02:00
parent 362ec9bb0b
commit b2045c7874
18 changed files with 477 additions and 20 deletions

View File

@ -8,11 +8,12 @@ import dk.camelot64.kickc.model.statements.StatementSource;
import dk.camelot64.kickc.model.symbols.Procedure;
import dk.camelot64.kickc.model.values.SymbolRef;
import dk.camelot64.kickc.parser.CParser;
import dk.camelot64.kickc.parser.KickCLexer;
import dk.camelot64.kickc.parser.KickCParser;
import dk.camelot64.kickc.passes.*;
import dk.camelot64.kickc.preprocessor.CPreprocessor;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenSource;
import java.io.File;
import java.nio.file.Path;
@ -145,10 +146,7 @@ public class Compiler {
CParser cParser = new CParser(program);
if(defines != null) {
for(String macroName : defines.keySet()) {
final String macroBodyText = "#define " + macroName + " " + defines.get(macroName) + "\n";
final CodePointCharStream macroCharStream = CharStreams.fromString(macroBodyText);
final KickCLexer macroLexer = cParser.makeLexer(macroCharStream);
cParser.addSourceFirst(macroLexer);
cParser.define(macroName, defines.get(macroName));
}
}
for(Path cFile : cFiles) {

View File

@ -349,10 +349,16 @@ public class KickC implements Callable<Integer> {
StringBuilder CFileNames = new StringBuilder();
cFiles.stream().forEach(path -> CFileNames.append(path.toString()).append(" "));
Map<String, String> effectiveDefines = new LinkedHashMap<>();
if(defines!=null)
effectiveDefines.putAll(defines);
if(program.getTargetPlatform().getDefines()!=null)
effectiveDefines.putAll(program.getTargetPlatform().getDefines());
if(preprocess) {
System.out.println("Preprocessing " + CFileNames);
try {
compiler.preprocess(cFiles, defines);
compiler.preprocess(cFiles, effectiveDefines);
} catch(CompileError e) {
// Print the error and exit with compile error
System.err.println(e.getMessage());
@ -363,7 +369,7 @@ public class KickC implements Callable<Integer> {
System.out.println("Compiling " + CFileNames);
try {
compiler.compile(cFiles, defines);
compiler.compile(cFiles, effectiveDefines);
} catch(CompileError e) {
// Print the error and exit with compile error
System.err.println(e.getMessage());

View File

@ -6,14 +6,17 @@ import dk.camelot64.kickc.model.statements.StatementSource;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;
import javax.json.stream.JsonParsingException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The target platform the compiler is creating a program for.
@ -113,6 +116,17 @@ public class TargetPlatform {
final String emulatorCommand = platformJson.getString("emulator", null);
if(emulatorCommand != null)
targetPlatform.setEmulatorCommand(emulatorCommand);
final JsonObject defines = platformJson.getJsonObject("defines");
if(defines!=null) {
final Set<String> macroNames = defines.keySet();
final LinkedHashMap<String, String> macros = new LinkedHashMap<>();
for(String macroName : macroNames) {
final JsonValue jsonValue = defines.get(macroName);
final String macroBody = jsonValue.toString();
macros.put(macroName, macroBody);
}
targetPlatform.setDefines(macros);
}
program.setTargetPlatform(targetPlatform);
} catch(CompileError | IOException | JsonParsingException e) {
throw new CompileError("Error parsing target platform file " + platformFile.getAbsolutePath() + "\n"+e.getMessage(), statementSource);

View File

@ -101,6 +101,26 @@ public class CParser {
return preprocessor;
}
/**
* Define a new macro
* @param macroName The macro name
* @param macroBody The macro body
*/
public void define(String macroName, String macroBody) {
final String macroBodyText = "#define " + macroName + " " + macroBody + "\n";
final CodePointCharStream macroCharStream = CharStreams.fromString(macroBodyText);
final KickCLexer macroLexer = makeLexer(macroCharStream);
addSourceFirst(macroLexer);
}
/**
* Undef a macro
* @param macroName The macro name
*/
public void undef(String macroName) {
getPreprocessor().undef(macroName);
}
/**
* Get the token stream containing tokens after the preprocessor.
*

View File

@ -146,12 +146,12 @@ parameterDecl
;
globalDirective
: (PRAGMA RESERVE) PAR_BEGIN NUMBER ( COMMA NUMBER )* PAR_END #globalDirectiveReserve
| (PRAGMA PC) PAR_BEGIN NUMBER PAR_END #globalDirectivePc
| (PRAGMA TARGET) PAR_BEGIN NAME PAR_END #globalDirectivePlatform
: (PRAGMA TARGET) PAR_BEGIN NAME PAR_END #globalDirectivePlatform
| (PRAGMA CPU) PAR_BEGIN NAME PAR_END #globalDirectiveCpu
| (PRAGMA LINK) PAR_BEGIN STRING PAR_END #globalDirectiveLinkScript
| (PRAGMA EMULATOR) PAR_BEGIN STRING PAR_END #globalDirectiveEmulator
| (PRAGMA RESERVE) PAR_BEGIN NUMBER ( COMMA NUMBER )* PAR_END #globalDirectiveReserve
| (PRAGMA PC) PAR_BEGIN NUMBER PAR_END #globalDirectivePc
| (PRAGMA CODESEG) PAR_BEGIN NAME PAR_END #globalDirectiveCodeSeg
| (PRAGMA DATASEG) PAR_BEGIN NAME PAR_END #globalDirectiveDataSeg
| (PRAGMA ENCODING) PAR_BEGIN NAME PAR_END #globalDirectiveEncoding

View File

@ -182,7 +182,17 @@ public class Pass0GenerateStatementSequence extends KickCParserBaseVisitor<Objec
final String platformName = ctx.NAME().getText();
final Path currentFolder = cParser.getSourceFolderPath(ctx);
final StatementSource statementSource = new StatementSource(ctx);
// Remove macros from existing platform!
if(program.getTargetPlatform().getDefines() !=null)
for(String macroName : program.getTargetPlatform().getDefines().keySet())
cParser.undef(macroName);
TargetPlatform.setTargetPlatform(platformName, currentFolder, program, statementSource);
// Define macros from new platform!
if(program.getTargetPlatform().getDefines() !=null)
for(String macroName : program.getTargetPlatform().getDefines().keySet())
cParser.define(macroName, program.getTargetPlatform().getDefines().get(macroName));
// Update the ASM fragment synthesizer to match the new CPU
program.initAsmFragmentSynthesizer();
return null;
}

View File

@ -135,10 +135,49 @@ public class CPreprocessor implements TokenSource {
return true;
} else if(inputToken.getType() == KickCLexer.NAME) {
return expand(inputToken, cTokenSource);
//} else if(inputToken.getType() == KickCLexer.PRAGMA) {
// return pragma(inputToken, cTokenSource);
}
return false;
}
/**
* Handle any #pragma that must be handled by the preprocessor
*
* @param inputToken The #pragma token
* @param cTokenSource The token source used for getting more tokens or for pushing macro expansions
* @return true if the input token was preprocessed (and should not be added to the output). False if the token was not a preprocessor token
*/
private boolean pragma(Token inputToken, CTokenSource cTokenSource) {
if(inputToken instanceof PragmaToken)
// Already examined by the preprocessor - and determined to be for the parser
return false;
final List<Token> ws = skipWhitespace(cTokenSource);
final Token pragmaType = cTokenSource.nextToken();
/*
if(KickCLexer.TARGET == pragmaType.getType()) {
skipWhitespace(cTokenSource);
nextToken(cTokenSource, KickCLexer.PAR_BEGIN);
skipWhitespace(cTokenSource);
final String targetName = nextToken(cTokenSource, KickCLexer.NAME).getText();
skipWhitespace(cTokenSource);
nextToken(cTokenSource, KickCLexer.PAR_END);
//TargetPlatform.setTargetPlatform(targetName, null, );
throw new InternalError("TODO: Implement #pragma target");
// return true;
}
*/
// Pass on the #pragma to the parser
final ArrayList<Token> pragmaTokens = new ArrayList<>();
pragmaTokens.add(new PragmaToken(inputToken));
pragmaTokens.addAll(ws);
pragmaTokens.add(pragmaType);
cTokenSource.addSourceFirst(new ListTokenSource(pragmaTokens));
return true;
}
/**
* Define a macro.
*
@ -258,7 +297,7 @@ public class CPreprocessor implements TokenSource {
List<Token> expandedBody = new ArrayList<>();
final List<Token> macroBody = macro.body;
for(Token macroBodyToken : macroBody) {
if(macroBodyToken.getType()==KickCLexer.NAME && macro.parameters.contains(macroBodyToken.getText())) {
if(macroBodyToken.getType() == KickCLexer.NAME && macro.parameters.contains(macroBodyToken.getText())) {
// body token is a parameter name - replace with expanded parameter value
final int paramIndex = macro.parameters.indexOf(macroBodyToken.getText());
final List<Token> expandedParamValue = paramValues.get(paramIndex);
@ -278,6 +317,7 @@ public class CPreprocessor implements TokenSource {
/**
* Add a macro token to the exapnded macro body. Keeps track of which macros has been used to expand the token using {@link ExpansionToken}
*
* @param macroBodyToken The macro body token to add
* @param macroNameToken The token containing the macro name. Used to get the name and as a source for copying token properties (ensuring file name, line etc. are OK).
* @param expandedBody The expanded macro body to add the token to
@ -305,6 +345,15 @@ public class CPreprocessor implements TokenSource {
// #undef a new macro - find the name
skipWhitespace(cTokenSource);
String macroName = nextToken(cTokenSource, KickCLexer.NAME).getText();
undef(macroName);
}
/**
* Undefine a macro.
*
* @param macroName The macro name
*/
public void undef(String macroName) {
this.defines.remove(macroName);
}
@ -592,16 +641,21 @@ public class CPreprocessor implements TokenSource {
* Skip whitespace tokens (except newlines), positioning iterator at the next non-whitespace
*
* @param cTokenSource The token iterator
* @return The skipped tokens
*/
private void skipWhitespace(CTokenSource cTokenSource) {
private List<Token> skipWhitespace(CTokenSource cTokenSource) {
List<Token> ws = new ArrayList<>();
while(true) {
final Token token = cTokenSource.peekToken();
if(token.getChannel() != CParser.CHANNEL_WHITESPACE)
break;
if(token.getText().contains("\n"))
break;
// The token is whitespace
ws.add(token);
cTokenSource.nextToken();
}
return ws;
}
/**
@ -676,5 +730,69 @@ public class CPreprocessor implements TokenSource {
}
}
/**
* A pragma token that should be handled in the parser (not in the lexer)
* Used to allow the lexer to ensure the preprocessor skips it the second time around.
**/
public static class PragmaToken implements Token {
/** The underlying pragma token. */
private Token subToken;
PragmaToken(Token subToken) {
this.subToken = subToken;
}
@Override
public String getText() {
return subToken.getText();
}
@Override
public int getType() {
return subToken.getType();
}
@Override
public int getLine() {
return subToken.getLine();
}
@Override
public int getCharPositionInLine() {
return subToken.getCharPositionInLine();
}
@Override
public int getChannel() {
return subToken.getChannel();
}
@Override
public int getTokenIndex() {
return subToken.getTokenIndex();
}
@Override
public int getStartIndex() {
return subToken.getStartIndex();
}
@Override
public int getStopIndex() {
return subToken.getStopIndex();
}
@Override
public TokenSource getTokenSource() {
return subToken.getTokenSource();
}
@Override
public CharStream getInputStream() {
return subToken.getInputStream();
}
}
}

View File

@ -1,5 +1,8 @@
{
"link": "c64.ld",
"cpu": "MOS6502X",
"emulator": "x64sc"
"emulator": "x64sc",
"defines": {
"__C64__": 1
}
}

View File

@ -1,5 +1,8 @@
{
"link": "c64basic.ld",
"cpu": "MOS6502X",
"emulator": "x64sc"
"emulator": "x64sc",
"defines": {
"__C64__": 1
}
}

View File

@ -1,5 +1,8 @@
{
"link": "plus4.ld",
"cpu": "MOS6502X",
"emulator": "xplus4"
"emulator": "xplus4",
"defines": {
"__PLUS4__": 1
}
}

View File

@ -1,5 +1,8 @@
{
"link": "plus4basic.ld",
"cpu": "MOS6502X",
"emulator": "xplus4"
"emulator": "xplus4",
"defines": {
"__PLUS4__": 1
}
}

View File

@ -42,6 +42,16 @@ public class TestPrograms {
public TestPrograms() {
}
//@Test
//public void testPlus4Define() throws IOException, URISyntaxException {
// compileAndCompare("plus4-define.c", log());
//}
@Test
public void testIncludeDefine() throws IOException, URISyntaxException {
compileAndCompare("include-define.c");
}
@Test
public void testStructPointerInts() throws IOException, URISyntaxException {
compileAndCompare("struct-pointer-ints.c");
@ -82,8 +92,6 @@ public class TestPrograms {
compileAndCompare("stars-1.c");
}
/* TODO: Add support for var*var
@Test
public void testMultiply3() throws IOException, URISyntaxException {
@ -4314,7 +4322,7 @@ public class TestPrograms {
files.add(filePath);
Program program = compiler.getProgram();
TargetPlatform.setTargetPlatform(TargetPlatform.DEFAULT_NAME, filePath, program, null);
compiler.compile(files, null);
compiler.compile(files, program.getTargetPlatform().getDefines());
compileAsm(fileName, program);
boolean success = true;
ReferenceHelper helper = new ReferenceHelperFolder(refPath);

View File

@ -0,0 +1,4 @@
// Test including a files with a #define and using it
// This file is included and contains a #define
#define DEFX char x='a'

View File

@ -0,0 +1,8 @@
// Test including a files with a #define and using it
#include "include-define-sub.h"
DEFX;
char * const SCREEN = 0x0400;
void main() {
SCREEN[0] = x;
}

View File

@ -0,0 +1,13 @@
// Test including a files with a #define and using it
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
.const x = 'a'
.label SCREEN = $400
main: {
// SCREEN[0] = x
lda #x
sta SCREEN
// }
rts
}

View File

@ -0,0 +1,17 @@
@begin: scope:[] from
[0] phi()
to:@1
@1: scope:[] from @begin
[1] phi()
[2] call main
to:@end
@end: scope:[] from @1
[3] phi()
(void()) main()
main: scope:[main] from @1
[4] *((const nomodify byte*) SCREEN) ← (const byte) x
to:main::@return
main::@return: scope:[main] from main
[5] return
to:@return

View File

@ -0,0 +1,221 @@
CONTROL FLOW GRAPH SSA
@begin: scope:[] from
to:@1
(void()) main()
main: scope:[main] from @1
*((const nomodify byte*) SCREEN + (number) 0) ← (const byte) x
to:main::@return
main::@return: scope:[main] from main
return
to:@return
@1: scope:[] from @begin
call main
to:@2
@2: scope:[] from @1
to:@end
@end: scope:[] from @2
SYMBOL TABLE SSA
(label) @1
(label) @2
(label) @begin
(label) @end
(const nomodify byte*) SCREEN = (byte*)(number) $400
(void()) main()
(label) main::@return
(const byte) x = (byte) 'a'
Adding number conversion cast (unumber) 0 in *((const nomodify byte*) SCREEN + (number) 0) ← (const byte) x
Successful SSA optimization PassNAddNumberTypeConversions
Simplifying constant pointer cast (byte*) 1024
Simplifying constant integer cast 0
Successful SSA optimization PassNCastSimplification
Finalized unsigned number type (byte) 0
Successful SSA optimization PassNFinalizeNumberTypeConversions
Simplifying expression containing zero SCREEN in [0] *((const nomodify byte*) SCREEN + (byte) 0) ← (const byte) x
Successful SSA optimization PassNSimplifyExpressionWithZero
Adding NOP phi() at start of @begin
Adding NOP phi() at start of @1
Adding NOP phi() at start of @2
Adding NOP phi() at start of @end
CALL GRAPH
Calls in [] to main:2
Created 0 initial phi equivalence classes
Coalesced down to 0 phi equivalence classes
Culled Empty Block (label) @2
Adding NOP phi() at start of @begin
Adding NOP phi() at start of @1
Adding NOP phi() at start of @end
FINAL CONTROL FLOW GRAPH
@begin: scope:[] from
[0] phi()
to:@1
@1: scope:[] from @begin
[1] phi()
[2] call main
to:@end
@end: scope:[] from @1
[3] phi()
(void()) main()
main: scope:[main] from @1
[4] *((const nomodify byte*) SCREEN) ← (const byte) x
to:main::@return
main::@return: scope:[main] from main
[5] return
to:@return
VARIABLE REGISTER WEIGHTS
(void()) main()
Initial phi equivalence classes
Complete equivalence classes
INITIAL ASM
Target platform is c64basic / MOS6502X
// File Comments
// Test including a files with a #define and using it
// Upstart
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
// Global Constants & labels
.const x = 'a'
.label SCREEN = $400
// @begin
__bbegin:
// [1] phi from @begin to @1 [phi:@begin->@1]
__b1_from___bbegin:
jmp __b1
// @1
__b1:
// [2] call main
jsr main
// [3] phi from @1 to @end [phi:@1->@end]
__bend_from___b1:
jmp __bend
// @end
__bend:
// main
main: {
// [4] *((const nomodify byte*) SCREEN) ← (const byte) x -- _deref_pbuc1=vbuc2
lda #x
sta SCREEN
jmp __breturn
// main::@return
__breturn:
// [5] return
rts
}
// File Data
REGISTER UPLIFT POTENTIAL REGISTERS
Statement [4] *((const nomodify byte*) SCREEN) ← (const byte) x [ ] ( main:2 [ ] { } ) always clobbers reg byte a
REGISTER UPLIFT SCOPES
Uplift Scope [main]
Uplift Scope []
Uplifting [main] best 27 combination
Uplifting [] best 27 combination
ASSEMBLER BEFORE OPTIMIZATION
// File Comments
// Test including a files with a #define and using it
// Upstart
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
// Global Constants & labels
.const x = 'a'
.label SCREEN = $400
// @begin
__bbegin:
// [1] phi from @begin to @1 [phi:@begin->@1]
__b1_from___bbegin:
jmp __b1
// @1
__b1:
// [2] call main
jsr main
// [3] phi from @1 to @end [phi:@1->@end]
__bend_from___b1:
jmp __bend
// @end
__bend:
// main
main: {
// [4] *((const nomodify byte*) SCREEN) ← (const byte) x -- _deref_pbuc1=vbuc2
lda #x
sta SCREEN
jmp __breturn
// main::@return
__breturn:
// [5] return
rts
}
// File Data
ASSEMBLER OPTIMIZATIONS
Removing instruction jmp __b1
Removing instruction jmp __bend
Removing instruction jmp __breturn
Succesful ASM optimization Pass5NextJumpElimination
Removing instruction __b1_from___bbegin:
Removing instruction __b1:
Removing instruction __bend_from___b1:
Succesful ASM optimization Pass5RedundantLabelElimination
Removing instruction __bbegin:
Removing instruction __bend:
Removing instruction __breturn:
Succesful ASM optimization Pass5UnusedLabelElimination
Removing instruction jsr main
Succesful ASM optimization Pass5SkipBegin
FINAL SYMBOL TABLE
(label) @1
(label) @begin
(label) @end
(const nomodify byte*) SCREEN = (byte*) 1024
(void()) main()
(label) main::@return
(const byte) x = (byte) 'a'
FINAL ASSEMBLER
Score: 12
// File Comments
// Test including a files with a #define and using it
// Upstart
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
// Global Constants & labels
.const x = 'a'
.label SCREEN = $400
// @begin
// [1] phi from @begin to @1 [phi:@begin->@1]
// @1
// [2] call main
// [3] phi from @1 to @end [phi:@1->@end]
// @end
// main
main: {
// SCREEN[0] = x
// [4] *((const nomodify byte*) SCREEN) ← (const byte) x -- _deref_pbuc1=vbuc2
lda #x
sta SCREEN
// main::@return
// }
// [5] return
rts
}
// File Data

View File

@ -0,0 +1,8 @@
(label) @1
(label) @begin
(label) @end
(const nomodify byte*) SCREEN = (byte*) 1024
(void()) main()
(label) main::@return
(const byte) x = (byte) 'a'