Skip to content

Commit

Permalink
Support for custom asserts
Browse files Browse the repository at this point in the history
  • Loading branch information
AngheloAlf committed Aug 15, 2024
1 parent c83af88 commit 0fc55bb
Show file tree
Hide file tree
Showing 20 changed files with 250 additions and 19 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Can't be combined with the global `hardcoded_gp_value`.
- Add new top-level attribute for the file format: `entry`.
- Specifies the entrypoint for the build.
- Add new top-level attribute for the file format: `asserts`.
- Allows to define multiple assertions that should be satisfied for the link
to success.

### Changed

Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ extra shiftability features not present on other tools.
- Support for both listing a library and letting the linker grab any used file
or to specify which speicific files should be linked by the linker.
- Support splat's "vram classes" for better memory layout management.
- Support emitting custom symbols and requiring some symbols to be defined on
during linking.
- Support for defining a shiftable `_gp` for small data support.
- Support for defining the entrypoint of the executable.
- Support for defining asserts to ensure the sanity of the build.

### Planned features

Expand Down
1 change: 1 addition & 0 deletions docs/file_format/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The document is composed by the following top-level attributes:
- A single optional string that specifies the entrypoint of the final build.
- A list of [`symbol_assignments`](symbol_assignments.md).
- A list of [`required_symbols`](required_symbols.md).
- A list of [`asserts`](asserts.md)

## Example

Expand Down
64 changes: 64 additions & 0 deletions docs/file_format/asserts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Asserts

An assert ensures that a given condition is satified by the linking process,
otherwise fail the link.

