From 596f66fd0d3bccfb067ced43a5169bf433b5e19f Mon Sep 17 00:00:00 2001
From: Alexander Mandera <alexander@mandera.eu>
Date: Sun, 21 May 2023 15:45:41 +0200
Subject: [PATCH] Fix error when using C++ virtual methods

---
 .gitignore                                    |  1 +
 ch32v003fun/ch32v003fun.c                     | 36 +++++++++++++++++++
 examples/cpp_virtual_methods/Makefile         | 26 ++++++++++++++
 examples/cpp_virtual_methods/README.md        | 29 +++++++++++++++
 .../cpp_virtual_methods.cpp                   | 30 ++++++++++++++++
 examples/cpp_virtual_methods/example.cpp      | 30 ++++++++++++++++
 examples/cpp_virtual_methods/example.h        | 25 +++++++++++++
 7 files changed, 177 insertions(+)
 create mode 100644 examples/cpp_virtual_methods/Makefile
 create mode 100644 examples/cpp_virtual_methods/README.md
 create mode 100644 examples/cpp_virtual_methods/cpp_virtual_methods.cpp
 create mode 100644 examples/cpp_virtual_methods/example.cpp
 create mode 100644 examples/cpp_virtual_methods/example.h

diff --git a/.gitignore b/.gitignore
index b562640..12fb2f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 .pio
 .vscode
+.idea
 *.elf
 *.hex
 *.lst
