1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-11 15:29:34 +00:00

Add align keyword

This commit is contained in:
Karol Stasiak 2018-10-04 21:33:10 +02:00
parent 3736b5ae56
commit fe1bf68295
11 changed files with 109 additions and 11 deletions

View File

@ -14,6 +14,8 @@
* Added preprocessor
* Added `align` keyword for choosing data and code alignment.
* Automatic selection of text encoding based on target platform.
* Text literals can be now used as expressions of type `pointer`.

View File

@ -4,7 +4,7 @@
Syntax:
`[segment (<segment>)] [<modifiers>] <return_type> <name> ( <params> ) [@ <address>] { <body> }`
`[segment (<segment>)] [<modifiers>] <return_type> <name> ( <params> ) [align ( <alignment> )] [@ <address>] { <body> }`
`[segment (<segment>)] asm <return_type> <name> ( <params> ) @ <address> extern`
@ -38,6 +38,13 @@ Syntax:
* `<params>` is a comma-separated list of parameters, in form `type name`. Allowed types are the same as for local variables.
* `<alignment>` is either a numeric literal that is a power of 2, or keyword `fast`.
The function will be allocated at the address divisible by alignment.
`fast` means different things depending on the target platform:
* on 6502, it means that the function will not cross a page boundary
* on Z80, it is ignored
* `<address>` is a constant expression that defines where in the memory the function is or will be located.
* `extern` is a keyword than marks functions that are not defined in the current program,

View File

@ -88,7 +88,7 @@ An array is a continuous sequence of bytes in memory.
Syntax:
`[segment(<segment>)] array <name> [[<size>]] [@<address>] [= <initial_values>]`
`[segment(<segment>)] array <name> [[<size>]] [align ( <alignment> )] [@<address>] [= <initial_values>]`
* `<segment>`: segment name; if absent,
then defaults to `default_code_segment` as defined for the platform if the array has initial values,
@ -100,6 +100,13 @@ and declares the array size to be equal to the number of variants in that enumer
If the size is not specified here, then it's deduced from the `<initial_values>`.
If the declared size and the size deduced from the `<initial_values>` don't match, then an error is raised.
* `<alignment>` is either a numeric literal that is a power of 2, or keyword `fast`.
The array will be allocated at the address divisible by alignment.
`fast` means different things depending on the target platform:
* on 6502, it means that the array will not cross a page boundary
* on Z80, it means that the array will not cross a page boundary
TODO
### Function declarations

View File

