In den meisten Sprachen müssen Sie die gesamte Bibliothek von Grund auf neu schreiben, und die ersten Ergebnisse werden gegen Ende des Projekts angezeigt. Diese Ports sind in der Regel recht teuer und fehleranfällig und fallen häufig zur Hälfte aus. Joel Spolsky erklärt dies viel besser als ich in einem Artikel darüber, warum eine vollständige Überarbeitung eine schlechte Idee ist .
Allerdings hat Rust eine Killer-Wendung, wenn es um solche Dinge geht. Es kann C-Code ohne Overhead aufrufen (d. H. Die P / Invoke- Umgebung in C #), und es werden Funktionen verfügbar gemacht, die wie jede andere C-Funktion in C verwendet werden können. Dies öffnet die Tür zu einem alternativen Ansatz:
Portbibliotheken, um jeweils eine Funktion zu rosten.
Hinweis
Der Code in diesem Artikel ist auf GitHub verfügbar . Schauen Sie doch einfach mal vorbei, um sich Code oder Inspiration auszuleihen.
Wenn Sie den Artikel hilfreich fanden oder einen Fehler bemerkten, lassen Sie es mich bitte im Blog Bug Tracker wissen !
Anfangen
Bevor Sie etwas tun können, müssen Sie ein neues Projekt erstellen. Ich habe eine Vorlage , die CI und Lizenzen für die Frachtgenerierung installiert .
$ cargo generate --git https://github.com/Michael-F-Bryan/github-template --name tinyvm-rs
$ cd tinyvm-rs && tree
tree -I 'vendor|target'
.
├── Cargo.toml
├── LICENSE_APACHE.md
├── LICENSE_MIT.md
├── README.md
├── .travis.yml
└── src
└── lib.rs
1 directory, 6 files
Unsere erste echte Herausforderung besteht darin, die Bibliothek, die wir portieren möchten, zu erstellen und ein wenig herauszufinden.
In diesem Fall portieren wir jakogut / tinyvm ,
TinyVM ist eine kleine, schnelle und leichte virtuelle Maschine, die in reinem ANSI C geschrieben ist.
Um es in Zukunft einfacher zu machen, darauf zu verweisen, fügen wir das Repository als Submodul zu unserem Projekt hinzu.
$ git submodule add https://github.com/jakogut/tinyvm vendor/tinyvm
Schauen wir uns nun den Quellcode an. Für den Anfang
README.md
die Montageanleitung.
TinyVM ist die kleinste virtuelle Maschine. Geringe Speicherauslastung, wenig Code und wenig Binärcode.(Hervorhebung hinzugefügt)
Der Build erfolgt auf UNIX-ähnlichen Systemen mit make und GCC.
Keine externen Abhängigkeiten, behalten Sie die Standard-C-Bibliothek bei.
Builds werden mit make oder make restore erstellt.
Um eine Debug-Version zu erstellen, fügen Sie nach make DEBUG = yes hinzu. Um eine Binärdatei mit aktivierter Profilerstellung zu erstellen, fügen Sie nach make PROFILE = yes hinzu.
Sie erreichen mich unter joseph.kogut (at) gmail.com
Okay, schauen wir uns das Verzeichnis an
tinyvm
und sehen, ob der Build einfach funktioniert .
$ cd vendor/tinyvm
$ make
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_program.c -o libtvm/tvm_program.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_lexer.c -o libtvm/tvm_lexer.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm.c -o libtvm/tvm.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_htab.c -o libtvm/tvm_htab.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_memory.c -o libtvm/tvm_memory.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_preprocessor.c -o libtvm/tvm_preprocessor.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_parser.c -o libtvm/tvm_parser.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_file.c -o libtvm/tvm_file.o
ar rcs lib/libtvm.a libtvm/tvm_program.o libtvm/tvm_lexer.o libtvm/tvm.o libtvm/tvm_htab.o libtvm/tvm_memory.o libtvm/tvm_preprocessor.o libtvm/tvm_parser.o libtvm/tvm_file.o
clang src/tvmi.c -ltvm -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -Llib/ -o bin/tvmi
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c tdb/main.c -o tdb/main.o
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c tdb/tdb.c -o tdb/tdb.o
clang tdb/main.o tdb/tdb.o -ltvm -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -Llib/ -o bin/tdb
Ich mag es wirklich, wenn C-Bibliotheken sofort kompiliert werden, ohne zufällige Pakete installieren
*-dev
oder mit dem Build-System herumspielen zu müssen.
Leider enthält die Bibliothek keine Tests, sodass wir nicht (sofort) sicher sein können, dass einzelne Funktionen korrekt übersetzt wurden. Sie enthält jedoch einen Beispielinterpreter, mit dem wir Funktionen auf hoher Ebene untersuchen können.
Somit wissen wir, dass wir es ohne großen Aufwand über die Befehlszeile erstellen können. Jetzt müssen wir sicherstellen, dass unsere Kiste
tinyvm
alles programmgesteuert zusammenbauen kann.
Hier kommen Build-Skripte ins Spiel. Unsere Strategie besteht darin, dass die Rust-Kiste das Build-Skript
build.rs
und die Kiste verwendet cc
, um äquivalente Befehle für unseren Aufruf aufzurufenmake
... Von dort aus können wir libtvm
wie bei jeder anderen nativen Bibliothek von Rust aus eine Verbindung herstellen .
Sie müssen eine Kiste
cc
als Abhängigkeit hinzufügen .
$ cargo add --build cc
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding cc v1.0.47 to build-dependencies
Stellen Sie außerdem sicher
build.rs
, dass Sie aus dem Quellcode kompilieren libtvm
.
// build.rs
use cc::Build;
use std::path::Path;
fn main() {
let tinyvm = Path::new("vendor/tinyvm");
let include = tinyvm.join("include");
let src = tinyvm.join("libtvm");
Build::new()
.warnings(false)
.file(src.join("tvm_file.c"))
.file(src.join("tvm_htab.c"))
.file(src.join("tvm_lexer.c"))
.file(src.join("tvm_memory.c"))
.file(src.join("tvm_parser.c"))
.file(src.join("tvm_preprocessor.c"))
.file(src.join("tvm_program.c"))
.file(src.join("tvm.c"))
.include(&include)
.compile("tvm");
}
Hinweis
Wenn Sie sich die Kistendokumentation angesehen habencc
, ist Ihnen möglicherweise eine Methode aufgefallenBuild::files()
, die einen Iterator von Pfaden akzeptiert. Wir könnten programmatisch entdecken Sie alle Dateien*.c
innerhalbvendor/tinyvm/libtvm
, aber da wir den Code einer Funktion zu einem Zeitpunkt portieren, es ist viel einfacher, einzelne Anrufe zu entfernen ,.file()
da wir Port.
Wir brauchen auch eine Möglichkeit, Rust mitzuteilen, von welchen Funktionen es aufgerufen werden kann
libtvm
. Dies geschieht normalerweise durch Schreiben der Definitionen für jede Funktion in einen externen Block . Glücklicherweise gibt es jedoch ein Tool namens bindgen , das eine Header-Datei im C-Stil lesen und Definitionen für uns generieren kann.
Lassen Sie uns Bindungen aus generieren
vendor/tinyvm/include/tvm/tvm.h
.
$ cargo install bindgen
$ bindgen vendor/tinyvm/include/tvm/tvm.h -o src/ffi.rs
$ wc --lines src/ffi.rs
992 src/ffi.rs
Sie müssen unserer Kiste ein Modul hinzufügen
ffi
.
// src/lib.rs
#[allow(non_camel_case_types, non_snake_case)]
pub mod ffi;
Wenn wir uns das Verzeichnis
src/
in tinyvm
ansehen, finden wir den Quellcode für den Interpreter tinyvm
.
// vendor/tinyvm/src/tvmi.c
#include <stdlib.h>
#include <stdio.h>
#include <tvm/tvm.h>
int main(int argc, char **argv)
{
struct tvm_ctx *vm = tvm_vm_create();
if (vm != NULL && tvm_vm_interpret(vm, argv[1]) == 0)
tvm_vm_run(vm);
tvm_vm_destroy(vm);
return 0;
}
Es ist unglaublich einfach. Das ist sehr schön, wenn man bedenkt, dass wir diesen Interpreter als eines unserer Beispiele verwenden werden.
Lassen Sie es uns zunächst direkt in Rust übersetzen und in das Verzeichnis stellen
examples/
.
// examples/tvmi.rs
use std::{env, ffi::CString};
use tinyvm::ffi;
fn main() {
let filename = CString::new(env::args().nth(1).unwrap()).unwrap();
// cast away the `const` because that's what libtvm expects
let filename = filename.as_ptr() as *mut _;
unsafe {
let vm = ffi::tvm_vm_create();
if !vm.is_null() && ffi::tvm_vm_interpret(vm, filename) == 0 {
ffi::tvm_vm_run(vm);
}
ffi::tvm_vm_destroy(vm);
}
}
Zur Kontrolle können wir auch die virtuelle Maschine starten und sicherstellen, dass alles funktioniert.
$ cargo run --example tvmi -- vendor/tinyvm/programs/tinyvm/fact.vm
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm`
1
2
6
24
120
720
5040
40320
362880
3628800
Klasse!
Niedrig hängende Früchte
Wenn Sie mit so etwas beginnen, ist es verlockend, in die wichtigsten Funktionen einzutauchen und sie zuerst zu migrieren. Versuchen Sie, diesem Drang zu widerstehen. Sie können leicht mehr abbeißen, als Sie kauen können, und am Ende entweder Zeit verschwenden oder Sie demoralisieren und Sie dazu bringen, aufzugeben.
Suchen wir stattdessen nach dem einfachsten.
$ ls libtvm
tvm.c tvm_file.c tvm_htab.c tvm_lexer.c tvm_memory.c tvm_parser.c
tvm_preprocessor.c tvm_program.c
Diese Datei
tvm_htab.
sieht vielversprechend aus. Ich bin mir ziemlich sicher, dass es htab
für "Hash-Tabelle" steht und die Rust-Standardbibliothek bereits eine qualitativ hochwertige Implementierung enthält. Wir sollten dies leicht genug ändern können.
Werfen wir einen Blick auf die Header-Datei
tvm_htab.h
und überprüfen, womit wir es zu tun haben.
// vendor/tinyvm/include/tvm/tvm_htab.h
#ifndef TVM_HTAB_H_
#define TVM_HTAB_H_
#define KEY_LENGTH 64
#define HTAB_SIZE 4096
struct tvm_htab_node {
char *key;
int value;
void *valptr;
struct tvm_htab_node *next;
};
struct tvm_htab_ctx {
unsigned int num_nodes;
unsigned int size;
struct tvm_htab_node **nodes;
};
struct tvm_htab_ctx *tvm_htab_create();
void tvm_htab_destroy(struct tvm_htab_ctx *htab);
int tvm_htab_add(struct tvm_htab_ctx *htab, const char *key, int value);
int tvm_htab_add_ref(struct tvm_htab_ctx *htab,
const char *key, const void *valptr, int len);
int tvm_htab_find(struct tvm_htab_ctx *htab, const char *key);
char *tvm_htab_find_ref(struct tvm_htab_ctx *htab, const char *key);
#endif
Sieht einfach zu implementieren aus. Das einzige Problem ist, dass die Definition
tvm_htab_ctx
und tvm_htab_node
in der Header-Datei enthalten sind, was bedeutet, dass ein Teil des Codes direkt auf die Interna der Hash-Tabelle zugreifen kann und nicht über die veröffentlichte Schnittstelle geht.
Wir können überprüfen, ob irgendetwas Zugriff auf die Interna der Hash-Tabelle hat, indem wir die Strukturdefinitionen vorübergehend in verschieben
tvm_htab.c
und prüfen, ob noch alles kompiliert ist.
diff --git a/include/tvm/tvm_htab.h b/include/tvm/tvm_htab.h
index 9feb7a9..e7346b7 100644
--- a/include/tvm/tvm_htab.h
+++ b/include/tvm/tvm_htab.h
@@ -4,18 +4,8 @@
#define KEY_LENGTH 64
#define HTAB_SIZE 4096
-struct tvm_htab_node {
- char *key;
- int value;
- void *valptr;
- struct tvm_htab_node *next;
-};
-
-struct tvm_htab_ctx {
- unsigned int num_nodes;
- unsigned int size;
- struct tvm_htab_node **nodes;
-};
+struct tvm_htab_node;
+struct tvm_htab_ctx;
struct tvm_htab_ctx *tvm_htab_create();
void tvm_htab_destroy(struct tvm_htab_ctx *htab);
Und dann nochmal laufen
make
:
$ make
make
clang -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -c libtvm/tvm_htab.c -o libtvm/tvm_htab.o
ar rcs lib/libtvm.a libtvm/tvm_program.o libtvm/tvm_lexer.o libtvm/tvm.o libtvm/tvm_htab.o libtvm/tvm_memory.o libtvm/tvm_preprocessor.o libtvm/tvm_parser.o libtvm/tvm_file.o
clang src/tvmi.c -ltvm -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -Llib/ -o bin/tvmi
clang tdb/main.o tdb/tdb.o -ltvm -Wall -pipe -Iinclude/ -std=gnu11 -Werror -pedantic -pedantic-errors -O3 -Llib/ -o bin/tdb
Es sieht so aus, als ob alles noch funktioniert. Jetzt beginnen wir die zweite Phase. Wir erstellen einen identischen Satz von Funktionen, die unter der Haube verwendet werden
HashMap<K, V>
.
Wenn wir uns auf einen Stummel mit dem Minimum beschränken, erhalten wir:
// src/htab.rs
use std::{
collections::HashMap,
ffi::CString,
os::raw::{c_char, c_int, c_void},
};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct HashTable(pub(crate) HashMap<CString, Item>);
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Item {
// not sure what to put here yet
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_create() -> *mut HashTable {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_destroy(htab: *mut HashTable) {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_add(
htab: *mut HashTable,
key: *const c_char,
value: c_int,
) -> c_int {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_add_ref(
htab: *mut HashTable,
key: *const c_char,
value_ptr: *mut c_void,
length: c_int,
) -> c_int {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_find(
htab: *mut HashTable,
key: *const c_char,
) -> c_int {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_find_ref(
htab: *mut HashTable,
key: *const c_char,
) -> *mut c_char {
unimplemented!()
}
Sie müssen das Modul auch deklarieren
htab
und seine Funktionen erneut exportieren lib.rs
.
// src/lib.rs
mod htab;
pub use htab::*;
Jetzt müssen wir sicherstellen, dass das Original
tvm_htab.c
nicht kompiliert oder mit der endgültigen Bibliothek verknüpft wird, da uns sonst der Linker mit einer Wand aus doppelten Symbolfehlern begegnet.
Fehlerwand, die Symbole wiederholt
error: linking with `/usr/bin/clang` failed: exit code: 1
|
= note: "/usr/bin/clang" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.17q5thi94e1eoj5i.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.19e8sqirbm56nu8g.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1g6ljku8dwzpfvhi.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1h5e5mxmiptpb7iz.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1herotdop66zv9ot.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1qbfxpvgd885u6o.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.21psdg8ni4vgdrzk.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2albhpxlxxvc0ccu.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2btm2dc9rhjhhna1.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2kct5ftnkrqqr0mf.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2lwgg3uosup4mkh0.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.2xduj46e9sw5vuan.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.35h8y7f23ua1qnz0.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.3cgfdtku63ltd8oc.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.3ot768hzkzzy7r76.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.3u2xnetcch8f2o02.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.4ldrdjvfzk58myrv.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.4omnum6bdjqsrq8b.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.4s8ch4ccmewulj22.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.4syl3x2rb8328h8x.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.532awiysf0h9r50f.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.dfjs079cp9si4o5.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.qxp6yb2gjpj0v6n.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.xz7ld20yvprst1r.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.z35ukhvchmmby1c.rcgu.o" "-o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.1d7wvlwdjap8p3g4.rcgu.o" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro" "-Wl,-znow" "-nodefaultlibs" "-L" "/home/michael/Documents/tinyvm-rs/target/debug/deps" "-L" "/home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out" "-L" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "-Wl,--whole-archive" "-ltvm" "-Wl,--no-whole-archive" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libtest-a39a3e9a77b17f55.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libterm-97a69cd310ff0925.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgetopts-66a42b1d94e3e6f9.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunicode_width-dd7761d848144e0d.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_std-f722acdb78755ba0.rlib" "-Wl,--start-group" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-974c3c08f6def4b3.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-eb49676f33a2c8a6.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-7ae0446feecc60f2.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-2de299b65d7f5721.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace-64514775bc06309a.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace_sys-1ed8aa185c63b9a5.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-a839df87f563fba5.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-8e726bdc2018d836.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-5285f42cbadf207d.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-b0362d20f8aa58fa.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-f3dd7051708453a4.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-83744846c43307ce.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-d5565a3a0f4cfe21.rlib" "-Wl,--end-group" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-ea790e85415e3bbf.rlib" "-Wl,-Bdynamic" "-ldl" "-lrt" "-lpthread" "-lgcc_s" "-lc" "-lm" "-lrt" "-lpthread" "-lutil" "-lutil" "-fuse-ld=lld"
= note: ld.lld: error: duplicate symbol: tvm_htab_create
>>> defined at htab.rs:14 (src/htab.rs:14)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_create)
>>> defined at tvm_htab.c:23 (vendor/tinyvm/libtvm/tvm_htab.c:23)
>>> tvm_htab.o:(.text.tvm_htab_create+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_destroy
>>> defined at htab.rs:17 (src/htab.rs:17)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_destroy)
>>> defined at tvm_htab.c:35 (vendor/tinyvm/libtvm/tvm_htab.c:35)
>>> tvm_htab.o:(.text.tvm_htab_destroy+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_add_ref
>>> defined at htab.rs:29 (src/htab.rs:29)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_add_ref)
>>> defined at tvm_htab.c:160 (vendor/tinyvm/libtvm/tvm_htab.c:160)
>>> tvm_htab.o:(.text.tvm_htab_add_ref+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_add
>>> defined at htab.rs:20 (src/htab.rs:20)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_add)
>>> defined at tvm_htab.c:147 (vendor/tinyvm/libtvm/tvm_htab.c:147)
>>> tvm_htab.o:(.text.tvm_htab_add+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_find
>>> defined at htab.rs:39 (src/htab.rs:39)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_find)
>>> defined at tvm_htab.c:189 (vendor/tinyvm/libtvm/tvm_htab.c:189)
>>> tvm_htab.o:(.text.tvm_htab_find+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
ld.lld: error: duplicate symbol: tvm_htab_find_ref
>>> defined at htab.rs:47 (src/htab.rs:47)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-599d57f523fdb1a4.5b2qwmmtc5pvnbh.rcgu.o:(tvm_htab_find_ref)
>>> defined at tvm_htab.c:199 (vendor/tinyvm/libtvm/tvm_htab.c:199)
>>> tvm_htab.o:(.text.tvm_htab_find_ref+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: aborting due to previous error
error: could not compile `tinyvm`.
Das Update ist eigentlich ziemlich einfach.
diff --git a/build.rs b/build.rs
index 6f274c8..af9d467 100644
--- a/build.rs
+++ b/build.rs
@@ -9,7 +9,6 @@ fn main() {
Build::new()
.warnings(false)
.file(src.join("tvm_file.c"))
- .file(src.join("tvm_htab.c"))
.file(src.join("tvm_lexer.c"))
.file(src.join("tvm_memory.c"))
.file(src.join("tvm_parser.c"))
Der Versuch, das Beispiel
tvmi
erneut auszuführen , stürzt ab, wie Sie es von einem vollständigen Programm erwarten würden unimplemented!()
.
$ cargo run --example tvmi -- vendor/tinyvm/programs/tinyvm/fact.vm
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm`
thread 'main' panicked at 'not yet implemented', src/htab.rs:14:57
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Wenn Sie FFI-Unterstützung für einen neuen Typ hinzufügen, beginnen Sie am einfachsten mit dem Konstruktor und dem Destruktor.
Info
C-Code kann nur über einen Zeiger auf unsere Hash-Tabelle zugreifen. Daher müssen wir einen davon auf dem Heap zuweisen und dann den Eigentümer dieses Heap-zugewiesenen Objekts auf den Aufrufer übertragen.
// src/htab.rs
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_create() -> *mut HashTable {
let hashtable = Box::new(HashTable::default());
Box::into_raw(hashtable)
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_destroy(htab: *mut HashTable) {
if htab.is_null() {
// nothing to free
return;
}
let hashtable = Box::from_raw(htab);
// explicitly destroy the hashtable
drop(hashtable);
}
Warnung
Es ist wichtig, dass AnruferHashTable
nur mit der Funktion tötentvm_htab_destroy ()
!
Wenn sie dies nicht tun und stattdessen versuchen,free()
direkt anzurufen , werden wir mit ziemlicher Sicherheit eine schlechte Situation haben. Im bestenBox
Fall führt dies zu einem großen Speicherverlust, aber es ist auch möglich, dass unser in Rust nicht das gleiche Bündel verwendet wiemalloc()
undfree ()
, was bedeutet, dass die Freigabe des Rust-Objekts von C den Stapel beschädigen und ihn in einem zerbrochenen Zustand belassen kann.
Das Hinzufügen von Elementen zu einer Hashmap ist fast genauso einfach zu implementieren.
// src/hmap.rs
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Item {
/// An integer value.
value: c_int,
/// An opaque value used with [`tvm_htab_add_ref()`].
///
/// # Safety
///
/// Storing the contents of a `void *` in a `Vec<u8>` *would* normally
/// result in alignment issues, but we've got access to the `libtvm` source
/// code and know it will only ever store `char *` strings.
opaque_value: Vec<u8>,
}
impl Item {
pub(crate) fn integer(value: c_int) -> Item {
Item {
value,
opaque_value: Vec::new(),
}
}
pub(crate) fn opaque<V>(opaque_value: V) -> Item
where
V: Into<Vec<u8>>,
{
Item {
value: 0,
opaque_value: opaque_value.into(),
}
}
pub(crate) fn from_void(pointer: *mut c_void, length: c_int) -> Item {
// we need to create an owned copy of the value
let opaque_value = if pointer.is_null() {
Vec::new()
} else {
unsafe {
std::slice::from_raw_parts(pointer as *mut u8, length as usize)
.to_owned()
}
};
Item::opaque(opaque_value)
}
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_add(
htab: *mut HashTable,
key: *const c_char,
value: c_int,
) -> c_int {
let hashtable = &mut *htab;
let key = CStr::from_ptr(key).to_owned();
hashtable.0.insert(key, Item::integer(value));
// the only time insertion can fail is if allocation fails. In that case
// we'll abort the process anyway, so if this function returns we can
// assume it was successful (0 = success).
0
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_add_ref(
htab: *mut HashTable,
key: *const c_char,
value_ptr: *mut c_void,
length: c_int,
) -> c_int {
let hashtable = &mut *htab;
let key = CStr::from_ptr(key).to_owned();
hashtable.0.insert(key, Item::from_void(value_ptr, length));
0
}
,CString
,String
, -,*const c_char
,String
Rust , UTF-8.
,CStr
&str
,String
, ASCII, ,unwrap()
,CString
.
Zwei Funktionen
*_find()
können direkt an die interne delegiert werden HashMap<CString, Item>
.
Der einzige Ort, an dem Sie vorsichtig sein müssen, ist sicherzustellen, dass der richtige Wert zurückgegeben wird, wenn das Element nicht gefunden werden kann. In diesem Fall können
tvm_htab.c
wir beim Betrachten sehen, was tvm_htab_find()
zurückkehrt −1
und tvm_htab_find_ref()
zurückkehrt NULL
.
// src/hmap.rs
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_find(
htab: *mut HashTable,
key: *const c_char,
) -> c_int {
let hashtable = &mut *htab;
let key = CStr::from_ptr(key);
match hashtable.get(key) {
Some(item) => item.value,
None => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn tvm_htab_find_ref(
htab: *mut HashTable,
key: *const c_char,
) -> *mut c_char {
let hashtable = &mut *htab;
let key = CStr::from_ptr(key);
match hashtable.0.get(key) {
Some(item) => item.value_ptr as *mut c_char,
None => ptr::null_mut(),
}
}
Nachdem wir die Stub-Funktionalität tatsächlich implementiert haben, sollte alles wieder funktionieren.
Der einfachste Weg, dies zu testen, besteht darin, unser Beispiel auszuführen.
cargo run --example tvmi -- vendor/tinyvm/programs/tinyvm/fact.vm
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm`
1
2
6
24
120
720
5040
40320
362880
3628800
Und um es noch einmal zu überprüfen, können wir es durchlaufen
valgrind
, um sicherzustellen, dass keine Speicherlecks oder Probleme mit Zeigern auftreten.
$ valgrind target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm
==1492== Memcheck, a memory error detector
==1492== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1492== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==1492== Command: target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm
==1492==
1
2
6
24
120
720
5040
40320
362880
3628800
==1492==
==1492== HEAP SUMMARY:
==1492== in use at exit: 0 bytes in 0 blocks
==1492== total heap usage: 270 allocs, 270 frees, 67,129,392 bytes allocated
==1492==
==1492== All heap blocks were freed -- no leaks are possible
==1492==
==1492== For lists of detected and suppressed errors, rerun with: -s
==1492== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Erfolg!
Implementierung der Datenvorverarbeitung
Die virtuelle Maschine
tinyvm
verwendet eine vereinfachte Form des Assemblers , ähnlich dem herkömmlichen Intel x86-Assembler. Der erste Schritt beim Parsen des Assemblers von tinyvm besteht darin, einen Präprozessor auszuführen, der Anweisungen %include filename
und Anweisungen interpretiert %define identifier value
.
Diese Art der Textmanipulation ist mit Typen
&str
in Rust viel einfacher. Schauen wir uns also die Benutzeroberfläche an, die unsere Kiste implementieren sollte.
// vendor/tinyvm/include/tvm/tvm_preprocessor.h
#ifndef TVM_PREPROCESSOR_H_
#define TVM_PREPROCESSOR_H_
#include "tvm_htab.h"
int tvm_preprocess(char **src, int *src_len, struct tvm_htab_ctx *defines);
#endif
Unter Verwendung
char **
sowohl int *
für Variablen src
und src_len
kann ein wenig seltsam auf den ersten Blick, aber wenn man das Äquivalent in Rust schrieb, würden Sie etwas am Ende wie folgt:
fn tvm_preprocess(
src: String,
defines: &mut HashTable,
) -> Result<String, PreprocessorError> {
...
}
Der C-Code verwendet nur die Ausgabeparameter, um die vorhandene Zeichenfolge zu ersetzen
src
, da weder eine neue Zeile noch ein Fehlercode zurückgegeben werden können.
Bevor Sie etwas anderes tun, müssen Sie einen Test für schreiben
tvm_preprocess()
. Auf diese Weise können wir sicherstellen, dass unsere Rostfunktion dem Original funktional entspricht.
Wir interagieren mit dem Dateisystem, daher müssen wir die Tempfile-Kiste herausziehen .
$ cargo add --dev tempfile
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding tempfile v3.1.0 to dev-dependencies
Wir brauchen auch eine Kiste
libc
, weil wir Linien passieren werden libtvm
, die sie möglicherweise befreien muss.
cargo add libc
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding libc v0.2.66 to dev-dependencies
Wenn wir uns den Quellcode ansehen, können wir sehen, dass die Funktion
tvm_preprocess()
weiterhin zulässig ist %include
und %define
solange dies nicht der Fall ist.
Lassen Sie uns zunächst einen Test erstellen, um sicherzustellen, dass der Präprozessor funktioniert
%define
. Wir wissen, dass dieser Code bereits funktioniert (dies ist schließlich Code von tinyvm
), daher sollte es keine Überraschungen geben.
// src/preprocessing.rs
#[cfg(test)]
mod tests {
use crate::ffi;
use std::{
ffi::{CStr, CString},
io::Write,
os::raw::c_int,
};
#[test]
fn find_all_defines() {
let src = "%define true 1\nsome random text\n%define FOO_BAR -42\n";
let original_length = src.len();
let src = CString::new(src).unwrap();
unsafe {
// get a copy of `src` that was allocated using C's malloc
let mut src = libc::strdup(src.as_ptr());
let mut len = original_length as c_int;
let defines = ffi::tvm_htab_create();
let ret = ffi::tvm_preprocess(&mut src, &mut len, defines);
// preprocessing should have been successful
assert_eq!(ret, 0);
// make sure the define lines were removed
let preprocessed = CStr::from_ptr(src).to_bytes();
let preprocessed =
std::str::from_utf8(&preprocessed[..len as usize]).unwrap();
assert_eq!(preprocessed, "\nsome random text\n\n");
// make sure the "true" and "FOO_BAR" defines were set
let true_define =
ffi::tvm_htab_find_ref(defines, b"true\0".as_ptr().cast());
let got = CStr::from_ptr(true_define).to_str().unwrap();
assert_eq!(got, "1");
let foo_bar =
ffi::tvm_htab_find_ref(defines, b"FOO_BAR\0".as_ptr().cast());
let got = CStr::from_ptr(foo_bar).to_str().unwrap();
assert_eq!(got, "-42");
// clean up our hashtable and copied source text
ffi::tvm_htab_destroy(defines);
libc::free(src.cast());
}
}
}
45 Zeilen sind viel mehr als ich normalerweise in Tests mag, aber es erfordert eine ganze Menge zusätzlichen Codes, um zwischen C-Zeilen hin und her zu konvertieren.
Wir müssen auch überprüfen, einschließlich einer weiteren Datei.
// src/preprocessing.rs
#[cfg(test)]
mod tests {
...
#[test]
fn include_another_file() {
const TOP_LEVEL: &str = "first line\n%include nested\nlast line\n";
const NESTED: &str = "nested\n";
// the preprocessor imports files from the filesystem, so we need to
// copy NESTED to a temporary location
let mut nested = NamedTempFile::new().unwrap();
nested.write_all(NESTED.as_bytes()).unwrap();
let nested_filename = nested.path().display().to_string();
// substitute the full path to the "nested" file
let top_level_src = TOP_LEVEL.replace("nested", &nested_filename);
std::fs::write(&nested, NESTED).unwrap();
unsafe {
let top_level_src = CString::new(top_level_src).unwrap();
// create a copy of the top_level_src which can be freed by C
let mut src = libc::strdup(top_level_src.as_ptr());
let mut len = libc::strlen(src) as c_int;
let defines = ffi::tvm_htab_create();
// after all that setup code we can *finally* call the preprocessor
let ret = ffi::tvm_preprocess(&mut src, &mut len, defines);
assert_eq!(ret, 0);
// make sure the define and import lines were removed
let preprocessed = CStr::from_ptr(src).to_bytes();
let got =
std::str::from_utf8(&preprocessed[..len as usize]).unwrap();
// after preprocessing, all include and define lines should have
// been removed
assert_eq!(got, "first line\nnested\nlast line\n");
ffi::tvm_htab_destroy(defines);
libc::free(src.cast());
}
}
Hinweis
Als Randnotiz wurde der Test ursprünglich geschrieben, um alles in drei Ebenen zu verschachteln (z. B.top_level.vm
enthält nested.vm, einschließlich real_nested.vm), um sicherzustellen, dass mehr als eine Ebene verarbeitet wird%include
, jedoch unabhängig davon Wie geschrieben, wurde der Test weiterhin fehlerfrei durchgeführt.
Dann habe ich versucht, die ursprüngliche C-Binärdatei auszuführentvmi
...
$ cd vendor/tinyvm/ $ cat top_level.vm %include nested $ cat nested.vm %include really_nested $ cat really_nested.vm Hello World $ ./bin/tvmi top_level.vm [1] 10607 segmentation fault (core dumped) ./bin/tvmi top_level.vm
Es stellt sich heraus, dass das ursprüngliche tinyvm aus irgendeinem Grund abstürzt, wenn Sie mehrere Ebenen habeninclude
...
Jetzt haben wir einige Tests, damit wir mit der Implementierung beginnen können
tvm_preprocess()
.
Zunächst müssen Sie die Art des Fehlers bestimmen.
// src/preprocessing.rs
#[derive(Debug)]
pub enum PreprocessingError {
FailedInclude {
name: String,
inner: IoError,
},
DuplicateDefine {
name: String,
original_value: String,
new_value: String,
},
EmptyDefine,
DefineWithoutValue(String),
}
Wenn man sich die Funktionen process_includes () und process_derives () ansieht , scheint es, als würden sie eine Zeichenfolge nach einer bestimmten Anweisung durchsuchen und diese Zeichenfolge dann durch etwas anderes ersetzen (entweder den Inhalt der Datei oder nichts, wenn die Zeile gelöscht werden sollte).
Wir müssen in der Lage sein, diese Logik in einen Helfer zu extrahieren und unnötige Doppelarbeit zu vermeiden.
// src/preprocessing.rs
/// Scan through the input string looking for a line starting with some
/// directive, using a callback to figure out what to replace the directive line
/// with.
fn process_line_starting_with_directive<F>(
mut src: String,
directive: &str,
mut replace_line: F,
) -> Result<(String, usize), PreprocessingError>
where
F: FnMut(&str) -> Result<String, PreprocessingError>,
{
// try to find the first instance of the directive
let directive_delimiter = match src.find(directive) {
Some(ix) => ix,
None => return Ok((src, 0)),
};
// calculate the span from the directive to the end of the line
let end_ix = src[directive_delimiter..]
.find('\n')
.map(|ix| ix + directive_delimiter)
.unwrap_or(src.len());
// the rest of the line after the directive
let directive_line =
src[directive_delimiter + directive.len()..end_ix].trim();
// use the callback to figure out what we should replace the line with
let replacement = replace_line(directive_line)?;
// remove the original line
let _ = src.drain(directive_delimiter..end_ix);
// then insert our replacement
src.insert_str(directive_delimiter, &replacement);
Ok((src, 1))
}
Wir haben jetzt einen Helfer
process_line_starting_with_directive()
, damit wir einen Parser implementieren können %include
.
// src/preprocessing.rs
fn process_includes(
src: String,
) -> Result<(String, usize), PreprocessingError> {
const TOK_INCLUDE: &str = "%include";
process_line_starting_with_directive(src, TOK_INCLUDE, |line| {
std::fs::read_to_string(line).map_err(|e| {
PreprocessingError::FailedInclude {
name: line.to_string(),
inner: e,
}
})
})
}
Leider ist der% define-Parser etwas komplexer.
// src/preprocessing.rs
n process_defines(
src: String,
defines: &mut HashTable,
) -> Result<(String, usize), PreprocessingError> {
const TOK_DEFINE: &str = "%define";
process_line_starting_with_directive(src, TOK_DEFINE, |line| {
parse_define(line, defines)?;
Ok(String::new())
})
}
fn parse_define(
line: &str,
defines: &mut HashTable,
) -> Result<(), PreprocessingError> {
if line.is_empty() {
return Err(PreprocessingError::EmptyDefine);
}
// The syntax is "%define key value", so after removing the leading
// "%define" everything after the next space is the value
let first_space = line.find(' ').ok_or_else(|| {
PreprocessingError::DefineWithoutValue(line.to_string())
})?;
// split the rest of the line into key and value
let (key, value) = line.split_at(first_space);
let value = value.trim();
match defines.0.entry(
CString::new(key).expect("The text shouldn't contain null bytes"),
) {
// the happy case, this symbol hasn't been defined before so we can just
// insert it.
Entry::Vacant(vacant) => {
vacant.insert(Item::opaque(value));
},
// looks like this key has already been defined, report an error
Entry::Occupied(occupied) => {
return Err(PreprocessingError::DuplicateDefine {
name: key.to_string(),
original_value: occupied
.get()
.opaque_value_str()
.unwrap_or("<invalid>")
.to_string(),
new_value: value.to_string(),
});
},
}
Ok(())
}
Um auf den Text in unserer Hash-Tabelle zugreifen zu können, müssen wir dem Element
Item
einige Hilfsmethoden geben:
// src/htab.rs
impl Item {
...
pub(crate) fn opaque_value(&self) -> &[u8] { &self.opaque_value }
pub(crate) fn opaque_value_str(&self) -> Option<&str> {
std::str::from_utf8(self.opaque_value()).ok()
}
}
Es ist eine gute Idee, an dieser Stelle weitere Tests hinzuzufügen.
// src/preprocessing.rs
#[cfg(test)]
mod tests {
...
#[test]
fn empty_string() {
let src = String::from("");
let mut hashtable = HashTable::default();
let (got, replacements) = process_defines(src, &mut hashtable).unwrap();
assert!(got.is_empty());
assert_eq!(replacements, 0);
assert!(hashtable.0.is_empty());
}
#[test]
fn false_percent() {
let src = String::from("this string contains a % symbol");
let mut hashtable = HashTable::default();
let (got, replacements) =
process_defines(src.clone(), &mut hashtable).unwrap();
assert_eq!(got, src);
assert_eq!(replacements, 0);
assert!(hashtable.0.is_empty());
}
#[test]
fn define_without_key_and_value() {
let src = String::from("%define\n");
let mut hashtable = HashTable::default();
let err = process_defines(src.clone(), &mut hashtable).unwrap_err();
match err {
PreprocessingError::EmptyDefine => {},
other => panic!("Expected EmptyDefine, found {:?}", other),
}
}
#[test]
fn define_without_value() {
let src = String::from("%define key\n");
let mut hashtable = HashTable::default();
let err = process_defines(src.clone(), &mut hashtable).unwrap_err();
match err {
PreprocessingError::DefineWithoutValue(key) => {
assert_eq!(key, "key")
},
other => panic!("Expected DefineWithoutValue, found {:?}", other),
}
}
#[test]
fn valid_define() {
let src = String::from("%define key value\n");
let mut hashtable = HashTable::default();
let (got, num_defines) = process_defines(src.clone(), &mut hashtable).unwrap();
assert_eq!(got, "\n");
assert_eq!(num_defines, 1);
assert_eq!(hashtable.0.len(), 1);
let key = CString::new("key").unwrap();
let item = hashtable.0.get(&key).unwrap();
assert_eq!(item.opaque_value_str().unwrap(), "value");
}
}
Zu diesem Zeitpunkt haben wir den größten Teil der Vorverarbeitungslogik reproduziert. Jetzt benötigen wir nur noch eine Funktion, die die Anweisungen
%include
und den Prozess weiter erweitert , %define
bis keine mehr vorhanden sind.
// src/preprocessing.rs
pub fn preprocess(
src: String,
defines: &mut HashTable,
) -> Result<String, PreprocessingError> {
let mut src = src;
loop {
let (modified, num_includes) = process_includes(src)?;
let (modified, num_defines) = process_defines(modified, defines)?;
if num_includes + num_defines == 0 {
return Ok(modified);
}
src = modified;
}
}
Natürlich ist diese Funktion
preprocess()
nur für Rust verfügbar. Wir müssen eine erstellen extern "C" fn
, die Argumente von C-Typen in etwas übersetzt, das Rust verarbeiten kann, und dann zurück in C übersetzt.
// src/preprocessing.rs
#[no_mangle]
pub unsafe extern "C" fn tvm_preprocess(
src: *mut *mut c_char,
src_len: *mut c_int,
defines: *mut tvm_htab_ctx,
) -> c_int {
if src.is_null() || src_len.is_null() || defines.is_null() {
return -1;
}
// Safety: This assumes the tvm_htab_ctx is actually our ported HashTable
let defines = &mut *(defines as *mut HashTable);
// convert the input string to an owned Rust string so it can be
// preprocessed
let rust_src = match CStr::from_ptr(*src).to_str() {
Ok(s) => s.to_string(),
// just error out if it's not valid UTF-8
Err(_) => return -1,
};
match preprocess(rust_src, defines) {
Ok(s) => {
let preprocessed = CString::new(s).unwrap();
// create a copy of the preprocessed string that can be free'd by C
// and use the output arguments to pass it to the caller
*src = libc::strdup(preprocessed.as_ptr());
// the original C implementation didn't add a null terminator to the
// preprocessed string, so we're required to set the length as well.
*src_len = libc::strlen(*src) as c_int;
// returning 0 indicates success
0
},
// tell the caller "an error occurred"
Err(_) => -1,
}
}
Tipp
Möglicherweise haben Sie bemerkt, dass unsere Funktion tvm_preprocess () keine Vorverarbeitungslogik hat und eher einem Adapter zum Übersetzen von Argumenten und Rückgabewerten und zum Sicherstellen einer korrekten Fehlerausbreitung ähnelt.
Das ist kein Zufall.
Das Geheimnis beim Codieren von FFI besteht darin, so wenig wie möglich zu schreiben und intelligente Tricks zu vermeiden . Im Gegensatz zu den meisten Rust-Codes können Fehler in solchen Interop-Funktionen zu Fehlern in Logik und Speicher führen.
Das Erstellen eines dünnen Wrappers um unsere Funktionpreprocess()
erleichtert auch die nächste Aufgabe: Wenn der größte Teil der Codebasis in Rust geschrieben ist, können wir den Wrapper entfernen undpreprocess()
direkt aufrufen .
Die Funktion ist jetzt
tvm_preprocess()
definiert und wir sollten bereit sein zu gehen.
Compiling tinyvm v0.1.0 (/home/michael/Documents/tinyvm-rs)
error: linking with `/usr/bin/clang` failed: exit code: 1
|
= note: "/usr/bin/clang" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.13h6j6k0dzqf6zi2.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.13l2b4uvr7p3ht4k.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.14bdbjhozo3id49g.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.14fw2gyd6mrq5730.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.19xc7n0bb25uaxgk.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1duzy573vjvyihco.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1e0yejy24qufh7ie.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1k4xuir9ezt4vkzp.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1mqdnrarww1zjlt.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1ubflbxzxkx7grpn.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1vtvcpzzusyku3mk.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1wal3ebwyfg4qllf.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.235k75fk09i43ba3.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.253rt7mnjcp3n8ex.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.27phuscrye2lmkyq.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2bwv51h7gucjizh0.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2ghuai4hs88aroml.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2gqnd9h4nmhvgxbn.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2hjvtf620gtog0qz.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2hq7kc2w3vix8i5q.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2ibwag4iedx494ft.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2jdt9fes53g5mxlp.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2kv4bwega1wfr8z6.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2lja418hz58xlryz.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2o0foimqe73p8ujt.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2ouuhyii88vg8tqs.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2tnynvvdxge4sv9a.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2u1hzhj3v0d8kn4s.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2v1ii2legejcp3ir.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2vkkoofkb7zs04v1.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2w5mgql1gpr1f9uz.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2wdyioq7lxh9uxu7.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2wokgurbjsmgz12r.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.2wwcrmvusj07mx2n.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.310mxv7piqfbf4tr.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3352aele91geo33m.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.36f4wrjtv0x5y00b.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.38f6o2m900r5q63j.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3b67z5wg30f9te4l.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3gyajmii4500y81t.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3ovwslgcz03sp0ov.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.3vwhwp967j90qfpp.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.41ox17npnikcezii.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4472ut4qn508rg19.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4bbcvjauqmyr7tjc.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4c9lrc1xbvaru030.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4fzwdkjhjtwv5uik.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4gy2dy14zw2o60sh.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4i8qxpi0pmwn8d2e.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4isstj7ytb9d9yep.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4isz4o5d1flv8pme.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4lnnaom9zd4u3xmv.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4mgvbbhn4jewmy60.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4q7wf9d53jp9j6y6.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4qimnegzmsif2zbr.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4scm7492lh4yspgt.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4ten9b8okg10ap4i.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4vrj7dhlet4j6oe.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4wtf4i2ggbrvqt63.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4zsqxnhj8yusiplh.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.50o8i1bmvqwd5eg7.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.50urmck1r52hucuw.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.51w3uc6agh3gynn3.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.55o6ad6nlq4o2zyt.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.57gih8p2bu1jbo0l.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.57rpuf5wpgkfmf1z.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.5920w55mlosqy9aj.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.5c1ra5cheein740g.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.5cuuq0m7tzehyrti.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.5e85z18y46lhofte.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.6yu7c01lw47met2.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.cn69np51jgriev2.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.d224rq9cs4mbv0q.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.e0vaqgnhc25c4ox.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.edm0ce3nfzegp4d.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.elxjhifv4wlzkc2.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.ifqyaukx6gnbb0a.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.kr8s9rcy6ux2d02.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.ley637x8c2etn66.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.njyqsm0frvb1j4d.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.r9ttxk3s5kacz9k.rcgu.o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.xrorvssabbgfjqz.rcgu.o" "-o" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88" "/home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.1iplfu0pt8fy07e4.rcgu.o" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro" "-Wl,-znow" "-nodefaultlibs" "-L" "/home/michael/Documents/tinyvm-rs/target/debug/deps" "-L" "/home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out" "-L" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "-Wl,--whole-archive" "-ltvm" "-Wl,--no-whole-archive" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libtest-a39a3e9a77b17f55.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libterm-97a69cd310ff0925.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgetopts-66a42b1d94e3e6f9.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunicode_width-dd7761d848144e0d.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_std-f722acdb78755ba0.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libtempfile-b08849d192e5c2e1.rlib"
"/home/michael/Documents/tinyvm-rs/target/debug/deps/librand-c85ceffb304c7385.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/librand_chacha-4e4839e3036afe89.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libc2_chacha-7555b62a53de8bdf.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libppv_lite86-0097c0f425957d6e.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/librand_core-de2208c863d15e9b.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libgetrandom-c696cd809d660e17.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/liblibc-d52d0b97a33a5f02.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libremove_dir_all-4035fb46dbd6fb92.rlib" "/home/michael/Documents/tinyvm-rs/target/debug/deps/libcfg_if-6adeb646d05b676c.rlib" "-Wl,--start-group" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-974c3c08f6def4b3.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-eb49676f33a2c8a6.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-7ae0446feecc60f2.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-2de299b65d7f5721.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace-64514775bc06309a.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libbacktrace_sys-1ed8aa185c63b9a5.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-a839df87f563fba5.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-8e726bdc2018d836.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-5285f42cbadf207d.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-b0362d20f8aa58fa.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-f3dd7051708453a4.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-83744846c43307ce.rlib" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-d5565a3a0f4cfe21.rlib" "-Wl,--end-group" "/home/michael/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-ea790e85415e3bbf.rlib" "-Wl,-Bdynamic" "-lutil" "-lutil" "-ldl" "-lrt" "-lpthread" "-lgcc_s" "-lc" "-lm" "-lrt" "-lpthread" "-lutil" "-lutil" "-fuse-ld=lld"
= note: ld.lld: error: duplicate symbol: tvm_preprocess
>>> defined at preprocessing.rs:13 (src/preprocessing.rs:13)
>>> /home/michael/Documents/tinyvm-rs/target/debug/deps/tinyvm-8eca24ff9a1cde88.4mgvbbhn4jewmy60.rcgu.o:(tvm_preprocess)
>>> defined at tvm_preprocessor.c:135 (vendor/tinyvm/libtvm/tvm_preprocessor.c:135)
>>> tvm_preprocessor.o:(.text.tvm_preprocess+0x0) in archive /home/michael/Documents/tinyvm-rs/target/debug/build/tinyvm-3f1a2766f78b5580/out/libtvm.a
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: aborting due to previous error
error: could not compile `tinyvm`.
To learn more, run the command again with --verbose.
Hoppla, der Linker beschwert sich über und
preprocessing.rs
und tvm_preprocessor.c
definiert eine Funktion tvm_preprocess()
. Es sieht so aus, als hätten wir vergessen, tvm_preprocessor.c
aus der Baugruppe zu entfernen ...
diff --git a/build.rs b/build.rs
index 0ed012c..42b8fa0 100644
--- a/build.rs
+++ b/build.rs
@@ -14,6 +14,7 @@ fn main() {
.file(src.join("tvm_memory.c"))
.file(src.join("tvm_parser.c"))
.file(src.join("tvm_program.c"))
- .file(src.join("tvm_preprocessor.c"))
.file(src.join("tvm.c"))
.include(&include)
.compile("tvm");
(END)
Lass es uns erneut versuchen.
cargo run --example tvmi -- vendor/tinyvm/programs/tinyvm/fact.vm
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/examples/tvmi vendor/tinyvm/programs/tinyvm/fact.vm`
1
2
6
24
120
720
5040
40320
362880
3628800
Viel besser!
Erinnern Sie sich an das letzte Beispiel, in dem Sie
tvmi
gefallen sind und drei Ebenen der Codetiefe erhalten haben? Als netter Nebeneffekt funktionieren die verschachtelten Ebenen nach dem Portieren Ihres Codes nach Rust einfach .
Hinweis
Möglicherweise haben Sie auch bemerkt, dass die Funktionpreprocess()
keine der Hash-Tabellenfunktionen von verwendettvm_htab.h
. Nachdem wir das Modul nach Rust portiert haben, verwenden wir einfach die Rust-Typen direkt.
Das ist das Schöne an diesem Prozess. Sobald Sie etwas auf Rust portiert haben, können Sie dies anwenden, um die Typen / Funktionen direkt zu verwenden - und sofort von der Fehlerbehandlung und Ergonomie profitieren.
Fazit
Wenn Sie diesen Artikel noch lesen, herzlichen Glückwunsch, wir haben gerade zwei Module von
tinyvm
nach Rust portiert .
Leider ist dieser Artikel schon ziemlich lang. Aber ich hoffe, dass Sie jetzt das große Ganze haben.
- Durchsuchen Sie die Anwendungsheader und finden Sie eine einfache Funktion / ein einfaches Modul
- Schreiben Sie einige Tests, um zu verstehen, wie eine vorhandene Funktion funktionieren sollte
- Schreiben Sie äquivalente Funktionen in Rust und stellen Sie sicher, dass sie dieselben Tests bestehen
- Erstellen Sie eine dünne Unterlegscheibe, die eine Rust-Funktion mit derselben C-Schnittstelle exportiert. Denken Sie daran, die ursprüngliche Funktion / das ursprüngliche Modul aus der Assembly zu entfernen, damit der Linker Rust-Code anstelle von C verwendet
- Fahren Sie mit Schritt 1 fort
Das Beste an dieser Methode ist, dass Sie die Codebasis schrittweise verbessern, die Anwendung funktionsfähig bleibt und nicht alles von Anfang bis Ende neu schreibt.
Es ist, als würde man ein Rad im laufenden Betrieb wechseln.
Der bevorzugte Weg, um eine Anwendung von C nach Rust zu portieren
Siehe auch: