tweak "not" removal/rewriting

This commit is contained in:
Irmen de Jong 2022-06-29 22:19:44 +02:00
parent 4ca0805de1
commit 97cb0cbd08
15 changed files with 243 additions and 172 deletions

View File

@ -3,7 +3,7 @@ package prog8.code.core
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val LogicalOperators = setOf("and", "or", "xor") // not x is replaced with x==0
val LogicalOperators = setOf("and", "or", "xor", "not")
val BitwiseOperators = setOf("&", "|", "^")
fun invertedComparisonOperator(operator: String) =

View File

@ -2,7 +2,6 @@ package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ExpressionError
import prog8.ast.base.FatalAstException
import prog8.ast.base.UndefinedSymbolError
import prog8.ast.expressions.*
@ -14,7 +13,6 @@ import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.AssociativeOperators
import prog8.code.core.DataType
import prog8.code.core.IntegerDatatypes
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
@ -36,54 +34,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
// Try to turn a unary prefix expression into a single constant value.
// Compile-time constant sub expressions will be evaluated on the spot.
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
val subexpr = expr.expression
if (subexpr is NumericLiteral) {
// accept prefixed literal values (such as -3, not true)
return when (expr.operator) {
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
"-" -> when (subexpr.type) {
in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteral.optimalInteger(-subexpr.number.toInt(), subexpr.position),
parent))
}
DataType.FLOAT -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteral(DataType.FLOAT, -subexpr.number, subexpr.position),
parent))
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
"~" -> when (subexpr.type) {
DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteral(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent))
}
DataType.UBYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteral(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
parent))
}
DataType.WORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteral(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent))
}
DataType.UWORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteral(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
else -> throw ExpressionError(expr.operator, subexpr.position)
}
}
return noModifications
val constValue = expr.constValue(program) ?: return noModifications
return listOf(IAstModification.ReplaceNode(expr, constValue, parent))
}
/*

View File

@ -330,6 +330,8 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report()
program.reorderStatements(errors, compilerOptions)
errors.report()
program.changeNotExpression(errors)
errors.report()
program.addTypecasts(errors, compilerOptions)
errors.report()
program.variousCleanups(errors, compilerOptions)

View File

@ -44,6 +44,14 @@ internal fun Program.reorderStatements(errors: IErrorReporter, options: Compilat
}
}
internal fun Program.changeNotExpression(errors: IErrorReporter) {
val changer = NotExpressionChanger(this, errors)
changer.visit(this)
while(errors.noErrors() && changer.applyModifications()>0) {
changer.visit(this)
}
}
internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, errors: IErrorReporter) {
val walker = object : AstWalker() {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {

View File

@ -3,7 +3,6 @@ package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.*
@ -114,17 +113,6 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
return noModifications
}
override fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator == "not") {
// not(x) --> x==0
// this means that "not" will never occur anywhere again in the ast
val dt = expr.expression.inferType(program).getOr(DataType.UBYTE)
val replacement = BinaryExpression(expr.expression, "==", NumericLiteral(dt,0.0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNodeSafe(expr, replacement, parent))
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val nextAssignment = decl.nextSibling() as? Assignment
if(nextAssignment!=null && nextAssignment.origin!=AssignmentOrigin.VARINIT) {
@ -164,6 +152,8 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
expr
else if(expr is BinaryExpression && expr.operator in LogicalOperators+ComparisonOperators)
expr
else if(expr is PrefixExpression && expr.operator in LogicalOperators)
expr
else
FunctionCallExpression(IdentifierReference(listOf("boolean"), expr.position), mutableListOf(expr), expr.position)
}

View File

@ -231,8 +231,12 @@ internal class BeforeAsmAstChanger(val program: Program,
var rightAssignment: Assignment? = null
var rightOperandReplacement: Expression? = null
val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall && expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall && expr.right !is ContainmentCheck
val separateLeftExpr = !expr.left.isSimple
&& expr.left !is IFunctionCall
&& expr.left !is ContainmentCheck
val separateRightExpr = !expr.right.isSimple
&& expr.right !is IFunctionCall
&& expr.right !is ContainmentCheck
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)

View File

@ -1,7 +1,10 @@
package prog8.compiler.astprocessing
import prog8.ast.*
import prog8.ast.expressions.*
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification

View File

@ -0,0 +1,113 @@
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.IErrorReporter
import prog8.code.core.IntegerDatatypes
internal class NotExpressionChanger(val program: Program, val errors: IErrorReporter) : AstWalker() {
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="==" || expr.operator=="!=") {
val left = expr.left as? BinaryExpression
if (left != null) {
val rightValue = expr.right.constValue(program)
if (rightValue?.number == 0.0 && rightValue.type in IntegerDatatypes) {
if (left.operator == "==" && expr.operator == "==") {
// (x==something)==0 --> x!=something
left.operator = "!="
return listOf(IAstModification.ReplaceNode(expr, left, parent))
} else if (left.operator == "!=" && expr.operator == "==") {
// (x!=something)==0 --> x==something
left.operator = "=="
return listOf(IAstModification.ReplaceNode(expr, left, parent))
} else if (left.operator == "==" && expr.operator == "!=") {
// (x==something)!=0 --> x==something
left.operator = "=="
return listOf(IAstModification.ReplaceNode(expr, left, parent))
} else if (left.operator == "!=" && expr.operator == "!=") {
// (x!=something)!=0 --> x!=something
left.operator = "!="
return listOf(IAstModification.ReplaceNode(expr, left, parent))
}
}
}
}
val left = expr.left as? BinaryExpression
val right = expr.right as? BinaryExpression
val leftValue = left?.right?.constValue(program)?.number
val rightValue = right?.right?.constValue(program)?.number
if(expr.operator == "or") {
if(left?.operator=="==" && right?.operator=="==" && leftValue==0.0 && rightValue==0.0) {
// (a==0) or (b==0) -> (a and b)==0
val orExpr = BinaryExpression(left.left, "and", right.left, expr.position)
val equalsZero = BinaryExpression(orExpr, "==", NumericLiteral.fromBoolean(false, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(expr, equalsZero, parent))
}
}
else if(expr.operator == "and") {
if(left?.operator=="==" && right?.operator=="==" && leftValue==0.0 && rightValue==0.0) {
// (a==0) and (b==0) -> (a or b)==0
val orExpr = BinaryExpression(left.left, "or", right.left, expr.position)
val equalsZero = BinaryExpression(orExpr, "==", NumericLiteral.fromBoolean(false, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(expr, equalsZero, parent))
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// not a or not b -> not(a and b)
if(expr.operator=="or") {
val left = expr.left as? PrefixExpression
val right = expr.right as? PrefixExpression
if(left?.operator=="not" && right?.operator=="not") {
val andExpr = BinaryExpression(left.expression, "and", right.expression, expr.position)
val notExpr = PrefixExpression("not", andExpr, expr.position)
return listOf(IAstModification.ReplaceNode(expr, notExpr, parent))
}
}
// not a and not b -> not(a or b)
if(expr.operator=="and") {
val left = expr.left as? PrefixExpression
val right = expr.right as? PrefixExpression
if(left?.operator=="not" && right?.operator=="not") {
val andExpr = BinaryExpression(left.expression, "or", right.expression, expr.position)
val notExpr = PrefixExpression("not", andExpr, expr.position)
return listOf(IAstModification.ReplaceNode(expr, notExpr, parent))
}
}
if(expr.operator=="==") {
val rightValue = expr.right.constValue(program)
if(rightValue?.number==0.0 && rightValue.type in IntegerDatatypes) {
// x==0 -> not x (only if occurs as a subexpression)
if(expr.parent is Expression || expr.parent is Assignment) {
val notExpr = PrefixExpression("not", expr.left.copy(), expr.position)
return listOf(IAstModification.ReplaceNodeSafe(expr, notExpr, parent))
}
}
}
return noModifications
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator == "not") {
// not(not(x)) -> x
if((expr.expression as? PrefixExpression)?.operator=="not")
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
}
return noModifications
}
}

View File

@ -306,12 +306,14 @@ internal class StatementReorderer(val program: Program,
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
}
else if(leftBinExpr.left.constValue(program)!=null && binExpr.right.constValue(program)!=null) {
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
else noModifications
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {

View File

@ -48,7 +48,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
if(parent is Assignment) {
val targetDt = (parent).target.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
if(sourceDt istype targetDt) {
// we can get rid of this typecast because the type is already
// we can get rid of this typecast because the type is already the target type
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
@ -71,6 +71,13 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
// +X --> X
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
}
else if(expr.operator == "not") {
// not(x) --> x==0
// this means that "not" will never occur anywhere again in the ast after this
val dt = expr.expression.inferType(program).getOr(DataType.UBYTE)
val replacement = BinaryExpression(expr.expression, "==", NumericLiteral(dt,0.0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNodeSafe(expr, replacement, parent))
}
return noModifications
}

View File

@ -1,12 +1,10 @@
package prog8tests
import io.kotest.assertions.fail
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldNotBeBlank
import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.types.instanceOf
import io.kotest.matchers.types.shouldBeSameInstanceAs
@ -14,9 +12,9 @@ import prog8.ast.ParentSentinel
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.*
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.compiler.astprocessing.processAstBeforeAsmGeneration
import prog8.compiler.printProgram
import prog8tests.helpers.*
@ -253,83 +251,41 @@ class TestOptimization: FunSpec({
(initY2.value as NumericLiteral).number shouldBe 11.0
}
test("not-typecasted assignment from ubyte logical expression to uword var should be auto upcasted") {
test("various 'not' operator rewrites even without optimizations on") {
val src = """
main {
sub start() {
ubyte bb
uword ww
ww = not bb or not ww ; expression combining ubyte and uword
ubyte a1
ubyte a2
a1 = not not a1 ; a1 = a1==0
a1 = not a1 or not a2 ; a1 = (a1 and a2)==0
a1 = not a1 and not a2 ; a1 = (a1 or a2)==0
}
}
"""
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
val wwAssign = result.program.entrypoint.statements.last() as Assignment
val expr = wwAssign.value as TypecastExpression
expr.type shouldBe DataType.UWORD
wwAssign.target.identifier?.nameInSource shouldBe listOf("ww")
expr.expression.inferType(result.program) istype DataType.UBYTE shouldBe true
}
test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") {
val src = """
main {
sub start() {
ubyte bb
uword ww
bb = not bb or not ww ; expression combining ubyte and uword
}
}
"""
val result = compileText(C64Target(), false, src, writeAssembly = false)!!
// bb = ((boolean(bb)==0) or (boolean(ww)==0))
val bbAssign = result.program.entrypoint.statements.last() as Assignment
val expr = bbAssign.value as BinaryExpression
expr.operator shouldBe "or"
expr.left shouldBe instanceOf<BinaryExpression>()
expr.right shouldBe instanceOf<BinaryExpression>()
expr.left.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
expr.right.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
val options = CompilationOptions(OutputType.PRG, CbmPrgLauncherType.BASIC, ZeropageType.DONTUSE, emptyList(),
floats = false,
noSysInit = true,
compTarget = C64Target(),
loadAddress = 0u, outputDir= outputDir)
result.program.processAstBeforeAsmGeneration(options, ErrorReporterForTests())
printProgram(result.program)
// TODO this is no longer the case:
// assignment is now split into:
// bb = not bb
// bb = (bb or (not ww as ubyte)
// val assigns = result.program.entrypoint.statements.filterIsInstance<Assignment>()
// val bbAssigns = assigns.filter { it.value !is NumericLiteral }
// bbAssigns.size shouldBe 2
//
// bbAssigns[0].target.identifier!!.nameInSource shouldBe listOf("bb")
// bbAssigns[0].value shouldBe instanceOf<PrefixExpression>()
// (bbAssigns[0].value as PrefixExpression).operator shouldBe "not"
// ((bbAssigns[0].value as PrefixExpression).expression as? IdentifierReference)?.nameInSource shouldBe listOf("bb")
// bbAssigns[0].value.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
//
// bbAssigns[1].target.identifier!!.nameInSource shouldBe listOf("bb")
// val bbAssigns1expr = bbAssigns[1].value as BinaryExpression
// bbAssigns1expr.operator shouldBe "or"
// (bbAssigns1expr.left as? IdentifierReference)?.nameInSource shouldBe listOf("bb")
// bbAssigns1expr.right shouldBe instanceOf<TypecastExpression>()
// val castedValue = (bbAssigns1expr.right as TypecastExpression).expression as PrefixExpression
// castedValue.operator shouldBe "not"
// (castedValue.expression as? IdentifierReference)?.nameInSource shouldBe listOf("ww")
// bbAssigns1expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
//
// val asm = generateAssembly(result.program, options)
// asm shouldNotBe null
// asm!!.name.shouldNotBeBlank()
val value1 = (stmts[4] as Assignment).value as BinaryExpression
val value2 = (stmts[5] as Assignment).value as BinaryExpression
val value3 = (stmts[6] as Assignment).value as BinaryExpression
value1.operator shouldBe "=="
value1.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
value2.operator shouldBe "=="
value2.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
value3.operator shouldBe "=="
value3.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
val left1 = value1.left as IdentifierReference
val left2 = value2.left as BinaryExpression
val left3 = value3.left as BinaryExpression
left1.nameInSource shouldBe listOf("a1")
left2.operator shouldBe "and"
(left2.left as IdentifierReference).nameInSource shouldBe listOf("a1")
(left2.right as IdentifierReference).nameInSource shouldBe listOf("a2")
left3.operator shouldBe "or"
(left3.left as IdentifierReference).nameInSource shouldBe listOf("a1")
(left3.right as IdentifierReference).nameInSource shouldBe listOf("a2")
}
test("intermediate assignment steps generated for typecasted expression") {

View File

@ -180,7 +180,7 @@ class TestTypecasts: FunSpec({
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
val statements = result.program.entrypoint.statements
statements.size shouldBe 13
statements.size shouldBe 14
}
test("no infinite typecast loop in assignment asmgen") {

View File

@ -109,6 +109,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
DataType.UWORD -> NumericLiteral(DataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
}
"not" -> NumericLiteral.fromBoolean(constval.number==0.0, constval.position)
else -> throw FatalAstException("invalid operator")
}
converted.linkParents(this.parent)
@ -214,7 +215,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
"&" -> leftDt
"|" -> leftDt
"^" -> leftDt
"and", "or", "xor" -> InferredTypes.knownFor(DataType.UBYTE)
"and", "or", "xor", "not" -> InferredTypes.knownFor(DataType.UBYTE)
"<", ">",
"<=", ">=",
"==", "!=" -> dynamicBooleanType()

View File

@ -3,33 +3,32 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- code gen for if statements has become bad
- assembler incorrectly assembles hello.asm now (crash when run)
- why can't while and until loops use NOT condition instead of Cond==0 ? Fix this!
- code gen for if statements has become inefficient? vm/6502?
if not diskio.iteration_in_progress or not num_bytes
return 0
- code gen for while loops has become bad (until loops probably as well)
- code gen for while loops has become inefficient: (until loops probably as well)
(maybe solved when if statements code has been fixed)
while c64.CHRIN()!='\"' {
if c64.READST()
goto close_end
}
- chess.prg became A LOT larger, why!? (perhaps due to new while/until condition handling?)
- imageviewer.prg became A LOT larger, why!?
- petaxian.prg became A LOT larger, why!?
- some programs became a bit larger since "not" was removed (assembler)
- 6502: fix logical and/or/xor routines to just be bitwise routines.
- petaxian.prg became quite a bit (200 bytes) larger, why!? because of the above?
- get rid of logical and/or/xor in the codegen (6502+vm)
because bitwise versions + correct use of boolean() operand wrapping are equivalent?
can do this for instance by replacing and/or/xor with their bitwise versions &, |, ^, ~
can do this for instance by replacing and/or/xor with their bitwise versions &, |, ^
- ...or: 6502: fix logical and/or/xor routines to just be bitwise routines.
- check all examples if they still work, maybe we find bug for...:
- compiling logical.p8 to virtual with optimization generates a lot larger code as without optimizations.
this is not the case for the 6502 codegen.
- add optimizations: not a or not b -> not(a and b) , not a and not b -> not(a or b)
actually this now means: (a==0) or (b==0) -> (a or b)==0, (a==0) and (b==0) -> (a or b)==0
add unit tests for that.
- bin expr splitter: split logical expressions on ands/ors/xors ?
- add some more optimizations in vmPeepholeOptimizer

View File

@ -6,22 +6,56 @@
main {
sub start() {
; a "pixelshader":
sys.gfx_enable(0) ; enable lo res screen
ubyte shifter
ubyte a1 = 0
ubyte a2 = 42
ubyte a3 = 1
repeat {
uword xx
uword yy = 0
repeat 240 {
xx = 0
repeat 320 {
sys.gfx_plot(xx, yy, xx*yy + shifter as ubyte)
xx++
}
yy++
}
shifter+=4
}
}
if (a1==0)==0
a3 = (a1==0)==0
if (a1!=0)==0
a3 = (a1!=0)==0
if (a1==0)!=0
a3 = (a1==0)!=0
if (a1!=0)!=0
a3 = (a1!=0)!=0
if (a1==0) or (a2==0)
a3 = (a1==0) or (a2==0)
if (a1==0) and (a2==0)
a3 = (a1==0) and (a2==0)
if not a1 or not a2 or not(not(a3))
a3=not a1 or not a2 or not(not(a3))
if (a1==0) or (a2==0)
a3 = (a1==0) or (a2==0)
if (a1==0) and (a2==0)
a3 = (a1==0) and (a2==0)
txt.print_ub(a3)
; ; a "pixelshader":
; sys.gfx_enable(0) ; enable lo res screen
; ubyte shifter
;
; repeat {
; uword xx
; uword yy = 0
; repeat 240 {
; xx = 0
; repeat 320 {
; sys.gfx_plot(xx, yy, xx*yy + shifter as ubyte)
; xx++
; }
; yy++
; }
; shifter+=4
; }
}
}