Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions challenges/term-challenge/src/evaluation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub fn evaluate(input: EvaluationInput) -> EvaluationOutput {
valid: true,
message,
metrics: None,
details: None,
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/challenge-sdk-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
bincode = { version = "1.3", default-features = false }

[features]
default = []
large-arena = [] # 4 MiB arena instead of the default 1 MiB
11 changes: 10 additions & 1 deletion crates/challenge-sdk-wasm/src/alloc_impl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use core::cell::UnsafeCell;

const ARENA_SIZE: usize = 4 * 1024 * 1024; // 4 MiB
#[cfg(feature = "large-arena")]
const ARENA_SIZE: usize = 4 * 1024 * 1024;

#[cfg(not(feature = "large-arena"))]
const ARENA_SIZE: usize = 1024 * 1024;

struct BumpAllocator {
arena: UnsafeCell<[u8; ARENA_SIZE]>,
Expand Down Expand Up @@ -50,6 +54,11 @@ pub extern "C" fn alloc(size: i32) -> i32 {
}
}

#[no_mangle]
pub extern "C" fn allocate(size: i32, _align: i32) -> i32 {
alloc(size)
}

pub fn sdk_alloc(size: usize) -> *mut u8 {
ALLOCATOR.alloc(size, 8)
}
Expand Down
32 changes: 32 additions & 0 deletions crates/challenge-sdk-wasm/src/host_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,35 @@ pub fn host_random_seed(buf: &mut [u8]) -> Result<(), i32> {
}
Ok(())
}

#[link(wasm_import_module = "platform_sandbox")]
extern "C" {
fn sandbox_exec(req_ptr: i32, req_len: i32, resp_ptr: i32, resp_len: i32) -> i32;
fn get_timestamp() -> i64;
fn log_message(level: i32, msg_ptr: i32, msg_len: i32);
}

pub fn host_sandbox_exec(request: &[u8]) -> Result<Vec<u8>, i32> {
let mut response_buf = vec![0u8; 262144];
let status = unsafe {
sandbox_exec(
request.as_ptr() as i32,
request.len() as i32,
response_buf.as_mut_ptr() as i32,
response_buf.len() as i32,
)
};
if status < 0 {
return Err(status);
}
response_buf.truncate(status as usize);
Ok(response_buf)
}

pub fn host_get_timestamp() -> i64 {
unsafe { get_timestamp() }
}

pub fn host_log(level: u8, msg: &str) {
unsafe { log_message(level as i32, msg.as_ptr() as i32, msg.len() as i32) }
}
37 changes: 36 additions & 1 deletion crates/challenge-sdk-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ pub mod term_types;
pub mod types;

pub use term_types::*;
pub use types::{
score_f64_scaled, SandboxExecRequest, SandboxExecResponse, TaskDefinition, TaskResult,
TermEvaluationParams,
};
pub use types::{EvaluationInput, EvaluationOutput};

pub trait Challenge {
Expand All @@ -23,6 +27,12 @@ pub trait Challenge {
fn setup_environment(&self, _config: &[u8]) -> bool {
true
}

fn tasks(&self) -> alloc::vec::Vec<u8> {
alloc::vec::Vec::new()
}

fn configure(&self, _config: &[u8]) {}
}

/// Pack a pointer and length into a single i64 value.
Expand All @@ -36,7 +46,7 @@ pub fn pack_ptr_len(ptr: i32, len: i32) -> i64 {

/// Register a [`Challenge`] implementation and export the required WASM ABI
/// functions (`evaluate`, `validate`, `get_name`, `get_version`,
/// `generate_task`, `setup_environment`, and `alloc`).
/// `generate_task`, `setup_environment`, `get_tasks`, `configure`, and `alloc`).
///
/// The type must provide a `const fn new() -> Self` constructor so that the
/// challenge instance can be placed in a `static`.
Expand Down Expand Up @@ -178,5 +188,30 @@ macro_rules! register_challenge {
0
}
}

#[no_mangle]
pub extern "C" fn get_tasks() -> i64 {
let output = <$ty as $crate::Challenge>::tasks(&_CHALLENGE);
if output.is_empty() {
return $crate::pack_ptr_len(0, 0);
}
let ptr = $crate::alloc_impl::sdk_alloc(output.len());
if ptr.is_null() {
return $crate::pack_ptr_len(0, 0);
}
unsafe {
core::ptr::copy_nonoverlapping(output.as_ptr(), ptr, output.len());
}
$crate::pack_ptr_len(ptr as i32, output.len() as i32)
}

#[no_mangle]
pub extern "C" fn configure(config_ptr: i32, config_len: i32) -> i32 {
let slice = unsafe {
core::slice::from_raw_parts(config_ptr as *const u8, config_len as usize)
};
<$ty as $crate::Challenge>::configure(&_CHALLENGE, slice);
1
}
};
}
85 changes: 85 additions & 0 deletions crates/challenge-sdk-wasm/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct EvaluationOutput {
pub valid: bool,
pub message: String,
pub metrics: Option<Vec<u8>>,
pub details: Option<Vec<u8>>,
}

impl EvaluationOutput {
Expand All @@ -26,6 +27,7 @@ impl EvaluationOutput {
valid: true,
message: String::from(message),
metrics: None,
details: None,
}
}

Expand All @@ -35,11 +37,94 @@ impl EvaluationOutput {
valid: false,
message: String::from(message),
metrics: None,
details: None,
}
}

pub fn with_metrics(mut self, metrics: Vec<u8>) -> Self {
self.metrics = Some(metrics);
self
}

pub fn with_details(mut self, details: Vec<u8>) -> Self {
self.details = Some(details);
self
}
}

pub fn score_f64_scaled(value: f64) -> i64 {
(value * 10_000.0) as i64
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TaskDefinition {
pub task_id: String,
pub description: String,
pub command: String,
pub expected_output: Option<String>,
pub timeout_ms: u64,
pub scoring_criteria: Vec<u8>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SandboxExecRequest {
pub command: String,
pub args: Vec<String>,
pub env_vars: Vec<(String, String)>,
pub working_dir: Option<String>,
pub stdin: Option<Vec<u8>>,
pub timeout_ms: u64,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SandboxExecResponse {
pub exit_code: i32,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
pub duration_ms: u64,
}

impl SandboxExecResponse {
pub fn is_success(&self) -> bool {
self.exit_code == 0
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TaskResult {
pub task_id: String,
pub passed: bool,
pub score: f64,
pub output: Option<String>,
pub metrics: Option<Vec<u8>>,
}

impl TaskResult {
pub fn success(task_id: &str, score: f64) -> Self {
Self {
task_id: String::from(task_id),
passed: true,
score,
output: None,
metrics: None,
}
}

pub fn failure(task_id: &str, output: &str) -> Self {
Self {
task_id: String::from(task_id),
passed: false,
score: 0.0,
output: Some(String::from(output)),
metrics: None,
}
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TermEvaluationParams {
pub challenge_id: String,
pub task_definitions: Vec<TaskDefinition>,
pub timeout_ms: u64,
pub environment_config: Option<Vec<u8>>,
}