adding short-circuit boolean expression evaluation (in IR codegen) also -noshortcircuit cli option

This commit is contained in:
Irmen de Jong 2023-10-30 00:09:18 +01:00
parent 813007a5d8
commit f790182f0b
13 changed files with 146 additions and 46 deletions

View File

@ -18,6 +18,7 @@ class CompilationOptions(val output: OutputType,
var optimize: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
var shortCircuit: Boolean = true,
var includeSourcelines: Boolean = false,
var experimentalCodegen: Boolean = false,
var varsHighBank: Int? = null,

View File

@ -697,35 +697,59 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.AND, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
if(codeGen.options.shortCircuit && (!binExpr.left.isSimple() && !binExpr.right.isSimple())) {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val shortcutLabel = codeGen.createLabelName()
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.ANDR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
addToResult(result, rightTr, leftTr.resultReg, -1)
result += IRCodeChunk(shortcutLabel, null)
return ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
} else {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.AND, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.ANDR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
}
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.OR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
if(codeGen.options.shortCircuit && (!binExpr.left.isSimple() && !binExpr.right.isSimple())) {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val shortcutLabel = codeGen.createLabelName()
addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.ORR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
addToResult(result, rightTr, leftTr.resultReg, -1)
result += IRCodeChunk(shortcutLabel, null)
return ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
} else {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.OR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.ORR, vmDt, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, leftTr.resultReg, -1)
}
}
}

View File

