add struct and pointer benchmark to benchmark program (btree, subscore=654, total 7420)

fix nullpointer in array initalizer
This commit is contained in:
Irmen de Jong
2025-09-12 04:46:37 +02:00
parent 5d9fbd2ccc
commit db2f28c4cd
7 changed files with 264 additions and 22 deletions

View File

@@ -0,0 +1,234 @@
; Binary Search Tree.
; It's a simple implementation for test/demonstration purposes of the pointer support;
; no balancing is done and memory is not freed when elements are removed.
%import textio
btree {
sub benchmark(uword max_time) -> uword {
txt.nl()
cbm.SETTIM(0,0,0)
uword score
while cbm.RDTIM16() < max_time {
bench_operations()
txt.chrout('.')
score++
}
txt.nl()
return score
}
sub bench_operations() {
arena.freeall()
btree.root = 0
for cx16.r0 in [321, 719, 194, 550, 187, 203, 520, 562, 221, 676, 97, 852, 273, 326, 589, 606, 275, 794, 63, 716]
btree.add(cx16.r0)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
void btree.contains(203)
void btree.contains(204)
void btree.contains(605)
void btree.contains(606)
btree.remove(9999)
btree.remove(97)
btree.remove(187)
btree.remove(203)
btree.remove(275)
btree.remove(321)
btree.remove(520)
btree.remove(562)
btree.remove(606)
btree.remove(719)
btree.remove(794)
cx16.r0L = btree.size()
btree.process_tree_inorder()
btree.process_tree_preorder()
}
struct Node {
^^Node left
^^Node right
uword value
}
^^Node root = 0
sub add(uword value) {
^^Node node = arena.alloc(sizeof(Node))
node.value = value
node.left = node.right = 0
if root==0
root=node
else {
^^Node parent = root
repeat {
if parent.value >= value {
if parent.left!=0
parent = parent.left
else {
parent.left = node
return
}
} else {
if parent.right!=0
parent = parent.right
else {
parent.right = node
return
}
}
}
}
}
sub contains(uword value) -> bool {
^^Node r = root
while r!=0 {
if r.value==value
return true
if r.value>value
r = r.left
else
r = r.right
}
return false
}
sub size() -> ubyte {
ubyte count
if root!=0
count_node(root)
return count
sub count_node(^^Node r) {
count++
if r.left!=0 {
sys.pushw(r)
count_node(r.left)
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
count_node(r.right)
r = sys.popw()
}
}
}
sub remove(uword value) {
; note: we don't deallocate the memory from the node, for simplicity sake
^^Node n = root
^^Node parent = 0
while n!=0 {
if n.value==value {
if n.left==0
replacechild(parent, n, n.right)
else if n.right==0
replacechild(parent, n, n.left)
else {
; Both left & right subtrees are present.
; N = node to delete.
; Find N's successor S. (N's right subtree's minimum element)
; Attach N's left subtree to S.left (S doesn't have a left child)
; Attach N's right subtree to Parent in place of N.
^^Node successor = find_successor(n)
successor.left = n.left
replacechild(parent, n, n.right)
}
return
}
parent = n
if n.value>value
n = n.left
else
n = n.right
}
sub find_successor(^^Node p) -> ^^Node {
^^Node succ = p
p = p.right
while p!=0 {
succ = p
p = p.left
}
return succ
}
sub replacechild(^^Node p, ^^Node child, ^^Node newchild) {
if p.left==child
p.left = newchild
else
p.right = newchild
}
}
sub process_tree_inorder() {
if root!=0
process_tree(root)
sub process_tree(^^Node r) {
if r.left!=0 {
sys.pushw(r)
process_tree(r.left)
r = sys.popw()
}
cx16.r0 = r.value
if r.right!=0 {
sys.pushw(r)
process_tree(r.right)
r = sys.popw()
}
}
}
sub process_tree_preorder() {
if root!=0
process_tree(root,0)
sub process_tree(^^Node r, ubyte depth) {
cx16.r0 = r.value
if r.left!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.left, depth+1)
depth = sys.pop()
r = sys.popw()
}
if r.right!=0 {
sys.pushw(r)
sys.push(depth)
process_tree(r.right, depth+1)
depth = sys.pop()
r = sys.popw()
}
}
}
}
arena {
; extremely trivial arena allocator (that never frees)
uword buffer = memory("arena", 2000, 0)
uword next = buffer
sub alloc(ubyte size) -> uword {
defer next += size
return next
}
sub freeall() {
next = buffer
}
}

View File