diff --git a/ch32v003fun/ch32v003fun.c b/ch32v003fun/ch32v003fun.c
index 056f3cd..98d6bac 100644
--- a/ch32v003fun/ch32v003fun.c
+++ b/ch32v003fun/ch32v003fun.c
@@ -660,6 +660,10 @@ mini_pprintf(int (*puts)(char*s, int len, void* buf), void* buf, const char *fmt
 	Copyright 2023 Charles Lohr, under the MIT-x11 or NewBSD licenses, you choose.
 */
 
+#ifdef CPLUSPLUS
+// Method to call the C++ constructors
+void __libc_init_array(void);
+#endif
 
 int main() __attribute__((used));
 void SystemInit( void ) __attribute__((used));
@@ -802,6 +806,12 @@ asm volatile(
 	bne a1, a2, 1b\n\
 2:\n" );
 
+#ifdef CPLUSPLUS
+    asm volatile(
+        "call %0 \n\t"        // Call __libc_init_array function
+        : : "i" (__libc_init_array) : );
+#endif
+
 	SETUP_SYSTICK_HCLK
 
 	// set mepc to be main as the root app.
@@ -973,3 +983,29 @@ void DelaySysTick( uint32_t n )
 	while( ((int32_t)( SysTick->CNT - targend )) < 0 );
 }
 
+// C++ Support
+
+#ifdef CPLUSPLUS
+// This is required to allow pure virtual functions to be defined.
+extern void __cxa_pure_virtual() { while (1); }
+
+// These magic symbols are provided by the linker.
+extern void (*__preinit_array_start[]) (void) __attribute__((weak));
+extern void (*__preinit_array_end[]) (void) __attribute__((weak));
+extern void (*__init_array_start[]) (void) __attribute__((weak));
+extern void (*__init_array_end[]) (void) __attribute__((weak));
+
+void __libc_init_array(void)
+{
+    size_t count;
+    size_t i;
+
+    count = __preinit_array_end - __preinit_array_start;
+    for (i = 0; i < count; i++)
+        __preinit_array_start[i]();
+
+    count = __init_array_end - __init_array_start;
+    for (i = 0; i < count; i++)
+        __init_array_start[i]();
+}
+#endif
\ No newline at end of file
diff --git a/examples/cpp_virtual_methods/Makefile b/examples/cpp_virtual_methods/Makefile
new file mode 100644
index 0000000..749b3b5
--- /dev/null
+++ b/examples/cpp_virtual_methods/Makefile
@@ -0,0 +1,26 @@
+all : flash
+
+TARGET:=cpp_virtual_methods
+TARGET_EXT:=cpp
+
+ADDITIONAL_C_FILES+=example.cpp
+
+include ../../ch32v003fun/ch32v003fun.mk
+
+# Removing compiler optimization to small file size
+# because it optimizes the virtual functions out
+# which are tested here.
+CFLAGS:= \
+	-g -flto -ffunction-sections \
+	-static-libgcc \
+	-march=rv32ec \
+	-mabi=ilp32e \
+	-I/usr/include/newlib \
+	-I$(CH32V003FUN) \
+	-nostdlib \
+	-I. -Wall
+
+CFLAGS+=-fno-rtti -DSTDOUT_UART -DCPLUSPLUS
+
+flash : cv_flash
+clean : cv_clean
\ No newline at end of file
diff --git a/examples/cpp_virtual_methods/README.md b/examples/cpp_virtual_methods/README.md
new file mode 100644
index 0000000..14f14ba
--- /dev/null
+++ b/examples/cpp_virtual_methods/README.md
@@ -0,0 +1,29 @@
+# C++ Virtual Methods Example
+
+This example tests the usage of classes with virtual methods in C++.
+
+Without the usage of `__libc_init_array`, the virtual table of the class instance `Example` is not initialized correctly:
+
+```
+vtable for 'ExampleClass' @ 0x0 (subobject @ 0x20000004):
+[0]: 0x11e0006f
+[1]: 0x0 <InterruptVectorDefault()>
+```
+
+In this case, a call to a virtual method will result to an invalid call
+and the program won't work as expected.
+Here, the program will also print out `Begin example`, but does not print any values.
+It seems like the MCU resets here.
+
+In other environments like with the Arduino Core, the hard fault handler is called
+and the program gets into an infinite loop.
+
+With the macro `CPLUSPLUS`, an implemention auf `__libc_init_array` is called on startup.
+
+Then the virtual table is initialized correctly and new values are printed as expected:
+
+```
+vtable for 'ExampleClass' @ 0x212c (subobject @ 0x20000004):
+[0]: 0x1fb0 <ExampleClass::doNewLine()>
+[1]: 0x1fda <ExampleClass::printValue(int)>
+```
\ No newline at end of file
diff --git a/examples/cpp_virtual_methods/cpp_virtual_methods.cpp b/examples/cpp_virtual_methods/cpp_virtual_methods.cpp
new file mode 100644
index 0000000..fccdcb8
--- /dev/null
+++ b/examples/cpp_virtual_methods/cpp_virtual_methods.cpp
@@ -0,0 +1,30 @@
+/*
+ * Example for using virtual methods in C++
+ * 05/21/2023 A. Mandera
+ */
+
+// Could be defined here, or in the processor defines.
+#define SYSTEM_CORE_CLOCK 48000000
+#define APB_CLOCK SYSTEM_CORE_CLOCK
+
+#include "ch32v003fun.h"
+#include "example.h"
+#include <stdio.h>
+
+int main() {
+    SystemInit48HSI();
+
+    // Setup UART @ 115200 baud
+    SetupUART(UART_BRR);
+    Delay_Ms(100);
+
+    printf("Begin example\n");
+
+    // Initialize variable in example class
+    Example.begin();
+
+    while (true) {
+        Example.doPrint(10);
+        Delay_Ms(1000);
+    }
+}
\ No newline at end of file
diff --git a/examples/cpp_virtual_methods/example.cpp b/examples/cpp_virtual_methods/example.cpp
new file mode 100644
index 0000000..19560d8
--- /dev/null
+++ b/examples/cpp_virtual_methods/example.cpp
@@ -0,0 +1,30 @@
+/*
+ * Example for using virtual methods in C++
+ * 05/21/2023 A. Mandera
+ */
+
+#include "example.h"
+#include "stdio.h"
+
+ExampleClass Example;
+
+void Print::doPrint(int i) {
+    printValue(i);
+    doNewLine();
+}
+
+void ExampleClass::begin() {
+    this->_value = 1;
+}
+
+int ExampleClass::value() {
+    return this->_value++;
+}
+
+void ExampleClass::doNewLine() {
+    printf("\n");
+}
+
+void ExampleClass::printValue(int i) {
+    printf("Value: %d", this->value() + i);
+};
\ No newline at end of file
diff --git a/examples/cpp_virtual_methods/example.h b/examples/cpp_virtual_methods/example.h
new file mode 100644
index 0000000..97c46f4
--- /dev/null
+++ b/examples/cpp_virtual_methods/example.h
@@ -0,0 +1,25 @@
+/*
+ * Example for using virtual methods in C++
+ * 05/21/2023 A. Mandera
+ */
+
+#pragma once
+
+class Print {
+public:
+    void doPrint(int);
+    virtual void doNewLine(void) = 0;
+    virtual void printValue(int) = 0;
+};
+
+class ExampleClass : public Print {
+public:
+    void begin();
+    int value();
+    void doNewLine(void) override;
+    void printValue(int) override;
+private:
+    int _value;
+};
+
+extern ExampleClass Example;
\ No newline at end of file
-- 
GitLab