@ -50,6 +50,7 @@ private fun compileMain(args: Array<String>): Boolean {
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val includeSourcelines by cli.option(ArgType.Boolean, fullName = "sourcelines", description = "include original Prog8 source lines in generated asm code")
val splitWordArrays by cli.option(ArgType.Boolean, fullName = "splitarrays", description = "treat all word arrays as tagged with @split to make them lsb/msb split in memory")
val noShortCircuit by cli.option(ArgType.Boolean, fullName = "noshortcircuit", description = "do not apply McCarthy/short-circuit evaluation to boolean expressions")
val breakpointCpuInstruction by cli.option(ArgType.Boolean, fullName = "breakinstr", description = "also use a CPU instruction for %breakpoint")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${PETTarget.NAME}', '${VMTarget.NAME}') (required)")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
@ -148,6 +149,7 @@ private fun compileMain(args: Array<String>): Boolean {
warnSymbolShadowing == true,
quietAssembler == true,
asmListfile == true,
noShortCircuit != true,
includeSourcelines == true,
experimentalCodegen == true,
varsHighBank,
@ -224,6 +226,7 @@ private fun compileMain(args: Array<String>): Boolean {
warnSymbolShadowing == true,
quietAssembler == true,
asmListfile == true,
noShortCircuit != true,
includeSourcelines == true,
experimentalCodegen == true,
varsHighBank,

View File

@ -33,6 +33,7 @@ class CompilerArguments(val filepath: Path,
val warnSymbolShadowing: Boolean,
val quietAssembler: Boolean,
val asmListfile: Boolean,
val shortCircuit: Boolean,
val includeSourcelines: Boolean,
val experimentalCodegen: Boolean,
val varsHighBank: Int?,
@ -76,6 +77,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
optimize = args.optimize
asmQuiet = args.quietAssembler
asmListfile = args.asmListfile
shortCircuit = args.shortCircuit
includeSourcelines = args.includeSourcelines
experimentalCodegen = args.experimentalCodegen
breakpointCpuInstruction = args.breakpointCpuInstruction

View File

@ -30,6 +30,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
warnSymbolShadowing = false,
quietAssembler = true,
asmListfile = false,
shortCircuit = true,
includeSourcelines = false,
experimentalCodegen = false,
varsHighBank = null,

View File

@ -28,6 +28,7 @@ class TestCompilerOptionSourcedirs: FunSpec({
warnSymbolShadowing = false,
quietAssembler = true,
asmListfile = false,
shortCircuit = true,
includeSourcelines = false,
experimentalCodegen = false,
varsHighBank = null,

View File

@ -27,6 +27,7 @@ internal fun compileFile(
warnSymbolShadowing = false,
quietAssembler = true,
asmListfile = false,
shortCircuit = true,
includeSourcelines = false,
experimentalCodegen = false,
varsHighBank = null,

View File

@ -195,6 +195,11 @@ One or more .p8 module files
This removes the need to add @split yourself but some programs may fail to compile with
this option as not all array operations are implemented yet on split arrays.
``-noshortcircuit``
Do *not* apply `McCarthy/short-circuit evaluation <https://en.wikipedia.org/wiki/Short-circuit_evaluation>`_ to boolean expressions.
This is a new feature and changes the behavior of existing programs so it can be turned off again for now.
This toggle will disappear eventually.
``-vm``
load and run a p8-virt or p8-ir listing in the internal VirtualMachine instead of compiling a prog8 program file..

View File

@ -2,7 +2,11 @@
TODO
====
- [on branch: shortcircuit] investigate McCarthy evaluation again? this may also reduce code size perhaps for things like if a>4 or a<2 ....
- [on branch: shortcircuit] complete McCarthy evaluation. This may also reduce code size perhaps for things like if a>4 or a<2 ....
- vm ircodegen (DONE)
- in 6502 codegen (see vm's ExpressionGen operatorAnd / operatorOr)
- IR: reduce amount of CMP/CMPI after instructions that set the status bits correctly (LOADs? INC? Bitwise operations, etc), but only after setting the status bits is verified!
...
@ -11,7 +15,6 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
Compiler:
- (after shortcircuit is in:) What happens when we make all subs return a boolean not as ubyte in A, but in the cpu's Carry flag?
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays.
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
(this is already done hardcoded for several of the builtin functions)
@ -27,7 +30,6 @@ Compiler:
- (need separate step in codegen and IR to write the "golden" variables)
- do we need (array)variable alignment tag instead of block alignment tag? You want to align the data, not the code in the block?
- ir: proper code gen for the CALLI instruction and that it (optionally) returns a word value that needs to be assigned to a reg
- ir: getting it in shape for code generation
- ir: related to the one above: block alignment doesn't translate well to variables in the block (the actual stuff that needs to be aligned in memory) but: need variable alignment tag instead of block alignment tag, really
- ir: idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
@ -48,7 +50,7 @@ Compiler:
Libraries:
- once a VAL_1 implementation is merged into the X16 kernal properly, remove all the workarounds in cx16 floats.parse_f() . Prototype parse routine in examples/cx16/floatparse.p8
- once a VAL_1 implementation is merged into the X16 kernal properly, remove all the workarounds in cx16 floats.parse_f()
- fix the problems in atari target, and flesh out its libraries.
- c128 target: make syslib more complete (missing kernal routines)?
- pet32 target: make syslib more complete (missing kernal routines)?
@ -56,6 +58,8 @@ Libraries:
Optimizations:
- give a warning for variables that could be a const - or even make them a const (if not @shared)?
- treat every scalar variable decl with initialization value, as const by default, unless the variable gets assigned to somewhere (or has its address taken, or is @shared)
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
for instance, vars used inside loops first, then loopvars, then uwords used as pointers, then the rest
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code,
@ -78,6 +82,9 @@ What if we were to re-introduce Structs in prog8? Some thoughts:
Other language/syntax features to think about
---------------------------------------------
- add (rom/ram)bank support to romsub. A call will then automatically switch banks, use callfar and something else when in banked ram.
challenges: how to not make this too X16 specific? How does the compiler know what bank to switch (ram/rom)?
How to make it performant when we want to (i.e. NOT have it use callfar/auto bank switching) ?
- module directive to set the text encoding for that whole file (iso, petscii, etc.)
- chained assignments `x=y=z=99`
- declare multiple variables `ubyte x,y,z` (if init value present, all get that init value)
- chained comparisons `10<x<20` , `x==y==z` (desugars to `10<x and x<20`, `x==y and y==z`)
- postincrdecr as expression, preincrdecr expression (`y = x++`, `y = ++x`) .... is this even possible, expression with side effects like this?
- negative array index to refer to an element from the end of the array. Python `[-1]` or Raku syntax `[\*-1]` , `[\*/2]` .... \*=size of the array

View File

@ -1,21 +1,67 @@
%import math
%import string
%import emudbg
%import palette
%import floats
%import textio
%zeropage dontuse
main {
sub start() {
emudbg.console_value1(123)
emudbg.console_value2(99)
emudbg.console_chrout('@')
emudbg.console_chrout('h')
emudbg.console_chrout('e')
emudbg.console_chrout('l')
emudbg.console_chrout('l')
emudbg.console_chrout('o')
emudbg.console_chrout('\n')
sub start () {
ubyte a1 = 10
ubyte a2 = 20
ubyte x1 = 30
ubyte x2 = 40
ubyte zero = 0
txt.print("1a:\n")
if calc_a1()<calc_x1() and calc_a2()<=calc_x2()
txt.print("* 1a and ok\n")
txt.print("\n1b:\n")
if calc_a1()<calc_x1() and calc_a2()>calc_x2()
txt.print("* 1b and fail\n")
txt.print("\n1c:\n")
if calc_a1()>calc_x1() and calc_a2()<=calc_x2()
txt.print("* 1c and fail\n")
txt.print("\n2a:\n")
if calc_a1()<calc_x1() or calc_a2()<=calc_x2()
txt.print("* 2a or ok\n")
txt.print("\n2b:\n")
if calc_a1()<calc_x1() or calc_a2()>calc_x2()
txt.print("* 2b or ok\n")
txt.print("\n3a:\n")
if calc_a1()>calc_x1() or calc_a2()<=calc_x2()
txt.print("* 3a or ok\n")
txt.print("\n3b:\n")
if calc_a1()>calc_x1() or calc_a2()>calc_x2()
txt.print("* 3b or fail\n")
txt.print("\n4a:\n")
bool result = calc_a1()<calc_x1() or calc_a2()>calc_x2()
txt.print_ub(result)
txt.nl()
txt.print("\n4b:\n")
result = calc_a1()>=calc_x1() and calc_a2()>calc_x2()
txt.print_ub(result)
txt.nl()
@($4000) &= 22
sub calc_a1() -> ubyte {
txt.print("calc_a1\n")
return a1+zero
}
sub calc_a2() -> ubyte {
txt.print("calc_a2\n")
return a2+zero
}
sub calc_x1() -> ubyte {
txt.print("calc_x1\n")
return x1+zero
}
sub calc_x2() -> ubyte {
txt.print("calc_x2\n")
return x2+zero
}
}
}

View File

@ -40,6 +40,7 @@ class RequestParser : Take {
quietAssembler = false,
includeSourcelines = false,
asmListfile = false,
shortCircuit = true,
experimentalCodegen = false,
splitWordArrays = false,
breakpointCpuInstruction = false,

View File

@ -14,10 +14,10 @@ Program to execute is not stored in the system memory, it's just a separate list
65536 virtual floating point registers (64 bits double precision) fr0-fr65535
65536 bytes of memory. Thus memory pointers (addresses) are limited to 16 bits.
Value stack, max 128 entries of 1 byte each.
Status flags: Carry, Zero, Negative. NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC!!!
LOAD instructions DO affect the Z and N flags
INC/DEC instructions DO affect the Z and N flags
other instructions such as logical or arithmetic operations DO NOT AFFECT THE STATUS FLAGS UNLESS EXPLICITLY NOTED!
Status flags: Carry, Zero, Negative. NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC,
LOAD instructions DO affect the Z and N flags.
INC/DEC instructions DO affect the Z and N flags,
other instructions only affect Z an N flags if the value in a result register is written.
Instruction set is mostly a load/store architecture, there are few instructions operating on memory directly.

View File

@ -311,8 +311,16 @@ class VirtualMachine(irProgram: IRProgram) {
private inline fun setResultReg(reg: Int, value: Int, type: IRDataType) {
when(type) {
IRDataType.BYTE -> registers.setUB(reg, value.toUByte())
IRDataType.WORD -> registers.setUW(reg, value.toUShort())
IRDataType.BYTE -> {
registers.setUB(reg, value.toUByte())
statusZero = value==0
statusNegative = value>=0x80
}
IRDataType.WORD -> {
registers.setUW(reg, value.toUShort())
statusZero = value==0
statusNegative = value>=0x8000
}
IRDataType.FLOAT -> throw IllegalArgumentException("attempt to set integer result register but float type")
}
}