From d37c9d168053399958ca225d0d8c381b8430b4c7 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 23 Jan 2019 00:19:29 +0100 Subject: [PATCH] argument type casts for builtin functions, added memset, tweaked memcopy/memset assembly a bit --- compiler/res/prog8lib/c64utils.p8 | 128 -------------- compiler/res/prog8lib/prog8lib.asm | 166 +++++++++++++++++- compiler/src/prog8/ast/AST.kt | 4 + compiler/src/prog8/ast/AstChecker.kt | 5 +- compiler/src/prog8/compiler/Compiler.kt | 29 ++- .../src/prog8/functions/BuiltinFunctions.kt | 12 +- compiler/src/prog8/stackvm/StackVm.kt | 31 +++- docs/source/programming.rst | 11 ++ examples/test.p8 | 23 +-- 9 files changed, 251 insertions(+), 158 deletions(-) diff --git a/compiler/res/prog8lib/c64utils.p8 b/compiler/res/prog8lib/c64utils.p8 index f22f60782..e6d486c8e 100644 --- a/compiler/res/prog8lib/c64utils.p8 +++ b/compiler/res/prog8lib/c64utils.p8 @@ -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 -%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) -> () { %asm {{ diff --git a/compiler/res/prog8lib/prog8lib.asm b/compiler/res/prog8lib/prog8lib.asm index 54d305ff8..7aadbffcd 100644 --- a/compiler/res/prog8lib/prog8lib.asm +++ b/compiler/res/prog8lib/prog8lib.asm @@ -1012,14 +1012,172 @@ func_memcopy .proc lda c64.ESTACK_HI+2,x sta c64.SCRATCH_ZPWORD1+1 lda c64.ESTACK_LO+1,x - ldy c64.ESTACK_HI+1,x - pha + sta c64.SCRATCH_ZPWORD2 + lda c64.ESTACK_HI+1,x + sta c64.SCRATCH_ZPWORD2+1 lda c64.ESTACK_LO,x tax - pla - jsr c64utils.memcopy + ldy #0 +- lda (c64.SCRATCH_ZPWORD1), y + sta (c64.SCRATCH_ZPWORD2), y + iny + dex + bne - ldx c64.SCRATCH_ZPREGX inx inx rts .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 + diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 9240e5673..e939cd0ec 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -51,6 +51,10 @@ enum class DataType { ARRAY_W -> targetType == UWORD ARRAY_F -> targetType == UWORD } + + + fun assignableTo(targetTypes: Set) = targetTypes.any { this.assignableTo(it) } + } enum class Register { diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 9188e97ec..bf97df12e 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -784,8 +784,9 @@ class AstChecker(private val namespace: INameScope, else { for (arg in args.withIndex().zip(func.parameters)) { val argDt=arg.first.value.resultingDatatype(namespace, heap) - if(argDt !in arg.second.possibleDatatypes) - checkResult.add(ExpressionError("builtin function argument ${arg.first.index+1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)) + 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)) + } } } } else if(target is Subroutine) { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index f9f947b6d..9f06deb06 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -6,6 +6,7 @@ import prog8.compiler.intermediate.IntermediateProgram import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.Value import prog8.compiler.intermediate.branchOpcodes +import prog8.functions.BuiltinFunctions import prog8.optimizing.same import prog8.parser.tryGetEmbeddedResource 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) { // only WIDENS a type, never NARROWS. To avoid loss of precision. if(givenDt==targetDt) @@ -784,7 +796,22 @@ private class StatementTranslator(private val prog: IntermediateProgram, 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) { "len" -> { // 1 argument, type determines the exact syscall to use diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index b88e29ef0..77214fe00 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -70,9 +70,17 @@ val BuiltinFunctions = mapOf( "clear_irqd" to FunctionSignature(false, emptyList(), null), "swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null), "memcopy" to FunctionSignature(false, listOf( - BuiltinFunctionParam("from", IntegerDatatypes + IterableDatatypes), - BuiltinFunctionParam("to", IntegerDatatypes + IterableDatatypes), + BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)), + BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)), 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_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null), diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index f7c6cac2f..edff88e0e 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -78,7 +78,9 @@ enum class Syscall(val callNr: Short) { FUNC_SUM_UW(132), FUNC_SUM_W(133), 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: // some of them are straight opcodes (such as MSB, LSB, LSL, LSR, ROL_BYTE, ROR, ROL2, ROR2, and FLT)! @@ -1755,7 +1757,32 @@ class StackVm(private var traceOutputFile: String?) { val from = evalstack.pop().integerValue() 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) { diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 70c857cfd..ba59f4bd5 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -663,6 +663,17 @@ memcopy(from, to, numbytes) 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. 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 the values of numerical variables (or memory locations) x and y in a fast way. diff --git a/examples/test.p8 b/examples/test.p8 index 724e93df7..574102050 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -5,26 +5,11 @@ 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