Skip to content

Commit

Permalink
Runtime types by value (#72)
Browse files Browse the repository at this point in the history
* presentation examples

* pass runtime types by value

* add clone types where Clone and Drop are called appropriately

* use routecore from github instead of local

* simplify inetnum dependency

* few more docstrings
  • Loading branch information
tertsdiepraam authored Oct 25, 2024
1 parent 1e8456e commit 6142b26
Show file tree
Hide file tree
Showing 24 changed files with 797 additions and 317 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ rev = "1af294ea2d6c18c5a8fa9b4f272398b7c98e0c48"

[dev-dependencies]
bytes = "1"
routecore = { version = "0.4.0", features = ["bgp", "bmp"] }
routecore = { version = "0.5.0-rc2-dev", features = ["bgp", "bmp", "serde"], git = "https://github.com/NLnetLabs/routecore.git"}

[profile.profiling]
inherits = "release"
Expand All @@ -58,4 +58,4 @@ version = "0.2.1-dev"
edition = "2021"
authors = ["NLnet Labs <routing-team@nlnetlabs.nl>"]
license = "BSD-3-Clause"
rust-version = "1.80"
rust-version = "1.80"
18 changes: 18 additions & 0 deletions examples/presentation.roto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
filter-map rib-in-pre(
output: Log,
route: Route,
) {
define {
my_prefix = 100.40.0.0/17;
}

apply {
if route.prefix_matches(my_prefix) {
output.log_custom(10, 100);
reject
} else {
output.log_prefix(my_prefix);
accept
}
}
}
158 changes: 158 additions & 0 deletions examples/presentation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use std::error::Error;

use inetnum::addr::Prefix;
use inetnum::asn::Asn;
use roto::{roto_method, TypedFunc, Val, Verdict};
use routecore::bgp::aspath::{Hop, HopPath};
use routecore::bgp::nlri::afisafi::IsPrefix;
use routecore::bgp::workshop::route::RouteWorkshop;

mod hidden {
use inetnum::addr::Prefix;
use routecore::bgp::{
nlri::afisafi::{
Ipv4MulticastNlri, Ipv4UnicastNlri, Ipv6MulticastNlri,
Ipv6UnicastNlri,
},
path_attributes::PaMap,
workshop::route::RouteWorkshop,
};

#[allow(dead_code)]
#[derive(Clone)]
pub enum RotondaRoute {
Ipv4Unicast(RouteWorkshop<Ipv4UnicastNlri>),
Ipv6Unicast(RouteWorkshop<Ipv6UnicastNlri>),
Ipv4Multicast(RouteWorkshop<Ipv4MulticastNlri>),
Ipv6Multicast(RouteWorkshop<Ipv6MulticastNlri>),
}

impl RotondaRoute {
pub fn attributes(&self) -> &PaMap {
match self {
RotondaRoute::Ipv4Unicast(n) => n.attributes(),
RotondaRoute::Ipv6Unicast(n) => n.attributes(),
RotondaRoute::Ipv4Multicast(n) => n.attributes(),
RotondaRoute::Ipv6Multicast(n) => n.attributes(),
}
}
}

#[derive(Debug)]
pub struct OutputStream<T>(Vec<T>);

impl<T> Default for OutputStream<T> {
fn default() -> Self {
Self(Vec::default())
}
}

impl<T> OutputStream<T> {
pub fn push(&mut self, value: T) {
self.0.push(value)
}

pub fn drain(&mut self) -> std::vec::Drain<'_, T> {
self.0.drain(..)
}
}

#[derive(Debug)]
#[allow(dead_code)]
pub enum Output {
Custom(u32, u32),
Prefix(Prefix),
}
}

use hidden::*;

type Log = *mut OutputStream<Output>;
type Func = TypedFunc<(Val<Log>, Val<RotondaRoute>), Verdict<(), ()>>;

