About Me
Welcome to my blog! This is where I collect my observations and notes on programming and technology. The main subjects range from implementation details to broader ideas about programming.
Main Topics
- Engineering Projects: Exploring implementation details and how technical systems work.
- C/C++: Notes on language features and programming techniques.
- The Programmer’s Perspective: Ideas about developing a career and a way of thinking as a programmer.
For more, visit the categories page.
Contact
If you have questions or would like to discuss something, please get in touch through the About page.
Thank you for reading and for your support. I hope these notes help you on your own technical journey!
volatile and Memory Access
Device registers can change independently of ordinary program execution, so accesses to them must not be optimized as if they were ordinary, stable variables. This is an important use of volatile.
The original notes explained this in terms of cache writeback, but those are separate mechanisms: volatile constrains the compiler’s treatment of accesses; it does not by itself disable CPU caching or provide a memory barrier. Device-memory attributes and ordering requirements must also be handled by the platform. See GCC’s discussion of volatile accesses.
Observations From Mario and the Hello Kernel
In the native configuration I tested, enabling the klib-selection macro allowed Mario to run, whereas the other configuration did not. The macro causes the corresponding klib implementations to be included in the build.
The hello example accesses devices through a common API. On an emulated platform, code implements the behavior associated with those reads and writes. The device model still ultimately runs within the real host’s GNU/Linux environment.
Following Startup More Closely
After implementing function tracing, I repeatedly saw _trm_init as the first recorded function. A question about mainargs prompted me to trace the whole startup sequence.
The ysyx tree contains more than C files and makefiles: it also includes assembly source such as start.S. The guest startup path begins there. A jal transfers control to _trm_init, which calls the program’s main with the configured argument string.
In am-tests, main inspects that argument and uses a switch, with a CASE helper macro, to choose the requested test. The macro relates a short selection character to a named test.
Where Does mainargs Come From?
Searching the code reveals more than one platform-specific path. A native-platform init_platform uses getenv to obtain an environment value. The makefiles also pass mainargs through build flags, where it can be embedded in the target image through compilation or assembly preprocessing.
ASFLAGS supplies options when compiling assembly sources, while CFLAGS supplies options for C compilation. These flags do not directly become arguments to main; the startup implementation uses the definitions they provide to construct the argument that reaches the program.
I initially treated the argument as a filename or a way of locating a program. Looking at the callers showed that this was too narrow. The init_platform path I had found was also not invoked on the particular target under investigation, so it did not explain that target’s startup by itself.
How main Uses the Argument
Different programs assign different meanings to the same argument interface. The hello kernel prints it as a string. The AM test driver interprets it to select a test. It is therefore not inherently a function name or a program filename.
An Experiment: Implement printf as a Macro
I had already implemented sprintf, declared as int sprintf(char *out, const char *fmt, ...);. Its interface resembles printf, whose declaration is int printf(const char *fmt, ...);.
Both scan the format string and handle conversion rules. sprintf writes the resulting characters into an output buffer; printf sends them to an output device. One possible implementation would format into a buffer and then print that buffer.
The Variadic Forwarding Problem
Inside printf, the variable arguments are represented by a va_list, and values are consumed using va_arg. Calling sprintf(out, fmt, args) does not forward that list as the original sequence of arguments. It passes the va_list object itself as one argument, which does not match the format conversions. The result can be invalid accesses at runtime.
My initial workaround was a macro:
1 |
|
It passed the small tests, but the fixed buffer can overflow if the formatted result exceeds its capacity.
Integration and Linking Problems
Existing code expects printf to be a function declared by a header such as stdio.h. Replacing it with a macro requires coordinating those headers. For this experiment I changed the affected test sources to include klib.h, which declares the project’s own I/O facilities.
I put the macro in klib.h, since callers normally include that interface rather than klib-macros.h. The macro depended on putstr, so I expanded that operation directly to simplify the experiment:
1 |
|
My own small printf test passed, but alu-test produced this diagnostic:
1 | + LD -> build/alutest-riscv32-nemu.elf |
I did not yet understand the linker well enough to establish the cause. An AI explanation suggested a RISC-V JAL relocation-range problem or a linker-script layout issue. Those were hypotheses, not verified diagnoses.
The error above occurred for ARCH=riscv32-nemu. A native build instead repeatedly reported:
1 | In file included from /home/bruce/project-a/git-project/ysyx-workbench/abstract-machine/am/src/native/ioe/audio.c:4: |
Changing the macro’s grouping, including trying do { ... } while (0) and additional parentheses, did not solve it. I left that experiment unresolved and returned to a function implementation.
Implementing printf as a Function
The format string is const, so the implementation should scan it without modifying it. Unlike sprintf, printf does not have to return a completed output string. It can emit converted characters directly, avoiding a temporary buffer and a second traversal.
The implementation reused putstr and itoa_special. I also found a macro-name collision: putstr declares a local iterator called p, so passing an expression named p can interact badly with that declaration. For example:
1 | char *p = va_arg(args, char*); putstr(p); |
This is another reason to be cautious about local identifiers inside macros.
Implementing the Clock
__am_timer_uptime needs three conceptual steps: establish a starting time, obtain the current time, and compute the elapsed interval.
The nearby __am_timer_init looked like the place for initialization. Following the RTC example through io_read and ioe_read, and observing that ioe_init calls __am_timer_init, supported that interpretation.
The RTC example was not, by itself, a reliable correctness test for uptime: adding arbitrary values to the uptime result did not obviously stop that example from working. I therefore implemented initialization explicitly. Diagnostic output then showed a startup value of zero in the environment being tested.
A Better Shared Formatting Interface
I eventually moved the formatting logic from sprintf into vsprintf. The v interface accepts a va_list, so both variadic front ends can delegate to it correctly.
The earlier difficulty was not that one variadic function can never call another, but that a va_list cannot be forwarded by passing it as a single ordinary variadic argument. A matching v function provides the needed interface.
Floating-Point Formatting
The benchmark tests included floating-point output before I had implemented %f. Their helper formatted parts of the number using integer output. I tried adding the conversion myself, but encountered:
1 |
|
The failure occurred around floating-point-to-integer conversion and the target’s supporting runtime. I left that part for later rather than claim it was already implemented.
If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !