mirror of
https://github.com/irmen/prog8.git
synced 2025-01-12 04:30:03 +00:00
expression optimizations
This commit is contained in:
parent
ec770b0f5f
commit
30e6bc92e5
@ -36,8 +36,8 @@ enum class DataType {
|
||||
fun assignableTo(targetType: DataType) =
|
||||
// what types are assignable to others without loss of precision?
|
||||
when(this) {
|
||||
UBYTE -> targetType == UBYTE || targetType == UWORD || targetType == FLOAT
|
||||
BYTE -> targetType == BYTE || targetType == WORD || targetType == FLOAT
|
||||
UBYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT
|
||||
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == WORD || targetType == FLOAT
|
||||
UWORD -> targetType == UWORD || targetType == FLOAT
|
||||
WORD -> targetType == WORD || targetType == FLOAT
|
||||
FLOAT -> targetType == FLOAT
|
||||
|
@ -68,6 +68,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
fun compileToAssembly(): AssemblyProgram {
|
||||
println("\nGenerating assembly code from intermediate code... ")
|
||||
|
||||
assemblyLines.clear()
|
||||
header()
|
||||
for(b in program.blocks)
|
||||
block2asm(b)
|
||||
@ -143,9 +144,14 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
private fun out(str: String) {
|
||||
// TODO: line splitting should be done here instead of at outputFragment
|
||||
assemblyLines.add(str)
|
||||
private fun out(str: String, splitlines: Boolean=true) {
|
||||
if(splitlines) {
|
||||
for (line in str.split('\n')) {
|
||||
var trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
|
||||
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
|
||||
assemblyLines.add(trimmed)
|
||||
}
|
||||
} else assemblyLines.add(str)
|
||||
}
|
||||
|
||||
|
||||
@ -414,17 +420,10 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
private fun outputAsmFragment(singleAsm: String) {
|
||||
if (singleAsm.isNotEmpty()) {
|
||||
if(singleAsm.startsWith("@inline@"))
|
||||
out(singleAsm.substring(8))
|
||||
out(singleAsm.substring(8), false)
|
||||
else {
|
||||
val withNewlines = singleAsm.replace('|', '\n')
|
||||
for (line in withNewlines.split('\n')) {
|
||||
// TODO move line splitting to out() function
|
||||
if (line.isNotEmpty()) {
|
||||
var trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
|
||||
trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
|
||||
out(trimmed)
|
||||
}
|
||||
}
|
||||
out(withNewlines)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,33 +6,27 @@ import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
|
||||
/*
|
||||
todo simplify expression terms:
|
||||
|
||||
X*Y - X -> X*(Y-1) ???
|
||||
Y*X - X -> X*(Y-1) ???
|
||||
|
||||
todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it)
|
||||
|
||||
todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it)
|
||||
*/
|
||||
|
||||
class SimplifyExpressions(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun process(assignment: Assignment): IStatement {
|
||||
if(assignment.aug_op!=null)
|
||||
if (assignment.aug_op != null)
|
||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
||||
return super.process(assignment)
|
||||
}
|
||||
|
||||
override fun process(expr: PrefixExpression): IExpression {
|
||||
if(expr.operator == "+") {
|
||||
if (expr.operator == "+") {
|
||||
// +X --> X
|
||||
optimizationsDone++
|
||||
return expr.expression.process(this)
|
||||
} else if (expr.operator == "not") {
|
||||
(expr.expression as? BinaryExpression)?.let {
|
||||
// NOT (...) -> invert ...
|
||||
when(it.operator) {
|
||||
when (it.operator) {
|
||||
"<" -> {
|
||||
it.operator = ">="
|
||||
optimizationsDone++
|
||||
@ -63,7 +57,8 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
optimizationsDone++
|
||||
return it
|
||||
}
|
||||
else -> {}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,16 +74,16 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
|
||||
val leftDt = expr.left.resultingDatatype(namespace, heap)
|
||||
val rightDt = expr.right.resultingDatatype(namespace, heap)
|
||||
if(leftDt!=null && rightDt!=null && leftDt!=rightDt) {
|
||||
if (leftDt != null && rightDt != null && leftDt != rightDt) {
|
||||
// try to convert a datatype into the other
|
||||
if(adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
|
||||
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
|
||||
optimizationsDone++
|
||||
return expr
|
||||
}
|
||||
}
|
||||
|
||||
// Value <associativeoperator> X --> X <associativeoperator> Value
|
||||
if(leftVal!=null && expr.operator in associativeOperators && rightVal==null) {
|
||||
if (leftVal != null && expr.operator in associativeOperators && rightVal == null) {
|
||||
val tmp = expr.left
|
||||
expr.left = expr.right
|
||||
expr.right = tmp
|
||||
@ -97,7 +92,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
|
||||
// X + (-A) --> X - A
|
||||
if(expr.operator=="+" && (expr.right as? PrefixExpression)?.operator=="-") {
|
||||
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||
expr.operator = "-"
|
||||
expr.right = (expr.right as PrefixExpression).expression
|
||||
optimizationsDone++
|
||||
@ -105,7 +100,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
|
||||
// (-A) + X --> X - A
|
||||
if(expr.operator=="+" && (expr.left as? PrefixExpression)?.operator=="-") {
|
||||
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
|
||||
expr.operator = "-"
|
||||
val newRight = (expr.left as PrefixExpression).expression
|
||||
expr.left = expr.right
|
||||
@ -115,9 +110,9 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
|
||||
// X + (-value) --> X - value
|
||||
if(expr.operator=="+" && rightVal!=null) {
|
||||
if (expr.operator == "+" && rightVal != null) {
|
||||
val rv = rightVal.asNumericValue?.toDouble()
|
||||
if(rv!=null && rv<0.0) {
|
||||
if (rv != null && rv < 0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position)
|
||||
optimizationsDone++
|
||||
@ -126,9 +121,9 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
|
||||
// (-value) + X --> X - value
|
||||
if(expr.operator=="+" && leftVal!=null) {
|
||||
if (expr.operator == "+" && leftVal != null) {
|
||||
val lv = leftVal.asNumericValue?.toDouble()
|
||||
if(lv!=null && lv<0.0) {
|
||||
if (lv != null && lv < 0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = LiteralValue.fromNumber(-lv, leftVal.type, leftVal.position)
|
||||
optimizationsDone++
|
||||
@ -137,7 +132,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
|
||||
// X - (-A) --> X + A
|
||||
if(expr.operator=="-" && (expr.right as? PrefixExpression)?.operator=="-") {
|
||||
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||
expr.operator = "+"
|
||||
expr.right = (expr.right as PrefixExpression).expression
|
||||
optimizationsDone++
|
||||
@ -145,9 +140,9 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
|
||||
// X - (-value) --> X + value
|
||||
if(expr.operator=="-" && rightVal!=null) {
|
||||
if (expr.operator == "-" && rightVal != null) {
|
||||
val rv = rightVal.asNumericValue?.toDouble()
|
||||
if(rv!=null && rv<0.0) {
|
||||
if (rv != null && rv < 0.0) {
|
||||
expr.operator = "+"
|
||||
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position)
|
||||
optimizationsDone++
|
||||
@ -155,70 +150,120 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
}
|
||||
|
||||
if (expr.operator == "+" || expr.operator == "-"
|
||||
&& leftVal == null && rightVal == null
|
||||
&& leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
if (leftBinExpr?.operator == "*") {
|
||||
if (expr.operator == "+") {
|
||||
// Y*X + X -> X*(Y - 1)
|
||||
// X*Y + X -> X*(Y - 1)
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
if(y!=null) {
|
||||
val yPlus1 = BinaryExpression(y, "+", LiteralValue.fromNumber(1, leftDt!!, y.position), y.position)
|
||||
return BinaryExpression(x, "*", yPlus1, x.position)
|
||||
}
|
||||
} else {
|
||||
// Y*X - X -> X*(Y - 1)
|
||||
// X*Y - X -> X*(Y - 1)
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
if(y!=null) {
|
||||
val yMinus1 = BinaryExpression(y, "-", LiteralValue.fromNumber(1, leftDt!!, y.position), y.position)
|
||||
return BinaryExpression(x, "*", yMinus1, x.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(rightBinExpr?.operator=="*") {
|
||||
if(expr.operator=="+") {
|
||||
// X + Y*X -> X*(Y + 1)
|
||||
// X + X*Y -> X*(Y + 1)
|
||||
val x = expr.left
|
||||
val y = determineY(x, rightBinExpr)
|
||||
if(y!=null) {
|
||||
val yPlus1 = BinaryExpression(y, "+", LiteralValue.optimalInteger(1, y.position), y.position)
|
||||
return BinaryExpression(x, "*", yPlus1, x.position)
|
||||
}
|
||||
} else {
|
||||
// X - Y*X -> X*(1 - Y)
|
||||
// X - X*Y -> X*(1 - Y)
|
||||
val x = expr.left
|
||||
val y = determineY(x, rightBinExpr)
|
||||
if(y!=null) {
|
||||
val oneMinusY = BinaryExpression(LiteralValue.optimalInteger(1, y.position), "-", y, y.position)
|
||||
return BinaryExpression(x, "*", oneMinusY, x.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// simplify when a term is constant and determines the outcome
|
||||
when(expr.operator) {
|
||||
when (expr.operator) {
|
||||
"or" -> {
|
||||
if((leftVal!=null && leftVal.asBooleanValue) || (rightVal!=null && rightVal.asBooleanValue)) {
|
||||
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue)) {
|
||||
optimizationsDone++
|
||||
return constTrue
|
||||
}
|
||||
if(leftVal!=null && !leftVal.asBooleanValue) {
|
||||
if (leftVal != null && !leftVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return expr.right
|
||||
}
|
||||
if(rightVal!=null && !rightVal.asBooleanValue) {
|
||||
if (rightVal != null && !rightVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
"and" -> {
|
||||
if((leftVal!=null && !leftVal.asBooleanValue) || (rightVal!=null && !rightVal.asBooleanValue)) {
|
||||
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue)) {
|
||||
optimizationsDone++
|
||||
return constFalse
|
||||
}
|
||||
if(leftVal!=null && leftVal.asBooleanValue) {
|
||||
if (leftVal != null && leftVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return expr.right
|
||||
}
|
||||
if(rightVal!=null && rightVal.asBooleanValue) {
|
||||
if (rightVal != null && rightVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
"xor" -> {
|
||||
if(leftVal!=null && !leftVal.asBooleanValue) {
|
||||
if (leftVal != null && !leftVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return expr.right
|
||||
}
|
||||
if(rightVal!=null && !rightVal.asBooleanValue) {
|
||||
if (rightVal != null && !rightVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return expr.left
|
||||
}
|
||||
if(leftVal!=null && leftVal.asBooleanValue) {
|
||||
if (leftVal != null && leftVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return PrefixExpression("not", expr.right, expr.right.position)
|
||||
}
|
||||
if(rightVal!=null && rightVal.asBooleanValue) {
|
||||
if (rightVal != null && rightVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return PrefixExpression("not", expr.left, expr.left.position)
|
||||
}
|
||||
}
|
||||
"|", "^" -> {
|
||||
if(leftVal!=null && !leftVal.asBooleanValue) {
|
||||
if (leftVal != null && !leftVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return expr.right
|
||||
}
|
||||
if(rightVal!=null && !rightVal.asBooleanValue) {
|
||||
if (rightVal != null && !rightVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
"&" -> {
|
||||
if(leftVal!=null && !leftVal.asBooleanValue) {
|
||||
if (leftVal != null && !leftVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return constFalse
|
||||
}
|
||||
if(rightVal!=null && !rightVal.asBooleanValue) {
|
||||
if (rightVal != null && !rightVal.asBooleanValue) {
|
||||
optimizationsDone++
|
||||
return constFalse
|
||||
}
|
||||
@ -233,6 +278,14 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
return expr
|
||||
}
|
||||
|
||||
private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? {
|
||||
return when {
|
||||
same(subBinExpr.left, x) -> subBinExpr.right
|
||||
same(subBinExpr.right, x) -> subBinExpr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun adjustDatatypes(expr: BinaryExpression,
|
||||
leftConstVal: LiteralValue?, leftDt: DataType,
|
||||
rightConstVal: LiteralValue?, rightDt: DataType): Boolean {
|
||||
|
@ -8,16 +8,16 @@ import kotlin.math.floor
|
||||
|
||||
|
||||
/*
|
||||
todo remove unused blocks
|
||||
todo remove unused variables
|
||||
todo remove unused subroutines
|
||||
todo remove unused strings and arrays from the heap
|
||||
todo: implement usage counters for blocks, variables, subroutines, heap variables. Then:
|
||||
todo remove unused blocks
|
||||
todo remove unused variables
|
||||
todo remove unused subroutines
|
||||
todo remove unused strings and arrays from the heap
|
||||
todo inline subroutines that are called exactly once (regardless of their size)
|
||||
todo inline subroutines that are only called a few times (3?) and that are "sufficiently small" (0-3 statements)
|
||||
|
||||
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to)
|
||||
|
||||
todo regular subroutines that have 1 or 2 (u)byte or 1 (u)word parameters -> change to asmsub to accept these in A/Y registers instead of on stack
|
||||
|
||||
todo inline subroutines that are called exactly once (regardless of their size)
|
||||
todo inline subroutines that are only called a few times (3?) and that are "sufficiently small" (0-3 statements)
|
||||
*/
|
||||
|
||||
class StatementOptimizer(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
|
||||
@ -82,6 +82,16 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
return subroutine
|
||||
}
|
||||
|
||||
private fun returnregisters(subroutine: Subroutine): List<RegisterOrStatusflag> {
|
||||
return when {
|
||||
subroutine.returntypes.size==0 -> listOf()
|
||||
subroutine.returntypes.size==1 && subroutine.returntypes[0] in setOf(DataType.BYTE, DataType.UBYTE) -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null))
|
||||
subroutine.returntypes.size==1 && subroutine.returntypes[0] in setOf(DataType.WORD, DataType.UWORD) -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
|
||||
subroutine.returntypes.size==2 && subroutine.returntypes.all { it in setOf(DataType.BYTE, DataType.UBYTE)} -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
|
||||
else -> throw FatalAstException("can't convert return values to registers")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isNotMemory(target: AssignTarget): Boolean {
|
||||
if(target.register!=null)
|
||||
return true
|
||||
@ -440,21 +450,6 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
return super.process(assignment)
|
||||
}
|
||||
|
||||
|
||||
private fun same(left: IExpression, right: IExpression): Boolean {
|
||||
if(left===right)
|
||||
return true
|
||||
when(left) {
|
||||
is RegisterExpr ->
|
||||
return (right is RegisterExpr && right.register==left.register)
|
||||
is IdentifierReference ->
|
||||
return (right is IdentifierReference && right.nameInSource==left.nameInSource)
|
||||
is ArrayIndexedExpression ->
|
||||
return (right is ArrayIndexedExpression && right.identifier==left.identifier && right.arrayspec==left.arrayspec)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun same(target: AssignTarget, value: IExpression): Boolean {
|
||||
return when {
|
||||
target.memoryAddress!=null -> false
|
||||
@ -491,3 +486,24 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun same(left: IExpression, right: IExpression): Boolean {
|
||||
if(left===right)
|
||||
return true
|
||||
when(left) {
|
||||
is RegisterExpr ->
|
||||
return (right is RegisterExpr && right.register==left.register)
|
||||
is IdentifierReference ->
|
||||
return (right is IdentifierReference && right.nameInSource==left.nameInSource)
|
||||
is ArrayIndexedExpression ->
|
||||
return (right is ArrayIndexedExpression && right.identifier==left.identifier && right.arrayspec==left.arrayspec)
|
||||
is PrefixExpression ->
|
||||
return (right is PrefixExpression && right.operator==left.operator && same(right.expression, left.expression))
|
||||
is BinaryExpression ->
|
||||
return (right is BinaryExpression && right.operator==left.operator
|
||||
&& same(right.left, left.left)
|
||||
&& same(right.right, left.right))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -70,8 +70,8 @@
|
||||
float rz = rotatedz[i]
|
||||
if rz >= 0.1 {
|
||||
float persp = (5.0+rz)/height
|
||||
ubyte sx = rotatedx[i] / persp + width/2 as ubyte
|
||||
ubyte sy = rotatedy[i] / persp + height/2 as ubyte
|
||||
ubyte sx = rotatedx[i] / persp + width/2.0 as ubyte
|
||||
ubyte sy = rotatedy[i] / persp + height/2.0 as ubyte
|
||||
c64scr.setcc(sx, sy, 46, i+2)
|
||||
}
|
||||
}
|
||||
@ -80,8 +80,8 @@
|
||||
float rz = rotatedz[i]
|
||||
if rz < 0.1 {
|
||||
float persp = (5.0+rz)/height
|
||||
ubyte sx = rotatedx[i] / persp + width/2 as ubyte
|
||||
ubyte sy = rotatedy[i] / persp + height/2 as ubyte
|
||||
ubyte sx = rotatedx[i] / persp + width/2.0 as ubyte
|
||||
ubyte sy = rotatedy[i] / persp + height/2.0 as ubyte
|
||||
c64scr.setcc(sx, sy, 81, i+2)
|
||||
}
|
||||
}
|
||||
|
@ -5,32 +5,29 @@
|
||||
|
||||
sub start() {
|
||||
|
||||
ubyte i=101
|
||||
|
||||
A=4
|
||||
A=5
|
||||
A=6
|
||||
A=i
|
||||
A=99 ; folded ok!
|
||||
inlinecall(1,2,3)
|
||||
ubyte r = inlinesub(3,4,5)
|
||||
c64scr.print_ub(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
i=4
|
||||
i=5
|
||||
i=6
|
||||
i=A
|
||||
i=99 ; folded ok
|
||||
|
||||
@($d020) = 4
|
||||
@($d020) = 5
|
||||
@($d020) = 6
|
||||
@($d020) = 7
|
||||
@($d020) = 8 ; @todo should not be folded
|
||||
|
||||
c64.EXTCOL = 4
|
||||
c64.EXTCOL = 5
|
||||
c64.EXTCOL = 6
|
||||
c64.EXTCOL = 7
|
||||
c64.EXTCOL = 8 ; @todo not fold
|
||||
sub inlinecall(byte b1, byte b2, byte b3) {
|
||||
float f=3.1415
|
||||
c64scr.print("this is inlinecall!\n")
|
||||
c64flt.print_f(f)
|
||||
f*=2.0
|
||||
c64flt.print_f(f)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("end of inlinecall!\n")
|
||||
}
|
||||
|
||||
sub inlinesub(ubyte b1, ubyte b2, ubyte b3) -> ubyte {
|
||||
c64scr.print("this is inlinesub!\n")
|
||||
ubyte qq = b1+b2
|
||||
qq += b3
|
||||
c64scr.print("end of inlinesub!\n")
|
||||
return qq
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user