fn main() -> Result<(), Box<dyn Error>> {
let mut rt = roto::Runtime::basic()?;

// Registering types and their methods

rt.register_clone_type_with_name::<RotondaRoute>("Route")?;

rt.register_clone_type_with_name::<Log>("Log")?;

#[roto_method(rt, RotondaRoute)]
fn prefix_matches(
rr: *const RotondaRoute,
to_match: *const Prefix,
) -> bool {
let rr = unsafe { &*rr };
let to_match = unsafe { &*to_match };
let rr_prefix = match rr {
RotondaRoute::Ipv4Unicast(n) => n.nlri().prefix(),
RotondaRoute::Ipv6Unicast(n) => n.nlri().prefix(),
RotondaRoute::Ipv4Multicast(n) => n.nlri().prefix(),
RotondaRoute::Ipv6Multicast(n) => n.nlri().prefix(),
};

rr_prefix == *to_match
}

#[allow(improper_ctypes_definitions)] // While Asn in inetnum is not FFI-safe
#[roto_method(rt, RotondaRoute, aspath_origin)]
fn rr_aspath_origin(rr: *const RotondaRoute, to_match: Asn) -> bool {
let rr = unsafe { &*rr };
if let Some(hoppath) = rr.attributes().get::<HopPath>() {
if let Some(Hop::Asn(asn)) = hoppath.origin() {
return *asn == to_match;
}
}

false
}

#[roto_method(rt, Log)]
fn log_prefix(stream: *mut Log, prefix: *const Prefix) {
let stream = unsafe { &mut **stream };
let prefix = unsafe { &*prefix };
stream.push(Output::Prefix(*prefix));
}

#[roto_method(rt, Log)]
fn log_custom(stream: *mut Log, id: u32, local: u32) {
let stream = unsafe { &mut **stream };
stream.push(Output::Custom(id, local));
}

let mut compiled = roto::read_files(["examples/presentation.roto"])?
.compile(rt, usize::BITS / 8)
.inspect_err(|e| eprintln!("{e}"))?;

let function = compiled.get_function("rib-in-pre").unwrap();

run_with_prefix(&function, "8.8.8.0/24")?;
run_with_prefix(&function, "100.40.0.0/17")?;

Ok(())
}

fn run_with_prefix(
function: &Func,
prefix: &str,
) -> Result<(), Box<dyn Error>> {
let route =
RotondaRoute::Ipv4Unicast(RouteWorkshop::new(prefix.parse()?));
let mut output = OutputStream::default();
let log = &mut output as *mut _;

let verdict = function.call(Val(log), Val(route));

println!("Input: {prefix}");
println!("Verdict: {verdict:?}");
println!();
println!("Output: ");
for entry in output.drain() {
println!(" - {entry:?}");
}
println!("=====================");

Ok(())
}
9 changes: 9 additions & 0 deletions examples/runtime.roto
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
filter-map main(bla: Bla) {
apply {
if bla.x() > 10 {
accept 2 * bla.x()
} else {
reject
}
}
}
42 changes: 42 additions & 0 deletions examples/runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use roto::{read_files, Runtime, Val, Verdict};
use roto_macros::roto_method;

#[derive(Clone, Copy)]
struct Bla {
x: u32,
}

