mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-30 00:33:14 +00:00
Add align keyword
This commit is contained in:
parent
3736b5ae56
commit
fe1bf68295
@ -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`.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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">"sta " "lda " "jmp " "bit " "eor " "adc " "ora " "and " "ldx " "ldy " "stx " "sty " "tax" "tay" "tya" "txa" "txs" "tsx" "sei" "cli" "clv" "clc" "cld" "sed" "sec" eq "bne " "bmi " "bpl " "bcc " "bcs " "bvs " bvc " "jsr " rts" "rti" "brk" "rol" "ror" "asl" "lsr" "inc " "dec " "cmp " "cpx " "cpy " inx iny dex dey pla pha plp hp phx plx phy ply "stz " "ldz " tza taz "tsb " "trb " ra txy tyx pld plb phb phd phk xce

"STA " "LDA " "JMP " "BIT " "EOR " "ADC " "ORA " "AND " "LDX " "LDY " "STX " "STY " "TAX" "TAY" "TYA" "TXA" "TXS" "TSX" "SEI" "CLI" "CLV" "CLC" "CLD" "SED" "SEC" EQ "BNE " "BMI " "BPL " "BCC " "BCS " "BVS " BVC " "JSR " RTS" "RTI" "BRK" "ROL" "ROR" "ASL" "LSR" "INC " "DEC " "CMP " "CPX " "CPY " INX INY DEX DEY PLA PHA PLP HP PHX PLX PHY PLY "STZ " "LDZ " TZA TAZ "TSB " "TRB " RA TXY TYX PLD PLB PHB PHD PHK XCE</Keywords>
|
||||
<Keywords name="Keywords5">"sbx " "isc " "dcp " "lax " "sax " "anc " "alr " "arr " "rra " "rla " "lxa " "ane " "xaa "
"SBX " "ISC " "DCP " "LAX " "SAX " "ANC " "ALR " "ARR " "RRA " "RLA " "LXA " "ANE " "XAA "</Keywords>
|
||||
<Keywords name="Keywords6"></Keywords>
|
||||
<Keywords name="Keywords7"></Keywords>
|
||||
<Keywords name="Keywords8"></Keywords>
|
||||
<Keywords name="Delimiters">00" 01 02" 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" 01 02" 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" />
|
||||
|
28
src/main/scala/millfork/env/Constant.scala
vendored
28
src/main/scala/millfork/env/Constant.scala
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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],
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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 {
|
||||
|
29
src/test/scala/millfork/test/AlignmentSuite.scala
Normal file
29
src/test/scala/millfork/test/AlignmentSuite.scala
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user