1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-08-02 09:29:35 +00:00

Implemented proper handling of referenced to constants inside inline ASM. Closes #146

This commit is contained in:
jespergravgaard 2019-03-15 00:02:33 +01:00
parent d293c47141
commit 4eed4f7659
14 changed files with 405 additions and 25 deletions

View File

@ -190,7 +190,7 @@ public class ControlFlowGraphCopyVisitor extends ControlFlowGraphBaseVisitor<Obj
@Override
public Object visitAsm(StatementAsm orig) {
return new StatementAsm(orig.getAsmLines(), orig.getSource(), orig.getComments());
return new StatementAsm(orig.getAsmLines(), orig.getReferenced(), orig.getSource(), orig.getComments());
}
@Override

View File

@ -202,6 +202,27 @@ public abstract class ProgramValue {
}
/** A variable/constant referenced inside inline ASM. */
public static class AsmReferenced extends ProgramValue {
private StatementAsm statementAsm;
private String label;
public AsmReferenced(StatementAsm statementAsm, String label) {
this.statementAsm = statementAsm;
this.label = label;
}
@Override
public RValue get() {
return statementAsm.getReferenced().get(label);
}
@Override
public void set(RValue value) {
statementAsm.getReferenced().put(label, (SymbolVariableRef) value);
}
}
/** Location inside inline kickasm code. */
public static class KickAsmLocation extends ProgramValue {
@ -615,4 +636,5 @@ public abstract class ProgramValue {
}
}
}

View File