fn main() -> Result<(), roto::RotoReport> {
env_logger::init();

let mut runtime = Runtime::basic().unwrap();

runtime.register_copy_type::<Bla>().unwrap();

#[roto_method(runtime, Bla, x)]
fn get_x(bla: *const Bla) -> u32 {
unsafe { &*bla }.x
}

let mut compiled = read_files(["examples/runtime.roto"])?
.compile(runtime, usize::BITS / 8)
.inspect_err(|e| eprintln!("{e}"))?;

let func = compiled
.get_function::<(Val<Bla>,), Verdict<u32, ()>>("main")
.inspect_err(|e| eprintln!("{e}"))
.unwrap();

for x in 0..20 {
let bla = Bla { x };

let res = func.call(Val(bla));
let expected = if x > 10 {
Verdict::Accept(x * 2)
} else {
Verdict::Reject(())
};
println!("main({x}) = {res:?} (expected: {expected:?})");
}
Ok(())
}
13 changes: 6 additions & 7 deletions examples/simple.roto
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
filter-map main(bla: Bla) {
filter-map main(x: IpAddr) {
define {
y = 0.0.0.0;
}
apply {
if bla.y() > 10 {
accept 2 * bla.y()
if x == y {
accept
} else {
reject
}
}
}

filter-map just_reject(x: u32) {
apply { reject }
}
50 changes: 8 additions & 42 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,26 @@
use roto::{read_files, Runtime, Verdict};
use roto_macros::roto_method;
use std::net::IpAddr;

struct Bla {
_x: u16,
y: u32,
_z: u32,
}
use roto::{read_files, Runtime, Verdict};

fn main() -> Result<(), roto::RotoReport> {
env_logger::init();

let mut runtime = Runtime::basic().unwrap();

runtime.register_type::<Bla>().unwrap();

#[roto_method(runtime, Bla, y)]
fn get_y(bla: *const Bla) -> u32 {
unsafe { &*bla }.y
}
let runtime = Runtime::basic().unwrap();

let mut compiled = read_files(["examples/simple.roto"])?
.compile(runtime, usize::BITS / 8)
.inspect_err(|e| eprintln!("{e}"))?;

let func = compiled
.get_function::<(*mut Bla,), Verdict<u32, ()>>("main")
.get_function::<(IpAddr,), Verdict<(), ()>>("main")
.inspect_err(|e| eprintln!("{e}"))
.unwrap();

let func2 = compiled
.get_function::<(u32,), Verdict<(), ()>>("just_reject")
.inspect_err(|e| eprintln!("{e}"))
.unwrap();
let res = func.call("0.0.0.0".parse().unwrap());
println!("main(0.0.0.0) = {res:?}");

// We should now be able to drop this safely, because each func has an Arc
// to the data it references.
drop(compiled);

for y in 0..20 {
let mut bla = Bla { _x: 1, y, _z: 1 };

let func = func.clone();
std::thread::spawn(move || {
let res = func.call(&mut bla as *mut _);
let expected = if y > 10 {
Verdict::Accept(y * 2)
} else {
Verdict::Reject(())
};
println!("main({y}) = {res:?} (expected: {expected:?})");
})
.join()
.unwrap();
let res = func.call("1.1.1.1".parse().unwrap());
println!("main(1.1.1.1) = {res:?}");

let res = func2.call(y);
println!("{res:?}");
}
Ok(())
}
21 changes: 19 additions & 2 deletions src/codegen/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,19 @@ fn check_roto_type(
Err(error_message)
}
}
TypeDescription::ConstPtr(_) => todo!(),
TypeDescription::MutPtr(_) => Ok(()), // TODO: actually check this
TypeDescription::Val(ty) => {
let Type::BuiltIn(_, id) = roto_ty else {
return Err(error_message);
};

if ty != id {
return Err(error_message);
}

Ok(())
}
TypeDescription::ConstPtr(_) => Err(error_message),
TypeDescription::MutPtr(_) => Err(error_message), // TODO: actually check this
TypeDescription::Verdict(rust_accept, rust_reject) => {
let Type::Verdict(roto_accept, roto_reject) = &roto_ty else {
return Err(error_message);
Expand Down Expand Up @@ -244,6 +255,12 @@ macro_rules! params {

unsafe fn invoke<R: Reflect>(mut self, func_ptr: *const u8, return_by_ref: bool) -> R {
let ($($t,)*) = self.as_params();

// We forget values that we pass into Roto. The script is responsible
// for cleaning them op. Forgetting copy types does nothing, but that's
// fine.
#[allow(forgetting_copy_types)]
std::mem::forget(self);
if return_by_ref {
let func_ptr = unsafe {
std::mem::transmute::<*const u8, fn(*mut R, $($t::AsParam),*) -> ()>(func_ptr)
Expand Down
Loading

0 comments on commit 6142b26

Please sign in to comment.