@ -24,15 +24,15 @@
<Keywords name="Folders in comment, open"></Keywords>
<Keywords name="Folders in comment, middle"></Keywords>
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">void byte sbyte ubyte array word farword pointer farpointer long word_be word_le file int8 int16 int24 int32 int40 int48 int56 int64 signed8</Keywords>
<Keywords name="Keywords2">if else for return while do asm extern import segment break continue default alias enum</Keywords>
<Keywords name="Keywords3">defaultz petscii ascii scr petscr pet atascii atari bbc sinclair apple2 jis jisx iso_de iso_yu iso_no iso_dk iso_se iso_fi petsciiz asciiz scrz petscrz petz atasciiz atariz bbcz sinclairz apple2z jisz jisxz iso_dez iso_yuz iso_noz iso_dkz iso_sez iso_fiz until to downto parallelto static stack ref const volatile paralleluntil inline noinline macro register kernal_interrupt interrupt reentrant hi lo sin cos tan nonet</Keywords>
<Keywords name="Keywords1">void byte sbyte ubyte array word farword pointer farpointer long word_be word_le file int8 int16 int24 int32 int40 int48 int56 int64 signed8 fast</Keywords>
<Keywords name="Keywords2">if else for return while do asm extern import segment break continue default alias enum</Keywords>
<Keywords name="Keywords3">defaultz petscii ascii scr petscr pet atascii atari bbc sinclair apple2 jis jisx iso_de iso_yu iso_no iso_dk iso_se iso_fi petsciiz asciiz scrz petscrz petz atasciiz atariz bbcz sinclairz apple2z jisz jisxz iso_dez iso_yuz iso_noz iso_dkz iso_sez iso_fiz until to downto parallelto static stack ref const volatile paralleluntil inline noinline macro register kernal_interrupt interrupt reentrant hi lo sin cos tan nonet align</Keywords>
<Keywords name="Keywords4">&quot;sta &quot; &quot;lda &quot; &quot;jmp &quot; &quot;bit &quot; &quot;eor &quot; &quot;adc &quot; &quot;ora &quot; &quot;and &quot; &quot;ldx &quot; &quot;ldy &quot; &quot;stx &quot; &quot;sty &quot; &quot;tax&quot; &quot;tay&quot; &quot;tya&quot; &quot;txa&quot; &quot;txs&quot; &quot;tsx&quot; &quot;sei&quot; &quot;cli&quot; &quot;clv&quot; &quot;clc&quot; &quot;cld&quot; &quot;sed&quot; &quot;sec&quot; eq &quot;bne &quot; &quot;bmi &quot; &quot;bpl &quot; &quot;bcc &quot; &quot;bcs &quot; &quot;bvs &quot; bvc &quot; &quot;jsr &quot; rts&quot; &quot;rti&quot; &quot;brk&quot; &quot;rol&quot; &quot;ror&quot; &quot;asl&quot; &quot;lsr&quot; &quot;inc &quot; &quot;dec &quot; &quot;cmp &quot; &quot;cpx &quot; &quot;cpy &quot; inx iny dex dey pla pha plp hp phx plx phy ply &quot;stz &quot; &quot;ldz &quot; tza taz &quot;tsb &quot; &quot;trb &quot; ra txy tyx pld plb phb phd phk xce&#x000D;&#x000A;&#x000D;&#x000A;&quot;STA &quot; &quot;LDA &quot; &quot;JMP &quot; &quot;BIT &quot; &quot;EOR &quot; &quot;ADC &quot; &quot;ORA &quot; &quot;AND &quot; &quot;LDX &quot; &quot;LDY &quot; &quot;STX &quot; &quot;STY &quot; &quot;TAX&quot; &quot;TAY&quot; &quot;TYA&quot; &quot;TXA&quot; &quot;TXS&quot; &quot;TSX&quot; &quot;SEI&quot; &quot;CLI&quot; &quot;CLV&quot; &quot;CLC&quot; &quot;CLD&quot; &quot;SED&quot; &quot;SEC&quot; EQ &quot;BNE &quot; &quot;BMI &quot; &quot;BPL &quot; &quot;BCC &quot; &quot;BCS &quot; &quot;BVS &quot; BVC &quot; &quot;JSR &quot; RTS&quot; &quot;RTI&quot; &quot;BRK&quot; &quot;ROL&quot; &quot;ROR&quot; &quot;ASL&quot; &quot;LSR&quot; &quot;INC &quot; &quot;DEC &quot; &quot;CMP &quot; &quot;CPX &quot; &quot;CPY &quot; INX INY DEX DEY PLA PHA PLP HP PHX PLX PHY PLY &quot;STZ &quot; &quot;LDZ &quot; TZA TAZ &quot;TSB &quot; &quot;TRB &quot; RA TXY TYX PLD PLB PHB PHD PHK XCE</Keywords>
<Keywords name="Keywords5">&quot;sbx &quot; &quot;isc &quot; &quot;dcp &quot; &quot;lax &quot; &quot;sax &quot; &quot;anc &quot; &quot;alr &quot; &quot;arr &quot; &quot;rra &quot; &quot;rla &quot; &quot;lxa &quot; &quot;ane &quot; &quot;xaa &quot;&#x000D;&#x000A;&quot;SBX &quot; &quot;ISC &quot; &quot;DCP &quot; &quot;LAX &quot; &quot;SAX &quot; &quot;ANC &quot; &quot;ALR &quot; &quot;ARR &quot; &quot;RRA &quot; &quot;RLA &quot; &quot;LXA &quot; &quot;ANE &quot; &quot;XAA &quot;</Keywords>
<Keywords name="Keywords6"></Keywords>
<Keywords name="Keywords7"></Keywords>
<Keywords name="Keywords8"></Keywords>
<Keywords name="Delimiters">00&quot; 01 02&quot; 03#if 03#else 03#elsif 03#endif 03#use 03#warn 03#error 03#info 03#fatal 03#pragma 04 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords>
<Keywords name="Delimiters">00&quot; 01 02&quot; 03#if 03#else 03#elsif 03#endif 03#use 03#warn 03#error 03#info 03#fatal 03#pragma 03#infoeval 04 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 05((EOL)) 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords>
</KeywordLists>
<Styles>
<WordsStyle name="DEFAULT" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />

View File

