From ddf990296b7a1462ce5913e5549639916ccc1f39 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 29 Jun 2024 17:47:13 +0200 Subject: [PATCH] fix subroutine inlining symbol scope error --- README.md | 2 +- .../prog8/optimizer/ExpressionSimplifier.kt | 2 +- codeOptimizers/src/prog8/optimizer/Inliner.kt | 20 ++++++++++--------- compiler/test/TestOptimization.kt | 12 ++++++----- docs/source/index.rst | 4 ++-- docs/source/todo.rst | 4 +--- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 49b5bc8ea..7e571ec1e 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,9 @@ GNU GPL 3.0 (see file LICENSE), with exception for generated code: What does Prog8 provide? ------------------------ -- can produce smaller and faster running programs than equivalent C code compiled with CC65 or even LLVM-MOS - all advantages of a higher level language over having to write assembly code manually - programs run very fast because compilation to native machine code +- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS - modularity, symbol scoping, subroutines - various data types other than just bytes (16-bit words, floats, strings) - floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do) diff --git a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt index 4a8dced5a..502002ffe 100644 --- a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt +++ b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt @@ -104,7 +104,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co val leftIDt = expr.left.inferType(program) val rightIDt = expr.right.inferType(program) if (!leftIDt.isKnown || !rightIDt.isKnown) - throw FatalAstException("can't determine datatype of both expression operands $expr") + return noModifications // X + (-A) --> X - A if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") { diff --git a/codeOptimizers/src/prog8/optimizer/Inliner.kt b/codeOptimizers/src/prog8/optimizer/Inliner.kt index 5fc803e9f..6ce822bcb 100644 --- a/codeOptimizers/src/prog8/optimizer/Inliner.kt +++ b/codeOptimizers/src/prog8/optimizer/Inliner.kt @@ -30,11 +30,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti } override fun visit(subroutine: Subroutine) { - if(!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) { - val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine} - if(!containsSubsOrVariables) { - if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) { - if(subroutine !== program.entrypoint) { + if (!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) { + val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine } + if (!containsSubsOrVariables) { + if (subroutine.statements.size == 1 || (subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))) { + if (subroutine !== program.entrypoint) { // subroutine is possible candidate to be inlined subroutine.inline = when (val stmt = subroutine.statements[0]) { @@ -87,9 +87,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti } else false targetInline || valueInline - } else if(stmt.target.identifier!=null && stmt.isAugmentable) { + } else if (stmt.target.identifier != null && stmt.isAugmentable) { val binExpr = stmt.value as BinaryExpression - if(binExpr.operator in "+-" && binExpr.right.constValue(program)?.number==1.0) { + if (binExpr.operator in "+-" && binExpr.right.constValue(program)?.number == 1.0) { makeFullyScoped(stmt.target.identifier!!) makeFullyScoped(binExpr.left as IdentifierReference) true @@ -121,8 +121,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti } } - if(subroutine.inline && subroutine.statements.size>1) { - require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1])) + if (subroutine.inline && subroutine.statements.size > 1) { + require(subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1])) subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it. } } @@ -147,6 +147,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti } private fun makeFullyScoped(call: FunctionCallStatement) { + makeFullyScoped(call.target) call.target.targetSubroutine(program)?.let { sub -> val scopedName = IdentifierReference(sub.scopedName, call.target.position) val scopedArgs = makeScopedArgs(call.args) @@ -169,6 +170,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti } private fun makeFullyScoped(call: FunctionCallExpression) { + makeFullyScoped(call.target) call.target.targetSubroutine(program)?.let { sub -> val scopedName = IdentifierReference(sub.scopedName, call.target.position) val scopedArgs = makeScopedArgs(call.args) diff --git a/compiler/test/TestOptimization.kt b/compiler/test/TestOptimization.kt index 96d7079af..0c047e8a1 100644 --- a/compiler/test/TestOptimization.kt +++ b/compiler/test/TestOptimization.kt @@ -1060,23 +1060,25 @@ main { compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null } - xtest("optimizing inlined functions must reference proper scopes") { + test("optimizing inlined functions must reference proper scopes") { val src=""" main { sub start() { - other.sub1() + void other.sub1() + cx16.r0L = other.sub1()+other.sub1() } } other { - sub sub2() { + sub sub2() -> ubyte{ cx16.r0++ cx16.r1++ + return cx16.r0L } - sub sub1() { - sub2() + sub sub1() -> ubyte { + return sub2() } }""" diff --git a/docs/source/index.rst b/docs/source/index.rst index fe7d80b2d..73a65748d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -70,9 +70,9 @@ Features -------- - it is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...) -- can produce smaller and faster running programs than equivalent C code compiled with CC65 or even LLVM-MOS - the compiled programs run very fast, because compilation to highly efficient native machine code. -- Provides a convenient and fast edit/compile/run cycle by being able to directly launch +- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS +- provides a convenient and fast edit/compile/run cycle by being able to directly launch the compiled program in an emulator and provide debugging information to this emulator. - the language looks like a mix of Python and C so should be quite easy to learn - Modular programming, scoping via modules, code blocks, and subroutines. No need for forward declarations. diff --git a/docs/source/todo.rst b/docs/source/todo.rst index e8a3abc6f..239f556ce 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,11 +1,9 @@ TODO ==== -optimizer bug, see "optimizing inlined functions must reference proper scopes" unittest (skipped for now) -causes compiler error for virtual: just calling txt.cls() gives compile error undefined symbol clear_screen - https://github.com/irmen/prog8/issues/136 (string.find register order issue) +other issues on github. optimize signed byte/word division by powers of 2 (and shift right?), it's now using divmod routine. (also % ?) see inplacemodificationByteVariableWithLiteralval() and inplacemodificationSomeWordWithLiteralval()