1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-12-23 08:29:35 +00:00

Improve passing of register parameters to assembly functions

This commit is contained in:
Karol Stasiak 2019-07-09 22:40:14 +02:00
parent c5135423f8
commit a29b2a994b
5 changed files with 77 additions and 14 deletions

View File

@ -125,9 +125,31 @@ and call `increase(score, 10)`, the entire call will compile into:
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
* `byte a`, `byte x`, `byte y`: a single byte passed via the given CPU register; any 1-byte type can be used
* `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
* `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; any 2-byte type can be used
For example, this piece of code:
asm void f(word ax) @F_ADDR extern
f(5)
will compile to
LDA #5
LDX #0
JSR F_ADDR
**Work in progress**:
Only the following combinations of register parameters work reliably:
* zero or one register parameters
* two register parameters where at least one of them is an 8-bit parameter passed via A
Other combinations are guaranteed to work only with constant arguments.
Macro assembly functions can have maximum one parameter passed via a register.

View File

@ -154,20 +154,20 @@ and call `increase(score, 10)`, the entire call will compile into:
Non-macro functions can only have their parameters passed via registers:
* `byte a`, `byte b`, etc.: a single byte passed via the given CPU register
* `byte a`, `byte b`, `byte c`, `byte d`, `byte e`, `byte h`, `byte l`: a single byte passed via the given CPU register; any 1-byte type can be used
* `word hl`, `word bc`, `word de`: a 2-byte word byte passed via given 16-bit register
* `word hl`, `word bc`, `word de`: a 2-byte word byte passed via given 16-bit register; any 2-byte type can be used
Parameters passed via other registers (`I`, `IX`, `IY`, `IXH` etc.) or combinations of registers do not work yet.
**Work in progress**:
Currently, only few parameter signatures are supported for non-macro assembly functions:
Only the following combinations of register parameters work reliably:
* `()`
* zero or one register parameters
* `(byte a)`, `(byte b)`, `(byte c)`, `(byte d)`, `(byte e)`, `(byte h)`, `(byte l)` ("byte" may be any other 2-byte type)
* two register parameters where at least one of them is a 16-bit parameter
* `(word hl)`, `(word bc)`, `(word de)` ("word" may be any other 2-byte type)
More parameters or parameters passed via other registers do not work yet.
Other combinations are guaranteed to work only with constant arguments.
Macro assembly functions cannot have any parameter passed via registers.

View File

@ -41,6 +41,7 @@ Syntax:
* `<return_type>` is a valid return type, see [Types](./types.md)
* `<params>` is a comma-separated list of parameters, in form `type name`. Allowed types are the same as for local variables.
For assembly functions, certain parameter names are interpreted as CPU registers.
* `<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.

View File

@ -1500,7 +1500,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case Seq((_, param)) => param
case Seq((MosRegister.A, pa), (_, pxy)) => pa ++ preserveRegisterIfNeeded(ctx, MosRegister.A, pxy)
case Seq((_, pxy), (MosRegister.A, pa)) => pa ++ preserveRegisterIfNeeded(ctx, MosRegister.A, pxy)
case other => other.flatMap(_._2) // TODO : make sure all registers are passed in correctly
case other =>
ctx.log.warn("Unsupported register parameter combination: " + other.map(_._1.toString).mkString("(", ",", ")"), expr.position)
other.flatMap(_._2) // TODO : make sure all registers are passed in correctly
}
secondViaMemory ++ thirdViaRegisters :+ AssemblyLine.absoluteOrLongAbsolute(JSR, function, ctx.options)
case NormalParamSignature(List(MemoryVariable(_, typ, _))) if typ.size == 1 =>

View File

@ -30,6 +30,8 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
toA.init
case ZLine0(ZOpcode.LD, TwoRegisters(ZRegister.A, source@(ZRegister.B | ZRegister.C | ZRegister.D | ZRegister.E | ZRegister.MEM_HL)), _) =>
toA.init :+ ZLine.ld8(register, source)
case ZLine0(ZOpcode.LD, TwoRegisters(ZRegister.A, ZRegister.IMM_8), param) if toA.size == 1 =>
List(ZLine.ldImm8(register, param))
case ZLine0(ZOpcode.LD, TwoRegistersOffset(ZRegister.A, ZRegister.MEM_IX_D, offset), _) =>
toA.init :+ ZLine.ldViaIx(register, offset)
case ZLine0(ZOpcode.LD, TwoRegistersOffset(ZRegister.A, ZRegister.MEM_IY_D, offset), _) =>
@ -1058,9 +1060,45 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
compileToBC(ctx, params.head) :+ ZLine(CALL, NoRegisters, function.toAddress)
case AssemblyParamSignature(Nil) =>
List(ZLine(CALL, NoRegisters, function.toAddress))
case AssemblyParamSignature(paramConvs) =>
// TODO: stop being lazy and implement this
???
case AssemblyParamSignature(paramConvs) =>val pairs = params.zip(paramConvs)
val viaMemory = pairs.flatMap {
case (paramExpr, AssemblyParam(typ, paramVar: VariableInMemory, AssemblyParameterPassingBehaviour.Copy)) =>
ctx.log.error("Variable parameters to assembly functions are not supported", expression.position)
Nil
case _ => Nil
}
val viaRegisters = pairs.flatMap {
case (paramExpr, AssemblyParam(typ, paramVar@ZRegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy)) =>
if (typ.size != ZRegister.registerSize(register)) {
ctx.log.error(s"Type ${typ.name} and register $register are of different sizes", expression.position)
}
val compi = ZRegister.registerSize(register) match {
case 1 => compile8BitTo(ctx, paramExpr, register)
case 2 => register match {
case ZRegister.HL => compileToHL(ctx, paramExpr)
case ZRegister.BC => compileToBC(ctx, paramExpr)
case ZRegister.DE => compileToDE(ctx, paramExpr)
case _ =>
ctx.log.error(s"Unsupported register $register", expression.position)
Nil
}
}
Some(register -> compi)
case _ => Nil
} match {
case Seq() => Nil
case Seq((_, param)) => param
case Seq((ZRegister.HL, phl), (_, pxx)) => phl ++ stashHLIfChanged(ctx, pxx)
case Seq((_, pxx), (ZRegister.HL, phl)) => phl ++ stashHLIfChanged(ctx, pxx)
case Seq((ZRegister.DE, pde), (_, pxx)) => pde ++ stashDEIfChanged(ctx, pxx)
case Seq((_, pxx), (ZRegister.DE, pde)) => pde ++ stashDEIfChanged(ctx, pxx)
case Seq((ZRegister.BC, pbc), (_, pxx)) => pbc ++ stashBCIfChanged(ctx, pxx)
case Seq((_, pxx), (ZRegister.BC, pbc)) => pbc ++ stashBCIfChanged(ctx, pxx)
case other =>
ctx.log.warn("Unsupported register parameter combination: " + other.map(_._1.toString).mkString("(", ",", ")"), expression.position)
other.flatMap(_._2) // TODO : make sure all registers are passed in correctly
}
viaMemory ++ viaRegisters :+ ZLine(CALL, NoRegisters, function.toAddress)
case NormalParamSignature(List(param)) if param.typ.size == 1 =>
compileToA(ctx, params.head) :+ ZLine(CALL, NoRegisters, function.toAddress)
case NormalParamSignature(List(param)) if param.typ.size == 2 =>