From 0ca1be0c00b015559138f3db1d98785b524a343a Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Thu, 1 Feb 2018 22:39:38 +0100 Subject: [PATCH] =?UTF-8?q?Tons=20of=20things:=20=E2=80=93=20changed=20`in?= =?UTF-8?q?line`=20to=20`macro`=20=E2=80=93=20added=20support=20for=20para?= =?UTF-8?q?meters=20for=20macros=20written=20in=20Millfork=20=E2=80=93=20a?= =?UTF-8?q?dded=20`inline`,=20`noinline`,=20`register`=20hints=20=E2=80=93?= =?UTF-8?q?=20added=20<<<<=20operator=20=E2=80=93=20pointer=20dereference?= =?UTF-8?q?=20expressions=20are=20now=20supported=20more=20widely=20?= =?UTF-8?q?=E2=80=93=20C64=20library=20fixes=20=E2=80=93=20added=20`-O1`?= =?UTF-8?q?=20command=20line=20option=20as=20an=20alias=20for=20`-O`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 10 ++++ doc/abi/inlining.md | 8 ++- doc/abi/variable-storage.md | 2 + doc/lang/assembly.md | 14 ++--- doc/lang/functions.md | 7 ++- doc/lang/operators.md | 5 +- doc/lang/syntax.md | 5 +- include/c64_cia.mfk | 26 ++++----- include/c64_hardware.mfk | 14 ++--- include/c64_vic.mfk | 40 ++++++------- include/stdlib.mfk | 10 ++-- src/main/scala/millfork/Main.scala | 4 +- .../opt/VariableToRegisterOptimization.scala | 19 +++++-- .../scala/millfork/compiler/BuiltIns.scala | 21 +++++-- .../compiler/CompilationContext.scala | 6 ++ .../scala/millfork/compiler/MfCompiler.scala | 57 +++++++++++++++---- src/main/scala/millfork/env/Constant.scala | 6 +- src/main/scala/millfork/env/Environment.scala | 38 ++++++++----- src/main/scala/millfork/env/Thing.scala | 12 ++-- src/main/scala/millfork/node/Node.scala | 4 +- .../scala/millfork/output/Assembler.scala | 4 +- .../millfork/output/InliningCalculator.scala | 22 ++++++- .../millfork/output/VariableAllocator.scala | 3 + src/main/scala/millfork/parser/MfParser.scala | 27 +++++---- ...nsSuite.scala => AssemblyMacroSuite.scala} | 12 ++-- .../test/AssemblyOptimizationSuite.scala | 2 +- .../scala/millfork/test/AssemblySuite.scala | 10 ++-- .../scala/millfork/test/BasicSymonTest.scala | 2 +- src/test/scala/millfork/test/NonetSuite.scala | 22 +++++++ 29 files changed, 281 insertions(+), 131 deletions(-) rename src/test/scala/millfork/test/{InlineAssemblyFunctionsSuite.scala => AssemblyMacroSuite.scala} (85%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d309959f..ed9c86d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,20 @@ ## Current version +* **Breaking change!** Renamed `inline` to `macro` + +* Added support for parameters for macros written in Millfork + +* Added optimizer hints: `inline`, `noinline`, `register` + +* Added `*'=` and `<<<<` operators + * Added return dispatch statements. * Fixed several optimization bugs. +* Fixed several C64 library bugs. + * Other minor improvements. ## 0.1 diff --git a/doc/abi/inlining.md b/doc/abi/inlining.md index 5afa3db8..34461df8 100644 --- a/doc/abi/inlining.md +++ b/doc/abi/inlining.md @@ -1,9 +1,11 @@ -# Function inlining +# Macros and inlining -## Explicit inlining +## Macros -`inline` keyword +`macro` keyword ## Automatic inlining `--inline` command-line option + +`inline` and `noinline` keyword diff --git a/doc/abi/variable-storage.md b/doc/abi/variable-storage.md index f872373d..ab80da1a 100644 --- a/doc/abi/variable-storage.md +++ b/doc/abi/variable-storage.md @@ -55,6 +55,8 @@ Some small automatic variables may be inlined to index registers. They are not automatically initialized before reading, reading them before initialization yields an undefined value. Automatic local variables are not safe to use with reentrant functions, see the [relevant documentation](../lang/reentrancy.md) for more details. +Automatic variables defined with the `register` keyword will have the priority when it comes to register allocation. + ## Parameters Automatic variables have lifetime starting with the beginning diff --git a/doc/lang/assembly.md b/doc/lang/assembly.md index be5ce235..30214db2 100644 --- a/doc/lang/assembly.md +++ b/doc/lang/assembly.md @@ -54,15 +54,15 @@ Currently there is no way to insert raw bytes into inline assembly ## Assembly functions -Assembly functions can be declared as `inline` or not. +Assembly functions can be declared as `macro` or not. -An inline assembly function is inserted into the calling function like an inline assembly block, +A macro assembly function is inserted into the calling function like an inline assembly block, and therefore usually it shouldn't end with `RTS` or `RTI`. -A non-inline assembly function should end with `RTS`, `JMP` or `RTI` as appropriate, +A non-macro assembly function should end with `RTS`, `JMP` or `RTI` as appropriate, or it should be an external function. -For both inline and non-inline assembly functions, +For both macro and non-macro assembly functions, the return type can be any valid return type, like for Millfork functions. If the size of the return type is one byte, then the result is passed via the accumulator. @@ -76,7 +76,7 @@ and the high byte of the result is passed via the X register. An assembly function can have parameters. They differ from what is used by Millfork functions. -Inline assembly functions can have the following parameter types: +Macro assembly functions can have the following parameter types: * reference parameters: `byte ref paramname`: every occurrence of the parameter will be replaced with the variable given as an argument @@ -98,13 +98,13 @@ and call `increase(score, 10)`, the entire call will compile into: ADC #10 STA score -Non-inline functions can only have their parameters passed via registers: +Non-macro functions can only have their parameters passed via registers: * `byte a`, `byte x`, `byte y`: a single byte passed via the given CPU register * `word xa`, `word ax`, `word ay`, `word ya`, `word xy`, `word yx`: a 2-byte word byte passed via given two CPU registers, with the high byte passed through the first register and the low byte passed through the second register -Inline assembly functions can have maximum one parameter passed via a register. +Macro assembly functions can have maximum one parameter passed via a register. ### External functions diff --git a/doc/lang/functions.md b/doc/lang/functions.md index 42897255..0aac6e29 100644 --- a/doc/lang/functions.md +++ b/doc/lang/functions.md @@ -11,8 +11,11 @@ Syntax: * `asm` – the function is written in assembly, not in Millfork (doesn't matter for `extern` functions), see [Using assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions) - * `inline` – the function should be always inlined, - see [Function inlining#Explicit inlining](../abi/inlining.md#explicit-inlining) + * `macro` – the function is a macro, + see [Macros_and inlining#Macros](../abi/inlining.md#macros) + + * `inline` and `noinline` – the function should preferably/should never be inlined + see [Macros_and inlining#Inlining](../abi/inlining.md#automatic_inlining.md) * `interrupt` – the function is a hardware interrupt handler diff --git a/doc/lang/operators.md b/doc/lang/operators.md index f5e48a3e..c8004046 100644 --- a/doc/lang/operators.md +++ b/doc/lang/operators.md @@ -12,7 +12,7 @@ Millfork has different operator precedence compared to most other languages. Fro * `*`, `*'` -* `+`, `+'`, `-`, `-'`, `|`, `&`, `^`, `>>`, `>>'`, `<<`, `<<'`, `>>>>` +* `+`, `+'`, `-`, `-'`, `|`, `&`, `^`, `>>`, `>>'`, `<<`, `<<'`, `>>>>`, `<<<<` * `:` @@ -87,6 +87,9 @@ There are no division, remainder or modulo operators. * `>>>>`: shifting a 9-bit value and returning a byte; `a >>>> b` is equivalent to `(a & $1FF) >> b`, but the latter doesn't compile yet `word >>>> constant byte` +* `>>>>`: shifting a byte and returning a 9-bit value; `a >>>> b` is equivalent to `(a << b) & 0x1ff` if there was no overflow, but the latter doesn't compile yet +`byte <<<< constant byte` + ## Decimal arithmetic operators These operators work using the decimal arithmetic and will not work on Ricoh CPU's. diff --git a/doc/lang/syntax.md b/doc/lang/syntax.md index 70cfa8ee..980f23c7 100644 --- a/doc/lang/syntax.md +++ b/doc/lang/syntax.md @@ -20,7 +20,8 @@ Syntax: `[] [@
] [= ]` -* `` can be only specified for local variables. It can be either `stack`, `static` or nothing. +* `` can be only specified for local variables. It can be either `stack`, `static`, `register` or nothing. +`register` is only a hint for the optimizer. See [the description of variable storage](../abi/variable-storage.md). * `
` is a constant expression that defines where in the memory the variable will be located. @@ -119,7 +120,7 @@ return [i] (param1, param2) { Return dispatch calculates the value of an index, picks the correct branch, assigns some global variables and jumps to another function. -The index has to evaluate to a byte. The functions cannot be `inline` and shouldn't have parameters. +The index has to evaluate to a byte. The functions cannot be `macro` and shouldn't have parameters. Jumping to a function with parameters gives those parameters undefined values. The functions are not called, so they don't return to the function the return dispatch statement is in, but to its caller. diff --git a/include/c64_cia.mfk b/include/c64_cia.mfk index f077b672..85c4db6f 100644 --- a/include/c64_cia.mfk +++ b/include/c64_cia.mfk @@ -10,7 +10,7 @@ byte cia2_prb @$DD01 byte cia2_ddra @$DD02 byte cia2_ddrb @$DD03 -inline asm void cia_disable_irq() { +macro asm void cia_disable_irq() { LDA #$7f LDA $dc0d LDA $dd0d @@ -19,22 +19,22 @@ inline asm void cia_disable_irq() { } -inline void vic_bank_0000() { - cia2_ddra = $C0 - cia2_pra = $C0 +macro void vic_bank_0000() { + cia2_ddra = 3 + cia2_pra = 3 } -inline void vic_bank_4000() { - cia2_ddra = $C0 - cia2_pra = $80 +macro void vic_bank_4000() { + cia2_ddra = 3 + cia2_pra = 2 } -inline void vic_bank_8000() { - cia2_ddra = $C0 - cia2_pra = $40 +macro void vic_bank_8000() { + cia2_ddra = 3 + cia2_pra = 1 } -inline void vic_bank_C000() { - cia2_ddra = $C0 - cia2_pra = $00 +macro void vic_bank_C000() { + cia2_ddra = 3 + cia2_pra = 0 } \ No newline at end of file diff --git a/include/c64_hardware.mfk b/include/c64_hardware.mfk index 0347eb68..50b493bb 100644 --- a/include/c64_hardware.mfk +++ b/include/c64_hardware.mfk @@ -5,37 +5,37 @@ import cpu6510 array c64_color_ram [1000] @$D800 -inline void c64_ram_only() { +macro void c64_ram_only() { cpu6510_ddr = 7 cpu6510_port = 0 } -inline void c64_ram_io() { +macro void c64_ram_io() { cpu6510_ddr = 7 cpu6510_port = 5 } -inline void c64_ram_io_kernal() { +macro void c64_ram_io_kernal() { cpu6510_ddr = 7 cpu6510_port = 6 } -inline void c64_ram_io_basic() { +macro void c64_ram_io_basic() { cpu6510_ddr = 7 cpu6510_port = 7 } -inline void c64_ram_charset() { +macro void c64_ram_charset() { cpu6510_ddr = 7 cpu6510_port = 1 } -inline void c64_ram_charset_kernal() { +macro void c64_ram_charset_kernal() { cpu6510_ddr = 7 cpu6510_port = 2 } -inline void c64_ram_charset_basic() { +macro void c64_ram_charset_basic() { cpu6510_ddr = 7 cpu6510_port = 3 } \ No newline at end of file diff --git a/include/c64_vic.mfk b/include/c64_vic.mfk index 25f33259..0e123bf0 100644 --- a/include/c64_vic.mfk +++ b/include/c64_vic.mfk @@ -52,77 +52,77 @@ byte vic_spr7_color @$D02E array vic_spr_coord [16] @$D000 array vic_spr_color [8] @$D027 -inline void vic_enable_multicolor() { +macro void vic_enable_multicolor() { vic_cr2 |= 0x10 } -inline void vic_disable_multicolor() { +macro void vic_disable_multicolor() { vic_cr2 &= 0xEF } -inline void vic_enable_bitmap() { +macro void vic_enable_bitmap() { vic_cr1 |= 0x20 } -inline void vic_disable_bitmap() { +macro void vic_disable_bitmap() { vic_cr1 &= 0xDF } -inline void vic_24_rows() { +macro void vic_24_rows() { vic_cr1 &= 0xF7 } -inline void vic_25_rows() { +macro void vic_25_rows() { vic_cr1 |= 8 } -inline void vic_38_columns() { +macro void vic_38_columns() { vic_cr2 &= 0xF7 } -inline void vic_40_columns() { +macro void vic_40_columns() { vic_cr2 |= 8 } -inline void vic_disable_irq() { +macro void vic_disable_irq() { vic_irq_ena = 0 vic_irq += 1 } // base: divisible by $400, $0000-$3C00 allowed -//inline void vic_screen(word const base) { +//macro void vic_screen(word const base) { // vic_mem = (vic_mem & $0F) | (base >> 6) //} -inline void vic_charset_0000() { +macro void vic_charset_0000() { vic_mem = (vic_mem & $F1) } -inline void vic_charset_0800() { +macro void vic_charset_0800() { vic_mem = (vic_mem & $F1) | 2 } -inline void vic_charset_1000() { +macro void vic_charset_1000() { vic_mem = (vic_mem & $F1) | 4 } -inline void vic_charset_1800() { +macro void vic_charset_1800() { vic_mem = (vic_mem & $F1) | 6 } -inline void vic_charset_2000() { +macro void vic_charset_2000() { vic_mem = (vic_mem & $F1) | 8 } -inline void vic_charset_2800() { +macro void vic_charset_2800() { vic_mem = (vic_mem & $F1) | $A } -inline void vic_charset_3000() { +macro void vic_charset_3000() { vic_mem = (vic_mem & $F1) | $C } -inline void vic_charset_3800() { +macro void vic_charset_3800() { vic_mem = (vic_mem & $F1) | $E } -inline void vic_bitmap_0000() { +macro void vic_bitmap_0000() { vic_mem &= $F7 } -inline void vic_bitmap_2000() { +macro void vic_bitmap_2000() { vic_mem |= 8 } diff --git a/include/stdlib.mfk b/include/stdlib.mfk index 7eb85322..46634d8f 100644 --- a/include/stdlib.mfk +++ b/include/stdlib.mfk @@ -4,19 +4,19 @@ word nmi_routine_addr @$FFFA word reset_routine_addr @$FFFC word irq_routine_addr @$FFFE -inline asm void poke(word const addr, byte a) { +macro asm void poke(word const addr, byte a) { STA addr } -inline asm byte peek(word const addr) { +macro asm byte peek(word const addr) { ?LDA addr } -inline asm void disable_irq() { +macro asm void disable_irq() { SEI } -inline asm void enable_irq() { +macro asm void enable_irq() { CLI } @@ -39,7 +39,7 @@ _lo_nibble_to_hex_lbl: RTS } -inline asm void panic() { +macro asm void panic() { JSR _panic } diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 5f80a397..b0f981f9 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -213,12 +213,12 @@ object Main { assertNone(c.optimizationLevel, "Optimization level already defined") c.copy(optimizationLevel = Some(1)) }.description("Optimize code.") - for (i <- 2 to 9) { + for (i <- 1 to 9) { val f = flag("-O" + i).action { c => assertNone(c.optimizationLevel, "Optimization level already defined") c.copy(optimizationLevel = Some(i)) }.description("Optimize code even more.") - if (i > 3) f.hidden() + if (i == 1 || i > 3) f.hidden() } flag("--inline").action { c => c.changeFlag(CompilationFlag.InlineFunctions, true) diff --git a/src/main/scala/millfork/assembly/opt/VariableToRegisterOptimization.scala b/src/main/scala/millfork/assembly/opt/VariableToRegisterOptimization.scala index 68356373..4488956a 100644 --- a/src/main/scala/millfork/assembly/opt/VariableToRegisterOptimization.scala +++ b/src/main/scala/millfork/assembly/opt/VariableToRegisterOptimization.scala @@ -74,10 +74,15 @@ object VariableToRegisterOptimization extends AssemblyOptimization { case _ => None }.toSet val localVariables = f.environment.getAllLocalVariables.filter { - case MemoryVariable(name, typ, VariableAllocationMethod.Auto) => + case MemoryVariable(name, typ, VariableAllocationMethod.Auto | VariableAllocationMethod.Register) => typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) case _ => false } + val variablesWithRegisterHint = f.environment.getAllLocalVariables.filter { + case MemoryVariable(name, typ, VariableAllocationMethod.Register) => + typ.size == 1 && !paramVariables(name) && stillUsedVariables(name) && !variablesWithAddressesTaken(name) + case _ => false + }.map(_.name).toSet val variablesWithLifetimes = localVariables.map(v => v.name -> VariableLifetime.apply(v.name, code) @@ -90,7 +95,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization { importances(range.start).x != Important }.flatMap { case (vName, range) => - canBeInlined(Some(vName), None, code.zip(importances).slice(range.start, range.end)).map(score => (vName, range, score)) + canBeInlined(Some(vName), None, code.zip(importances).slice(range.start, range.end)).map { score => + (vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score) + } } val yCandidates = variablesWithLifetimes.filter { @@ -98,7 +105,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization { importances(range.start).y != Important }.flatMap { case (vName, range) => - canBeInlined(None, Some(vName), code.zip(importances).slice(range.start, range.end)).map(score => (vName, range, score)) + canBeInlined(None, Some(vName), code.zip(importances).slice(range.start, range.end)).map { score => + (vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score) + } } val aCandidates = variablesWithLifetimes.filter { @@ -111,7 +120,9 @@ object VariableToRegisterOptimization extends AssemblyOptimization { start = true, synced = false, vName, - code.zip(importances).slice(range.start, range.end)).map(score => (vName, range, score)) + code.zip(importances).slice(range.start, range.end)).map { score => + (vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score) + } } // println(s"X: $xCandidates") // println(s"Y: $yCandidates") diff --git a/src/main/scala/millfork/compiler/BuiltIns.scala b/src/main/scala/millfork/compiler/BuiltIns.scala index 26c16f7b..bff755f8 100644 --- a/src/main/scala/millfork/compiler/BuiltIns.scala +++ b/src/main/scala/millfork/compiler/BuiltIns.scala @@ -92,11 +92,14 @@ object BuiltIns { case IndexChoice.PreferY => val array = env.getArrayOrPointer(arrayName) val calculateIndex = MfCompiler.compile(ctx, index, Some(b -> RegisterVariable(Register.Y, b)), NoBranching) - val baseAddress = array match { - case c: ConstantThing => c.value - case a: MfArray => a.toAddress + array match { + case c: ConstantThing => + calculateIndex -> List(AssemblyLine.absoluteY(opcode, c.value)) + case a: MfArray => + calculateIndex -> List(AssemblyLine.absoluteY(opcode, a.toAddress)) + case v: VariableInMemory if v.typ.name == "pointer" => + calculateIndex -> List(AssemblyLine.indexedY(opcode, v.toAddress)) } - calculateIndex -> List(AssemblyLine.absoluteY(opcode, baseAddress)) } case FunctionCallExpression(name, List(param)) if env.maybeGet[Type](name).isDefined => return simpleOperation(opcode, ctx, param, indexChoice, preserveA, commutative, decimal) @@ -250,6 +253,16 @@ object BuiltIns { } } + def compileNonetLeftShift(ctx: CompilationContext, lhs: Expression, rhs: Expression): List[AssemblyLine] = { + val label = MfCompiler.nextLabel("sh") + compileShiftOps(ASL, ctx, lhs, rhs) ++ List( + AssemblyLine.immediate(LDX, 0), + AssemblyLine.relative(BCC, label), + AssemblyLine.implied(INX), + AssemblyLine.label(label) + ) + } + def compileInPlaceByteShiftOps(opcode: Opcode.Value, ctx: CompilationContext, lhs: LhsExpression, rhs: Expression): List[AssemblyLine] = { val env = ctx.env val b = env.get[Type]("byte") diff --git a/src/main/scala/millfork/compiler/CompilationContext.scala b/src/main/scala/millfork/compiler/CompilationContext.scala index f5d3def2..7c72d3aa 100644 --- a/src/main/scala/millfork/compiler/CompilationContext.scala +++ b/src/main/scala/millfork/compiler/CompilationContext.scala @@ -7,6 +7,12 @@ import millfork.env.{Environment, MangledFunction, NormalFunction} * @author Karol Stasiak */ case class CompilationContext(env: Environment, function: NormalFunction, extraStackOffset: Int, options: CompilationOptions){ + def withInlinedEnv(environment: Environment): CompilationContext = { + val newEnv = new Environment(Some(env), MfCompiler.nextLabel("en")) + newEnv.things ++= environment.things + copy(env = newEnv) + } + def addStack(i: Int): CompilationContext = this.copy(extraStackOffset = extraStackOffset + i) diff --git a/src/main/scala/millfork/compiler/MfCompiler.scala b/src/main/scala/millfork/compiler/MfCompiler.scala index 62f561d4..3ad388dc 100644 --- a/src/main/scala/millfork/compiler/MfCompiler.scala +++ b/src/main/scala/millfork/compiler/MfCompiler.scala @@ -112,6 +112,7 @@ object MfCompiler { case FunctionCallExpression("<<'", params) => b case FunctionCallExpression(">>'", params) => b case FunctionCallExpression(">>>>", params) => b + case FunctionCallExpression("<<<<", params) => w case FunctionCallExpression("&&", params) => bool case FunctionCallExpression("||", params) => bool case FunctionCallExpression("^^", params) => bool @@ -390,7 +391,7 @@ object MfCompiler { def callingContext(ctx: CompilationContext, v: MemoryVariable): CompilationContext = { val result = new Environment(Some(ctx.env), "") - result.registerVariable(VariableDeclarationStatement(v.name, v.typ.name, stack = false, global = false, constant = false, volatile = false, initialValue = None, address = None), ctx.options) + result.registerVariable(VariableDeclarationStatement(v.name, v.typ.name, stack = false, global = false, constant = false, volatile = false, register = false, initialValue = None, address = None), ctx.options) ctx.copy(env = result) } @@ -866,6 +867,10 @@ object MfCompiler { case v: LhsExpression => BuiltIns.compileNonetOps(ctx, v, r) } + case "<<<<" => + assertAllBytes("Long shift ops not supported", ctx, params) + val (l, r, 1) = assertBinary(ctx, params) + BuiltIns.compileNonetLeftShift(ctx, l, r) case "<<" => assertAllBytes("Long shift ops not supported", ctx, params) val (l, r, 1) = assertBinary(ctx, params) @@ -1104,8 +1109,8 @@ object MfCompiler { // fallthrough to the lookup below } lookupFunction(ctx, f) match { - case function: InlinedFunction => - val (paramPreparation, statements) = inlineFunction(ctx, function, params) + case function: MacroFunction => + val (paramPreparation, statements) = inlineFunction(ctx, function, params, expr.position) paramPreparation ++ statements.map { case AssemblyStatement(opcode, addrMode, expression, elidable) => val param = env.evalForAsm(expression).getOrElse { @@ -1344,7 +1349,29 @@ object MfCompiler { SequenceChunk(statements.map(s => compile(ctx, s))) } - def inlineFunction(ctx: CompilationContext, i: InlinedFunction, params: List[Expression]): (List[AssemblyLine], List[ExecutableStatement]) = { + def replaceVariable(stmt: Statement, paramName: String, target: Expression): Statement = { + def f[T <: Expression](e:T) = e.replaceVariable(paramName, target) + def fx[T <: Expression](e:T) = e.replaceVariable(paramName, target).asInstanceOf[LhsExpression] + def g[T <: Statement](s:T) = replaceVariable(s, paramName, target) + def gx[T <: ExecutableStatement](s:T) = replaceVariable(s, paramName, target).asInstanceOf[ExecutableStatement] + def h(s:String) = if (s == paramName) target.asInstanceOf[VariableExpression].name else s + stmt match { + case ExpressionStatement(e) => ExpressionStatement(e.replaceVariable(paramName, target)) + case ReturnStatement(e) => ReturnStatement(e.map(f)) + case ReturnDispatchStatement(i,ps, bs) => ReturnDispatchStatement(i.replaceVariable(paramName, target), ps.map(fx), bs.map{ + case ReturnDispatchBranch(l, fu, pps) => ReturnDispatchBranch(l, f(fu), pps.map(f)) + }) + case WhileStatement(c, b) => WhileStatement(f(c), b.map(gx)) + case DoWhileStatement(b, c) => DoWhileStatement(b.map(gx), f(c)) + case ForStatement(v, start, end, dir, body) => ForStatement(h(v), f(start), f(end), dir, body.map(gx)) + case IfStatement(c, t, e) => IfStatement(f(c), t.map(gx), e.map(gx)) + case s:AssemblyStatement => s.copy(expression = f(s.expression)) + case Assignment(d,s) => Assignment(fx(d), f(s)) + case _ => ??? + } + } + + def inlineFunction(ctx: CompilationContext, i: MacroFunction, params: List[Expression], position: Option[Position]): (List[AssemblyLine], List[ExecutableStatement]) = { var paramPreparation = List[AssemblyLine]() var actualCode = i.code i.params match { @@ -1375,7 +1402,7 @@ object MfCompiler { } case (AssemblyParam(typ, v@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) => if (hadRegisterParam) { - ErrorReporting.error("Only one inline assembly function parameter can be passed via a register") + ErrorReporting.error("Only one macro assembly function parameter can be passed via a register", position) } hadRegisterParam = true paramPreparation = compile(ctx, actualParam, Some(typ, v), BranchSpec.None) @@ -1383,8 +1410,18 @@ object MfCompiler { ??? case (_, actualParam) => } - case NormalParamSignature(Nil) => - case NormalParamSignature(normalParams) => ??? + case NormalParamSignature(normalParams) => + if (params.length != normalParams.length) { + ErrorReporting.error(s"Invalid number of params for macro function ${i.name}", position) + } else { + params.zip(normalParams).foreach{ + case (v@VariableExpression(_), MemoryVariable(paramName, paramType, _)) => + actualCode = actualCode.map(stmt => replaceVariable(stmt, paramName, v).asInstanceOf[ExecutableStatement]) + case _ => + ErrorReporting.error(s"Parameters to macro functions have to be variables", position) + } + } + } // fix local labels: // TODO: do it even if the labels are in an inline assembly block inside a Millfork function @@ -1501,9 +1538,9 @@ object MfCompiler { LinearChunk(compileAssignment(ctx, source, dest)) case ExpressionStatement(e@FunctionCallExpression(name, params)) => env.lookupFunction(name, params.map(p => getExpressionType(ctx, p) -> p)) match { - case Some(i: InlinedFunction) => - val (paramPreparation, inlinedStatements) = inlineFunction(ctx, i, params) - SequenceChunk(List(LinearChunk(paramPreparation), compile(ctx, inlinedStatements))) + case Some(i: MacroFunction) => + val (paramPreparation, inlinedStatements) = inlineFunction(ctx, i, params, e.position) + SequenceChunk(List(LinearChunk(paramPreparation), compile(ctx.withInlinedEnv(i.environment), inlinedStatements))) case _ => LinearChunk(compile(ctx, e, None, NoBranching)) } diff --git a/src/main/scala/millfork/env/Constant.scala b/src/main/scala/millfork/env/Constant.scala index ebcd4a1f..1375bd1c 100644 --- a/src/main/scala/millfork/env/Constant.scala +++ b/src/main/scala/millfork/env/Constant.scala @@ -148,7 +148,7 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant { } object MathOperator extends Enumeration { - val Plus, Minus, Times, Shl, Shr, + val Plus, Minus, Times, Shl, Shr, Shl9, Shr9, DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShr, And, Or, Exor = Value } @@ -166,6 +166,8 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co case MathOperator.Times => lv * rv case MathOperator.Shl => lv << rv case MathOperator.Shr => lv >> rv + case MathOperator.Shl9 => (lv << rv) & 0x1ff + case MathOperator.Shr9 => (lv & 0x1ff) >> rv case MathOperator.Exor => lv ^ rv case MathOperator.Or => lv | rv case MathOperator.And => lv & rv @@ -229,6 +231,8 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co case Times => f"$plhs * $prhs" case Shl => f"$plhs << $prhs" case Shr => f"$plhs >> $prhs" + case Shl9 => f"$plhs <<<< $prhs" + case Shr9 => f"$plhs >>>> $prhs" case DecimalPlus => f"$plhs +' $prhs" case DecimalMinus => f"$plhs -' $prhs" case DecimalTimes => f"$plhs *' $prhs" diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 0800434f..6d06aea8 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -33,7 +33,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { val allThings: Map[String, Thing] = things.values.map { case m: FunctionInMemory => m.environment.getAllPrefixedThings - case m: InlinedFunction => + case m: MacroFunction => m.environment.getAllPrefixedThings case _ => Map[String, Thing]() }.fold(things.toMap)(_ ++ _) @@ -63,7 +63,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { def allConstants: List[ConstantThing] = things.values.flatMap { case m: NormalFunction => m.environment.allConstants - case m: InlinedFunction => m.environment.allConstants + case m: MacroFunction => m.environment.allConstants case m: ConstantThing => List(m) case _ => Nil }.toList @@ -105,7 +105,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p) ) } - case VariableAllocationMethod.Auto | VariableAllocationMethod.Static => + case VariableAllocationMethod.Auto | VariableAllocationMethod.Register | VariableAllocationMethod.Static => m.sizeInBytes match { case 0 => Nil case 2 => @@ -278,6 +278,10 @@ class Environment(val parent: Option[Environment], val prefix: String) { constantOperation(MathOperator.Shr, params) case "<<" => constantOperation(MathOperator.Shl, params) + case "<<<<" => + constantOperation(MathOperator.Shl9, params) + case ">>>>" => + constantOperation(MathOperator.Shr9, params) case "*'" => constantOperation(MathOperator.DecimalTimes, params) case "*" => @@ -366,20 +370,19 @@ class Environment(val parent: Option[Environment], val prefix: String) { if (stmt.reentrant && stmt.interrupt) ErrorReporting.error(s"Reentrant function `$name` cannot be an interrupt handler", stmt.position) if (stmt.reentrant && stmt.params.nonEmpty) ErrorReporting.error(s"Reentrant function `$name` cannot have parameters", stmt.position) if (stmt.interrupt && stmt.params.nonEmpty) ErrorReporting.error(s"Interrupt function `$name` cannot have parameters", stmt.position) - if (stmt.inlined) { + if (stmt.isMacro) { if (!stmt.assembly) { - if (stmt.params.nonEmpty) ErrorReporting.error(s"Inline non-assembly function `$name` cannot have parameters", stmt.position) // TODO: ??? - if (resultType != VoidType) ErrorReporting.error(s"Inline non-assembly function `$name` must return void", stmt.position) + if (resultType != VoidType) ErrorReporting.error(s"Macro non-assembly function `$name` must return void", stmt.position) } - if (stmt.params.exists(_.assemblyParamPassingConvention.inNonInlinedOnly)) - ErrorReporting.error(s"Inline function `$name` cannot have by-variable parameters", stmt.position) + if (stmt.assembly && stmt.params.exists(_.assemblyParamPassingConvention.inNonInlinedOnly)) + ErrorReporting.error(s"Macro function `$name` cannot have by-variable parameters", stmt.position) } else { if (!stmt.assembly) { if (stmt.params.exists(!_.assemblyParamPassingConvention.isInstanceOf[ByVariable])) ErrorReporting.error(s"Non-assembly function `$name` cannot have non-variable parameters", stmt.position) } if (stmt.params.exists(_.assemblyParamPassingConvention.inInlinedOnly)) - ErrorReporting.error(s"Non-inline function `$name` cannot have inlinable parameters", stmt.position) + ErrorReporting.error(s"Non-macro function `$name` cannot have inlinable parameters", stmt.position) } val env = new Environment(Some(this), name + "$") @@ -432,9 +435,9 @@ class Environment(val parent: Option[Environment], val prefix: String) { case e: ExecutableStatement => Some(e) case _ => None } - val needsExtraRTS = !stmt.inlined && !stmt.assembly && (statements.isEmpty || !statements.last.isInstanceOf[ReturnStatement]) - if (stmt.inlined) { - val mangled = InlinedFunction( + val needsExtraRTS = !stmt.isMacro && !stmt.assembly && (statements.isEmpty || !statements.last.isInstanceOf[ReturnStatement]) + if (stmt.isMacro) { + val mangled = MacroFunction( name, resultType, params, @@ -603,6 +606,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } if (stmt.constant) { if (stmt.stack) ErrorReporting.error(s"`$name` is a constant and cannot be on stack", position) + if (stmt.register) ErrorReporting.error(s"`$name` is a constant and cannot be in a register", position) if (stmt.address.isDefined) ErrorReporting.error(s"`$name` is a constant and cannot have an address", position) if (stmt.initialValue.isEmpty) ErrorReporting.error(s"`$name` is a constant and requires a value", position) val constantValue: Constant = stmt.initialValue.flatMap(eval).getOrElse(Constant.error(s"`$name` has a non-constant value", position)) @@ -614,8 +618,12 @@ class Environment(val parent: Option[Environment], val prefix: String) { } } else { if (stmt.stack && stmt.global) ErrorReporting.error(s"`$name` is static or global and cannot be on stack", position) + if (stmt.register && typ.size != 1) ErrorReporting.error(s"A register variable `$name` is too large", position) + if (stmt.register && stmt.global) ErrorReporting.error(s"`$name` is static or global and cannot be in a register", position) + if (stmt.register && stmt.stack) ErrorReporting.error(s"`$name` cannot be simultaneously on stack and in a register", position) if (stmt.initialValue.isDefined && parent.isDefined) ErrorReporting.error(s"`$name` is local and not a constant and therefore cannot have a value", position) if (stmt.initialValue.isDefined && stmt.address.isDefined) ErrorReporting.warn(s"`$name` has both address and initial value - this may not work as expected!", options, position) + if (stmt.register && stmt.address.isDefined) ErrorReporting.error(s"`$name` cannot by simultaneously at an address and in a register", position) if (stmt.stack) { val v = StackVariable(prefix + name, typ, this.baseStackOffset) baseStackOffset += typ.size @@ -626,7 +634,11 @@ class Environment(val parent: Option[Environment], val prefix: String) { } } else { val (v, addr) = stmt.address.fold[(VariableInMemory, Constant)]({ - val alloc = if (typ.name == "pointer") VariableAllocationMethod.Zeropage else if (stmt.global) VariableAllocationMethod.Static else VariableAllocationMethod.Auto + val alloc = + if (typ.name == "pointer") VariableAllocationMethod.Zeropage + else if (stmt.global) VariableAllocationMethod.Static + else if (stmt.register) VariableAllocationMethod.Register + else VariableAllocationMethod.Auto if (alloc != VariableAllocationMethod.Static && stmt.initialValue.isDefined) { ErrorReporting.error(s"`$name` cannot be preinitialized`", position) } diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index 5b0fa244..f08e308f 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -117,7 +117,7 @@ sealed trait UninitializedMemory extends ThingInMemory { } object VariableAllocationMethod extends Enumeration { - val Auto, Static, Zeropage, None = Value + val Auto, Register, Static, Zeropage, None = Value } case class StackVariable(name: String, typ: Type, baseOffset: Int) extends Variable { @@ -188,11 +188,11 @@ case class EmptyFunction(name: String, override def interrupt = false } -case class InlinedFunction(name: String, - returnType: Type, - params: ParamSignature, - environment: Environment, - code: List[ExecutableStatement]) extends MangledFunction { +case class MacroFunction(name: String, + returnType: Type, + params: ParamSignature, + environment: Environment, + code: List[ExecutableStatement]) extends MangledFunction { override def interrupt = false } diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index 412c806f..7d26f4c7 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -97,6 +97,7 @@ case class VariableDeclarationStatement(name: String, stack: Boolean, constant: Boolean, volatile: Boolean, + register: Boolean, initialValue: Option[Expression], address: Option[Expression]) extends DeclarationStatement { override def getAllExpressions: List[Expression] = List(initialValue, address).flatten @@ -121,7 +122,8 @@ case class FunctionDeclarationStatement(name: String, params: List[ParameterDeclaration], address: Option[Expression], statements: Option[List[Statement]], - inlined: Boolean, + isMacro: Boolean, + inlinable: Option[Boolean], assembly: Boolean, interrupt: Boolean, reentrant: Boolean) extends DeclarationStatement { diff --git a/src/main/scala/millfork/output/Assembler.scala b/src/main/scala/millfork/output/Assembler.scala index c203966d..b983e630 100644 --- a/src/main/scala/millfork/output/Assembler.scala +++ b/src/main/scala/millfork/output/Assembler.scala @@ -145,9 +145,7 @@ class Assembler(private val program: Program, private val rootEnv: Environment) val assembly = mutable.ArrayBuffer[String]() val potentiallyInlineable: Map[String, Int] = - if (options.flags(CompilationFlag.InlineFunctions)) - InliningCalculator.getPotentiallyInlineableFunctions(program) - else Map() + InliningCalculator.getPotentiallyInlineableFunctions(program, options.flags(CompilationFlag.InlineFunctions)) var inlinedFunctions = Map[String, List[AssemblyLine]]() val compiledFunctions = mutable.Map[String, List[AssemblyLine]]() diff --git a/src/main/scala/millfork/output/InliningCalculator.scala b/src/main/scala/millfork/output/InliningCalculator.scala index a84f5104..a7ebe86d 100644 --- a/src/main/scala/millfork/output/InliningCalculator.scala +++ b/src/main/scala/millfork/output/InliningCalculator.scala @@ -14,10 +14,14 @@ object InliningCalculator { private val sizes = Seq(64, 64, 8, 6, 5, 5, 4) - def getPotentiallyInlineableFunctions(program: Program): Map[String, Int] = { + def getPotentiallyInlineableFunctions(program: Program, + inlineByDefault: Boolean, + aggressivenessForNormal: Double = 1.0, + aggressivenessForRecommended: Double = 1.2): Map[String, Int] = { val callCount = mutable.Map[String, Int]().withDefaultValue(0) val allFunctions = mutable.Set[String]() val badFunctions = mutable.Set[String]() + val recommendedFunctions = mutable.Set[String]() getAllCalledFunctions(program.declarations).foreach{ case (name, true) => badFunctions += name case (name, false) => callCount(name) += 1 @@ -25,7 +29,11 @@ object InliningCalculator { program.declarations.foreach{ case f:FunctionDeclarationStatement => allFunctions += f.name - if (f.inlined + if (f.inlinable.contains(true)) { + recommendedFunctions += f.name + } + if (f.isMacro + || f.inlinable.contains(false) || f.address.isDefined || f.interrupt || f.reentrant @@ -34,7 +42,12 @@ object InliningCalculator { case _ => } allFunctions --= badFunctions - allFunctions.map(f => f -> sizes(callCount(f) min (sizes.size - 1))).toMap + recommendedFunctions --= badFunctions + (if (inlineByDefault) allFunctions else recommendedFunctions).map(f => f -> { + val size = sizes(callCount(f) min (sizes.size - 1)) + val aggressiveness = if (recommendedFunctions(f)) aggressivenessForRecommended else aggressivenessForNormal + (size * aggressiveness).floor.toInt + }).toMap } private def getAllCalledFunctions(expressions: List[Node]): List[(String, Boolean)] = expressions.flatMap { @@ -71,6 +84,9 @@ object InliningCalculator { if (code.isEmpty) return None if (code.last.opcode != RTS) return None var result = code.init + while (result.nonEmpty && OpcodeClasses.NoopDiscardsFlags(result.last.opcode)) { + result = result.init + } if (result.head.opcode == LABEL && result.head.parameter == Label(fname).toAddress) result = result.tail if (result.exists(l => badOpcodes(l.opcode))) return None Some(result) diff --git a/src/main/scala/millfork/output/VariableAllocator.scala b/src/main/scala/millfork/output/VariableAllocator.scala index 9bb39d69..60393adf 100644 --- a/src/main/scala/millfork/output/VariableAllocator.scala +++ b/src/main/scala/millfork/output/VariableAllocator.scala @@ -27,6 +27,9 @@ sealed trait ByteAllocator { if (counter == 0) { lastFree = i } + if (count == 0) { + return lastFree + } counter += 1 if (counter == count) { return lastFree diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index 815d9bfd..cf491c69 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -106,7 +106,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o List("&&"), List("==", "<=", ">=", "!=", "<", ">"), List(":"), - List("+'", "-'", "<<'", ">>'", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"), + List("+'", "-'", "<<'", ">>'", "<<<<", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"), List("*'", "*")) val nonStatementLevel = 1 // everything but not `=` @@ -116,7 +116,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def variableDefinition(implicitlyGlobal: Boolean): P[DeclarationStatement] = for { p <- position() - flags <- flags("const", "static", "volatile", "stack") ~ HWS + flags <- flags("const", "static", "volatile", "stack", "register") ~ HWS typ <- identifier ~ SWS name <- identifier ~/ HWS ~/ Pass addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("
") ~ HWS @@ -128,6 +128,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o stack = flags("stack"), constant = flags("const"), volatile = flags("volatile"), + register = flags("register"), initialValue, addr).pos(p) } @@ -332,11 +333,11 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def statement: P[Statement] = (position() ~ P(keywordStatement | variableDefinition(false) | expressionStatement)).map { case (p, s) => s.pos(p) } - def asmStatements: P[List[ExecutableStatement]] = ("{" ~/ AWS ~/ asmStatement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList) + def asmStatements: P[List[ExecutableStatement]] = ("{" ~/ AWS ~/ asmStatement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList) - def statements: P[List[Statement]] = ("{" ~/ AWS ~ statement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList) + def statements: P[List[Statement]] = ("{" ~/ AWS ~ statement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList) - def executableStatements: P[Seq[ExecutableStatement]] = "{" ~/ AWS ~/ executableStatement.rep(sep = EOL ~ !"}" ~/ Pass) ~/ AWS ~ "}" + def executableStatements: P[Seq[ExecutableStatement]] = "{" ~/ AWS ~/ executableStatement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~ "}" def dispatchLabel: P[ReturnDispatchLabel] = ("default" ~ !letterOrDigit ~/ AWS ~/ ("(" ~/ position("default branch range") ~ AWS ~/ mlExpression(nonStatementLevel).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?).map{ @@ -406,16 +407,19 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def functionDefinition: P[DeclarationStatement] = for { p <- position() - flags <- flags("asm", "inline", "interrupt", "reentrant") ~ HWS + flags <- flags("asm", "inline", "interrupt", "macro", "noinline", "reentrant") ~ HWS returnType <- identifier ~ SWS name <- identifier ~ HWS params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS addr <- ("@" ~/ HWS ~/ mlExpression(1)).?.opaque("
") ~/ AWS statements <- (externFunctionBody | (if (flags("asm")) asmStatements else statements).map(l => Some(l))) ~/ Pass } yield { - if (flags("interrupt") && flags("inline")) ErrorReporting.error(s"Interrupt function `$name` cannot be inline", Some(p)) + if (flags("interrupt") && flags("macro")) ErrorReporting.error(s"Interrupt function `$name` cannot be macros", Some(p)) if (flags("interrupt") && flags("reentrant")) ErrorReporting.error("Interrupt function `$name` cannot be reentrant", Some(p)) - if (flags("inline") && flags("reentrant")) ErrorReporting.error("Reentrant and inline exclude each other", Some(p)) + if (flags("macro") && flags("reentrant")) ErrorReporting.error("Reentrant and macro exclude each other", Some(p)) + if (flags("inline") && flags("noinline")) ErrorReporting.error("Noinline and inline exclude each other", Some(p)) + if (flags("macro") && flags("noinline")) ErrorReporting.error("Noinline and macro exclude each other", Some(p)) + if (flags("inline") && flags("macro")) ErrorReporting.error("Macro and inline exclude each other", Some(p)) if (flags("interrupt") && returnType != "void") ErrorReporting.error("Interrupt function `$name` has to return void", Some(p)) if (addr.isEmpty && statements.isEmpty) ErrorReporting.error("Extern function `$name` must have an address", Some(p)) if (statements.isEmpty && !flags("asm") && params.nonEmpty) ErrorReporting.error("Extern non-asm function `$name` cannot have parameters", Some(p)) @@ -433,14 +437,14 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o case _ => false }) ErrorReporting.warn("Assembly non-interrupt function `$name` contains RTI, did you mean RTS?", options, Some(p)) } - if (!flags("inline")) { + if (!flags("macro")) { xs.last match { case AssemblyStatement(Opcode.RTS, _, _, _) => () // OK case AssemblyStatement(Opcode.RTI, _, _, _) => () // OK case AssemblyStatement(Opcode.JMP, _, _, _) => () // OK case _ => val validReturn = if (flags("interrupt")) "RTI" else "RTS" - ErrorReporting.warn(s"Non-inline assembly function `$name` should end in " + validReturn, options, Some(p)) + ErrorReporting.warn(s"Non-macro assembly function `$name` should end in " + validReturn, options, Some(p)) } } case None => () @@ -448,7 +452,8 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o FunctionDeclarationStatement(name, returnType, params.toList, addr, statements, - flags("inline"), + flags("macro"), + if (flags("inline")) Some(true) else if (flags("noinline")) Some(false) else None, flags("asm"), flags("interrupt"), flags("reentrant")).pos(p) diff --git a/src/test/scala/millfork/test/InlineAssemblyFunctionsSuite.scala b/src/test/scala/millfork/test/AssemblyMacroSuite.scala similarity index 85% rename from src/test/scala/millfork/test/InlineAssemblyFunctionsSuite.scala rename to src/test/scala/millfork/test/AssemblyMacroSuite.scala index c6c33447..f0be6db4 100644 --- a/src/test/scala/millfork/test/InlineAssemblyFunctionsSuite.scala +++ b/src/test/scala/millfork/test/AssemblyMacroSuite.scala @@ -8,12 +8,12 @@ import org.scalatest.{FunSuite, Matchers} /** * @author Karol Stasiak */ -class InlineAssemblyFunctionsSuite extends FunSuite with Matchers { +class AssemblyMacroSuite extends FunSuite with Matchers { test("Poke test 1") { EmuBenchmarkRun( """ - | inline asm void poke(word ref addr, byte a) { + | macro asm void poke(word ref addr, byte a) { | STA addr | } | @@ -28,7 +28,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers { test("Peek test 1") { EmuBenchmarkRun( """ - | inline asm byte peek(word ref addr) { + | macro asm byte peek(word ref addr) { | ?LDA addr | } | @@ -45,7 +45,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers { test("Poke test 2") { EmuBenchmarkRun( """ - | inline asm void poke(word const addr, byte a) { + | macro asm void poke(word const addr, byte a) { | STA addr | } | @@ -62,7 +62,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers { test("Peek test 2") { EmuBenchmarkRun( """ - | inline asm byte peek(word const addr) { + | macro asm byte peek(word const addr) { | ?LDA addr | } | @@ -80,7 +80,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers { test("Labels test") { EmuBenchmarkRun( """ - | inline asm void doNothing () { + | macro asm void doNothing () { | JMP label | label: | } diff --git a/src/test/scala/millfork/test/AssemblyOptimizationSuite.scala b/src/test/scala/millfork/test/AssemblyOptimizationSuite.scala index 393a528a..c1ee74f8 100644 --- a/src/test/scala/millfork/test/AssemblyOptimizationSuite.scala +++ b/src/test/scala/millfork/test/AssemblyOptimizationSuite.scala @@ -41,7 +41,7 @@ class AssemblyOptimizationSuite extends FunSuite with Matchers { """ | array output [100] @$C000 | void main () { - | byte i + | register byte i | i = 1 | while (i<50) { | output[i] = i diff --git a/src/test/scala/millfork/test/AssemblySuite.scala b/src/test/scala/millfork/test/AssemblySuite.scala index deb0b6b8..76263184 100644 --- a/src/test/scala/millfork/test/AssemblySuite.scala +++ b/src/test/scala/millfork/test/AssemblySuite.scala @@ -61,7 +61,7 @@ class AssemblySuite extends FunSuite with Matchers { """.stripMargin)(_.readByte(0xc000) should equal(10)) } - test("Inline asm functions") { + test("Macro asm functions") { EmuBenchmarkRun( """ | byte output @$c000 @@ -70,14 +70,14 @@ class AssemblySuite extends FunSuite with Matchers { | f() | f() | } - | inline asm void f() { + | macro asm void f() { | inc $c000 | rts | } """.stripMargin)(_.readByte(0xc000) should equal(1)) } - test("Inline asm functions 2") { + test("macro asm functions 2") { EmuBenchmarkRun( """ | byte output @$c000 @@ -86,7 +86,7 @@ class AssemblySuite extends FunSuite with Matchers { | add(output, 5) | add(output, 5) | } - | inline asm void add(byte ref v, byte const c) { + | macro asm void add(byte ref v, byte const c) { | lda v | clc | adc #c @@ -104,7 +104,7 @@ class AssemblySuite extends FunSuite with Matchers { | output = 0 | add256(output) | } - | inline asm void add256(word ref v) { + | macro asm void add256(word ref v) { | inc v+1 | } """.stripMargin)(_.readWord(0xc000) should equal(0x100)) diff --git a/src/test/scala/millfork/test/BasicSymonTest.scala b/src/test/scala/millfork/test/BasicSymonTest.scala index 88b4c567..55ea9c31 100644 --- a/src/test/scala/millfork/test/BasicSymonTest.scala +++ b/src/test/scala/millfork/test/BasicSymonTest.scala @@ -23,7 +23,7 @@ class BasicSymonTest extends FunSuite with Matchers { | void main () { | panic() | } - | inline asm void panic() { + | macro asm void panic() { | JSR _panic | } | void _panic() { diff --git a/src/test/scala/millfork/test/NonetSuite.scala b/src/test/scala/millfork/test/NonetSuite.scala index f78f055e..db71b6f4 100644 --- a/src/test/scala/millfork/test/NonetSuite.scala +++ b/src/test/scala/millfork/test/NonetSuite.scala @@ -26,4 +26,26 @@ class NonetSuite extends FunSuite with Matchers { m.readByte(0xc001) should equal(0x88) m.readByte(0xc002) should equal(0x44) } + + test("Nonet left shift") { + val m = EmuUnoptimizedRun( + """ + | word output0 @$c000 + | word output1 @$c002 + | word output2 @$c004 + | word output3 @$c006 + | void main () { + | byte a + | a = 3 + | output0 = a <<<< 1 + | output1 = a <<<< 2 + | output2 = a <<<< 6 + | output3 = a <<<< 7 + | } + """.stripMargin) + m.readWord(0xc000) should equal(0x06) + m.readWord(0xc002) should equal(0x0C) + m.readWord(0xc004) should equal(0xC0) + m.readWord(0xc006) should equal(0x180) + } }