Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cdp): the crab comes for us all #26997

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
18 changes: 16 additions & 2 deletions hogvm/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# HogVM

A HogVM is a 🦔 that runs Hog bytecode. It's purpose is to locally evaluate Hog/QL expressions against any object.
A HogVM is a 🦔 that runs Hog bytecode. Its purpose is to locally evaluate Hog/QL expressions against any object.

## Hog bytecode

Expand All @@ -19,7 +19,7 @@ call('arg', 'another') # [_H, op.STRING, "another", op.STRING, "arg", op.CALL_GL

## Compliant implementation

The `python/execute.py` function in this folder acts as the reference implementation in case of disputes.
The `rust/hogvm/src/execute.rs` function in this folder acts as the reference implementation in case of disputes.

### Operations

Expand Down Expand Up @@ -84,3 +84,17 @@ In Hog/QL equality comparisons, `null` is treated as any other variable. Its pre
```

Nulls are just ignored in `concat`

## Building and Testing the Rust Implementation

To build the Rust implementation of HogVM, navigate to the `rust/hogvm` directory and run:

```bash
cargo build --release
```

To run the tests for the Rust implementation, navigate to the `hogvm` directory and run:

```bash
./test.sh
```
4 changes: 2 additions & 2 deletions hogvm/test.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ SKIP_COMPILEDJS_FILES=("crypto.hog")
cd "$(dirname "$0")"

# Build the project
cd typescript
pnpm run build
cd ../rust
cargo build --release
cd ..

# Navigate to the project root (parent directory of 'hogvm')
Expand Down
1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ members = [
"cyclotron-janitor",
"cyclotron-fetch",
"cymbal",
"hogvm",
]

[workspace.lints.rust]
Expand Down
10 changes: 10 additions & 0 deletions rust/hogvm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "hogvm"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
87 changes: 87 additions & 0 deletions rust/hogvm/src/execute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;

#[derive(Debug, Serialize, Deserialize)]
pub struct ExecOptions {
pub globals: Option<HashMap<String, serde_json::Value>>,
pub functions: Option<HashMap<String, fn(Vec<serde_json::Value>) -> serde_json::Value>>,
pub async_functions: Option<HashMap<String, fn(Vec<serde_json::Value>) -> std::pin::Pin<Box<dyn std::future::Future<Output = serde_json::Value>>>>>,
pub timeout: Option<u64>,
pub max_async_steps: Option<u64>,
pub memory_limit: Option<u64>,
}

impl Default for ExecOptions {
fn default() -> Self {
ExecOptions {
globals: None,
functions: None,
async_functions: None,
timeout: Some(5000),
max_async_steps: Some(100),
memory_limit: Some(64 * 1024 * 1024),
}
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ExecResult {
pub result: serde_json::Value,
pub finished: bool,
pub error: Option<String>,
pub async_function_name: Option<String>,
pub async_function_args: Option<Vec<serde_json::Value>>,
}

#[derive(Debug, Error)]
pub enum ExecError {
#[error("Execution error: {0}")]
ExecutionError(String),
}

pub fn exec_sync(bytecode: &[u8], options: &ExecOptions) -> Result<ExecResult, ExecError> {
let result = exec(bytecode, options)?;
if result.finished {
Ok(result)
} else {
Err(ExecError::ExecutionError("Unexpected async function call".to_string()))
}
}

pub async fn exec_async(bytecode: &[u8], options: &ExecOptions) -> Result<ExecResult, ExecError> {
let mut vm_state: Option<ExecResult> = None;
loop {
let result = exec(bytecode, options)?;
if result.finished {
return Ok(result);
}
if let Some(async_function_name) = &result.async_function_name {
if let Some(async_function) = options.async_functions.as_ref().and_then(|f| f.get(async_function_name)) {
let async_result = async_function(result.async_function_args.clone().unwrap_or_default()).await;
vm_state = Some(ExecResult {
result: async_result,
finished: false,
error: None,
async_function_name: None,
async_function_args: None,
});
} else {
return Err(ExecError::ExecutionError(format!("Invalid async function call: {}", async_function_name)));
}
} else {
return Err(ExecError::ExecutionError("Invalid async function call".to_string()));
}
}
}

fn exec(bytecode: &[u8], options: &ExecOptions) -> Result<ExecResult, ExecError> {
// Placeholder implementation
Ok(ExecResult {
result: serde_json::Value::Null,
finished: true,
error: None,
async_function_name: None,
async_function_args: None,
})
}
41 changes: 41 additions & 0 deletions rust/hogvm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio::runtime::Runtime;

mod execute;
mod operation;
mod utils;

#[derive(Debug, Serialize, Deserialize)]
pub struct HogVM {
bytecode: Vec<u8>,
globals: Option<serde_json::Value>,
}

impl HogVM {
pub fn new(bytecode: Vec<u8>, globals: Option<serde_json::Value>) -> Self {
HogVM { bytecode, globals }
}

pub fn execute(&self) -> Result<serde_json::Value, HogVMError> {
let rt = Runtime::new().map_err(|e| HogVMError::RuntimeError(e.to_string()))?;
rt.block_on(async { self.execute_async().await })
}

pub async fn execute_async(&self) -> Result<serde_json::Value, HogVMError> {
let options = execute::ExecOptions {
globals: self.globals.clone(),
..Default::default()
};
let result = execute::exec_async(&self.bytecode, &options).await?;
Ok(result.result)
}
}

#[derive(Debug, Error)]
pub enum HogVMError {
#[error("Runtime error: {0}")]
RuntimeError(String),
#[error("Execution error: {0}")]
ExecutionError(String),
}
189 changes: 189 additions & 0 deletions rust/hogvm/src/operation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub enum Operation {
GetGlobal,
CallGlobal,
And,
Or,
Not,
Plus,
Minus,
Multiply,
Divide,
Mod,
Eq,
NotEq,
Gt,
GtEq,
Lt,
LtEq,
Like,
ILike,
NotLike,
NotILike,
In,
NotIn,
Regex,
NotRegex,
IRegex,
NotIRegex,
InCohort,
NotInCohort,
True,
False,
Null,
String,
Integer,
Float,
Pop,
GetLocal,
SetLocal,
Return,
Jump,
JumpIfFalse,
DeclareFn,
Dict,
Array,
Tuple,
GetProperty,
SetProperty,
JumpIfStackNotNull,
GetPropertyNullish,
Throw,
Try,
PopTry,
Callable,
Closure,
CallLocal,
GetUpvalue,
SetUpvalue,
CloseUpvalue,
}

impl Operation {
pub fn from_str(s: &str) -> Option<Operation> {
match s {
"GET_GLOBAL" => Some(Operation::GetGlobal),
"CALL_GLOBAL" => Some(Operation::CallGlobal),
"AND" => Some(Operation::And),
"OR" => Some(Operation::Or),
"NOT" => Some(Operation::Not),
"PLUS" => Some(Operation::Plus),
"MINUS" => Some(Operation::Minus),
"MULTIPLY" => Some(Operation::Multiply),
"DIVIDE" => Some(Operation::Divide),
"MOD" => Some(Operation::Mod),
"EQ" => Some(Operation::Eq),
"NOT_EQ" => Some(Operation::NotEq),
"GT" => Some(Operation::Gt),
"GT_EQ" => Some(Operation::GtEq),
"LT" => Some(Operation::Lt),
"LT_EQ" => Some(Operation::LtEq),
"LIKE" => Some(Operation::Like),
"ILIKE" => Some(Operation::ILike),
"NOT_LIKE" => Some(Operation::NotLike),
"NOT_ILIKE" => Some(Operation::NotILike),
"IN" => Some(Operation::In),
"NOT_IN" => Some(Operation::NotIn),
"REGEX" => Some(Operation::Regex),
"NOT_REGEX" => Some(Operation::NotRegex),
"IREGEX" => Some(Operation::IRegex),
"NOT_IREGEX" => Some(Operation::NotIRegex),
"IN_COHORT" => Some(Operation::InCohort),
"NOT_IN_COHORT" => Some(Operation::NotInCohort),
"TRUE" => Some(Operation::True),
"FALSE" => Some(Operation::False),
"NULL" => Some(Operation::Null),
"STRING" => Some(Operation::String),
"INTEGER" => Some(Operation::Integer),
"FLOAT" => Some(Operation::Float),
"POP" => Some(Operation::Pop),
"GET_LOCAL" => Some(Operation::GetLocal),
"SET_LOCAL" => Some(Operation::SetLocal),
"RETURN" => Some(Operation::Return),
"JUMP" => Some(Operation::Jump),
"JUMP_IF_FALSE" => Some(Operation::JumpIfFalse),
"DECLARE_FN" => Some(Operation::DeclareFn),
"DICT" => Some(Operation::Dict),
"ARRAY" => Some(Operation::Array),
"TUPLE" => Some(Operation::Tuple),
"GET_PROPERTY" => Some(Operation::GetProperty),
"SET_PROPERTY" => Some(Operation::SetProperty),
"JUMP_IF_STACK_NOT_NULL" => Some(Operation::JumpIfStackNotNull),
"GET_PROPERTY_NULLISH" => Some(Operation::GetPropertyNullish),
"THROW" => Some(Operation::Throw),
"TRY" => Some(Operation::Try),
"POP_TRY" => Some(Operation::PopTry),
"CALLABLE" => Some(Operation::Callable),
"CLOSURE" => Some(Operation::Closure),
"CALL_LOCAL" => Some(Operation::CallLocal),
"GET_UPVALUE" => Some(Operation::GetUpvalue),
"SET_UPVALUE" => Some(Operation::SetUpvalue),
"CLOSE_UPVALUE" => Some(Operation::CloseUpvalue),
_ => None,
}
}

pub fn to_str(&self) -> &str {
match self {
Operation::GetGlobal => "GET_GLOBAL",
Operation::CallGlobal => "CALL_GLOBAL",
Operation::And => "AND",
Operation::Or => "OR",
Operation::Not => "NOT",
Operation::Plus => "PLUS",
Operation::Minus => "MINUS",
Operation::Multiply => "MULTIPLY",
Operation::Divide => "DIVIDE",
Operation::Mod => "MOD",
Operation::Eq => "EQ",
Operation::NotEq => "NOT_EQ",
Operation::Gt => "GT",
Operation::GtEq => "GT_EQ",
Operation::Lt => "LT",
Operation::LtEq => "LT_EQ",
Operation::Like => "LIKE",
Operation::ILike => "ILIKE",
Operation::NotLike => "NOT_LIKE",
Operation::NotILike => "NOT_ILIKE",
Operation::In => "IN",
Operation::NotIn => "NOT_IN",
Operation::Regex => "REGEX",
Operation::NotRegex => "NOT_REGEX",
Operation::IRegex => "IREGEX",
Operation::NotIRegex => "NOT_IREGEX",
Operation::InCohort => "IN_COHORT",
Operation::NotInCohort => "NOT_IN_COHORT",
Operation::True => "TRUE",
Operation::False => "FALSE",
Operation::Null => "NULL",
Operation::String => "STRING",
Operation::Integer => "INTEGER",
Operation::Float => "FLOAT",
Operation::Pop => "POP",
Operation::GetLocal => "GET_LOCAL",
Operation::SetLocal => "SET_LOCAL",
Operation::Return => "RETURN",
Operation::Jump => "JUMP",
Operation::JumpIfFalse => "JUMP_IF_FALSE",
Operation::DeclareFn => "DECLARE_FN",
Operation::Dict => "DICT",
Operation::Array => "ARRAY",
Operation::Tuple => "TUPLE",
Operation::GetProperty => "GET_PROPERTY",
Operation::SetProperty => "SET_PROPERTY",
Operation::JumpIfStackNotNull => "JUMP_IF_STACK_NOT_NULL",
Operation::GetPropertyNullish => "GET_PROPERTY_NULLISH",
Operation::Throw => "THROW",
Operation::Try => "TRY",
Operation::PopTry => "POP_TRY",
Operation::Callable => "CALLABLE",
Operation::Closure => "CLOSURE",
Operation::CallLocal => "CALL_LOCAL",
Operation::GetUpvalue => "GET_UPVALUE",
Operation::SetUpvalue => "SET_UPVALUE",
Operation::CloseUpvalue => "CLOSE_UPVALUE",
}
}
}
Loading
Loading