Skip to content

Commit

Permalink
Adds linking utilities to frontend (#32)
Browse files Browse the repository at this point in the history
* patch 0.8.0

* don't index here in case

* partially implement CLI

* finish linker support & CLI

* bump lc3-ensemble 0.8.0, drop crossbeam

* Remove CLI

* add link button + bump 0.8.1

* make Simulator error on loading object file with external symbol
  • Loading branch information
endorpersand authored Oct 16, 2024
1 parent 39e7c4a commit 44df97b
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 33 deletions.
21 changes: 2 additions & 19 deletions src/backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@ crate-type = ["cdylib"]

[dependencies]
ariadne = "0.4.0"
crossbeam-channel = "0.5.12"
lc3-ensemble = "0.7.2"
lc3-ensemble = "0.9.0"
neon = "1"
8 changes: 8 additions & 0 deletions src/backend/src/lib.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ declare module "lc3-backend" {
*/
export function assemble(fp: string): void;

/**
* Takes several `.obj` files and links them.
* @param fps The filepaths of the `.obj` files
* @param out The output path where the linked object file should be
* @throws if linking fails
*/
export function link(fps: string[], out: string): void;

/**
* Gets the symbol table, mapping each memory address to a label.
*/
Expand Down
56 changes: 46 additions & 10 deletions src/backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::sync::atomic::Ordering;
use std::sync::{LazyLock, Mutex, MutexGuard};

use cast::{IntoJsValue, ResultExtJs, TryIntoJsValue};
use lc3_ensemble::asm::encoding::{BinaryFormat, ObjFileFormat, TextFormat};
use lc3_ensemble::asm::{assemble_debug, ObjectFile};
use lc3_ensemble::ast::Reg::{R0, R1, R2, R3, R4, R5, R6, R7};
use lc3_ensemble::parse::parse_ast;
Expand All @@ -32,6 +33,14 @@ fn obj_contents() -> MutexGuard<'static, ObjContents> {
fn controller() -> MutexGuard<'static, SimController> {
CONTROLLER.lock().unwrap_or_else(|e| e.into_inner())
}

pub fn deserialize_obj_file(bytes: Vec<u8>) -> Option<ObjectFile> {
match String::from_utf8(bytes) {
Ok(s) => TextFormat::deserialize(&s),
Err(e) => BinaryFormat::deserialize(e.as_bytes()),
}
}

fn reset_machine(zeroed: bool) {
let init = match zeroed {
true => MachineInitStrategy::Known { value: 0 },
Expand All @@ -44,15 +53,16 @@ fn reset_machine(zeroed: bool) {

obj_contents().clear();
}
fn load_obj_file(obj: ObjectFile) {
fn load_obj_file(obj: ObjectFile) -> Result<(), SimErr> {
reset_machine(false);
let mut controller = controller();

controller.simulator()
.unwrap_or_else(|_| panic!("simulator should've been idle after reset"))
.load_obj_file(&obj);
.load_obj_file(&obj)?;

obj_contents().load_contents(obj);
Ok(())
}
//--------- CONFIG FUNCTIONS ---------//
fn set_ignore_privilege(mut cx: FunctionContext) -> JsResult<JsUndefined> {
Expand Down Expand Up @@ -96,20 +106,43 @@ fn assemble(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let out_path = in_path.with_extension("obj");

// should be unreachable cause frontend validates IO
let src = std::fs::read_to_string(in_path).unwrap();
let src = std::fs::read_to_string(in_path).or_throw(&mut cx)?;

let ast = parse_ast(&src)
.map_err(|e| error_reporter(&e, in_path, &src).report_and_throw(&mut *controller().output_buf(), &mut cx))?;
let asm = assemble_debug(ast, &src)
let obj = assemble_debug(ast, &src)
.map_err(|e| error_reporter(&e, in_path, &src).report_and_throw(&mut *controller().output_buf(), &mut cx))?;

std::fs::write(&out_path, asm.write_bytes())
std::fs::write(&out_path, TextFormat::serialize(&obj))
.map_err(|e| io_reporter(&e, in_path).report_and_throw(&mut *controller().output_buf(), &mut cx))?;

writeln!(controller().output_buf(), "successfully assembled {} into {}", in_path.display(), out_path.display()).unwrap();
Ok(cx.undefined())
}

fn link(mut cx: FunctionContext) -> JsResult<JsUndefined> {
// fn (fp: String[], out: String) -> Result<()>
let out = cx.argument::<JsString>(1)?.value(&mut cx);
let file_paths = cx.argument::<JsArray>(0)?.to_vec(&mut cx)?;

let mut result_obj = ObjectFile::empty();
for fp in file_paths {
// Parse object file:
let fp = fp.downcast_or_throw::<JsString, _>(&mut cx)?.value(&mut cx);
let src = std::fs::read_to_string(&fp).or_throw(&mut cx)?;
let obj = deserialize_obj_file(src.into_bytes())
.ok_or(())
.or_else(|()| cx.throw_error(format!("cannot deserialize object file at {fp}")))?;

// Link to current result obj:
result_obj = ObjectFile::link(result_obj, obj)
.map_err(|e| simple_reporter(&e).report_and_throw(&mut *controller().output_buf(), &mut cx))?;
}
std::fs::write(&out, TextFormat::serialize(&result_obj)).or_throw(&mut cx)?;

writeln!(controller().output_buf(), "successfully linked object files to {out}").unwrap();
Ok(cx.undefined())
}
//--------- SIMULATOR FUNCTIONS ---------//

fn get_curr_sym_table(mut cx: FunctionContext) -> JsResult<JsObject> {
Expand All @@ -119,7 +152,7 @@ fn get_curr_sym_table(mut cx: FunctionContext) -> JsResult<JsObject> {
let mut map = HashMap::new();

if let Some((sym, _)) = contents.get_sym_source() {
map.extend(sym.label_iter().map(|(label, addr)| (addr, label)));
map.extend(sym.label_iter().map(|(label, addr, _)| (addr, label)));
}

map.try_into_js(&mut cx)
Expand All @@ -130,14 +163,16 @@ fn load_object_file(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let in_path = AsRef::<Path>::as_ref(&fp);

// should be unreachable cause frontend validates IO
let bytes = std::fs::read(in_path).unwrap();
let bytes = std::fs::read(in_path).or_throw(&mut cx)?;

let Some(obj) = ObjectFile::read_bytes(&bytes) else {
let Some(obj) = deserialize_obj_file(bytes) else {
return Err(io_reporter("malformed object file", in_path).report_and_throw(&mut *controller().output_buf(), &mut cx));
};

load_obj_file(obj);
Ok(cx.undefined())
match load_obj_file(obj) {
Ok(_) => Ok(cx.undefined()),
Err(e) => Err(simple_reporter(&e).report_and_throw(&mut *controller().output_buf(), &mut cx)),
}
}
fn reinitialize_machine(mut cx: FunctionContext) -> JsResult<JsUndefined> {
// fn () -> Result<()>
Expand Down Expand Up @@ -497,6 +532,7 @@ fn set_timer_max(mut cx: FunctionContext) -> JsResult<JsUndefined> {
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("assemble", assemble)?;
cx.export_function("link", link)?;
cx.export_function("getCurrSymTable", get_curr_sym_table)?;
cx.export_function("setIgnorePrivilege", set_ignore_privilege)?;
cx.export_function("setPauseOnFatalTrap", set_pause_on_fatal_trap)?;
Expand Down
3 changes: 2 additions & 1 deletion src/backend/src/obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ fn add_mem_lines_from_obj(mem_lines: &mut HashMap<u16, String>, obj: &ObjectFile
sym.line_iter()
.filter_map(|(lno, addr)| {
let span = src_info.line_span(lno)?;
Some((addr, src_info.source()[span].to_string()))
let text = src_info.source().get(span)?.to_string();
Some((addr, text))
})
});

Expand Down
38 changes: 37 additions & 1 deletion src/gui/src/components/editor/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@
text="Toggle Console"
/>
</v-list-item>
<v-list-item
:prepend-icon="mdiLinkVariant"
@click="link()"
>
<v-tooltip
location="right"
activator="parent"
text="Link Object Files"
/>
</v-list-item>
</v-navigation-drawer>
<!-- Main editor content -->
<v-main>
Expand Down Expand Up @@ -124,7 +134,7 @@ import type { VAceEditorInstance } from "vue3-ace-editor/types";
import { CreateLc3CompletionProvider } from "./completions";
//
import Console from "../Console.vue";
import { mdiConsole, mdiContentSave, mdiContentSaveEdit, mdiFolderOpen, mdiWrench } from "@mdi/js";
import { mdiConsole, mdiContentSave, mdiContentSaveEdit, mdiFolderOpen, mdiLinkVariant, mdiWrench } from "@mdi/js";
const { lc3, dialog, fs } = window.api;
const activeFileStore = useActiveFileStore();
Expand Down Expand Up @@ -210,6 +220,32 @@ onMounted(() => {
function toggleConsole() {
showConsole.value = !showConsole.value;
}
async function link() {
const inputs = await dialog.showModal("open", {
properties: ["openFile", "multiSelections"],
filters: [
{ name: "Object Files", extensions: ["obj"] }
]
});
if (!inputs.canceled) {
const output = await dialog.showModal("save", {
defaultPath: "linked.obj",
filters: [
{ name: "Object Files", extensions: ["obj"] }
]
})
try {
lc3.link(inputs.filePaths, output.filePath);
} catch (e) {
// Don't crash on link failure.
}
showConsole.value = true;
consoleStr.value = lc3.getAndClearOutput();
}
}
async function _writeFile(fp: string, content: string | undefined = undefined) {
if (typeof content === "undefined") content = editor.value.current_content;
Expand Down

0 comments on commit 44df97b

Please sign in to comment.