@@ -16,6 +16,7 @@
%import b_textelite
%import b_maze
%import b_sprites
%import b_btree
%zeropage basicsafe
%option no_sysinit
@@ -67,10 +68,6 @@ main {
benchmark_score[benchmark_number] = circles.draw(false, 300)
benchmark_number++
; announce_benchmark("circles with kernal")
; benchmark_score[benchmark_number] = circles.draw(true, 300)
; benchmark_number++
announce_benchmark("text-elite")
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
@@ -79,13 +76,17 @@ main {
benchmark_score[benchmark_number] = animsprites.benchmark(300)
benchmark_number++
announce_benchmark("btree-struct-pointers")
benchmark_score[benchmark_number] = btree.benchmark(200)
benchmark_number++
benchmark_names[benchmark_number] = 0
benchmark_score[benchmark_number] = 0
cx16.set_screen_mode(3)
txt.uppercase()
txt.color2(1, 6)
uword final_score
uword total_score
benchmark_number = 0
txt.print("\nscore benchmark\n\n")
do {
@@ -93,14 +94,14 @@ main {
txt.print_uw(benchmark_score[benchmark_number])
txt.column(6)
txt.print(benchmark_names[benchmark_number])
final_score += benchmark_score[benchmark_number]
total_score += benchmark_score[benchmark_number]
txt.nl()
benchmark_number++
} until benchmark_names[benchmark_number]==0
txt.print("\n\nfinal score : ")
txt.print_uw(final_score)
txt.nl()
txt.print("\n\ntotal score : ")
txt.print_uw(total_score)
txt.print(" (higher=better)\n")
sub announce_benchmark(str name) {
benchmark_names[benchmark_number] = name

View File

@@ -169,10 +169,12 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
override fun visit(initializer: StaticStructInitializer) {
val fields = initializer.structname.targetStructDecl()!!.fields
if(initializer.args.isNotEmpty() && initializer.args.size != fields.size) {
val pos = (if(initializer.args.any()) initializer.args[0] else initializer).position
invalidNumberOfArgsError(pos, initializer.args.size, fields.map { it.second }, true)
val struct = initializer.structname.targetStructDecl()
if(struct!=null) {
if (initializer.args.isNotEmpty() && initializer.args.size != struct.fields.size) {
val pos = (if (initializer.args.any()) initializer.args[0] else initializer).position
invalidNumberOfArgsError(pos, initializer.args.size, struct.fields.map { it.second }, true)
}
}
}

View File

@@ -5,9 +5,15 @@
Porting Guide
*************
Here is a guide for porting Prog8 to other compilation targets.
Here is a guide for making Prog8 work for other compilation targets.
Answers to the questions below are used to configure the new target and supporting libraries.
**It is not required to change the compiler itself to make it support new compilation targets.**
A few of the most commonly used ones are built-in (such as c64, cx16), but you can use
separate configuration files to create new targets that the compiler can use.
See :ref:`customizable_target` for details about this. You still need to provide most of the
information asked for in this porting guide and code that into the configuration file.
.. note::
The assembly code that prog8 generates is not suitable to be put into ROM. (It contains
embedded variables, and self-modifying code).
@@ -25,7 +31,7 @@ Memory Map
Zeropage
========
#. *Absolute requirement:* Provide four times 2 consecutive bytes (i.e. four 16-bit words) in the zeropage that are free to use at all times.
#. *Absolute requirement:* Provide four words (16 bit byte pairs) in the zeropage that are free to use at all times.
#. Provide list of any additional free zeropage locations for a normal running system (BASIC + Kernal enabled)
#. Provide list of any additional free zeropage locations when BASIC is off, but floating point routines should still work
#. Provide list of any additional free zeropage locations when only the Kernal remains enabled
@@ -43,14 +49,13 @@ RAM, ROM, I/O
#. what part(s) of the address space is memory-mapped I/O registers?
#. is there a block of "high ram" available (ram that is not the main ram used to load programs in) that could be used for variables?
#. is there a banking system? How does it work (how do you select Ram/Rom banks)? How is the default bank configuration set?
Note that prog8 itself has no notion of banking, but this knowledge may be required for proper system initialization.
Character encodings
-------------------
#. if not PETSCII or CBM screencodes: provide the primary character encoding table that the system uses (i.e. how is text represented in memory)
#. provide the primary character encoding table that the system uses (i.e. how is text represented in memory. For example, PETSCII)
#. provide alternate character encodings (if any)
#. what are the system's standard character screen dimensions?
#. is there a screen character matrix directly accessible in Ram? What's it address? Same for color attributes if any.
#. is there a screen character matrix directly accessible in RAM? What's it address? Same for color attributes if any.
ROM routines
@@ -63,7 +68,7 @@ The more the merrier.
Floating point
==============
Prog8 can support floating point math *if* the target system has floating point math routines in ROM. If that is the case:
Prog8 can support floating point math *if* the target system has suitable floating point math routines in ROM. If that is the case:
#. what is the binary representation format of the floating point numbers? (how many bytes, how the bits are set up)
#. what are the valid minimum negative and maximum positive floating point values?

View File

@@ -16,6 +16,7 @@ Currently these machines can be selected as a compilation target (via the ``-tar
- 'c128': the Commodore 128 (*limited support*)
- 'pet32': the Commodore PET 4032 (*limited support*)
- 'virtual': a builtin virtual machine
- custom targets via a separate configuration file (see :ref:`customizable_target`)
This chapter explains some relevant system details of the c64 and cx16 machines.

View File

@@ -58,7 +58,7 @@ and for example the below code omits line 5::
STRUCTS and TYPED POINTERS
--------------------------
- allow struct initialization syntax in an array such as [ Node(), Node(), Node() ], update sorting example to use list of countries like that
- allow struct initialization syntax in an array such as [ ^^Node:[], ^^Node:[], ^^Node:[] ], update sorting example to use list of countries like that
- fix code size regressions (if any left)
- optimize deref in PointerAssignmentsGen: optimize 'forceTemporary' to only use a temporary when the offset is >0
- update structpointers.rst docs with 6502 specific things?
@@ -68,7 +68,6 @@ STRUCTS and TYPED POINTERS
- try to optimize pointer arithmetic used in peek/poke a bit more so the routines in sorting module can use typed pointers without increasing code size, see test.p8 in commit d394dc1e
- should @(wordpointer) be equivalent to wordpointer^^ (that would require a LOT of code rewrite that now knows that @() is strictly byte based) ?
or do an implicit cast @(wpointer as ubyte^^) ? And/or add a warning about that?
- add struct and pointer benchmark to benchmark program?
- optimize addUnsignedByteOrWordToAY in PointerAssignmentsGen a bit more
- support for typed function pointers? (&routine could be typed by default as well then)
- support @nosplit pointer arrays?

View File

@@ -3,4 +3,4 @@ org.gradle.console=rich
org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
version=12.0-BETA1
version=12.0-BETA2