@ -9,10 +9,14 @@ import dk.camelot64.kickc.model.symbols.ProgramScope;
import dk.camelot64.kickc.model.symbols.SymbolVariable;
import dk.camelot64.kickc.model.types.SymbolTypeArray;
import dk.camelot64.kickc.model.values.*;
import dk.camelot64.kickc.parser.KickCBaseVisitor;
import dk.camelot64.kickc.parser.KickCParser;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ListIterator;
import java.util.Map;
/**
* Capable of iterating the different structures of a Program (graph, block, statement, symboltable, symbol).
@ -134,7 +138,11 @@ public class ProgramValueIterator {
execute(new ProgramValue.KickAsmCycles(statementKickAsm), handler, statement, statementsIt, block);
}
} else if(statement instanceof StatementAsm) {
StatementAsm statementAsm = (StatementAsm) statement;
Map<String, SymbolVariableRef> referenced = statementAsm.getReferenced();
for(String label : referenced.keySet()) {
execute(new ProgramValue.AsmReferenced(statementAsm, label), handler, statement, statementsIt, block);
}
}
}

View File

@ -2,9 +2,11 @@ package dk.camelot64.kickc.model.statements;
import dk.camelot64.kickc.model.Comment;
import dk.camelot64.kickc.model.Program;
import dk.camelot64.kickc.model.values.SymbolVariableRef;
import dk.camelot64.kickc.parser.KickCParser;
import java.util.List;
import java.util.Map;
/** Inline ASM code */
public class StatementAsm extends StatementBase {
@ -12,9 +14,13 @@ public class StatementAsm extends StatementBase {
/** ASM Fragment code. */
private KickCParser.AsmLinesContext asmLines;
public StatementAsm(KickCParser.AsmLinesContext asmLines, StatementSource source, List<Comment> comments) {
/** All variables/constants referenced in the inline assembler. */
private Map<String, SymbolVariableRef> referenced;
public StatementAsm(KickCParser.AsmLinesContext asmLines, Map<String, SymbolVariableRef> referenced, StatementSource source, List<Comment> comments) {
super(null, source, comments);
this.asmLines = asmLines;
this.referenced = referenced;
}
@Override
@ -32,4 +38,11 @@ public class StatementAsm extends StatementBase {
return asmLines;
}
public void setReferenced(Map<String, SymbolVariableRef> referenced) {
this.referenced = referenced;
}
public Map<String, SymbolVariableRef> getReferenced() {
return referenced;
}
}

View File

@ -18,10 +18,7 @@ import org.antlr.v4.runtime.tree.TerminalNode;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Stack;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -73,7 +70,7 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
@Override
public Void visitFile(KickCParser.FileContext ctx) {
if(program.getFileComments()==null) {
if(program.getFileComments() == null) {
// Only set program file level comments for the first file.
program.setFileComments(ensureUnusedComments(getCommentsFile(ctx)));
}
@ -325,7 +322,7 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
ConstantInteger zero = new ConstantInteger(0l);
Statement stmt = new StatementAssignment(lValue.getRef(), zero, new StatementSource(ctx), ensureUnusedComments(comments));
sequence.addStatement(stmt);
} else if(type instanceof SymbolTypeArray) {
} else if(type instanceof SymbolTypeArray) {
// Add an zero-array initializer
SymbolTypeArray typeArray = (SymbolTypeArray) type;
RValue size = typeArray.getSize();
@ -334,14 +331,14 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
}
Statement stmt = new StatementAssignment(lValue.getRef(), new ArrayFilled(typeArray.getElementType(), size), new StatementSource(ctx), ensureUnusedComments(comments));
sequence.addStatement(stmt);
} else if(type instanceof SymbolTypePointer) {
} else if(type instanceof SymbolTypePointer) {
// Add an zero value initializer
SymbolTypePointer typePointer = (SymbolTypePointer) type;
ConstantValue zero = new ConstantPointer(0l, typePointer.getElementType());
Statement stmt = new StatementAssignment(lValue.getRef(), zero, new StatementSource(ctx), ensureUnusedComments(comments));
sequence.addStatement(stmt);
} else {
throw new CompileError("Default initializer not implemented for type "+type.getTypeName(), new StatementSource(ctx));
throw new CompileError("Default initializer not implemented for type " + type.getTypeName(), new StatementSource(ctx));
}
}
@ -538,7 +535,7 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
Label doJumpLabel = getCurrentSymbols().addLabelIntermediate();
Label endJumpLabel = getCurrentSymbols().addLabelIntermediate();
List<Comment> comments = ensureUnusedComments(getCommentsSymbol(ctx));
StatementLabel beginJumpTarget = new StatementLabel(beginJumpLabel.getRef(), new StatementSource(ctx),comments);
StatementLabel beginJumpTarget = new StatementLabel(beginJumpLabel.getRef(), new StatementSource(ctx), comments);
sequence.addStatement(beginJumpTarget);
PrePostModifierHandler.addPreModifiers(this, ctx.expr());
RValue rValue = (RValue) this.visit(ctx.expr());
@ -694,7 +691,26 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
@Override
public Object visitStmtAsm(KickCParser.StmtAsmContext ctx) {
List<Comment> comments = ensureUnusedComments(getCommentsSymbol(ctx));
sequence.addStatement(new StatementAsm(ctx.asmLines(), new StatementSource(ctx), comments));
Map<String, SymbolVariableRef> referenced = new LinkedHashMap<>();
// Find all referenced symbols in the asm lines
KickCBaseVisitor<Void> visitor = new KickCBaseVisitor<Void>() {
@Override
public Void visitAsmExprLabel(KickCParser.AsmExprLabelContext ctxLabel) {
String label = ctxLabel.NAME().toString();
Symbol symbol = getCurrentSymbols().getSymbol(ctxLabel.NAME().getText());
if(symbol instanceof Variable) {
Variable variable = (Variable) symbol;
referenced.put(label, variable.getRef());
} else {
throw new CompileError("Symbol referenced in inline ASM not found " + label, new StatementSource(ctxLabel));
}
return super.visitAsmExprLabel(ctxLabel);
}
};
visitor.visit(ctx.asmLines());
StatementAsm statementAsm = new StatementAsm(ctx.asmLines(), referenced, new StatementSource(ctx), comments);
sequence.addStatement(statementAsm);
return null;
}
@ -775,7 +791,7 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
public Object visitExprAssignment(KickCParser.ExprAssignmentContext ctx) {
Object val = visit(ctx.expr(0));
if(!(val instanceof LValue)) {
throw new CompileError("Error! Illegal assignment Lvalue " + val.toString(), new StatementSource(ctx));
throw new CompileError("Error! Illegal assignment Lvalue " + val.toString(), new StatementSource(ctx));
}
LValue lValue = (LValue) val;
if(lValue instanceof VariableRef && ((VariableRef) lValue).isIntermediate()) {
@ -966,7 +982,7 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
if(hiddenToken.getChannel() == CHANNEL_WHITESPACE) {
String text = hiddenToken.getText();
long newlineCount = text.chars().filter(ch -> ch == '\n').count();
if(newlineCount > 1 && comments.size()>0) {
if(newlineCount > 1 && comments.size() > 0) {
// Create new comment block
commentBlocks.add(comments);
comments = new ArrayList<>();
@ -978,17 +994,17 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
text = text.substring(2);
}
if(text.startsWith("/*")) {
text = text.substring(2, text.length()-2);
text = text.substring(2, text.length() - 2);
isBlock = true;
}
Comment comment = new Comment(text);
comment.setBlock(isBlock);
comment.setTokenIndex(hiddenToken.getTokenIndex());
comments.add( comment);
comments.add(comment);
}
}
}
if(comments.size()>0) {
if(comments.size() > 0) {
commentBlocks.add(comments);
}
return commentBlocks;
@ -1004,7 +1020,7 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
* @return The comments if they are unused. An empty comment if they had already been used.
*/
private List<Comment> ensureUnusedComments(List<Comment> candidate) {
if(candidate.size()==0) {
if(candidate.size() == 0) {
return candidate;
}
int tokenIndex = candidate.get(0).getTokenIndex();
@ -1027,7 +1043,7 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
*/
private List<Comment> getCommentsFile(ParserRuleContext ctx) {
List<List<Comment>> commentBlocks = getCommentBlocks(ctx);
if(commentBlocks.size()==0) {
if(commentBlocks.size() == 0) {
return new ArrayList<>();
}
return commentBlocks.get(0);
@ -1042,7 +1058,7 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor<Object> {
*/
private List<Comment> getCommentsSymbol(ParserRuleContext ctx) {
List<List<Comment>> commentBlocks = getCommentBlocks(ctx);
if(commentBlocks.size()==0) {
if(commentBlocks.size() == 0) {
return new ArrayList<>();
}
return commentBlocks.get(commentBlocks.size() - 1);

View File

@ -4,14 +4,22 @@ import dk.camelot64.kickc.model.CompileError;
import dk.camelot64.kickc.model.ControlFlowBlock;
import dk.camelot64.kickc.model.Program;
import dk.camelot64.kickc.model.statements.Statement;
import dk.camelot64.kickc.model.statements.StatementAsm;
import dk.camelot64.kickc.model.statements.StatementKickAsm;
import dk.camelot64.kickc.model.values.ConstantRef;
import dk.camelot64.kickc.model.values.ConstantValue;
import dk.camelot64.kickc.model.values.RValue;
import dk.camelot64.kickc.model.values.SymbolVariableRef;
import java.util.Map;
/**
* Asserts that some RValues have been resolved to Constants.
* Checks:
* - KickAssembler locations
* - KickAssembler bytes
* - KickAssembler cycles
* - ASM referenced variables
*/
public class Pass3AssertConstants extends Pass2SsaAssertion {
@ -36,6 +44,15 @@ public class Pass3AssertConstants extends Pass2SsaAssertion {
if(cycles!= null && !(cycles instanceof ConstantValue)) {
throw new CompileError("Error! KickAssembler cycles is not constant " + cycles.toString(), statement);
}
} else if(statement instanceof StatementAsm) {
StatementAsm statementAsm = (StatementAsm) statement;
Map<String, SymbolVariableRef> referenced = statementAsm.getReferenced();
for(String label : referenced.keySet()) {
SymbolVariableRef symbolRef = referenced.get(label);
if(!(symbolRef instanceof ConstantRef)) {
throw new CompileError("Error! Inline ASM reference is not constant " + label, statement);
}
}
}
}
}

View File

@ -44,12 +44,22 @@ public class TestPrograms {
AsmFragmentTemplateUsages.logUsages(log, false, false, false, false, false, false);
}
/*
@Test
public void testInlineAsmRefoutProblem() throws IOException, URISyntaxException {
compileAndCompare("inline-asm-refout-problem");
public void testInlineAsmRefoutIllegal() throws IOException, URISyntaxException {
assertError("inline-asm-refout-illegal", "Inline ASM reference is not constant");
}
@Test
public void testInlineAsmRefoutConst() throws IOException, URISyntaxException {
compileAndCompare("inline-asm-refout-const");
}
@Test
public void testInlineAsmRefoutUndef() throws IOException, URISyntaxException {
assertError("inline-asm-refout-undef", "Symbol referenced in inline ASM not found");
}
/*
@Test
public void testConstIfProblem() throws IOException, URISyntaxException {
compileAndCompare("const-if-problem");

View File

@ -1,4 +1,4 @@
// Illustrates how inline assembler can reference data from the outside program
// Illustrates how inline assembler can reference data from the outside program without the data being optimized away as unused
const byte* SCREEN = $400;
byte[] table = "cml!";

View File

@ -0,0 +1,14 @@
// Illustrates how inline assembler referencing variables is illegal
const byte* SCREEN = $400;
void main() {
for( byte i: 0..10) {
asm {
lda #'a'
ldx i
sta SCREEN,x
}
}
}

View File

@ -0,0 +1,6 @@
// Reference to undefined symbol in inline asm
void main() {
asm {
lda qwe
}
}

View File

@ -0,0 +1,16 @@
// Illustrates how inline assembler can reference data from the outside program without the data being optimized away as unused
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
.label SCREEN = $400
main: {
ldx #0
!:
lda table,x
sta SCREEN+1,x
inx
cpx #4
bne !-
rts
}
table: .text "cml!"

View File

@ -0,0 +1,15 @@
@begin: scope:[] from
[0] phi()
to:@1
@1: scope:[] from @begin
[1] phi()
[2] call main
to:@end
@end: scope:[] from @1
[3] phi()
main: scope:[main] from @1
asm { ldx#0 !: ldatable,x staSCREEN+1,x inx cpx#4 bne!- }
to:main::@return
main::@return: scope:[main] from main
[5] return
to:@return

View File

@ -0,0 +1,233 @@
CONTROL FLOW GRAPH SSA
@begin: scope:[] from
(byte*) SCREEN#0 ← ((byte*)) (word/signed word/dword/signed dword) $400
(byte[]) table#0 ← (const string) $0
to:@1
main: scope:[main] from @1
asm { ldx#0 !: ldatable,x staSCREEN+1,x inx cpx#4 bne!- }
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
(const string) $0 = (string) "cml!"
(label) @1
(label) @2
(label) @begin
(label) @end
(byte*) SCREEN
(byte*) SCREEN#0
(void()) main()
(label) main::@return
(byte[]) table
(byte[]) table#0
Culled Empty Block (label) @2
Successful SSA optimization Pass2CullEmptyBlocks
Constant (const byte*) SCREEN#0 = ((byte*))$400
Constant (const byte[]) table#0 = $0
Successful SSA optimization Pass2ConstantIdentification
Constant inlined $0 = (const byte[]) table#0
Successful SSA optimization Pass2ConstantInlining
Adding NOP phi() at start of @begin
Adding NOP phi() at start of @1
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
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()
main: scope:[main] from @1
asm { ldx#0 !: ldatable,x staSCREEN+1,x inx cpx#4 bne!- }
to:main::@return
main::@return: scope:[main] from main
[5] return
to:@return
VARIABLE REGISTER WEIGHTS
(byte*) SCREEN
(void()) main()
(byte[]) table
Initial phi equivalence classes
Complete equivalence classes
INITIAL ASM
//SEG0 File Comments
// Illustrates how inline assembler can reference data from the outside program without the data being optimized away as unused
//SEG1 Basic Upstart
.pc = $801 "Basic"
:BasicUpstart(bbegin)
.pc = $80d "Program"
//SEG2 Global Constants & labels
.label SCREEN = $400
//SEG3 @begin
bbegin:
//SEG4 [1] phi from @begin to @1 [phi:@begin->@1]
b1_from_bbegin:
jmp b1
//SEG5 @1
b1:
//SEG6 [2] call main
jsr main
//SEG7 [3] phi from @1 to @end [phi:@1->@end]
bend_from_b1:
jmp bend
//SEG8 @end
bend:
//SEG9 main
main: {
//SEG10 asm { ldx#0 !: ldatable,x staSCREEN+1,x inx cpx#4 bne!- }
ldx #0
!:
lda table,x
sta SCREEN+1,x
inx
cpx #4
bne !-
jmp breturn
//SEG11 main::@return
breturn:
//SEG12 [5] return
rts
}
table: .text "cml!"
REGISTER UPLIFT POTENTIAL REGISTERS
Statement asm { ldx#0 !: ldatable,x staSCREEN+1,x inx cpx#4 bne!- } always clobbers reg byte a reg byte x
REGISTER UPLIFT SCOPES
Uplift Scope [main]
Uplift Scope []
Uplifting [main] best 39 combination
Uplifting [] best 39 combination
ASSEMBLER BEFORE OPTIMIZATION
//SEG0 File Comments
// Illustrates how inline assembler can reference data from the outside program without the data being optimized away as unused
//SEG1 Basic Upstart
.pc = $801 "Basic"
:BasicUpstart(bbegin)
.pc = $80d "Program"
//SEG2 Global Constants & labels
.label SCREEN = $400
//SEG3 @begin
bbegin:
//SEG4 [1] phi from @begin to @1 [phi:@begin->@1]
b1_from_bbegin:
jmp b1
//SEG5 @1
b1:
//SEG6 [2] call main
jsr main
//SEG7 [3] phi from @1 to @end [phi:@1->@end]
bend_from_b1:
jmp bend
//SEG8 @end
bend:
//SEG9 main
main: {
//SEG10 asm { ldx#0 !: ldatable,x staSCREEN+1,x inx cpx#4 bne!- }
ldx #0
!:
lda table,x
sta SCREEN+1,x
inx
cpx #4
bne !-
jmp breturn
//SEG11 main::@return
breturn:
//SEG12 [5] return
rts
}
table: .text "cml!"
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 bend:
Removing instruction breturn:
Succesful ASM optimization Pass5UnusedLabelElimination
Updating BasicUpstart to call main directly
Removing instruction jsr main
Succesful ASM optimization Pass5SkipBegin
Removing instruction bbegin:
Succesful ASM optimization Pass5UnusedLabelElimination
FINAL SYMBOL TABLE
(label) @1
(label) @begin
(label) @end
(byte*) SCREEN
(const byte*) SCREEN#0 SCREEN = ((byte*))(word/signed word/dword/signed dword) $400
(void()) main()
(label) main::@return
(byte[]) table
(const byte[]) table#0 table = (string) "cml!"
FINAL ASSEMBLER
Score: 24
//SEG0 File Comments
// Illustrates how inline assembler can reference data from the outside program without the data being optimized away as unused
//SEG1 Basic Upstart
.pc = $801 "Basic"
:BasicUpstart(main)
.pc = $80d "Program"
//SEG2 Global Constants & labels
.label SCREEN = $400
//SEG3 @begin
//SEG4 [1] phi from @begin to @1 [phi:@begin->@1]
//SEG5 @1
//SEG6 [2] call main
//SEG7 [3] phi from @1 to @end [phi:@1->@end]
//SEG8 @end
//SEG9 main
main: {
//SEG10 asm { ldx#0 !: ldatable,x staSCREEN+1,x inx cpx#4 bne!- }
ldx #0
!:
lda table,x
sta SCREEN+1,x
inx
cpx #4
bne !-
//SEG11 main::@return
//SEG12 [5] return
rts
}
table: .text "cml!"

View File

@ -0,0 +1,10 @@
(label) @1
(label) @begin
(label) @end
(byte*) SCREEN
(const byte*) SCREEN#0 SCREEN = ((byte*))(word/signed word/dword/signed dword) $400
(void()) main()
(label) main::@return
(byte[]) table
(const byte[]) table#0 table = (string) "cml!"