Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Could we port OMicroB to the Numworks CPU (an ARMv7-M Cortex-M7 on 32bits) ? Step ONE in a larger project #36

Open
Naereen opened this issue Oct 21, 2024 · 103 comments

Comments

@Naereen
Copy link

Naereen commented Oct 21, 2024

Hi there everybody 😃 !

Following the discussion started on #14 I'll explain what I would like to achieve, here. Sorry if this is lengthy, I prefer to explain clearly and in details.

As the title of the issue suggest, I would like to continue the work done by @Vertmo into porting the OMicroB project to Micro:bit, and port it now to another ARM32 processor, which is very close to the Micro:bit and thus it shouldn't be too hard. From what I understood, the Micro:bit is running on a Arm Cortex-M4 32 bit processor with FPU, with RAM 128KB and at 64MHz. Quite less than what the Numworks calculators offer, but in a very close setup!

About the Numworks

The Numworks calculators are open-source: their OS is Epsilon, written in C++ (and parts in C).
Its CPU is an ARMv7-M Cortex-M7 on 32bits, with its core clocked at 216 MHz and 256K of Static RAM. There exists two other open-source fork OS, Upsilon and Omega. I'm aiming only at Epsilon, to aim at the largest possible audience.

By default on the main OS, there is already a MicroPython interpreter as well as a (very good) text editor. All that is written in C++ and in C. It works amazingly well, considering the limited hardware...

My goal(s)

My first goal: I would like to port OMicroB to the Numworks devices. That is, being able to write on my laptop, in OCaml, tiny code that could be compiled to bytecode, and then to the correct assembly language (then binary) for the CPU of the Numworks, using OMicroB.
But because of the nature of the calculator environment, I don't want to flash the program to the Numworks. If I do that, I'm most certain that nothing will work. And even if it worked, that would overwrite the OS of the Numworks, that's not what I want.
Instead, such assembly language or object (.arm_o file) could then be used to be embedded in an application for the Numworks (see below).

My dream goal: it is to have the same experience as the Python app (that is, a full text editor, interpreter + terminal) for the OCaml language.
If not possible, most likely because the RAM of the Numworks is limited, then I could focus instead on the camllight language, or even on tinier OCaml-like languages (like Mini-Caml-Interpreter).
My guess is that, if the language interpreter itself is written in pure C (like it is the case for Lua, see below, or for caml-light), this should be possible: all I would need is to have a execute_this_phrase(const char* line_of_code) function, available in the rest of the C code.

What I tried so far

It is possible to write an installable ("flashable") application (a .nwa file) either in C++, but also in C and in Rust. Following the tutorial, I successfully wrote a tiny C application, which does nothing interesting yet, to try it out.
I want to add at least one object file, compiled for the correct ARM32 bit architecture, to this C application, like what is done for the lua app (see below also).

I tried (for hours, all weekend long) to obtain on my laptop a compiler for OCaml that could produce object code for ARM 32bits, this way I hoped of being able to follow the official tutorial which explains how to include compiled OCaml code in a C app. I wanted to have a tiny C app for the Numworks, where a very simple basic function could be written in OCaml separately (on the laptop), and the called in the app.
I tried all the projects I could find online, none of them worked for me (and I tried a lot). This lead me to be interested in OMicroB.

Yesterday, I tried using the omicrob -device microbit2 compiler, to produce a .arm_o object file, from a very tiny OCaml application, targeting an ARM32 processor.
I encountered several issues, mainly that there are no print_... functions, thus making the whole process quite hard: if the OCaml code cannot print to the screen of the Numworks, it is quite pointless. Let's address this issue later.
Then when I focused on a simpler program, I managed to compile it (and obtain a bunch of compiled file, one being the .arm_o object file).
I tried to include it in my baby C app, and got a lot of compilation issues (from what I gathered, because of float-abi=hard vs float-abi=softfp differences, and because the main.c file declares a main() function and the compiled OCaml code also declares a main() function).

An app for Lua: a possible inspiration

And... There is also the lua application, which ports a full Lua 5.4.4 interpreter on the Numworks. It doesn't ship a text editor, and only loads and prints the result of one script, that has to be shipped with the app while installing it. It's not amazing in terms of user experience, but it's already great.

If that is not possible to ship a text editor + interpreter (+ terminal/toplevel?) on the Numworks, then at least I would be happy to be have what the Lua app provides, or even less.

Many thanks for reading all the way down. I'll be actively working on this for the next two weeks at least. Thanks if you can help!

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 22, 2024

Hi, thank you for the detailed plan ! I'll comment on the first goal and your current issues.

The lack of print_ function should be easily fixable; we just didn't implement them because they are not really applicable to a micro:bit, but we have all the setup we need to do so by implementing new hardware primitives.

First, we probably need to tweak the configuration that OMicroB is using when calling arm-gcc to fit that expected for numworks binaries. Maybe you could share with me the repo of your "baby C app", so I can get up to speed on exactly what this configuration is, and test on my side ? Unfortunately I do not own a numworks calculator, but I can probably get one fairly quickly; in the meantime, I can at least see if things compile.

@Naereen
Copy link
Author

