argument type casts for builtin functions, added memset, tweaked memcopy/memset assembly a bit

This commit is contained in:
Irmen de Jong 2019-01-23 00:19:29 +01:00
parent dd02d97db4
commit d37c9d1680
9 changed files with 251 additions and 158 deletions

View File

@ -292,134 +292,6 @@ asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
; @todo string to 32 bit unsigned integer http://www.6502.org/source/strings/ascii-to-32bit.html ; @todo string to 32 bit unsigned integer http://www.6502.org/source/strings/ascii-to-32bit.html
%asm {{
; copy memory UP from (SCRATCH_ZPWORD1) to (SCRATCH_ZPWORD2) of length X/Y (16-bit, X=lo, Y=hi)
; clobbers register A,X,Y
memcopy16_up .proc
source = SCRATCH_ZPWORD1
dest = SCRATCH_ZPWORD2
length = SCRATCH_ZPB1 ; (and SCRATCH_ZPREG)
stx length
sty length+1
ldx length ; move low byte of length into X
bne + ; jump to start if X > 0
dec length ; subtract 1 from length
+ ldy #0 ; set Y to 0
- lda (source),y ; set A to whatever (source) points to offset by Y
sta (dest),y ; move A to location pointed to by (dest) offset by Y
iny ; increment Y
bne + ; if Y<>0 then (rolled over) then still moving bytes
inc source+1 ; increment hi byte of source
inc dest+1 ; increment hi byte of dest
+ dex ; decrement X (lo byte counter)
bne - ; if X<>0 then move another byte
dec length ; we've moved 255 bytes, dec length
bpl - ; if length is still positive go back and move more
rts ; done
.pend
; copy memory UP from (SCRATCH_ZPWORD1) to (AY) with length X (1 to 256, 0 meaning 256)
; destination must not overlap, or be before start, then overlap is possible.
; clobbers A, X, Y
memcopy .proc
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #0
- lda (c64.SCRATCH_ZPWORD1), y
sta (c64.SCRATCH_ZPWORD2), y
iny
dex
bne -
rts
.pend
; fill memory from (SCRATCH_ZPWORD1), length XY, with value in A.
; clobbers X, Y
memset .proc
stx SCRATCH_ZPB1
sty SCRATCH_ZPREG
ldy #0
ldx SCRATCH_ZPREG
beq _lastpage
_fullpage sta (SCRATCH_ZPWORD1),y
iny
bne _fullpage
inc SCRATCH_ZPWORD1+1 ; next page
dex
bne _fullpage
_lastpage ldy SCRATCH_ZPB1
beq +
- dey
sta (SCRATCH_ZPWORD1),y
bne -
+ rts
.pend
; fill memory from (SCRATCH_ZPWORD1) number of words in SCRATCH_ZPWORD2, with word value in AY.
; clobbers A, X, Y
memsetw .proc
sta _mod1+1 ; self-modify
sty _mod1b+1 ; self-modify
sta _mod2+1 ; self-modify
sty _mod2b+1 ; self-modify
ldx SCRATCH_ZPWORD1
stx SCRATCH_ZPB1
ldx SCRATCH_ZPWORD1+1
inx
stx SCRATCH_ZPREG ; second page
ldy #0
ldx SCRATCH_ZPWORD2+1
beq _lastpage
_fullpage
_mod1 lda #0 ; self-modified
sta (SCRATCH_ZPWORD1),y ; first page
sta (SCRATCH_ZPB1),y ; second page
iny
_mod1b lda #0 ; self-modified
sta (SCRATCH_ZPWORD1),y ; first page
sta (SCRATCH_ZPB1),y ; second page
iny
bne _fullpage
inc SCRATCH_ZPWORD1+1 ; next page pair
inc SCRATCH_ZPWORD1+1 ; next page pair
inc SCRATCH_ZPB1+1 ; next page pair
inc SCRATCH_ZPB1+1 ; next page pair
dex
bne _fullpage
_lastpage ldx SCRATCH_ZPWORD2
beq _done
ldy #0
-
_mod2 lda #0 ; self-modified
sta (SCRATCH_ZPWORD1), y
inc SCRATCH_ZPWORD1
bne _mod2b
inc SCRATCH_ZPWORD1+1
_mod2b lda #0 ; self-modified
sta (SCRATCH_ZPWORD1), y
inc SCRATCH_ZPWORD1
bne +
inc SCRATCH_ZPWORD1+1
+ dex
bne -
_done rts
.pend
}}
asmsub set_irqvec_excl() -> clobbers(A) -> () { asmsub set_irqvec_excl() -> clobbers(A) -> () {
%asm {{ %asm {{

View File

@ -1012,14 +1012,172 @@ func_memcopy .proc
lda c64.ESTACK_HI+2,x lda c64.ESTACK_HI+2,x
sta c64.SCRATCH_ZPWORD1+1 sta c64.SCRATCH_ZPWORD1+1
lda c64.ESTACK_LO+1,x lda c64.ESTACK_LO+1,x
ldy c64.ESTACK_HI+1,x sta c64.SCRATCH_ZPWORD2
pha lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD2+1
lda c64.ESTACK_LO,x lda c64.ESTACK_LO,x
tax tax
pla ldy #0
jsr c64utils.memcopy - lda (c64.SCRATCH_ZPWORD1), y
sta (c64.SCRATCH_ZPWORD2), y
iny
dex
bne -
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
inx inx
inx inx
rts rts
.pend .pend
func_memset .proc
; note: clobbers A,Y
inx
stx c64.SCRATCH_ZPREGX
lda c64.ESTACK_LO+2,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+2,x
sta c64.SCRATCH_ZPWORD1+1
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
ldy c64.ESTACK_HI+1,x
lda c64.ESTACK_LO,x
ldx c64.SCRATCH_ZPB1
jsr memset
ldx c64.SCRATCH_ZPREGX
inx
inx
rts
.pend
func_memsetw .proc
; note: clobbers A,Y
; -- fill memory from (SCRATCH_ZPWORD1) number of words in SCRATCH_ZPWORD2, with word value in AY.
inx
stx c64.SCRATCH_ZPREGX
lda c64.ESTACK_LO+2,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+2,x
sta c64.SCRATCH_ZPWORD1+1
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD2+1
lda c64.ESTACK_LO,x
ldy c64.ESTACK_HI,x
jsr memsetw
ldx c64.SCRATCH_ZPREGX
inx
inx
rts
.pend
memcopy16_up .proc
; -- copy memory UP from (SCRATCH_ZPWORD1) to (SCRATCH_ZPWORD2) of length X/Y (16-bit, X=lo, Y=hi)
; clobbers register A,X,Y
source = c64.SCRATCH_ZPWORD1
dest = c64.SCRATCH_ZPWORD2
length = c64.SCRATCH_ZPB1 ; (and SCRATCH_ZPREG)
stx length
sty length+1
ldx length ; move low byte of length into X
bne + ; jump to start if X > 0
dec length ; subtract 1 from length
+ ldy #0 ; set Y to 0
- lda (source),y ; set A to whatever (source) points to offset by Y
sta (dest),y ; move A to location pointed to by (dest) offset by Y
iny ; increment Y
bne + ; if Y<>0 then (rolled over) then still moving bytes
inc source+1 ; increment hi byte of source
inc dest+1 ; increment hi byte of dest
+ dex ; decrement X (lo byte counter)
bne - ; if X<>0 then move another byte
dec length ; we've moved 255 bytes, dec length
bpl - ; if length is still positive go back and move more
rts ; done
.pend
memset .proc
; -- fill memory from (SCRATCH_ZPWORD1), length XY, with value in A.
; clobbers X, Y
stx c64.SCRATCH_ZPB1
sty c64.SCRATCH_ZPREG
ldy #0
ldx c64.SCRATCH_ZPREG
beq _lastpage
_fullpage sta (c64.SCRATCH_ZPWORD1),y
iny
bne _fullpage
inc c64.SCRATCH_ZPWORD1+1 ; next page
dex
bne _fullpage
_lastpage ldy c64.SCRATCH_ZPB1
beq +
- dey
sta (c64.SCRATCH_ZPWORD1),y
bne -
+ rts
.pend
memsetw .proc
; -- fill memory from (SCRATCH_ZPWORD1) number of words in SCRATCH_ZPWORD2, with word value in AY.
; clobbers A, X, Y
sta _mod1+1 ; self-modify
sty _mod1b+1 ; self-modify
sta _mod2+1 ; self-modify
sty _mod2b+1 ; self-modify
ldx c64.SCRATCH_ZPWORD1
stx c64.SCRATCH_ZPB1
ldx c64.SCRATCH_ZPWORD1+1
inx
stx c64.SCRATCH_ZPREG ; second page
ldy #0
ldx c64.SCRATCH_ZPWORD2+1
beq _lastpage
_fullpage
_mod1 lda #0 ; self-modified
sta (c64.SCRATCH_ZPWORD1),y ; first page
sta (c64.SCRATCH_ZPB1),y ; second page
iny
_mod1b lda #0 ; self-modified
sta (c64.SCRATCH_ZPWORD1),y ; first page
sta (c64.SCRATCH_ZPB1),y ; second page
iny
bne _fullpage
inc c64.SCRATCH_ZPWORD1+1 ; next page pair
inc c64.SCRATCH_ZPWORD1+1 ; next page pair
inc c64.SCRATCH_ZPB1+1 ; next page pair
inc c64.SCRATCH_ZPB1+1 ; next page pair
dex
bne _fullpage
_lastpage ldx c64.SCRATCH_ZPWORD2
beq _done
ldy #0
-
_mod2 lda #0 ; self-modified
sta (c64.SCRATCH_ZPWORD1), y
inc c64.SCRATCH_ZPWORD1
bne _mod2b
inc c64.SCRATCH_ZPWORD1+1
_mod2b lda #0 ; self-modified
sta (c64.SCRATCH_ZPWORD1), y
inc c64.SCRATCH_ZPWORD1
bne +
inc c64.SCRATCH_ZPWORD1+1
+ dex
bne -
_done rts
.pend

View File

@ -51,6 +51,10 @@ enum class DataType {
ARRAY_W -> targetType == UWORD ARRAY_W -> targetType == UWORD
ARRAY_F -> targetType == UWORD ARRAY_F -> targetType == UWORD
} }
fun assignableTo(targetTypes: Set<DataType>) = targetTypes.any { this.assignableTo(it) }
} }
enum class Register { enum class Register {

View File

@ -784,10 +784,11 @@ class AstChecker(private val namespace: INameScope,
else { else {
for (arg in args.withIndex().zip(func.parameters)) { for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.resultingDatatype(namespace, heap) val argDt=arg.first.value.resultingDatatype(namespace, heap)
if(argDt !in arg.second.possibleDatatypes) if(argDt!=null && !argDt.assignableTo(arg.second.possibleDatatypes)) {
checkResult.add(ExpressionError("builtin function argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)) checkResult.add(ExpressionError("builtin function argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
} }
} }
}
} else if(target is Subroutine) { } else if(target is Subroutine) {
if(args.size!=target.parameters.size) if(args.size!=target.parameters.size)
checkResult.add(SyntaxError("invalid number of arguments", position)) checkResult.add(SyntaxError("invalid number of arguments", position))

View File

@ -6,6 +6,7 @@ import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.Opcode
import prog8.compiler.intermediate.Value import prog8.compiler.intermediate.Value
import prog8.compiler.intermediate.branchOpcodes import prog8.compiler.intermediate.branchOpcodes
import prog8.functions.BuiltinFunctions
import prog8.optimizing.same import prog8.optimizing.same
import prog8.parser.tryGetEmbeddedResource import prog8.parser.tryGetEmbeddedResource
import prog8.stackvm.Syscall import prog8.stackvm.Syscall
@ -685,6 +686,17 @@ private class StatementTranslator(private val prog: IntermediateProgram,
} }
} }
private fun tryConvertType(givenDt: DataType, targetDt: DataType): Boolean {
return try {
convertType(givenDt, targetDt)
true
} catch (x: CompilerException) {
false
}
}
private fun convertType(givenDt: DataType, targetDt: DataType) { private fun convertType(givenDt: DataType, targetDt: DataType) {
// only WIDENS a type, never NARROWS. To avoid loss of precision. // only WIDENS a type, never NARROWS. To avoid loss of precision.
if(givenDt==targetDt) if(givenDt==targetDt)
@ -784,7 +796,22 @@ private class StatementTranslator(private val prog: IntermediateProgram,
return return
} }
args.forEach { translate(it) } // place function argument(s) on the stack val builtinFuncParams = BuiltinFunctions[funcname]?.parameters
args.forEachIndexed { index, arg ->
// place function argument(s) on the stack
translate(arg)
// cast type if needed
if(builtinFuncParams!=null) {
val paramDts = builtinFuncParams[index].possibleDatatypes
val argDt = arg.resultingDatatype(namespace, heap)!!
if(argDt !in paramDts) {
for(paramDt in paramDts.sorted())
if(tryConvertType(argDt, paramDt))
break
}
}
}
when (funcname) { when (funcname) {
"len" -> { "len" -> {
// 1 argument, type determines the exact syscall to use // 1 argument, type determines the exact syscall to use

View File

@ -70,9 +70,17 @@ val BuiltinFunctions = mapOf(
"clear_irqd" to FunctionSignature(false, emptyList(), null), "clear_irqd" to FunctionSignature(false, emptyList(), null),
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null), "swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
"memcopy" to FunctionSignature(false, listOf( "memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IntegerDatatypes + IterableDatatypes), BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("to", IntegerDatatypes + IterableDatatypes), BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numbytes", IntegerDatatypes)), null), BuiltinFunctionParam("numbytes", IntegerDatatypes)), null),
"memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", setOf(DataType.UBYTE, DataType.BYTE))), null),
"memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null), "vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),

View File

@ -78,7 +78,9 @@ enum class Syscall(val callNr: Short) {
FUNC_SUM_UW(132), FUNC_SUM_UW(132),
FUNC_SUM_W(133), FUNC_SUM_W(133),
FUNC_SUM_F(134), FUNC_SUM_F(134),
FUNC_MEMCOPY(138) FUNC_MEMCOPY(138),
FUNC_MEMSET(139),
FUNC_MEMSETW(140)
// note: not all builtin functions of the Prog8 language are present as functions: // note: not all builtin functions of the Prog8 language are present as functions:
// some of them are straight opcodes (such as MSB, LSB, LSL, LSR, ROL_BYTE, ROR, ROL2, ROR2, and FLT)! // some of them are straight opcodes (such as MSB, LSB, LSL, LSR, ROL_BYTE, ROR, ROL2, ROR2, and FLT)!
@ -1755,8 +1757,33 @@ class StackVm(private var traceOutputFile: String?) {
val from = evalstack.pop().integerValue() val from = evalstack.pop().integerValue()
mem.copy(from, to, numbytes) mem.copy(from, to, numbytes)
} }
Syscall.FUNC_MEMSET -> {
val value = evalstack.pop()
val address = evalstack.pop().integerValue()
val numbytes = evalstack.pop().integerValue()
val bytevalue = value.integerValue().toShort()
when {
value.type==DataType.UBYTE -> for(addr in address until address+numbytes)
mem.setUByte(addr, bytevalue)
value.type==DataType.BYTE -> for(addr in address until address+numbytes)
mem.setSByte(addr, bytevalue)
else -> throw VmExecutionException("(u)byte value expected")
} }
} }
Syscall.FUNC_MEMSETW -> {
val value = evalstack.pop()
val address = evalstack.pop().integerValue()
val numwords = evalstack.pop().integerValue()
val wordvalue = value.integerValue()
when {
value.type==DataType.UWORD -> for(addr in address until address+numwords*2 step 2)
mem.setUWord(addr, wordvalue)
value.type==DataType.WORD -> for(addr in address until address+numwords*2 step 2)
mem.setSWord(addr, wordvalue)
else -> throw VmExecutionException("(u)word value expected")
}
} }
}
fun irq(timestamp: Long) { fun irq(timestamp: Long) {
// 60hz IRQ handling // 60hz IRQ handling

View File

@ -663,6 +663,17 @@ memcopy(from, to, numbytes)
Because this function imposes some overhead to handle the parameters, Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold. it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not. Compare the generated code to see if it was beneficial or not.
The most efficient will always be to write a specialized copy routine in assembly yourself!
memset(address, numbytes, bytevalue)
Efficiently set a part of memory to the given (u)byte value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
Note that for clearing the character screen, very fast specialized subroutines are
available in the ``c64scr`` block (part of the ``c64utils`` module)
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
swap(x, y) swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way. Swap the values of numerical variables (or memory locations) x and y in a fast way.

View File

@ -5,26 +5,11 @@
sub start() { sub start() {
; memset($0400, $0400+40, 81) memset($0400+40*3, 40*8, 81)
memsetw($0400+40*12, 8*40/2, $80a0)
memset($0400, 20, 33)
memcopy($0400, $0400+121, 20)
A=99
if(A<99) goto first else goto second
first:
c64scr.print("a<99 !\n")
goto next
second:
c64scr.print("wrong: a>=99 ?!\n")
next:
A=99
if(A<99) goto first2 else {
c64scr.print("wrong: a>=99 ?!\n")
}
return
first2:
c64scr.print("a<99 !\n")
return return