@ -1,6 +1,7 @@
package millfork.env
import millfork.DecimalUtils._
import millfork.output.DivisibleAlignment
object Constant {
val Zero: Constant = NumericConstant(0, 1)
@ -27,6 +28,7 @@ sealed trait Constant {
case _ => false
}
def fitsProvablyIntoByte: Boolean = false
def isProvablyDivisibleBy256: Boolean = false
def asl(i: Constant): Constant = i match {
case NumericConstant(sa, _) => asl(sa.toInt)
@ -47,7 +49,7 @@ sealed trait Constant {
def loByte: Constant = {
if (requiredSize == 1) return this
SubbyteConstant(this, 0)
if (isProvablyDivisibleBy256) Constant.Zero else SubbyteConstant(this, 0)
}
def hiByte: Constant = {
@ -58,7 +60,7 @@ sealed trait Constant {
def subbyte(index: Int): Constant = {
if (requiredSize <= index) Constant.Zero
else index match {
case 0 => loByte
case 0 => if (isProvablyDivisibleBy256) Constant.Zero else loByte
case 1 => hiByte
case _ => SubbyteConstant(this, index)
}
@ -128,13 +130,14 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant {
}
}
override def isProvablyGreaterOrEqualThan(other: Constant): Boolean = value >= 0 && (other match {
case NumericConstant(o, _) if o >= 0 => value >=o
case NumericConstant(o, _) if o >= 0 => value >= o
case _ => false
})
override def isProvablyZero: Boolean = value == 0
override def isProvably(i: Int): Boolean = value == i
override def isProvablyNonnegative: Boolean = value >= 0
override def fitsProvablyIntoByte: Boolean = requiredSize == 1
override def isProvablyDivisibleBy256: Boolean = (value & 0xff) == 0
override def isLowestByteAlwaysEqual(i: Int) : Boolean = (value & 0xff) == (i&0xff)
@ -189,6 +192,14 @@ case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant {
case _ => false
}
override def isProvablyDivisibleBy256: Boolean = thing match {
case t:PreallocableThing => t.alignment match {
case DivisibleAlignment(divisor) => divisor.&(0xff) == 0
case _ => false
}
case _ => false
}
override def requiredSize = 2
override def toString: String = thing.name
@ -215,6 +226,7 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant {
override def isProvablyNonnegative: Boolean = true
override def fitsProvablyIntoByte: Boolean = true
override def isProvablyDivisibleBy256: Boolean = index == 0 && base.isProvablyDivisibleBy256
override def toString: String = index match {
case 0 => s"lo($base)"
@ -252,6 +264,16 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
}
}
override def isProvablyDivisibleBy256: Boolean = operator match {
case MathOperator.And | MathOperator.Times =>
lhs.isProvablyDivisibleBy256 || rhs.isProvablyDivisibleBy256
case MathOperator.Or | MathOperator.Exor | MathOperator.Plus | MathOperator.Minus =>
lhs.isProvablyDivisibleBy256 && rhs.isProvablyDivisibleBy256
case MathOperator.Shl =>
rhs.isProvablyGreaterOrEqualThan(NumericConstant(8, 1))
case _ => false
}
override def quickSimplify: Constant = {
val l = lhs.quickSimplify
val r = rhs.quickSimplify

View File

@ -787,7 +787,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
reentrant = stmt.reentrant,
position = stmt.position,
declaredBank = stmt.bank,
alignment = if (name == "main") NoAlignment else defaultFunctionAlignment(options, hot = true) // TODO: decide actual hotness in a smarter way
alignment = stmt.alignment.getOrElse(if (name == "main") NoAlignment else defaultFunctionAlignment(options, hot = true)) // TODO: decide actual hotness in a smarter way
)
addThing(mangled, stmt.position)
registerAddressConstant(mangled, stmt.position, options)

View File

@ -287,6 +287,7 @@ case class FunctionDeclarationStatement(name: String,
params: List[ParameterDeclaration],
bank: Option[String],
address: Option[Expression],
alignment: Option[MemoryAlignment],
statements: Option[List[Statement]],
isMacro: Boolean,
inlinable: Option[Boolean],

View File

@ -8,6 +8,7 @@ import fastparse.all._
import millfork.env._
import millfork.error.{ConsoleLogger, Logger}
import millfork.node._
import millfork.output.{DivisibleAlignment, MemoryAlignment, NoAlignment}
import millfork.{CompilationFlag, CompilationOptions, SeparatedList}
/**
@ -211,13 +212,30 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
target <- "=" ~/ HWS ~/ identifier ~/ HWS
} yield Seq(AliasDefinitionStatement(name, target).pos(p))
def fastAlignmentForArrays: MemoryAlignment
def fastAlignmentForFunctions: MemoryAlignment
def alignmentDeclaration(fast: MemoryAlignment): P[MemoryAlignment] = (position() ~ "align" ~/ AWS ~/ "(" ~/ AWS ~/ atom ~/ AWS ~/ ")").map {
case (_, LiteralExpression(1, _)) => NoAlignment
case (pos, LiteralExpression(n, _)) =>
if (n >= 1 && n <= 0x8000 & (n & (n - 1)) == 0) DivisibleAlignment(n.toInt)
else {
log.error("Invalid alignment: " + n, Some(pos))
NoAlignment
}
case (pos, VariableExpression("fast")) => fast
case (pos, _) =>
log.error("Invalid alignment", Some(pos))
NoAlignment
}
val arrayDefinition: P[Seq[ArrayDeclarationStatement]] = for {
p <- position()
bank <- bankDeclaration
name <- "array" ~ !letterOrDigit ~/ SWS ~ identifier ~ HWS
length <- ("[" ~/ AWS ~/ mfExpression(nonStatementLevel, false) ~ AWS ~ "]").? ~ HWS
alignment <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ HWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).? ~/ HWS
alignment = None // TODO
contents <- ("=" ~/ HWS ~/ arrayContents).? ~/ HWS
} yield Seq(ArrayDeclarationStatement(name, bank, length, addr, contents, alignment).pos(p))
@ -391,6 +409,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
if !InvalidReturnTypes(returnType)
name <- identifier ~ HWS
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS
alignment <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ AWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).?.opaque("<address>") ~/ AWS
statements <- (externFunctionBody | (if (flags("asm")) asmStatements else statements).map(l => Some(l))) ~/ Pass
} yield {
@ -404,11 +423,14 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
if (flags("inline") && flags("macro")) log.error("Macro and inline exclude each other", Some(p))
if (flags("interrupt") && returnType != "void") log.error("Interrupt function `$name` has to return void", Some(p))
if (addr.isEmpty && statements.isEmpty) log.error("Extern function `$name` must have an address", Some(p))
if (addr.isDefined && alignment.isDefined) log.error("Function `$name` has both address and alignment", Some(p))
if (statements.isEmpty && alignment.isDefined) log.error("Extern function `$name` cannot have alignment", Some(p))
if (statements.isEmpty && !flags("asm") && params.nonEmpty) log.error("Extern non-asm function `$name` cannot have parameters", Some(p))
if (flags("asm")) validateAsmFunctionBody(p, flags, name, statements)
Seq(FunctionDeclarationStatement(name, returnType, params.toList,
bank,
addr,
alignment,
statements,
flags("macro"),
if (flags("inline")) Some(true) else if (flags("noinline")) Some(false) else None,

View File

@ -6,6 +6,7 @@ import millfork.env._
import millfork.error.ConsoleLogger
import millfork.node._
import millfork.CompilationOptions
import millfork.output.{MemoryAlignment, NoAlignment, WithinPageAlignment}
/**
* @author Karol Stasiak
@ -16,6 +17,9 @@ case class MosParser(filename: String, input: String, currentDirectory: String,
def allowIntelHexAtomsInAssembly: Boolean = false
def fastAlignmentForArrays: MemoryAlignment = WithinPageAlignment
def fastAlignmentForFunctions: MemoryAlignment = WithinPageAlignment
// TODO: label and instruction in one line
val asmLabel: P[ExecutableStatement] = (identifier ~ HWS ~ ":" ~/ HWS).map(l => MosAssemblyStatement(Opcode.LABEL, AddrMode.DoesNotExist, VariableExpression(l), elidable = true))

View File

@ -10,6 +10,7 @@ import millfork.assembly.z80.{ZOpcode, _}
import millfork.env.{ByZRegister, Constant, ParamPassingConvention}
import millfork.error.ConsoleLogger
import millfork.node._
import millfork.output.{MemoryAlignment, NoAlignment, WithinPageAlignment}
/**
* @author Karol Stasiak
@ -25,6 +26,9 @@ case class Z80Parser(filename: String,
def allowIntelHexAtomsInAssembly: Boolean = useIntelSyntax
def fastAlignmentForArrays: MemoryAlignment = WithinPageAlignment
def fastAlignmentForFunctions: MemoryAlignment = NoAlignment
private val zero = LiteralExpression(0, 1)
val appcSimple: P[ParamPassingConvention] = (P("hl" | "bc" | "de" | "a" | "b" | "c" | "d" | "e" | "h" | "l").! ~ !letterOrDigit).map {

View File

@ -0,0 +1,29 @@
package millfork.test
import millfork.Cpu
import millfork.test.emu.EmuUnoptimizedCrossPlatformRun
import org.scalatest.{FunSuite, Matchers}
/**
* @author Karol Stasiak
*/
class AlignmentSuite extends FunSuite with Matchers {
test("Alignment") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(
"""
| array output [5] @$c000
| void main () {
| output[0] = lo(f.addr) & 0x3f
| output[1] = dump.addr.lo & 0x1f
| }
| void f() align(64){
| }
| array dump[4] align(32)
""".stripMargin) {m =>
m.readByte(0xc000) should equal(0)
m.readByte(0xc001) should equal(0)
}
}
}