From fedef0ed4cf6aa2c9558228a2fc6a1b6fdf8607d Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Fri, 18 Sep 2026 15:47:54 +0200 Subject: [PATCH 1/5] Bump llvm crate version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- Containerfile | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d51290..ba049c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -733,9 +733,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "llvm-sys" -version = "201.0.1" +version = "221.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb947e8b79254ca10d496d0798a9ba1287dcf68e50a92b016fec1cc45bef447" +checksum = "2e52e36cd9eef0d5c4ba35c751c252f207642293009bb774185f84f678c7683c" dependencies = [ "anyhow", "cc", diff --git a/Cargo.toml b/Cargo.toml index de3484b..d8705f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ io-uring = "0.7.10" enum_dispatch = "0.3.13" pest = "2.8.1" pest_derive = "2.8.1" -llvm-sys = "201.0.1" +llvm-sys = "221.1.0" docopt = "1.1.1" signal-hook = "0.3.18" diff --git a/Containerfile b/Containerfile index 66c2afe..79ca86c 100644 --- a/Containerfile +++ b/Containerfile @@ -1,4 +1,4 @@ -FROM registry.fedoraproject.org/fedora:43 AS builder +FROM registry.fedoraproject.org/fedora:44 AS builder ARG RUST_VERSION=stable From 6310eb2beb9c31102d2b8374f068ac7ab4bcfdd9 Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Fri, 18 Sep 2026 15:49:24 +0200 Subject: [PATCH 2/5] Introduce ConstType To distinguish between different constant types --- src/script/ast.rs | 9 ++++- src/script/grammar.peg | 9 ++--- src/script/parser.rs | 78 ++++++++++++++++++++++++++++++++++-------- src/worker/script.rs | 45 ++++++++++++++++-------- 4 files changed, 107 insertions(+), 34 deletions(-) diff --git a/src/script/ast.rs b/src/script/ast.rs index 92e7fe3..2bda8b8 100644 --- a/src/script/ast.rs +++ b/src/script/ast.rs @@ -1,12 +1,19 @@ use std::collections::HashMap; +#[derive(Debug, Clone, PartialEq)] +pub enum ConstType { + Text(String), + Int(u64), + Float(f64), +} + #[derive(Debug, Clone, PartialEq)] pub enum Arg { /// Null constant Null, /// Simple constant - Const { text: String }, + Const { value: ConstType }, /// Variable available at runtime Var { name: String }, diff --git a/src/script/grammar.peg b/src/script/grammar.peg index 51e02fa..cb543bb 100644 --- a/src/script/grammar.peg +++ b/src/script/grammar.peg @@ -4,10 +4,11 @@ COMMENT = _{"//" ~ (!NEWLINE ~ ANY)*} ident_char = {ASCII_ALPHA | "_" | "$"} ident = @{ident_char ~ (ASCII_DIGIT | ident_char)*} -constant = { - "\"" ~ value ~ "\"" - | ASCII_DIGIT+ -} +constant = { text | float | int } + +text = { "\"" ~ value ~ "\"" } +int = { ASCII_DIGIT+ } +float = { ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ } randomPath = { "random_path" } randomString = { "random_string" } diff --git a/src/script/parser.rs b/src/script/parser.rs index 6fe5873..b16ed26 100644 --- a/src/script/parser.rs +++ b/src/script/parser.rs @@ -2,7 +2,9 @@ use log::trace; use pest::{self, Parser, error::Error}; use std::collections::HashMap; -use crate::script::ast::{Arg, Dist, Instruction, MachineInstruction, Node}; +use crate::script::ast::{ + Arg, ConstType, Dist, Instruction, MachineInstruction, Node, +}; #[derive(Debug)] pub enum ParseError { @@ -152,9 +154,25 @@ fn build_ast_from_instr( .map(|arg| { let a = first_nested_pair(arg); match a.as_rule() { - Rule::constant => Arg::Const { - text: pair_to_string(first_nested_pair(a)), - }, + Rule::constant => { + let value = first_nested_pair(a); + match value.as_rule() { + Rule::text => Arg::Const { + value: ConstType::Text(pair_to_string( + first_nested_pair(value), + )), + }, + Rule::int => Arg::Const { + value: ConstType::Int(pair_to_int(value)), + }, + Rule::float => Arg::Const { + value: ConstType::Float(pair_to_float(value)), + }, + unknown => { + panic!("Unknown constant type {unknown:?}") + } + } + } Rule::ident => Arg::Var { name: pair_to_string(a), }, @@ -167,10 +185,27 @@ fn build_ast_from_instr( let args: Vec = args_pair .into_inner() .map(|arg| { - let a = + let value = first_nested_pair(first_nested_pair(arg)); - Arg::Const { - text: pair_to_string(a), + match value.as_rule() { + Rule::text => Arg::Const { + value: ConstType::Text(pair_to_string( + first_nested_pair(value), + )), + }, + Rule::int => Arg::Const { + value: ConstType::Int(pair_to_int( + value, + )), + }, + Rule::float => Arg::Const { + value: ConstType::Float(pair_to_float( + value, + )), + }, + unknown => panic!( + "Unknown constant type {unknown:?}" + ), } }) .collect(); @@ -253,9 +288,9 @@ fn build_ast_from_dist(pair: pest::iterators::Pair) -> Dist { fn string_from_pair(pair: pest::iterators::Pair) -> String { assert!(matches!(pair.as_rule(), Rule::constant | Rule::ident)); - // Extract "value" (Constants) or "name" (Identifier) + // Extract "value" (text Constants) or "name" (Identifier) // and convert it to String - pair_to_string(first_nested_pair(pair)) + pair_to_string(first_nested_pair(first_nested_pair(pair))) } fn string_from_argument( @@ -276,10 +311,23 @@ fn pair_to_string(pair: pest::iterators::Pair) -> String { pair.as_span().as_str().to_string() } +fn pair_to_int(pair: pest::iterators::Pair) -> u64 { + pair.as_span().as_str().to_string().parse().unwrap() +} + +fn pair_to_float(pair: pest::iterators::Pair) -> f64 { + pair.as_span().as_str().to_string().parse().unwrap() +} + fn first_nested_pair( pair: pest::iterators::Pair, ) -> pest::iterators::Pair { - pair.into_inner().next().expect("Cannot get first pair") + let mut inner = pair.clone().into_inner(); + if inner.is_empty() { + pair + } else { + inner.next().expect("Cannot get first pair") + } } #[cfg(test)] @@ -310,7 +358,7 @@ mod tests { instructions[0], Instruction::Open { path: Arg::Const { - text: "/tmp/test".to_string() + value: ConstType::Text("/tmp/test".to_string()) } } ); @@ -346,7 +394,7 @@ mod tests { path: Arg::Dynamic { name: "random_path".to_string(), args: vec![Arg::Const { - text: "/tmp".to_string() + value: ConstType::Text("/tmp".to_string()) }], } } @@ -383,7 +431,7 @@ mod tests { instructions[0], Instruction::Debug { text: Arg::Const { - text: "run task stub".to_string(), + value: ConstType::Text("run task stub".to_string()), } } ); @@ -431,7 +479,7 @@ mod tests { instructions[0], Instruction::Debug { text: Arg::Const { - text: "ping server".to_string(), + value: ConstType::Text("ping server".to_string()), } } ); @@ -440,7 +488,7 @@ mod tests { instructions[1], Instruction::Ping { server: Arg::Const { - text: "127.0.0.1:8080".to_string(), + value: ConstType::Text("127.0.0.1:8080".to_string()), }, } ); diff --git a/src/worker/script.rs b/src/worker/script.rs index 9af1d35..8a284f3 100644 --- a/src/worker/script.rs +++ b/src/worker/script.rs @@ -29,12 +29,13 @@ use std::mem; use crate::{Worker, WorkerError}; -use crate::script::ast::{Arg, Dist, Instruction, Node}; +use crate::script::ast::{Arg, ConstType, Dist, Instruction, Node}; #[derive(Debug, Clone)] enum RuntimeType { Int, Pointer, + Float, } #[derive(Debug, Clone)] @@ -308,19 +309,32 @@ impl ScriptWorker { let iptr = LLVMIntPtrTypeInContext(ctx.context, td); LLVMConstNull(iptr) }, - Arg::Const { text } => unsafe { - // The name of all constants created this way will be "const", - // which is ugly, but not a problem as LLVM modifies this to - // make sure uniqueness, i.e. they will be: - // - // @const, @const.1, @const.2, ... - // - // in the jited code. - LLVMBuildGlobalString( - ctx.builder, - format!("{text}\0").as_ptr() as *const _, - c"const".as_ptr() as *const _, - ) + Arg::Const { value } => unsafe { + match value { + ConstType::Text(text) => { + // The name of all constants created this way will be + // "const", which is ugly, but + // not a problem as LLVM modifies this to + // make sure uniqueness, i.e. they will be: + // + // @const, @const.1, @const.2, ... + // + // in the jited code. + LLVMBuildGlobalString( + ctx.builder, + format!("{text}\0").as_ptr() as *const _, + c"const".as_ptr() as *const _, + ) + } + ConstType::Int(value) => { + let i64t = LLVMInt64TypeInContext(ctx.context); + LLVMConstInt(i64t, value, 0) + } + ConstType::Float(value) => { + let double = LLVMDoubleTypeInContext(ctx.context); + LLVMConstReal(double, value) + } + } }, Arg::Var { name } => { *ctx.module_state.get(&name).expect("No variable") @@ -414,6 +428,7 @@ impl ScriptWorker { // get a type for main function let i64t = LLVMInt64TypeInContext(context); let boolt = LLVMInt1TypeInContext(context); + let float = LLVMFloatTypeInContext(context); let iptr = LLVMIntPtrTypeInContext(context, td); // Insert runtime functions into the module @@ -428,6 +443,7 @@ impl ScriptWorker { .map(|t| match t { RuntimeType::Pointer => iptr, RuntimeType::Int => i64t, + RuntimeType::Float => float, }) .collect::>(); @@ -435,6 +451,7 @@ impl ScriptWorker { match f.return_type { RuntimeType::Int => i64t, RuntimeType::Pointer => iptr, + RuntimeType::Float => float, }, function_args.as_mut_ptr(), f.param_count, From ac6eae02181768a42e2cdf25304e11313dd2cd94 Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Fri, 18 Sep 2026 15:50:29 +0200 Subject: [PATCH 3/5] Introduce new instructions and helpers Add listen, sleep and a new helper zipf --- src/script/ast.rs | 6 ++ src/script/grammar.peg | 5 ++ src/script/parser.rs | 11 ++++ src/worker/script.rs | 122 ++++++++++++++++++++++++++++++++++-- workloads/example.short.ber | 2 + 5 files changed, 142 insertions(+), 4 deletions(-) diff --git a/src/script/ast.rs b/src/script/ast.rs index 2bda8b8..156a8ea 100644 --- a/src/script/ast.rs +++ b/src/script/ast.rs @@ -35,6 +35,12 @@ pub enum Instruction { /// Send a message to a server at specified address Ping { server: Arg }, + + /// Listen on a specified number of endpoints from the lower boundary + Listen { lower: Arg, n: Arg }, + + /// Sleep for specified amount of time + Sleep { interval: Arg }, } #[derive(Debug, Clone, PartialEq)] diff --git a/src/script/grammar.peg b/src/script/grammar.peg index cb543bb..81bc480 100644 --- a/src/script/grammar.peg +++ b/src/script/grammar.peg @@ -16,6 +16,7 @@ randomString = { "random_string" } dynamicName = { randomPath | randomString + | zipf } dynamic = {dynamicName ~ args} @@ -39,6 +40,8 @@ port = { "port" } open = { "open" } ping = { "ping" } debug = { "debug" } +listen = { "listen" } +sleep = { "sleep" } funcName = { task @@ -47,6 +50,8 @@ funcName = { | open | ping | debug + | listen + | sleep } exp = { "exp" } diff --git a/src/script/parser.rs b/src/script/parser.rs index b16ed26..b51289b 100644 --- a/src/script/parser.rs +++ b/src/script/parser.rs @@ -246,6 +246,17 @@ fn build_ast_from_instr( server: args[0].clone(), }); } + Rule::listen => { + instr.push(Instruction::Listen { + lower: args[0].clone(), + n: args[1].clone(), + }); + } + Rule::sleep => { + instr.push(Instruction::Sleep { + interval: args[0].clone(), + }); + } unknown => panic!("Unknown instruction type {unknown:?}"), } } diff --git a/src/worker/script.rs b/src/worker/script.rs index 8a284f3..1404f1a 100644 --- a/src/worker/script.rs +++ b/src/worker/script.rs @@ -7,9 +7,13 @@ use std::{ fs::OpenOptions, io::Write, io::prelude::*, - net::{Shutdown, TcpStream}, + net::{Shutdown, TcpListener, TcpStream}, + os::fd::{AsRawFd, RawFd}, process::Command, - sync::Arc, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, thread, time, }; @@ -17,7 +21,7 @@ use std::sync::LazyLock; use log::{Level, debug, log_enabled, trace}; use rand::{Rng, distributions::Alphanumeric, thread_rng}; -use rand_distr::Exp; +use rand_distr::{Exp, Zipf}; use llvm::core::*; use llvm::execution_engine::*; @@ -139,8 +143,67 @@ pub unsafe extern "C" fn task(name: *const i8, args: *const i8) -> u64 { .unwrap() } +/// Listen on a specified number of ports starting from the lower boundary. +/// Open connections will live until the end of the block of work and will be +/// shutdown in cleanup instruction. +/// +/// # Safety +/// The caller must ensure the pointer is valid and points to a null +/// terminated C-string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn listen_on_ports(lower: u64, n: u64) -> u64 { + debug!("Listen {lower} {n}"); + let max_ports = + Arc::clone(&MAX_PORTS).fetch_add(n as usize, Ordering::Relaxed); + + let start_port = lower + max_ports as u64; + let _listeners: Vec<_> = (start_port..start_port + n) + .map(|port| { + let addr = format!("0.0.0.0:{port}"); + let listener = TcpListener::bind(&addr) + .expect("Couldn't listen on the specified address"); + let fd = listener.as_raw_fd(); + + trace!("Listen {addr}, fd {fd}"); + SOCKETS.with(|socks| socks.borrow_mut().push(fd)); + + thread::spawn(move || for _stream in listener.incoming() {}) + }) + .collect(); + + 0 +} + thread_local! { static POINTERS: RefCell> = const { RefCell::new(vec![]) }; + static SOCKETS: RefCell> = const { RefCell::new(vec![]) }; +} + +pub static MAX_PORTS: LazyLock> = + LazyLock::new(|| Arc::new(AtomicUsize::new(0))); + +/// Return a random integer from zipf distribution with specified +/// size and exponent. +/// +/// # Safety +/// The caller must ensure the pointer is valid and points to a null +/// terminated C-string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zipf(size: u64, exp: f64) -> u64 { + debug!("zipf {size} {exp}"); + thread_rng().sample(Zipf::new(size, exp).unwrap()) as u64 +} + +/// Sleeps for specified amount of time. +/// +/// # Safety +/// The caller must ensure the pointer is valid and points to a null +/// terminated C-string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sleep(interval: f64) -> u64 { + debug!("Sleep {interval}"); + thread::sleep(time::Duration::from_secs_f64(interval)); + 0 } /// Return a randomly generated string. @@ -185,15 +248,29 @@ pub unsafe extern "C" fn random_path(base: *const i8) -> *const i8 { /// # Safety #[unsafe(no_mangle)] pub unsafe extern "C" fn cleanup(_: *const i8) -> u64 { + debug!("Cleanup"); POINTERS.with(|ps| { let mut vec = ps.borrow_mut(); for p in vec.as_slice() { + trace!("Cleanup {:?}", p); let _ = unsafe { CString::from_raw(*p) }; } vec.clear(); }); + SOCKETS.with(|socks| { + let mut vec = socks.borrow_mut(); + for fd in vec.as_slice() { + trace!("Shutdown {fd}"); + unsafe { + libc::shutdown(*fd, libc::SHUT_RD); + } + } + + vec.clear(); + }); + 0 } @@ -246,6 +323,24 @@ pub static RUNTIME: LazyLock> = return_type: RuntimeType::Int, }, ), + ( + "listen".to_string(), + RuntimeFunc { + func: listen_on_ports as *const () as usize, + param_count: 2, + param_types: &[RuntimeType::Int, RuntimeType::Int], + return_type: RuntimeType::Int, + }, + ), + ( + "sleep".to_string(), + RuntimeFunc { + func: sleep as *const () as usize, + param_count: 1, + param_types: &[RuntimeType::Float], + return_type: RuntimeType::Pointer, + }, + ), // dynamic values ( "random_path".to_string(), @@ -265,6 +360,15 @@ pub static RUNTIME: LazyLock> = return_type: RuntimeType::Pointer, }, ), + ( + "zipf".to_string(), + RuntimeFunc { + func: zipf as *const () as usize, + param_count: 2, + param_types: &[RuntimeType::Int, RuntimeType::Float], + return_type: RuntimeType::Pointer, + }, + ), // utils ( "cleanup".to_string(), @@ -373,7 +477,7 @@ impl ScriptWorker { *func, args_ptr.as_mut_ptr(), args.len().try_into().unwrap(), - c"{name}".as_ptr() as *const _, + c"const".as_ptr() as *const _, ) } } @@ -550,6 +654,16 @@ impl ScriptWorker { Self::jit_instruction(c"debug", vec![text], &ctx); "debug" } + + Instruction::Listen { lower, n } => { + Self::jit_instruction(c"listen", vec![lower, n], &ctx); + "listen" + } + + Instruction::Sleep { interval } => { + Self::jit_instruction(c"sleep", vec![interval], &ctx); + "sleep" + } }; // Populate the global mapping with observed runtime functions diff --git a/workloads/example.short.ber b/workloads/example.short.ber index b4fcafa..0b58070 100644 --- a/workloads/example.short.ber +++ b/workloads/example.short.ber @@ -10,4 +10,6 @@ main (workers = 1) { open("/tmp/test"); debug("ping server"); ping("127.0.0.1:8080"); + debug("listen on random ports starting from 8081"); + listen(8081, zipf(200, 1.4)); } From 12106fa73743d452ddb08ca7ce515b79f75239aa Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Fri, 18 Sep 2026 15:51:34 +0200 Subject: [PATCH 4/5] Cleanup --- src/worker/script.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/worker/script.rs b/src/worker/script.rs index 1404f1a..c787ec1 100644 --- a/src/worker/script.rs +++ b/src/worker/script.rs @@ -79,7 +79,6 @@ pub unsafe extern "C" fn debug(text: *const i8) -> u64 { /// terminated C-string. #[unsafe(no_mangle)] pub unsafe extern "C" fn open_file(path: *const i8) -> u64 { - //let path = unsafe { CString::from_raw(path as *mut i8) }; let path = unsafe { CStr::from_ptr(path) }; debug!("Open path {:?}", path); let mut file = OpenOptions::new() From 567d8e84122834635f2ea33c50d30fbdfd7fbb6f Mon Sep 17 00:00:00 2001 From: Dmitrii Dolgov <9erthalion6@gmail.com> Date: Fri, 18 Sep 2026 15:51:42 +0200 Subject: [PATCH 5/5] Update main example --- workloads/example.ber | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/workloads/example.ber b/workloads/example.ber index 7b5a690..cabf881 100644 --- a/workloads/example.ber +++ b/workloads/example.ber @@ -4,15 +4,18 @@ machine { } // Named work block -main (workers = 2, duration = 10) { +main (workers = 1, duration = 10) { // Anon work block with only one unit. // task(name) -- spawn a process with specified name // debug(text) -- log with DEBUG level // open(path) -- open file by path, create if needed and write something to it debug("run task stub"); task(stub, random_string()); + debug(random_string()); debug("open file /tmp/test"); open("/tmp/test"); + debug("listen on random ports starting from 8081"); + listen(8081, zipf(10, 1.4)); } : exp { // If no distribution provided, do the unit only once. rate = 10.0;