add if-expression versions for the conditionals if_cc, if_cs, if_vc etc

This commit is contained in:
Irmen de Jong
2025-09-11 01:57:30 +02:00
parent 1c77d5d5e7
commit 79419a98d0
18 changed files with 294 additions and 19 deletions

View File

@@ -860,7 +860,7 @@ class AsmGen6502Internal (
}
}
private fun branchInstruction(condition: BranchCondition, complement: Boolean) =
internal fun branchInstruction(condition: BranchCondition, complement: Boolean) =
if(complement) {
when (condition) {
BranchCondition.CS -> "bcc"
@@ -1698,6 +1698,10 @@ $repeatLabel""")
ifExpressionAsmgen.assignIfExpression(target, value)
}
internal fun assignBranchCondExpression(target: AsmAssignTarget, value: PtBranchCondExpression) {
ifExpressionAsmgen.assignBranchCondExpression(target, value)
}
internal fun cmpAwithByteValue(value: PtExpression, useSbc: Boolean) {
val compare = if(useSbc) "sec | sbc" else "cmp"
fun cmpViaScratch() {

View File

@@ -44,6 +44,69 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
}
internal fun assignBranchCondExpression(target: AsmAssignTarget, expr: PtBranchCondExpression) {
require(target.datatype==expr.type ||
target.datatype.isUnsignedWord && (expr.type.isString || expr.type.isPointer) ||
target.datatype.isPointer && (expr.type.isUnsignedWord || expr.type.isPointer || expr.type.isString))
if(target.kind==TargetStorageKind.REGISTER && target.datatype.isUnsignedByte && target.register==RegisterOrPair.A) {
if(expr.condition==BranchCondition.CC) {
if(expr.truevalue.asConstInteger()==0 && expr.falsevalue.asConstInteger()==1) {
asmgen.out(" lda #0 | rol a")
return
}
else if(expr.truevalue.asConstInteger()==1 && expr.falsevalue.asConstInteger()==0) {
asmgen.out(" lda #0 | rol a | eor #1")
return
}
}
else if(expr.condition==BranchCondition.CS) {
if(expr.truevalue.asConstInteger()==0 && expr.falsevalue.asConstInteger()==1) {
asmgen.out(" lda #0 | rol a | eor #1")
return
}
else if(expr.truevalue.asConstInteger()==1 && expr.falsevalue.asConstInteger()==0) {
asmgen.out(" lda #0 | rol a")
return
}
}
}
val trueLabel = asmgen.makeLabel("branchexpr_true")
val endLabel = asmgen.makeLabel("branchexpr_end")
val branch = asmgen.branchInstruction(expr.condition, false)
asmgen.out(" $branch $trueLabel")
when {
expr.type.isByteOrBool -> {
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A)
asmgen.jmp(endLabel)
asmgen.out(trueLabel)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
expr.type.isWord || expr.type.isString -> {
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
asmgen.jmp(endLabel)
asmgen.out(trueLabel)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY)
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
expr.type.isFloat -> {
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true)
asmgen.jmp(endLabel)
asmgen.out(trueLabel)
asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true)
asmgen.out(endLabel)
asmgen.assignRegister(RegisterOrPair.FAC1, target)
}
else -> throw AssemblyError("weird dt")
}
}
private fun evalIfExpressionConditonAndBranchWhenFalse(condition: PtExpression, falseLabel: String) {
when (condition) {
is PtBinaryExpression -> {

View File

@@ -526,6 +526,7 @@ internal class AssignmentAsmGen(
}
}
is PtIfExpression -> asmgen.assignIfExpression(assign.target, value)
is PtBranchCondExpression -> asmgen.assignBranchCondExpression(assign.target, value)
is PtPointerDeref -> pointergen.assignPointerDerefExpression(assign.target, value)
else -> throw AssemblyError("weird assignment value type $value")
}

View File

@@ -83,6 +83,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
is PtArrayIndexer -> translate(expr)
is PtBinaryExpression -> translate(expr)
is PtIfExpression -> translate(expr)
is PtBranchCondExpression -> translate(expr)
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr)
is PtFunctionCall -> translate(expr)
is PtContainmentCheck -> translate(expr)
@@ -253,6 +254,69 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun translate(branchExpr: PtBranchCondExpression): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val trueTr = translateExpression(branchExpr.truevalue)
val falseTr = translateExpression(branchExpr.falsevalue)
val trueLabel = codeGen.createLabelName()
val endLabel = codeGen.createLabelName()
val irDt = irType(branchExpr.type)
if(branchExpr.condition==BranchCondition.CC && irDt==IRDataType.BYTE) {
if(branchExpr.truevalue.asConstInteger()==0 && branchExpr.falsevalue.asConstInteger()==1) {
result.add(IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg)
})
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
}
else if(branchExpr.truevalue.asConstInteger()==1 && branchExpr.falsevalue.asConstInteger()==0) {
result.add(IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg)
it += IRInstruction(Opcode.XOR, irDt, reg1=trueTr.resultReg, immediate = 1)
})
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
}
}
else if(branchExpr.condition==BranchCondition.CS) {
if(branchExpr.truevalue.asConstInteger()==0 && branchExpr.falsevalue.asConstInteger()==1) {
result.add(IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg)
it += IRInstruction(Opcode.XOR, irDt, reg1=trueTr.resultReg, immediate = 1)
})
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
}
else if(branchExpr.truevalue.asConstInteger()==1 && branchExpr.falsevalue.asConstInteger()==0) {
result.add(IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, irDt, reg1=trueTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.ROXL, irDt, reg1=trueTr.resultReg)
})
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
}
}
val branchInstr = codeGen.IRBranchInstr(branchExpr.condition, trueLabel)
addInstr(result, branchInstr, null)
if (irDt != IRDataType.FLOAT) {
addToResult(result, falseTr, trueTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
result += IRCodeChunk(trueLabel, null)
addToResult(result, trueTr, trueTr.resultReg, -1)
result += IRCodeChunk(endLabel, null)
return ExpressionCodeResult(result, irDt, trueTr.resultReg, -1)
} else {
addToResult(result, falseTr, -1, trueTr.resultFpReg)
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
result += IRCodeChunk(trueLabel, null)
addToResult(result, trueTr, -1, trueTr.resultFpReg)
result += IRCodeChunk(endLabel, null)
return ExpressionCodeResult(result, irDt, -1, trueTr.resultFpReg)
}
}
private fun translate(expr: PtAddressOf): ExpressionCodeResult {
val vmDt = irType(expr.type)
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location

View File

@@ -336,7 +336,7 @@ class IRCodeGen(
return result
}
private fun IRBranchInstr(condition: BranchCondition, label: String?=null, address: Int?=null): IRInstruction {
internal fun IRBranchInstr(condition: BranchCondition, label: String?=null, address: Int?=null): IRInstruction {
if(label!=null)
return when(condition) {
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)

View File

@@ -54,6 +54,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|| removeDoubleSecClc(chunk1, indexedInstructions)
|| cleanupPushPop(chunk1, indexedInstructions)
|| simplifyConstantReturns(chunk1, indexedInstructions)
|| removeNeedlessLoads(chunk1, indexedInstructions)
} while (changed)
}
}
@@ -353,6 +354,29 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
return changed
}
private fun removeNeedlessLoads(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
/*
load.b r2,#2
loadr.b r1,r2
jump p8_label_gen_2
*/
var changed=false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(idx>=2 && ins.opcode in OpcodesThatJump) {
val previous = indexedInstructions[idx-1].value
val previous2 = indexedInstructions[idx-2].value
if(previous.opcode==Opcode.LOADR && previous2.opcode in OpcodesThatLoad) {
if(previous.reg2==previous2.reg1) {
chunk.instructions[idx-2] = previous2.copy(reg1=previous.reg1)
chunk.instructions.removeAt(idx-1)
changed=true
}
}
}
}
return changed
}
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
var changed = false

View File

@@ -100,6 +100,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
is StringLiteral -> transform(expr)
is TypecastExpression -> transform(expr)
is IfExpression -> transform(expr)
is BranchConditionExpression -> transform(expr)
is PtrDereference -> transform(expr)
is StaticStructInitializer -> transform(expr)
is ArrayIndexedPtrDereference -> throw FatalAstException("this should have been converted to some other ast nodes")
@@ -141,6 +142,14 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
return ifexpr
}
private fun transform(branchExpr: BranchConditionExpression): PtBranchCondExpression {
val type = branchExpr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val bexpr = PtBranchCondExpression(branchExpr.condition, type, branchExpr.position)
bexpr.add(transformExpression(branchExpr.truevalue))
bexpr.add(transformExpression(branchExpr.falsevalue))
return bexpr
}
private fun transform(srcDefer: Defer): PtDefer {
val defer = PtDefer(srcDefer.position)
srcDefer.scope.statements.forEach {

View File

@@ -66,6 +66,13 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
ifExpr.falsevalue.accept(this)
}
override fun visit(branchExpr: BranchConditionExpression) {
output("if_${branchExpr.condition.name.lowercase()} ")
branchExpr.truevalue.accept(this)
output(" else ")
branchExpr.falsevalue.accept(this)
}
override fun visit(continueStmt: Continue) {
output("continue")
}

View File

@@ -551,10 +551,10 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
return IfExpression(condition, truevalue, falsevalue, ctx.toPosition())
}
override fun visitBranchcondition_expression(ctx: Branchcondition_expressionContext): IfExpression {
val branchcondition = branchCondition(ctx.branchcondition())
override fun visitBranchcondition_expression(ctx: Branchcondition_expressionContext): BranchConditionExpression {
val condition = branchCondition(ctx.branchcondition())
val (truevalue, falsevalue) = ctx.expression().map { it.accept(this) as Expression }
throw SyntaxError("branchcondition expression not yet supported", ctx.toPosition())
return BranchConditionExpression(condition, truevalue, falsevalue, ctx.toPosition())
}
override fun visitBranch_stmt(ctx: Branch_stmtContext): ConditionalBranch {

View File

@@ -1660,6 +1660,42 @@ class IfExpression(var condition: Expression, var truevalue: Expression, var fal
}
}
class BranchConditionExpression(var condition: BranchCondition, var truevalue: Expression, var falsevalue: Expression, override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
truevalue.linkParents(this)
falsevalue.linkParents(this)
}
override val isSimple: Boolean = truevalue.isSimple && falsevalue.isSimple
override fun constValue(program: Program) = null
override fun toString() = "BranchExpr(cond=$condition, true=$truevalue, false=$falsevalue, pos=$position)"
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>): Boolean = truevalue.referencesIdentifier(nameInSource) || falsevalue.referencesIdentifier(nameInSource)
override fun inferType(program: Program): InferredTypes.InferredType {
val t1 = truevalue.inferType(program)
val t2 = falsevalue.inferType(program)
if(t1==t2) return t1
if(t1.isPointer && t2.isUnsignedWord || t1.isUnsignedWord && t2.isPointer) return InferredTypes.knownFor(BaseDataType.UWORD)
return InferredTypes.unknown()
}
override fun copy(): Expression = BranchConditionExpression(condition, truevalue.copy(), falsevalue.copy(), position)
override fun replaceChildNode(node: Node, replacement: Node) {
if(replacement !is Expression)
throw FatalAstException("invalid replace")
else if(node===truevalue) truevalue=replacement
else if(node===falsevalue) falsevalue=replacement
else throw FatalAstException("invalid replace")
}
}
class PtrDereference(
val chain: List<String>,
val derefLast: Boolean,

View File

@@ -114,6 +114,7 @@ abstract class AstWalker {
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(ifExpr: IfExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(branchExpr: BranchConditionExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> = noModifications
@@ -165,6 +166,7 @@ abstract class AstWalker {
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(ifExpr: IfExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(branchExpr: BranchConditionExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(unrollLoop: UnrollLoop, parent: Node): Iterable<IAstModification> = noModifications
@@ -510,6 +512,13 @@ abstract class AstWalker {
track(after(ifExpr, parent), ifExpr, parent)
}
fun visit(branchExpr: BranchConditionExpression, parent: Node) {
track(before(branchExpr, parent), branchExpr, parent)
branchExpr.truevalue.accept(this, branchExpr)
branchExpr.falsevalue.accept(this, branchExpr)
track(after(branchExpr, parent), branchExpr, parent)
}
fun visit(inlineAssembly: InlineAssembly, parent: Node) {
track(before(inlineAssembly, parent), inlineAssembly, parent)
track(after(inlineAssembly, parent), inlineAssembly, parent)

View File

@@ -100,6 +100,11 @@ interface IAstVisitor {
ifExpr.falsevalue.accept(this)
}
fun visit(branchExpr: BranchConditionExpression) {
branchExpr.truevalue.accept(this)
branchExpr.falsevalue.accept(this)
}
fun visit(label: Label) {
}

View File

@@ -742,6 +742,12 @@ An example, to select the number of cards to use depending on what game is playe
else
numcards = 52
The expression form is also available for the conditionals ``if_cc``, ``if_cs``, ``if_z`` etc.
(These particular variants for checking the value of the Carry status bit actually compile into very efficient branchless assembly code)::
ubyte carryvalue = if_cs 1 else 0
on .. goto / on .. call statement (jump table)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -78,7 +78,6 @@ STRUCTS and TYPED POINTERS
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- add if-expression versions for the conditionals if_cc, if_cs, if_vc etc etc so you can write a = b + if_cs 1 else 0 (make sure it gets compiled to nice instructions lda #0; rol a etc.) See visitBranchcondition_expression()
- %breakpoint after an assignment is parsed as part of the expression (x % breakpoint), that should not happen
- when a complete block is removed because unused, suppress all info messages about everything in the block being removed
- fix the line, cols in Position, sometimes they count from 0 sometimes from 1

View File

@@ -1,19 +1,42 @@
%import textio
%zeropage basicsafe
main {
struct Node {
ubyte id
}
^^byte @shared bptr = 2000
^^word @shared swptr = 2000
^^uword @shared uwptr = 2000
^^Node @shared node = 2000
sub start() {
alias nid = node.id
cx16.r0 = mkword(0, cx16.r9L)
swptr^^ = mkword(0, cx16.r9L) as word
uwptr^^ = mkword(0, cx16.r9L)
sys.set_carry()
txt.print_ub(if_cc 0 else 1)
txt.nl()
sys.clear_carry()
txt.print_ub(if_cc 0 else 1)
txt.nl()
sys.set_carry()
txt.print_ub(if_cs 0 else 1)
txt.nl()
sys.clear_carry()
txt.print_ub(if_cs 0 else 1)
txt.nl()
}
}
;%import textio
;%zeropage basicsafe
;
;main {
; struct Node {
; ubyte id
; str name
; uword array
; }
;
; ^^Node @shared @zp node = 2000
;
; sub start() {
; txt.print_uw(node)
; txt.spc()
; cx16.r0 = &node.array ; TODO don't clobber node pointer itself!!
; txt.print_uw(cx16.r0)
; txt.spc()
; txt.print_uw(node)
; txt.nl()
; }
;}

View File

@@ -514,6 +514,23 @@ val OpcodesThatDependOnCarry = arrayOf(
Opcode.ROXRM
)
val OpcodesThatLoad = arrayOf(
Opcode.LOAD,
Opcode.LOADM,
Opcode.LOADI,
Opcode.LOADX,
Opcode.LOADIX,
Opcode.LOADR,
Opcode.LOADHA,
Opcode.LOADHX,
Opcode.LOADHY,
Opcode.LOADHAX,
Opcode.LOADHAY,
Opcode.LOADHXY,
Opcode.LOADFIELD,
Opcode.LOADHFACZERO,
Opcode.LOADHFACONE
)
val OpcodesThatSetStatusbits = OpcodesThatSetStatusbitsButNotCarry + OpcodesThatSetStatusbitsIncludingCarry

View File

@@ -141,6 +141,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtPointerDeref -> false
is PtTypeCast -> value.isSimple()
is PtIfExpression -> condition.isSimple() && truevalue.isSimple() && falsevalue.isSimple()
is PtBranchCondExpression -> truevalue.isSimple() && falsevalue.isSimple()
}
}
@@ -259,6 +260,12 @@ class PtIfExpression(type: DataType, position: Position): PtExpression(type, pos
get() = children[2] as PtExpression
}
class PtBranchCondExpression(val condition: BranchCondition, type: DataType, position: Position): PtExpression(type, position) {
val truevalue: PtExpression
get() = children[0] as PtExpression
val falsevalue: PtExpression
get() = children[1] as PtExpression
}
class PtContainmentCheck(position: Position): PtExpression(DataType.BOOL, position) {
val needle: PtExpression

View File

@@ -188,6 +188,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}
is PtDefer -> "<defer>"
is PtIfExpression -> "<ifexpr>"
is PtBranchCondExpression -> "<branchexpr> if_${node.condition.name.lowercase()}"
is PtJmpTable -> "<jmptable>"
is PtStructDecl -> {
"struct ${node.name} { " + node.fields.joinToString(" ") { "${it.first} ${it.second}" } + " }"