7 releases (1 stable)

1.0.6 Jun 27, 2020
1.0.6-alpha5 Sep 21, 2020
1.0.6-alpha3 Sep 20, 2020
1.0.6-alpha2 Jul 1, 2020

#96 in Simulation


42K SLoC

Rust 36K SLoC // 0.0% comments C++ 5.5K SLoC // 0.0% comments JavaScript 1K SLoC // 0.1% comments Assembly 376 SLoC // 0.1% comments Vue 75 SLoC // 0.0% comments Python 51 SLoC // 0.3% comments


Build Status License: Apache 2.0 crates.io API Docs

Rust bindings for LC3Tools.


Add this to your Cargo.toml:

lc3tools-sys = "1.0.6"

Since the bindings this crate exposes are exactly one to one with the LC3Tools API, the LC3Tools source code and documentation are the best place to go for information about how to use this crate, especially the API documentation.


Headers are exposed at the path the DEP_LC3CORE_LINKS env var points to.


However, note that LC3Tools exposes a C++ API. Though bindings for it are provided in this crate, it's extremely unlikely they will work with your OS/platform/compiler/compiler flags. Different platforms seem to have different name-mangling conventions and layout isn't (afaik) stable across different configurations (switching from -O3 to -O0 breaks the C++ interface example with my configuration, for example).

All of this (mangled symbols, layout information) is encoded in the generated bindings. Note that because linkers are often lazy, even though the symbols in the generated bindings don't match those that are actually produced on your platform, you may not get a compile error unless/until you actually go to use (transitively) those symbols in your final binary. This is why the C++ interface example is feature gated.

We offer a generate-fresh feature so that you can generate this file locally at build time, but it still remains unlikely that the C++ interface will work/be of use. Things like vtables are represented by opaque types and even if you manage to get a hold of a C++ generated vtable to pass along sometimes things still don't work.

For example, for reasons still unknown, when running the C++ interface example, the printer that's passed to the simulator mysteriously turns into a NULL but only in the copy of the logger that's given to the state instance; the copy that's in the logger remains unchanged. I was only able to get the example to work after making the following changes to LC3Tools:

Click to show the diff.
diff --git a/backend/logger.h b/backend/logger.h
index b7146ac..c172acb 100644
--- a/backend/logger.h
+++ b/backend/logger.h
@@ -28,10 +28,17 @@ namespace utils
       template<typename ... Args>
       void printf(PrintType level, bool bold, std::string const & format, Args ... args) const;
       void newline(PrintType level = PrintType::P_ERROR) const {
-            if(static_cast<uint32_t>(level) <= print_level) { printer.newline(); }
       void print(std::string const & str) {
-            if(print_level > static_cast<uint32_t>(PrintType::P_NONE)) { printer.print(str); }
       uint32_t getPrintLevel(void) const { return print_level; }
       void setPrintLevel(uint32_t print_level) { this->print_level = print_level; }
diff --git a/backend/simulator.cpp b/backend/simulator.cpp
index c8004e8..bd7f1db 100644
--- a/backend/simulator.cpp
+++ b/backend/simulator.cpp
@@ -112,7 +112,7 @@ void Simulator::simulate(void)
       collecting_input = true;
-        inputter.beginInput();
       if(threaded_input) {
           input_thread = std::thread(&core::Simulator::inputThread, this);
@@ -125,7 +125,7 @@ void Simulator::simulate(void)
           if(! threaded_input) {
-                collectInput();
@@ -139,7 +139,7 @@ void Simulator::simulate(void)
   if(threaded_input && input_thread.joinable()) {
-    inputter.endInput();


To make this crate at least somewhat usable, we offer a limited set of C bindings that are only really good for running whole programs.

This is incredibly clunky but it was good enough™ for our use case. If actual Rust bindings for LC3Tools are a thing you need, cxx is probably worth looking into. Since this crate exports the LC3Tools headers you could depend on this crate and use it for it's cc setup (ignoring the bindings it has).

Alternatively, if there are specific additions to the C bindings you need, PRs are very welcome!


LC3Tools functionality features

The backend part of LC3Tools is always included. The frontend feature includes the files in frontend/common. The grader feature (which requires the frontend feature) includes the files in frontend/grader but strips out the main function in framework.cpp.

These features are both enabled by default.

Other features


By default, Rust bindings for LC3Tools aren't generated anew when building this crate. Instead we bundle pre-generated bindings and use them, by default. We do this because generating the bindings is a little time consuming (takes about a minute — unless you care deeply about how long clean builds take, this is a non-issue), but more importantly because generating the bindings is somewhat system-specific. bindgen walks through the system libc and C++ standard library headers as part of doing so and we maintain a list of types and things for it to skip that's very libc/system/OS specific.

You'll probably never need to, but if you find yourself wanting to generate these bindings yourself (i.e. because you modified some headers in LC3Tools), then you can build with the generate-fresh feature (build.rs goes and passes the right instructions to cargo so you can just leave the feature enabled — it'll only actually do the work when one of the headers/files in the build graph change).


Unfortunately, we can't enable LTO by default as it requires some setup and somewhat specific tools to be used (see this commit and this commit for some context and this page for details about what setup is needed).

So, we offer an lto feature that passes the compiler cc invokes the necessary flag. When using this feature you'll also need to make sure that you pass rustc the LTO linker plugin flag and instruct it to use an appropriate linker, as described here. For this specific crate the necessary flags exist here, but commented out.

The final thing you need to do when using the lto feature is to make sure that the compiler that cc ends up using will work with the LTO linker plugin. The table here offers some information on what version should work, but it's somewhat out of date; if the same version of clang is used by cc to build LC3Tools and by rustc to do the linking, things should work (provided it's a relatively modern version of clang — CI in this repo uses version 9, successfully).

Actually figuring out and changing which compiler cc uses is tricker; on Linux based systems ensuring that the c++ alias points to the desired version seems to do the trick (update-alternatives might be able to help you with this).


Right now we have one example that runs an LC-3 program that multiplies two unsigned numbers. As mentioned, it has a C++ interface part and a C interface part. By default the C++ part is disabled as it's unlikely it will work on your machine.

cargo run --example mul should run the C interface part.

Minimum Supported Rust Version (MSRV)

This crate is currently guaranteed to compile on stable Rust 1.43 and newer. We offer no guarantees that this will remain true in future releases but do promise to always support (at minimum) the latest stable Rust version and to document changes to the MSRV in the changelog.


PRs are (very) welcome! See CONTRIBUTING.md for details.