Naereen commented Oct 22, 2024

Hi, thanks for your quick reply.
I don't expect you to purchase a Numworks "just" for my weird project. I can try on my side.
My main objective today is to be able to obtain .arm_o object code, compiled from a test.ml OCaml test file, for the correct architecture of the Numworks.

On one side, I think I can easily find all the options that I should change in the compilation process (when ./bin/omicrob -device numworks test.ml should call /usr/bin/arm-none-eabi-gcc) !
Taking inspiration from what is called, for instance when compiling the example external app in C.

On the other side, I know I'm not that skilled enough to write drivers and all such complicated code...

@Naereen
Copy link
Author

Naereen commented Oct 22, 2024

From what I was able to read in the output.c file that is produced by the bc2c compiler, if my input OCaml file had let say a fact : int -> int function, the output.c file doesn't let the fact function be accessible from anywhere?
It "simply" implements the virtual machine, which will run the content of the test.ml file.
Am I correct?

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 22, 2024

From what I was able to read in the output.c file that is produced by the bc2c compiler, if my input OCaml file had let say a fact : int -> int function, the output.c file doesn't let the fact function be accessible from anywhere? It "simply" implements the virtual machine, which will run the content of the test.ml file. Am I correct?

Pretty much yes, you can only run the program in its totality.

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 22, 2024