GNU LD documentation for
[`ASSERT`](https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html#index-ASSERT)

Every attribute listed is optional unless explicitly stated.

## Table of contents

- [Asserts](#asserts)
- [Table of contents](#table-of-contents)
- [`check`](#check)
- [Example](#example)
- [Valid values](#valid-values)
- [error\_message](#error_message)
- [Example](#example-1)
- [Valid values](#valid-values-1)
- [`include_if_any`, `include_if_all`, `exclude_if_any` and `exclude_if_all`](#include_if_any-include_if_all-exclude_if_any-and-exclude_if_all)

## `check`

This field is **required**.

The actual condition to check. If this check evaluates to zero then the linker
exits with an error code and prints [`error_message`](#error_message).

### Example

```yaml
asserts:
- check: boot_ROM_END <= 0x101000
error_message: boot segment is larger than 1 MiB
```
### Valid values
Non empty string.
## error_message
The error message to show if [`check`](#check) is not satisfied.

### Example

```yaml
asserts:
- check: boot_VRAM_END <= 0x80400000
error_message: VRAM is larger than 4 MiB
include_if_any: [[ram_size, 4]]
```

### Valid values

Non empty string.

## `include_if_any`, `include_if_all`, `exclude_if_any` and `exclude_if_all`

These fields allow to conditionally include or exclude a given segment depending
on the current [custom options](custom_options.md).

Their syntax is the same as their [`file`](file.md#include_if_any) counterparts.
2 changes: 1 addition & 1 deletion slinky/src/absent_nullable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::SlinkyError;

// https://stackoverflow.com/a/44332837/6292472

#[derive(Debug, PartialEq, Default)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
pub(crate) enum AbsentNullable<T> {
#[default]
Absent,
Expand Down
75 changes: 75 additions & 0 deletions slinky/src/assert_entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* SPDX-FileCopyrightText: © 2024 decompals */
/* SPDX-License-Identifier: MIT */

use serde::Deserialize;

use crate::{absent_nullable::AbsentNullable, traits::Serial, Settings, SlinkyError};

#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct AssertEntry {
pub check: String,
pub error_message: String,

pub include_if_any: Vec<(String, String)>,
pub include_if_all: Vec<(String, String)>,
pub exclude_if_any: Vec<(String, String)>,
pub exclude_if_all: Vec<(String, String)>,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct AssertEntrySerial {
pub check: String,
pub error_message: String,

#[serde(default)]
pub include_if_any: AbsentNullable<Vec<(String, String)>>,
#[serde(default)]
pub include_if_all: AbsentNullable<Vec<(String, String)>>,
#[serde(default)]
pub exclude_if_any: AbsentNullable<Vec<(String, String)>>,
#[serde(default)]
pub exclude_if_all: AbsentNullable<Vec<(String, String)>>,
}

impl Serial for AssertEntrySerial {
type Output = AssertEntry;

fn unserialize(self, _settings: &Settings) -> Result<Self::Output, SlinkyError> {
if self.check.is_empty() {
return Err(SlinkyError::EmptyValue {
name: "check".to_string(),
});
}
let check = self.check;

if self.error_message.is_empty() {
return Err(SlinkyError::EmptyValue {
name: "error_message".to_string(),
});
}
let error_message = self.error_message;

let include_if_any = self
.include_if_any
.get_non_null_not_empty("include_if_any", Vec::new)?;
let include_if_all = self
.include_if_all
.get_non_null_not_empty("include_if_all", Vec::new)?;
let exclude_if_any = self
.exclude_if_any
.get_non_null_not_empty("exclude_if_any", Vec::new)?;
let exclude_if_all = self
.exclude_if_all
.get_non_null_not_empty("exclude_if_all", Vec::new)?;

Ok(Self::Output {
check,
error_message,
include_if_any,
include_if_all,
exclude_if_any,
exclude_if_all,
})
}
}
17 changes: 13 additions & 4 deletions slinky/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use std::{fs, path::Path};
use serde::Deserialize;

use crate::{
absent_nullable::AbsentNullable, required_symbol::RequiredSymbolSerial, segment::SegmentSerial,
settings::SettingsSerial, symbol_assignment::SymbolAssignmentSerial, traits::Serial,
vram_class::VramClassSerial, RequiredSymbol, Segment, Settings, SlinkyError, SymbolAssignment,
VramClass,
absent_nullable::AbsentNullable, assert_entry::AssertEntrySerial,
required_symbol::RequiredSymbolSerial, segment::SegmentSerial, settings::SettingsSerial,
symbol_assignment::SymbolAssignmentSerial, traits::Serial, vram_class::VramClassSerial,
AssertEntry, RequiredSymbol, Segment, Settings, SlinkyError, SymbolAssignment, VramClass,
};

#[derive(PartialEq, Debug)]
Expand All @@ -23,6 +23,7 @@ pub struct Document {
pub entry: Option<String>,
pub symbol_assignments: Vec<SymbolAssignment>,
pub required_symbols: Vec<RequiredSymbol>,
pub asserts: Vec<AssertEntry>,
}

impl Document {
Expand Down Expand Up @@ -66,6 +67,8 @@ pub(crate) struct DocumentSerial {
pub symbol_assignments: AbsentNullable<Vec<SymbolAssignmentSerial>>,
#[serde(default)]
pub required_symbols: AbsentNullable<Vec<RequiredSymbolSerial>>,
#[serde(default)]
pub asserts: AbsentNullable<Vec<AssertEntrySerial>>,
}

impl DocumentSerial {
Expand Down Expand Up @@ -100,13 +103,19 @@ impl DocumentSerial {
.get_non_null("required_symbols", Vec::new)?
.unserialize(&settings)?;

let asserts = self
.asserts
.get_non_null("asserts", Vec::new)?
.unserialize(&settings)?;

Ok(Document {
settings,
vram_classes,
segments,
entry,
symbol_assignments,
required_symbols,
asserts,
})
}
}
2 changes: 2 additions & 0 deletions slinky/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod utils;
mod linker_symbols_style;
mod settings;

mod assert_entry;
mod file_info;
mod file_kind;
mod gp_info;
Expand All @@ -36,6 +37,7 @@ pub use escaped_path::EscapedPath;
pub use linker_symbols_style::LinkerSymbolsStyle;
pub use settings::Settings;

pub use assert_entry::AssertEntry;
pub use file_info::FileInfo;
pub use file_kind::FileKind;
pub use required_symbol::RequiredSymbol;
Expand Down
48 changes: 45 additions & 3 deletions slinky/src/linker_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
use std::io::Write;

use crate::{
utils, version, Document, EscapedPath, FileInfo, FileKind, RequiredSymbol, RuntimeSettings,
ScriptExporter, ScriptGenerator, ScriptImporter, Segment, SlinkyError, SymbolAssignment,
VramClass,
utils, version, AssertEntry, Document, EscapedPath, FileInfo, FileKind, RequiredSymbol,
RuntimeSettings, ScriptExporter, ScriptGenerator, ScriptImporter, Segment, SlinkyError,
SymbolAssignment, VramClass,
};

use crate::script_buffer::ScriptBuffer;
Expand Down Expand Up @@ -137,6 +137,20 @@ impl ScriptImporter for LinkerWriter<'_> {

Ok(())
}

fn add_all_asserts(&mut self, asserts: &[AssertEntry]) -> Result<(), SlinkyError> {
if asserts.is_empty() {
return Ok(());
}

self.begin_asserts()?;
for assert_entry in asserts {
self.add_assert(assert_entry)?;
}
self.end_asserts()?;

Ok(())
}
}

impl ScriptExporter for LinkerWriter<'_> {
Expand Down Expand Up @@ -682,6 +696,34 @@ impl LinkerWriter<'_> {

Ok(())
}

pub(crate) fn begin_asserts(&mut self) -> Result<(), SlinkyError> {
if !self.buffer.is_empty() {
self.buffer.write_empty_line();
}

Ok(())
}

pub(crate) fn end_asserts(&mut self) -> Result<(), SlinkyError> {
Ok(())
}

pub(crate) fn add_assert(&mut self, assert_entry: &AssertEntry) -> Result<(), SlinkyError> {
if !self.rs.should_emit_entry(
&assert_entry.exclude_if_any,
&assert_entry.exclude_if_all,
&assert_entry.include_if_any,
&assert_entry.include_if_all,
) {
return Ok(());
}

self.buffer
.write_assert(&assert_entry.check, &assert_entry.error_message);

Ok(())
}
}

// internal functions
Expand Down
8 changes: 6 additions & 2 deletions slinky/src/partial_linker_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
/* SPDX-License-Identifier: MIT */

use crate::{
Document, EscapedPath, FileInfo, LinkerWriter, RequiredSymbol, RuntimeSettings, ScriptExporter,
ScriptGenerator, ScriptImporter, Segment, SlinkyError, SymbolAssignment,
AssertEntry, Document, EscapedPath, FileInfo, LinkerWriter, RequiredSymbol, RuntimeSettings,
ScriptExporter, ScriptGenerator, ScriptImporter, Segment, SlinkyError, SymbolAssignment,
};

pub struct PartialLinkerWriter<'a> {
Expand Down Expand Up @@ -93,6 +93,10 @@ impl ScriptImporter for PartialLinkerWriter<'_> {
) -> Result<(), SlinkyError> {
self.main_writer.add_all_required_symbols(required_symbols)
}

fn add_all_asserts(&mut self, asserts: &[AssertEntry]) -> Result<(), SlinkyError> {
self.main_writer.add_all_asserts(asserts)
}
}

impl ScriptExporter for PartialLinkerWriter<'_> {
Expand Down
2 changes: 1 addition & 1 deletion slinky/src/script_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl ScriptBuffer {
}

pub fn write_assert(&mut self, cond: &str, error_msg: &str) {
self.writeln(&format!("ASSERT({}, \"Error: {}\");", cond, error_msg));
self.writeln(&format!("ASSERT(({}), \"Error: {}\");", cond, error_msg));
}

pub fn write_required_symbol(&mut self, name: &str) {
Expand Down
13 changes: 9 additions & 4 deletions slinky/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
/* SPDX-License-Identifier: MIT */

use crate::{
Document, EscapedPath, RequiredSymbol, Segment, Settings, SlinkyError, SymbolAssignment,
AssertEntry, Document, EscapedPath, RequiredSymbol, Segment, Settings, SlinkyError,
SymbolAssignment,
};

mod private {
use crate::{
file_info::FileInfoSerial, gp_info::GpInfoSerial, required_symbol::RequiredSymbolSerial,
segment::SegmentSerial, symbol_assignment::SymbolAssignmentSerial,
vram_class::VramClassSerial, LinkerWriter, PartialLinkerWriter,
assert_entry::AssertEntrySerial, file_info::FileInfoSerial, gp_info::GpInfoSerial,
required_symbol::RequiredSymbolSerial, segment::SegmentSerial,
symbol_assignment::SymbolAssignmentSerial, vram_class::VramClassSerial, LinkerWriter,
PartialLinkerWriter,
};

pub trait Sealed {}
Expand All @@ -23,6 +25,7 @@ mod private {
impl Sealed for VramClassSerial {}
impl Sealed for SymbolAssignmentSerial {}
impl Sealed for RequiredSymbolSerial {}
impl Sealed for AssertEntrySerial {}

impl<T> Sealed for Vec<T> {}
impl<T> Sealed for Option<T> {}
Expand All @@ -39,6 +42,7 @@ pub trait ScriptImporter: private::Sealed {
&mut self,
required_symbols: &[RequiredSymbol],
) -> Result<(), SlinkyError>;
fn add_all_asserts(&mut self, asserts: &[AssertEntry]) -> Result<(), SlinkyError>;

fn add_whole_document(&mut self, document: &Document) -> Result<(), SlinkyError> {
self.add_all_segments(&document.segments)?;
Expand All @@ -47,6 +51,7 @@ pub trait ScriptImporter: private::Sealed {
}
self.add_all_symbol_assignments(&document.symbol_assignments)?;
self.add_all_required_symbols(&document.required_symbols)?;
self.add_all_asserts(&document.asserts)?;

Ok(())
}
Expand Down
2 changes: 2 additions & 0 deletions tests/partial_linking/follow_segment.ld
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,5 @@ SECTIONS
}

ENTRY(ENTRYPOINT);

ASSERT((main_VRAM_END <= 0x80400000), "Error: VRAM is larger than 4 MiB");
Loading

0 comments on commit 0fc55bb

Please sign in to comment.