1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-31 18:41:30 +00:00

Tons of things:

– changed `inline` to `macro`
– added support for parameters for macros written in Millfork
– added `inline`, `noinline`, `register` hints
– added <<<< operator
– pointer dereference expressions are now supported more widely
– C64 library fixes
– added `-O1` command line option as an alias for `-O`
This commit is contained in:
Karol Stasiak 2018-02-01 22:39:38 +01:00
parent c599db0068
commit 0ca1be0c00
29 changed files with 281 additions and 131 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -20,7 +20,8 @@ Syntax:
`[<storage>] <type> <name> [@<address>] [= <initial_value>]`
* `<storage>` can be only specified for local variables. It can be either `stack`, `static` or nothing.
* `<storage>` 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).
* `<address>` 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.

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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")

View File

@ -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")

View File

@ -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)

View File

@ -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))
}

View File

@ -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"

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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]]()

View File

@ -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)

View File

@ -27,6 +27,9 @@ sealed trait ByteAllocator {
if (counter == 0) {
lastFree = i
}
if (count == 0) {
return lastFree
}
counter += 1
if (counter == count) {
return lastFree

View File

@ -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("<address>") ~ 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("<address>") ~/ 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)

View File

@ -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:
| }

View File

@ -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

View File

@ -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))

View File

@ -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() {

View File

@ -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)
}
}