I have created a new branch (https://github.com/stevenvar/OMicroB/tree/numworks) and started to do some tweaks. Currently, OMicroB should produce a binary compatible with numworks, but that does not even start the OCaml interpreter. Maybe you can give it a try ? You can configure omicrob with ./configure -target numworks, and then use make in the subfolder targets/numworks/tests/hello. The hex file you get should be compatible with the .nwa format.

@Naereen
Copy link
Author

Naereen commented Oct 22, 2024

It looks like the changes I started to implement on OMicroB, only more mature and cleaner, thanks!
So for the hello.ml file you wrote, it should be compiled as a standalone NWA app for my Numworks if it works well... but it's maybe just gonna print, and return directly, so I don't think I'll see anything (if it works, it's gonna be so quick than the Numworks UI will be displayed right after it's done, erasing the printed message "Hello?").

@Naereen
Copy link
Author

Naereen commented Oct 22, 2024

I think the microbit_print_string function is undefined, I got the following error when running the targets/numworks/tests/hello/Makefile make command:

/usr/bin/ld : /home/lilian/publis/OMicroB.git/lib/libcamlrun.a(bindings.o) : dans la fonction « caml_numworks_print_string » 
bindings.c:(.text+0x22b) : référence indéfinie vers « microbit_print_string »
collect2: error: ld returned 1 exit status
File "hello.ml", line 1:
Error: Error while building custom runtime system
make: *** [/home/lilian/publis/OMicroB.git/targets/numworks/tests/hello_world/Makefile:11 : hello.elf] Erreur 2
make : on quitte le répertoire « /home/lilian/publis/OMicroB.git/targets/numworks/tests/hello_world »

@Naereen
Copy link
Author

Naereen commented Oct 22, 2024

I tweaked a few things:

  • added #include <assert.h> in the beginning of src/byterun/vm/array.c : assert() were used but assert was undefined?
  • added (value v) in both targets/microbit/byterun/arch-specific.c and targets/numworks/byterun/arch-specific.c when needed (for uncaught_exception(value) that had an undefined parameter);
  • commented away the possible call to undefined microbit_print_string(...) in targets/numworks/byterun/prims/bindings.c, defaulting to the definition below with printf.

@Naereen
Copy link
Author

Naereen commented Oct 22, 2024

With these changes, the hello.hex compiles without any errors.
But when I try to upload it (flash it) to my calculator, on the official flashing website for external apps (https://my.numworks.com/apps), I get many errors:

apps:1 Uncaught (in promise) ./this.program: Missing api level. Please define a .rodata.eadk_api_level section.
./this.program: Missing app name. Please define a .rodata.eadk_app_name section.
./this.program: Missing app icon. Please define a .rodata.eadk_app_icon section.
./this.program: app.nwa: in function `caml_cos_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:186: undefined reference to `cos'
./this.program: app.nwa: in function `caml_sin_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:190: undefined reference to `sin'
./this.program: app.nwa: in function `caml_sqrt_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:198: undefined reference to `sqrt'
./this.program: app.nwa: in function `caml_atan2_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:202: undefined reference to `atan2'
./this.program: app.nwa: in function `caml_fmod_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:222: undefined reference to `fmod'
./this.program: app.nwa: in function `caml_set_bit':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:65: undefined reference to `set_bit'
./this.program: app.nwa: in function `caml_clear_bit':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:70: undefined reference to `clear_bit'
./this.program: app.nwa: in function `caml_read_bit':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:75: undefined reference to `read_bit'
./this.program: app.nwa: in function `caml_write_register':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:81: undefined reference to `write_register'
./this.program: app.nwa: in function `caml_read_register':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:86: undefined reference to `read_register'
./this.program: app.nwa: in function `caml_delay':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:92: undefined reference to `delay'
./this.program: app.nwa: in function `caml_millis':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:97: undefined reference to `millis'
./this.program: app.nwa: in function `abort':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/stdlib/../../../../../../../../newlib/libc/stdlib/abort.c:59: undefined reference to `_exit'
./this.program: app.nwa: in function `_kill_r':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/reent/../../../../../../../../newlib/libc/reent/signalr.c:53: undefined reference to `_kill'
./this.program: app.nwa: in function `_getpid_r':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/reent/../../../../../../../../newlib/libc/reent/signalr.c:83: undefined reference to `_getpid'

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 22, 2024

I think the microbit_print_string function is undefined, I got the following error when running the targets/numworks/tests/hello/Makefile make command:

/usr/bin/ld : /home/lilian/publis/OMicroB.git/lib/libcamlrun.a(bindings.o) : dans la fonction « caml_numworks_print_string » 
bindings.c:(.text+0x22b) : référence indéfinie vers « microbit_print_string »
collect2: error: ld returned 1 exit status
File "hello.ml", line 1:
Error: Error while building custom runtime system
make: *** [/home/lilian/publis/OMicroB.git/targets/numworks/tests/hello_world/Makefile:11 : hello.elf] Erreur 2
make : on quitte le répertoire « /home/lilian/publis/OMicroB.git/targets/numworks/tests/hello_world »

Oh right, this is a mistake on my part. I will fix it.

With these changes, the hello.hex compiles without any errors. But when I try to upload it (flash it) to my calculator, on the official flashing website for external apps (https://my.numworks.com/apps), I get many errors:

apps:1 Uncaught (in promise) ./this.program: Missing api level. Please define a .rodata.eadk_api_level section.
./this.program: Missing app name. Please define a .rodata.eadk_app_name section.
./this.program: Missing app icon. Please define a .rodata.eadk_app_icon section.
./this.program: app.nwa: in function `caml_cos_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:186: undefined reference to `cos'
./this.program: app.nwa: in function `caml_sin_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:190: undefined reference to `sin'
./this.program: app.nwa: in function `caml_sqrt_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:198: undefined reference to `sqrt'
./this.program: app.nwa: in function `caml_atan2_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:202: undefined reference to `atan2'
./this.program: app.nwa: in function `caml_fmod_float':
/home/lilian/publis/OMicroB.git/src/byterun/vm/float.c:222: undefined reference to `fmod'
./this.program: app.nwa: in function `caml_set_bit':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:65: undefined reference to `set_bit'
./this.program: app.nwa: in function `caml_clear_bit':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:70: undefined reference to `clear_bit'
./this.program: app.nwa: in function `caml_read_bit':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:75: undefined reference to `read_bit'
./this.program: app.nwa: in function `caml_write_register':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:81: undefined reference to `write_register'
./this.program: app.nwa: in function `caml_read_register':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:86: undefined reference to `read_register'
./this.program: app.nwa: in function `caml_delay':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:92: undefined reference to `delay'
./this.program: app.nwa: in function `caml_millis':
/home/lilian/publis/OMicroB.git/src/byterun/vm/../prims/bindings.c:97: undefined reference to `millis'
./this.program: app.nwa: in function `abort':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/stdlib/../../../../../../../../newlib/libc/stdlib/abort.c:59: undefined reference to `_exit'
./this.program: app.nwa: in function `_kill_r':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/reent/../../../../../../../../newlib/libc/reent/signalr.c:53: undefined reference to `_kill'
./this.program: app.nwa: in function `_getpid_r':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/reent/../../../../../../../../newlib/libc/reent/signalr.c:83: undefined reference to `_getpid'

I think all of these errors are due to things I forgot in the linking parameters. The first three should be easy enough to fix by following the default app Makefile. The other ones probably require adding some - lm,... flags. I will look into it. Is it possible for me to test this flashing website without owning a calculator?

@Naereen
Copy link
Author

Naereen commented Oct 22, 2024

  • The missing references for src/byterun/vm/float.c are most likely due to -lm (math lib) missing in the linking step, as you said.
  • It is not possible to test the flashing website without having Chrome and a Numworks calculator connected by USB. But I can test 👍 !

@Naereen
Copy link
Author

Naereen commented Oct 22, 2024

./this.program: Missing api level. Please define a .rodata.eadk_api_level section.
./this.program: Missing app name. Please define a .rodata.eadk_app_name section.
./this.program: Missing app icon. Please define a .rodata.eadk_app_icon section.

These should be added to the file defining the main function, I guess.

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 23, 2024

I've pushed a new commit which should hopefully fix all of that, let me know if that works :)

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

Who nice, the names and icons are correctly found on the flashing website.
It doesn't let me load the app, and it gives this error:

./this.program: app.nwa: in function `_sbrk':
/build/newlib-pB30de/newlib-3.3.0/build/arm-none-eabi/thumb/v7e-m+fp/hard/libgloss/libnosys/../../../../../../../libgloss/libnosys/sbrk.c:21: undefined reference to `end'

I guess the issue could be solved by what was proposed in the main.c file of the nwagyu/lua app (https://github.com/nwagyu/lua/blob/master/src/main.c#L10):

// TODO: Check why __exidx_start/__exidx_end is needed
void __exidx_start() { }
void __exidx_end() { }

I'll try that!

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

It didn't work.The issue is harder. https://stackoverflow.com/questions/5764414/undefined-reference-to-sbrk seems to reference this same issue, and I'm talking to a very skilled person on the Omega (a fork of Epsilon, the OS of Numworks) Discord server.
Apparently we should find a way to have a end defined by the linker, as stated here : libgloss/libnosys/sbrk.c:10

In the options given to the linker, we have -specs=nano.specs, and apparently we should add -specs=nosys.specs as well.
However, no matter how I try that (both, or only the nosys) in the default example C app, the same bug of undefined reference to 'end' appears, on the flashing website.

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 23, 2024

I'm not sure I understand, does that mean the example app (https://github.com/numworks/epsilon-sample-app-c) does not compile as is ? If it does, what is the fundamental difference between it and our binary (when using nano.specs, as I've pushed in the last commit) ? Could you give me the error message you get with the latest commit ?

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

Exactly, the example app does not compile when the linker has the option -specs=nosys.specs (either if it is alone or with nano.specs). I dont know how to proceed, I'm looking it up.

With the latest commit, the flashing website gives this error:

./this.program: app.nwa: in function `abort':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/stdlib/../../../../../../../../newlib/libc/stdlib/abort.c:59: undefined reference to `_exit'
./this.program: app.nwa: in function `_kill_r':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/reent/../../../../../../../../newlib/libc/reent/signalr.c:53: undefined reference to `_kill'
./this.program: app.nwa: in function `_getpid_r':
/build/newlib-pB30de/newlib-3.3.0/build_nano/arm-none-eabi/thumb/v7e-m+fp/hard/newlib/libc/reent/../../../../../../../../newlib/libc/reent/signalr.c:83: undefined reference to `_getpid'

It seems even worse 😭

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 23, 2024

No, I think we're making progress. I pushed a new commit with these three stub functions. Does it work better ?

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

YEAS! It works! I tweaked the hello.ml file in order to display the "Hello?" string for quite some time, and it does get printed on the screen when launching the app!

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 23, 2024

Ah good. Do you mean that with the version I put (which already had a while true loop at the end), the string did not stay on the screen ? How did you change the ml program exactly ?

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

I was afraid that the version you had would be impossible to exit, so my hello.ml has a (long) for loop instead.

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 23, 2024

Oh right, that makes sense. I dont know exactly how "premptive" the OS of the calculator is, so it's probably a bad idea to block forever you're right.
I think that means Step 1 is done.

A good Step 2 would be to add some well chosen primitives to allow for writing interactive programs (like polling for the buttons, drawing on the screen, etc). I can give you some pointers on how to implement these if you want. We could also port some of the existing programs and benchmarks to see the how much power we're dealing with compared to what we had previously.

Then we can start thinking about the larger goal, writing a REPL (Read-Eval-Print-Loop) that runs on the calculator. The main difficulty will be to fit the OCaml compiler in there of course.

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

Congrats for making it work, it's already a good step forward!

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

For step 2, maybe we could first focus on a non interactive system.

For example, there exists plenty of "tiny OCaml-like language" implemented in OCaml itself, like for instance a student project Mini Caml Interpreter.
I've managed to recompile their project using my OCaml v4.14.0 opam switch (and writing a dune project for it), for my x86_64 architecture.

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

I think if we can have a tiny OCaml-like interpreter that executes a given string, it shouldn't be impossible to connect it to the local filesystem of the calculator.
There exists a project offering a C library that exposes a few C functions to connect to the local storage of the calculator, where the Python scripts are found for instance. See https://framagit.org/Yaya.Cout/numworks-extapp-storage/ and src/storage.c for reference.
Note: I'm talking to its developer since Saturday, he/she is very reactive and helpful.

The main idea here is that we could leverage the power of the internal Python editor!
I can imagine that we could easily ask the users to create/edit a "ocaml.py" file, using the (great) Python editor app, then to start the "OCaml interpreter" app we will produce, and the app will be in charge of interpreting the content of this file, without any interactive REPL or file-selection menu.

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

Just to show you, because it's nice to have achieved together this first step 🎉 !
Capture d’écran_2024-10-23_14-45-03

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

I started writing additional OCaml functions, to see a little bit how it works.

  • So far, I added a print_int: int -> unit function, a working implementation of millis(): int.
  • Anything that tries to do a newline fails completely: it gets printed but with no newline.
  • I tried adding a print_float: float -> unit function, but even though the macro Double_val exists in mlvalues.h in the OCaml C API library, exactly as the Int_val exists, it is not recognized. I removed it, I guess it's not that important right now.

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

I'm struggling to understand what files to modify, between numworks.ml/numworks.mli and bindings.c and arch-specific.c.
I've managed to update and fix the millis() function, calling the correct function of the Numworks EADK library (from eadk.h).

But now I'm trying to add a val display_draw_string : string -> int -> int -> unit in the numworks.mli file (with correct lines in the three others). I can compile omicrob with no issue, but when compiling the hello_world example (which now is a test of all the new functions I defined), i get this linking error:

/usr/bin/ld : /home/lilian/publis/OMicroB.git/lib/libcamlrun.a(bindings.o) : dans la fonction « caml_delay_usec » 
bindings.c:(.text+0x2cb) : référence indéfinie vers « delay_usec »
/usr/bin/ld : /home/lilian/publis/OMicroB.git/lib/libcamlrun.a(bindings.o) : dans la fonction « caml_display_draw_string » 
bindings.c:(.text+0x2f1) : référence indéfinie vers « display_draw_string »
collect2: error: ld returned 1 exit status
File "hello.ml", line 1:
Error: Error while building custom runtime system
make: *** [/home/lilian/publis/OMicroB.git/targets/numworks/tests/hello_world/Makefile:12 : hello.elf] Erreur 2

So the hello.ml > numworks.ml(i) part works, and it gets to the bindings.c file, which should use the definitions written in arch-specific.c, except it does not.
Is there a part of the main OMicroB project which defines the list of functions that can be available in bindings.c and/or arch-specific.c ? It's more complicated than I expected.

@Vertmo
Copy link
Collaborator

Vertmo commented Oct 23, 2024

That's because this specific compilation pass is for the PC binary, which arch-specific.c is not used to produce. You need to add simulation functions in targets/numworks/byterun/simul/sf-regs.c. These will be compiled with the regular GCC to be ran on your computer, so you may not use eadk functions.

@Naereen
Copy link
Author

Naereen commented Oct 23, 2024

Hm could we not implement "fake" UI functions of the Numworks in the simulator? I don't really care about the simulator actually.

@Naereen
Copy link
Author

Naereen commented Nov 18, 2024

Indeed, the prolog app and the minicaml one were just baby steps to start experimenting with all that. It's satisfactory that they work well now, but none of them were the real goal.

I would prefer porting an interpreter rather than a compiler: as a user of this "dreamed OCaml-Numworks app", I think the goal should be to get the type of a function (or the value of some function evaluation), quickly written in the (Python) text editor, saved as "ocaml.py" and ran from the Numworks OCaml app.
Having a compiler does not allow to interactively see the type of a value, and is probably harder to port too.

@Naereen
Copy link
Author

Naereen commented Nov 18, 2024

the M&C GC bug. Normally it should raise an Out_of_memory error when running out of memory.

I've seen a flag like -fno-exceptions in the commands calling the different compilers, maybe the exceptions are disabled when compiling using omicrob ?

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 18, 2024

No not really, this flag just tells gcc not to include the runtime tooling for C++ exceptions in the final binary, it has nothing to do with OCaml.

@Naereen
Copy link
Author

Naereen commented Nov 18, 2024

I delved a bit into the Toploop module of OCaml, and wrote this script which could ideally be compiled to the Numworks (interpreter.ml) :

(* OCaml Interpreter for the Numworks *)

let long_delay = 3000
let exit (code:int) = ()

let filename = "ocaml.py"

let default_program = "
(* Example of an OCaml script to use
   with the OCaml-Numworks app *)
let rec fibonacci n =
  if n <= 1 then
    n
  else
    fibonacci (n-1) + fibonacci (n-2)
;;
"

(* https://stackoverflow.com/questions/55689054/ocamlbuild-with-toploop-toplevel *)
let eval code =
  (* let as_buf = Mylexing.from_string code in *) (* see below *)
  let as_buf = Stdlib.Lexing.from_string code in
  let parsed = !Toploop.parse_toplevel_phrase as_buf in
  ignore (Toploop.execute_phrase true Format.std_formatter parsed)

let () =
  Toploop.initialize_toplevel_env ();  (* to initialize the toplevel env, mandatory *)

  clear_screen (); (* remove if not compiling for the Numworks *)
  print_endline ("Loading code from '" ^ filename ^ "' ...");

  let file_content = read_any_file filename in
  let file_content = if file_content = "" then default_program else file_content in

  print_endline "Parsing the file...";
  eval file_content;

  print_newline ();
  delay long_delay; (* remove if not compiling for the Numworks *)
  exit 0

I managed to compile this script (just without the few lines that rely on our new Numworks stdlib, so the calls to clear_screen, delay), using ocamlbuild:

$ ocamlbuild interpreter.byte -pkgs compiler-libs,compiler-libs.toplevel
$ ./interpreter.byte
Loading code from 'ocaml.py' ...
Parsing the file...
val fibonacci : int -> int = <fun>

😄 This behavior is exactly what I hope to obtain, for the "OCaml interpreter" Numworks app: read and parse the content of a (predefined) Python file from the local storage, run the toplevel loop on its content, and display the output.

The compilation with ocamlbuild loads the toploop (and other modules) from my compiler-libs/ folder on my OCaml v4.14.1 switch locally (/home/lilian/.opam/4.14.1/lib/ocaml/compiler-libs).

😭 The compilation with omicrob obviously fails because it cannot find the required files.

So I brutally tried to copy the entire content of the compiler-libs/ folder into a fresh sub-folder in OMicroB_stevenvar.git/targets/numworks/tests/ocaml-interpreter-full.
The compilation then fails again, not because it cannot load files, but because the files it loads yield these issues:

  • Error: Unbound module Stdlib.Lexing: okay, change to Lexing ;
  • Error: Unbound module Lexing: okay, change to Mylexing, I already have this file available from the minicaml/ test app, and it worked fine there so it should work fine here too!
  • File "interpreter.ml", line 1: Error: The files /home/lilian/publis/OMicroB_stevenvar.git/lib/stdlib.cmi and toploop.cmi make inconsistent assumptions over interface Stdlib, ach now this one will be harder to fix!

So from this point forward, I don't really know what to do or to try. @Vertmo if you followed until here and have any idea... thanks in advance (again).

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 19, 2024

Hi,
I think using Toploop is the way to go ! The problem you have is that you're using the compiled version of toploop, which was compiled against the "normal" stdlib interface. To use Toploop in OMicroB, you will need to recompile its source (and that of its dependencies, that is most of the compiler libs) using OMicroB. This might require a bit of work if compiler libs use Stdlib functions not yet supported by OMicroB.

@Naereen
Copy link
Author

Naereen commented Nov 19, 2024

Hi :)
I think one of the issue of recompiling Toploop is the Format module, which does fancy pretty-printing, where our Stdlib version in OMicroB has just a few printing function.

Just for the Lexing module, for the minicaml/ test app, I had to work a bit on the lexing.ml/lexing.mli files by removing some parts which where dependent on some C implementation, not available when compiling with OMicroB. It wasn't straightforward.

I'm kinda overwhelmed by all the files in the compiler-libs folder, honestly 😨.

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 19, 2024

Well, the good news is, we have someone on this project who knows a lot about Format (the bad news is, he might not be very available).
If you want to push what you've done until now to your fork, we can take a look at it together.

@Naereen
Copy link
Author

Naereen commented Nov 19, 2024

Do you think it could be worth it to open a question on https://discuss.ocaml.org/, asking how should I start in this direction of recompiling Toploop and compiler-libs?
Some OCaml developers are very active on this platform.

@Naereen
Copy link
Author

Naereen commented Nov 19, 2024

Regarding Format, maybe it's not that difficult, if all there is to do is to provide a type formatter_out_functions, (see "Redefining output functions" in the doc of Format).

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 19, 2024

Yes, I think most of the complexity of Format is due to its typing, but that's handled by the OCaml compiler anyway.

@Naereen
Copy link
Author

Naereen commented Nov 19, 2024

Regarding

If you want to push what you've done until now to your fork, we can take a look at it together.

I've worked on this repository and on my fork, separately, so I'll have to work a bit to merge things, I'll try that this week.
Sorry for the delay!

@Naereen
Copy link
Author

Naereen commented Nov 20, 2024

I've worked for a couple of hours on trying to compile the above script with omicrob. No need to mention that this is not trivial...

I think I manually resolved the issues with the Format library (and its dependencies, Int, Either, Stack, Queue, camlinternalFormatBasics, camlinternalFormat).
Now the Topeval module can start to get compiled, but it obviously fails as it requires other modules, the first being Parsetree... I'm sure it has many other dependencies of modules in the compiler-libs family...

There is not even a parsetree.ml file in the parsing/ folder of the OCaml sourcecode. So I have no clue to know how to continue 😢.
(I've been using the latest version of the OCaml repository, checked out at branch 4.14 as I'm building for this branch locally on my opam switch).

@Naereen
Copy link
Author

Naereen commented Nov 20, 2024

I'm lost.

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 20, 2024

I've worked for a couple of hours on trying to compile the above script with omicrob. No need to mention that this is not trivial...

I think I manually resolved the issues with the Format library (and its dependencies, Int, Either, Stack, Queue, camlinternalFormatBasics, camlinternalFormat). Now the Topeval module can start to get compiled, but it obviously fails as it requires other modules, the first being Parsetree... I'm sure it has many other dependencies of modules in the compiler-libs family...

Indeed, the OCaml compiler is a complex piece of software, it makes sense that it would require a lot of work. I'm sure we can get there.

There is not even a parsetree.ml file in the parsing/ folder of the OCaml sourcecode. So I have no clue to know how to continue 😢. (I've been using the latest version of the OCaml repository, checked out at branch 4.14 as I'm building for this branch locally on my opam switch).

I think that's because the parsetree.mli file is self-sufficient

@Naereen
Copy link
Author

Naereen commented Nov 20, 2024

Thanks for the encouragement!
A couple of questions:

  1. what version of OCaml stdlib is partially included in OMicroB? 4.07? How to know for sure? I'm encountering issues I can't fix when tweaking existing OCaml compiler-libs files that I copy-paste from my local OCaml v4.14, and I think I should restart from scratch, using the correct version to be sure there is a minimal work to be done on such local modifications.
  2. I've tried and failed to configure, from the Makefile, a omicrob command that would load libraries from a local folder when calling ocamlc (in my current version, it should be called with arguments -I stdlib/ and -I compiler-libs/). No matter how I tried I couldn't manage to use the -ccopt=... option of omicrob correctly. Do you know how to do that? I tried: omicrob -ccopt=-Istdlib/ and with or without spaces between -I and stdlib/ and with or without quotes.

Thanks!

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 20, 2024

  1. If I remember correctly, we worked extra-hard to adapt OMicroB from 4.06 to 4.07 (because the organization of the Stdlib changed at that point). So I'm pretty sure it's 4.07. If there is anything missing that we need for this project, we can add it on a case-by-case basis.
  2. The source you want to include are OCaml source files right ? So I think the command line option for OMicroB would be -mlopt(s). For instance you could try -mlopts -I,stdlib/,-I,compiler-libs/.

@Naereen
Copy link
Author

Naereen commented Nov 20, 2024

Regarding the version of OCaml, it's also what the badge in the README.md shows: 4.07. Thanks.
I'll try again to add locally the files requires to compile the OCaml interpreter.ml script with omicrob.

@Naereen
Copy link
Author

Naereen commented Nov 20, 2024

It's quite heavy, every module relies on everything: to have a "simple" eval function as I hoped, the cmo and cmi and cmt format modules have to be included, and the Marshall one too.
So the Format module is definitely required, as well as the input/output functions which use in_channel/out_channel (as defined in stdlib.ml/.mli)...

Either this will be very hard to port to the Numworks, or in fact the Toploop eval part can run without needing to generate cmi/cmo/cmt files and without Marshall-ing anything.

@Naereen
Copy link
Author

Naereen commented Nov 20, 2024

I managed to get the omicrob compilation to go until the bc2c step, which seems to work fine (however all the process is quite slow: a couple of minutes, but it has 218 OCaml .ml and .mli dependencies, carefully written in the correct topological order).

The generated C file is very large (13 Mb). I guess it includes the bytecode for the all the OCaml files included in the compilation command - and there are a lot!
Just this would mean that the generated .nwa app would probably be way too large for the Numworks? I don't know.

+ /home/lilian/.opam/4.14.1/bin/ocamlclean interpreter.byte -o interpreter.byte
+ /home/lilian/publis/OMicroB_Vertmo.git/bin/bc2c -local -stack-size 16384 -heap-size 16384 -gc SAC -arch 32 interpreter.byte -o interpreter.c
Warning: unknown primitive "caml_ml_open_descriptor_in"(0).                                                                                                                                   
+ /usr/bin/arm-none-eabi-gcc -std=c99 -mthumb -mfloat-abi=hard -mcpu=cortex-m7 -mfpu=fpv5-sp-d16 -I/home/lilian/.npm/_npx/6d991536f923a4f6/node_modules/nwlink/dist/eadk -fno-exceptions -fno-unwind-tables -Os -Wall -ggdb '-DEADK_APP_NAME="OCaml-Interpreter"' -D__NUMWORKS__ -I /home/lilian/publis/OMicroB_Vertmo.git/src/byterun/numworks -o interpreter.arm_o -c interpreter.c

Many errors like:

interpreter.c:325820:23: error: 'caml_obj_tag' undeclared here (not in a function); did you mean 'caml_obj_dup'?                                                                              
325820 |   /* 32 */  (void *) &caml_obj_tag,                                                                                                                                                  
       |                       ^~~~~~~~~~~~                                                                                                                                                   
       |                       caml_obj_dup
interpreter.c:325824:23: error: 'caml_ml_input' undeclared here (not in a function)
325824 |   /* 36 */  (void *) &caml_ml_input,
       |                       ^~~~~~~~~~~~~
interpreter.c:325827:23: error: 'caml_ml_flush' undeclared here (not in a function); did you mean 'caml_mul_float'?
325827 |   /* 39 */  (void *) &caml_ml_flush,
       |                       ^~~~~~~~~~~~~
...

Because all the primitives are not implemented in the C runtime, I guess.

I have to stop working on that now, maybe I'll find time to continue next week. Sorry 😞

@Naereen
Copy link
Author

Naereen commented Nov 20, 2024

And due to the changes I added in numworks.ml and numworks.mli, right now I broke the other examples... SUPER, dit-il avec ironie.

@Naereen
Copy link
Author

Naereen commented Nov 20, 2024

I fixed back the three previously working examples, and continued to tweak on targets/numworks/byterun/prims/bindings.c to try to define a minimalist version of the io.c library which is required in order to define the in_channel/out_channel.

I think right now, my key issue is here: in the C code in bindings.c which tries to do memory allocation for in/out channels.
The rest of the stdlib and compiler libs seems to compile fine, if a stdin, stdout and stderr are correctly defined in numworks.ml(i).
But when running the hello_world/ example, if these in/out channels were defined, I obtain:

  • a segfault (core dumped), when running the hello.byte bytecode version (independent of OMicroB if i understood correctly)
  • a Error: uncaught exception: Out_of_memory, when running the hello.elf simulator version.
  • the segfault for hello.byte can't be debugged using Valgrind: launching valgrind on hello.byte makes it... working completely fine! It seems to use a lot of RAM (the report follows:)
==466452== HEAP SUMMARY:
==466452==     in use at exit: 3,485,029 bytes in 29 blocks
==466452==   total heap usage: 47 allocs, 18 frees, 3,554,622 bytes allocated
...
...
==466452== LEAK SUMMARY:
==466452==    definitely lost: 0 bytes in 0 blocks
==466452==    indirectly lost: 0 bytes in 0 blocks
==466452==      possibly lost: 1,028,152 bytes in 2 blocks
==466452==    still reachable: 2,456,877 bytes in 27 blocks
==466452==         suppressed: 0 bytes in 0 blocks
==466452==
==466452== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 21, 2024

Great ! This is progress ! I've been trying to compile the work you pushed, but I seem to be missing a config.ml in the ocaml example folder; have you pushed it ?

The generated C file is very large (13 Mb). I guess it includes the bytecode for the all the OCaml files included in the compilation command - and there are a lot! Just this would mean that the generated .nwa app would probably be way too large for the Numworks? I don't know.

Indeed, that's a lot. We will try to get that down :)

I think right now, my key issue is here: in the C code in bindings.c which tries to do memory allocation for in/out channels. The rest of the stdlib and compiler libs seems to compile fine, if a stdin, stdout and stderr are correctly defined in numworks.ml(i). But when running the hello_world/ example, if these in/out channels were defined, I obtain:

* a segfault (core dumped), when running the `hello.byte` bytecode version (independent of OMicroB if i understood correctly)

* a `Error: uncaught exception: Out_of_memory`, when running the `hello.elf` simulator version.

* the segfault for `hello.byte` can't be debugged using Valgrind: launching `valgrind` on `hello.byte` makes it... working completely fine! It seems to use a lot of RAM (the report follows:)

These errors are a bit weird I admit. I cannot reproduce them right now on your branch, because I think you commented the allocator calls ? I will try to get a look at them shortly :)

@Vertmo Vertmo closed this as completed Nov 21, 2024
@Vertmo Vertmo reopened this Nov 21, 2024
@Naereen
Copy link
Author

Naereen commented Nov 21, 2024

Indeed I forgot about the config.ml file, I've made a commit with it. I didn't see quickly enough that you pushed to this numworks branch, so I'll have to git pull merge. I'm not skilled in this peculiar git command, I rarely collaborate with others like in this project, sorry!

@Naereen
Copy link
Author

Naereen commented Nov 21, 2024

I manage as well to compile the interpreter.byte bytecode executable, but it fails with this error:

$ ./interpreter.byte 
./interpreter.byte: symbol lookup error: ./interpreter.byte: undefined symbol: caml_static_alloc

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 21, 2024

Yes, I ran in the same issue at first.
For some weird reason, the compiler does some static allocation. I've replaced it by a dynamic allocation in the commit I just pushed. I made some other changes, which allow the interpreter to execute... with an exception, because it's not loading the stdlib. I'm working on this :)

@Naereen
Copy link
Author

Naereen commented Nov 21, 2024

Awesome! I won't have time before Sunday so I'll just read the commits you upload and the comments here. Thanks!

@Vertmo
Copy link
Collaborator

Vertmo commented Nov 25, 2024

Ok, let's do a little recap of the progress/what's left to do:

First, @bvaugon has been really helpful with the M&C GC bug: he found it, resolved it, and even found another unrelated bug. I merged his work on our branch.

Second, on the ocaml-interpreter front: I've made some progress in eliminating unsupported C-functions in the Stdlib/Compiler Libs. Unfortunately there are at least two functions left that I fear will be an issue:

The first is input_value (currently located in numworks.ml). As far as I can tell, it is used to read compiled OCaml file (cmis, cmos) and rebuild OCaml values. It is first used to read Stdlib cmis for type-checking the input program, but it will probably also be used to read the corresponding cmos for the linking pass.
The difficulty in rewriting this function is that OCaml values encoded in files by the OCaml compiler must be translated into OMicroB values to be interpreted by the OMicroB interpreter. These two representation of values are not identical, which will cause mismatches (and probably segfaults). We would therefore need to write a translation from OCaml value representation into OMicroB value representation.

The second is Meta.reify_bytecode. From what I understand it takes a bit of bytecode as an input, and essentially runs the OCaml runtime on it : this is the core of the interpretation ! I can see a few ways to adapt this part.
A first solution would be to adapt the C function to run the OMicroB interpreter instead; I've already written a system of callbacks from C to OCaml bytecode to do so. The issue is that OMicroB is hard-wired to run bytecode translated by bc2c and stored in the flash; I'm not sure how we could get past this issue without a lot of re-writing.
A second solution would be to write an interpreter at a higher level: I'm thinking of the Lambda intermediate language, which the syntax of is quite simple. This would replace the whole "backend" of the current interpreter. The problem would then be the handling of already-compiled object files (cmos) such as the one of the stdlib. Also, these functions may themselves call C code, which we would need to connect with the OMicroB C runtime (this last point should be easy but time-consuming).

Ok, sorry for the long text, but as you can see these are somewhat critical issues which we need to think about carefully before we continue. Let me know what you think :)

@Naereen
Copy link
Author

Naereen commented Nov 26, 2024

Hi, thanks for the recap, and congratulations to @bvaugon for fixing these two bugs in the GC!
Sorry but I'm busy with work, I'll reply as soon as I have some more time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

2 participants