This commit is contained in:
Steve Biedermann 2024-12-12 13:53:56 +01:00
parent b89cf36f20
commit 0247820127
13 changed files with 367 additions and 2839 deletions

2616
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,11 @@ edition = "2021"
[dependencies]
anyhow = "1.0.94"
byteorder = "1.5.0"
clap = { version = "4.5.22", features = ["derive"] }
common = { version = "0.1.0", path = "common" }
extism = "1.9.1"
serde = { version = "1.0.215", features = ["derive"] }
wasm-bindgen = "0.2.97"
num-derive = "0.4.2"
num-traits = "0.2.19"
[workspace]
members = ["common", "std_cpu"]
members = ["common"]

View File

@ -4,5 +4,6 @@ version = "0.1.0"
edition = "2021"
[dependencies]
extism-pdk = "1.3.0"
serde = { version = "1.0.215", features = ["derive"] }
num-derive = "0.4.2"
num-traits = "0.2.19"
thiserror = "2.0.6"

View File

@ -1,60 +1,42 @@
use extism_pdk::*;
use serde::{Deserialize, Serialize};
use num_traits::Unsigned;
use thiserror::Error;
#[derive(Serialize, Deserialize, ToBytes, FromBytes)]
#[encoding(Json)]
pub struct Init {
pub verbose: bool,
pub mem_size: usize,
pub registers: usize,
#[derive(Error, Debug)]
pub enum ExecutionError {
#[error("Trying to access invalid memory location!")]
InvalidMemoryLocation,
}
#[derive(Serialize, Deserialize, ToBytes, FromBytes)]
#[encoding(Json)]
pub struct SetVerbose(pub bool);
#[derive(Serialize, Deserialize, ToBytes, FromBytes)]
#[encoding(Json)]
pub struct SetMemory {
pub offset: usize,
pub memory: Vec<u8>,
}
#[derive(Serialize, Deserialize, ToBytes, FromBytes)]
#[encoding(Json)]
pub enum RunResult {
Success,
#[derive(Debug)]
pub struct CpuStats {
pub cycles: usize,
pub memory_access_score: usize,
}
pub trait CpuTrait {
fn new() -> Self
where
Self: Sized;
type Size: Unsigned;
fn set_verbose(&mut self, verbose: bool);
fn load_memory(&mut self, memory: &[u8]);
fn load_memory(&mut self, address: Self::Size, memory: &[Self::Size]);
fn execute(&mut self, run_mode: RunMode);
fn execute(&mut self, run_mode: RunMode) -> Result<CpuStats, ExecutionError>;
fn get_registers(&self) -> &[u16];
fn get_registers(&self) -> &[Self::Size];
fn get_memory(&self) -> &[u8];
fn get_memory(&self) -> &[Self::Size];
}
#[derive(Serialize, Deserialize, ToBytes, FromBytes)]
#[encoding(Json)]
#[derive(Debug)]
pub enum RunMode {
Run, // Run to completion
Debug(DebugMode), // Debug mode
StepOver, // Step over calls
StepInto, // Step into calls
StepOut, // Step out of calls
RunFor(usize), // Run for a specific number of cycles
RunFor(isize), // Run for a specific number of cycles
}
#[derive(Serialize, Deserialize, ToBytes, FromBytes)]
#[encoding(Json)]
#[derive(Debug)]
pub enum DebugMode {
All, // Break on any breakpoint
Code, // Break on code breakpoints

View File

@ -1,41 +0,0 @@
use common::{RunMode, RunResult, SetVerbose};
use extism::*;
pub struct WasmCpu {
plugin: Plugin,
}
impl WasmCpu {
pub fn new(manifest: Manifest) -> anyhow::Result<Self> {
let plugin = PluginBuilder::new(manifest).build()?;
Ok(WasmCpu { plugin })
}
pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> anyhow::Result<Self> {
let manifest = Manifest::new([Wasm::file(path)]);
Self::new(manifest)
}
pub fn from_url(url: &str) -> anyhow::Result<Self> {
let manifest = Manifest::new([Wasm::url(url)]);
Self::new(manifest)
}
pub fn set_verbose(&mut self, verbose: bool) {
self.plugin
.call::<SetVerbose, ()>("set_verbose", SetVerbose(verbose))
.unwrap()
}
pub fn load_memory(&mut self, memory: &[u8]) {
self.plugin
.call::<&[u8], ()>("load_memory", memory)
.unwrap()
}
pub fn execute(&mut self, run_mode: RunMode) -> RunResult {
self.plugin
.call::<RunMode, RunResult>("execute", run_mode)
.unwrap()
}
}

View File

@ -1,19 +1,73 @@
use std::path::PathBuf;
use hecate::WasmCpu;
use byteorder::ReadBytesExt;
use clap::Parser;
use common::{CpuTrait, RunMode};
use native::{Bytecode, NativeCpu};
use std::io::BufReader;
mod native;
#[derive(Parser)]
struct Args {
cpu: PathBuf,
memory: PathBuf,
memory: Option<PathBuf>,
#[arg(short, long)]
verbose: bool,
}
fn main() -> anyhow::Result<()> {
let Args { cpu, memory } = Args::parse();
let mut cpu = WasmCpu::from_path(cpu)?;
let mem = std::fs::read(memory)?;
cpu.load_memory(&mem);
let Args { memory, verbose } = Args::parse();
let memory = memory
.and_then(|p| {
std::fs::File::open(p)
.and_then(|file| {
let mut reader = BufReader::new(file);
let mut values = Vec::new();
let result = reader.read_u32_into::<byteorder::LittleEndian>(&mut values);
result.map(|_| values)
})
.ok()
})
.unwrap_or_else(|| {
vec![
Bytecode::Nop as u32,
Bytecode::Nop as u32,
Bytecode::Nop as u32,
Bytecode::Nop as u32,
Bytecode::Set as u32,
0x00,
42,
Bytecode::Set as u32,
0x01,
10,
Bytecode::Add as u32,
0x00,
0x01,
Bytecode::Store as u32,
0x40,
0x00,
Bytecode::Inspect as u32,
0x40,
0xFF,
]
});
let mut cpu = NativeCpu::new(1024 * 1024, 4);
cpu.set_verbose(verbose);
cpu.load_memory(0, &memory);
let stats = cpu.execute(RunMode::Run)?;
if verbose {
println!(
"Total cycles: {}, Memory Access score: {}",
stats.cycles, stats.memory_access_score
);
}
println!("Total Score: {}", stats.cycles + stats.memory_access_score);
Ok(())
}

266
src/native.rs Normal file
View File

@ -0,0 +1,266 @@
use common::{CpuStats, CpuTrait, DebugMode, ExecutionError, RunMode};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
#[derive(Debug, PartialEq, PartialOrd, Copy, Clone, Hash, Eq, Ord, FromPrimitive, ToPrimitive)]
#[repr(usize)]
pub enum Bytecode {
Nop = 0x00,
Set = 0x01,
Load = 0x02,
Store = 0x03,
Add = 0x12,
Inspect = 0xFE,
Halt = 0xFF,
}
#[derive(Debug)]
pub struct NativeCpu {
memory: Vec<u32>,
memory_score_multiplier: usize,
registers: Vec<u32>,
pc: u32, // Program counter
// sp: u32, // Stack pointer
// call_stack: Vec<u32>, // Call stack for function calls
verbose: bool,
cycles: usize, // Performance counter for cycles
memory_access_score: usize, // Tracks the performance score
l1_start: u32,
l1_size: u32,
l1_score_multiplier: usize,
l2_start: u32,
l2_size: u32,
l2_score_multiplier: usize,
l3_start: u32,
l3_size: u32,
l3_score_multiplier: usize,
}
impl CpuTrait for NativeCpu {
type Size = u32;
fn set_verbose(&mut self, verbose: bool) {
self.verbose = verbose;
}
fn load_memory(&mut self, address: Self::Size, memory: &[Self::Size]) {
let len = self.memory.len().min(memory.len());
self.memory[address as usize..address as usize + len].copy_from_slice(&memory[..len]);
}
fn execute(&mut self, run_mode: RunMode) -> Result<CpuStats, ExecutionError> {
match run_mode {
RunMode::Run => {
self.run(-1)?;
}
RunMode::Debug(debug_mode) => match debug_mode {
DebugMode::All => println!("Debugging with all breakpoints"),
DebugMode::Code => println!("Debugging with code breakpoints"),
DebugMode::Data => println!("Debugging with data breakpoints"),
},
RunMode::StepOver => println!("Stepping over"),
RunMode::StepInto => println!("Stepping into"),
RunMode::StepOut => println!("Stepping out"),
RunMode::RunFor(cycles) => {
self.run(cycles)?;
}
}
Ok(CpuStats {
cycles: self.cycles,
memory_access_score: self.memory_access_score,
})
}
fn get_registers(&self) -> &[Self::Size] {
&self.registers
}
fn get_memory(&self) -> &[Self::Size] {
&self.memory
}
}
impl NativeCpu {
pub fn new(memory: u64, registers: u8) -> Self {
Self {
memory: vec![0; memory as usize],
memory_score_multiplier: 10,
registers: vec![0; registers as usize],
pc: 0,
// sp: 1024 * 1024, // Stack grows downward
// call_stack: Vec::new(),
verbose: false,
cycles: 0,
memory_access_score: 0,
l1_start: 0,
l1_size: 64 * 1024, // 64 KB L1 cache
l1_score_multiplier: 1,
l2_start: 64 * 1024,
l2_size: 256 * 1024, // 256 KB L2 cache
l2_score_multiplier: 2,
l3_start: 320 * 1024,
l3_size: 1 * 1024 * 1024, // 1 MB L3 cache
l3_score_multiplier: 5,
}
}
fn run(&mut self, cycles: isize) -> Result<CpuStats, ExecutionError> {
let mut executed = 0;
while (cycles < 0 || executed < cycles) && (self.pc as usize) < self.memory.len() {
let opcode = self.read_memory(self.pc)?;
self.pc += 1;
match Bytecode::from_u32(opcode) {
Some(Bytecode::Set) => {
let reg = self.read_memory(self.pc)?;
self.pc += 1;
let imm = self.read_memory(self.pc)?;
self.pc += 1;
if self.verbose {
println!("SET R{}, {}", reg, imm);
}
self.registers[reg as usize] = imm;
self.cycles += 2;
}
Some(Bytecode::Load) => {
let reg = self.read_memory(self.pc)?;
self.pc += 1;
let addr = self.read_memory(self.pc)?;
self.pc += 1;
if self.verbose {
println!("LOAD R{}, @{:#02x}", reg, addr);
}
self.registers[reg as usize] = self.read_memory(addr)?;
self.cycles += 2;
}
Some(Bytecode::Store) => {
let addr = self.read_memory(self.pc)?;
self.pc += 1;
let reg = self.read_memory(self.pc)?;
self.pc += 1;
if self.verbose {
println!("STORE @{:#02x}, R{}", addr, reg);
}
self.write_memory(addr, self.registers[reg as usize])?;
self.cycles += 2;
}
Some(Bytecode::Inspect) => {
let addr = self.read_memory(self.pc)?;
self.pc += 1;
println!("INSPECT @{:#02x} = {}", addr, self.read_memory(addr)?);
}
Some(Bytecode::Add) => {
let reg1 = self.read_memory(self.pc)?;
self.pc += 1;
let reg2 = self.read_memory(self.pc)?;
self.pc += 1;
if self.verbose {
println!(
"ADD R{}({}), R{}({})",
reg1,
self.registers[reg1 as usize],
reg2,
self.registers[reg2 as usize]
);
}
self.registers[reg1 as usize] =
self.registers[reg1 as usize].wrapping_add(self.registers[reg2 as usize]);
self.cycles += 1;
}
Some(Bytecode::Halt) => {
if self.verbose {
println!("HALT");
}
break;
}
Some(Bytecode::Nop) => {
if self.verbose {
println!("NOP");
}
}
None => {
println!("Unknown opcode: {:X}", opcode);
break;
}
}
executed += 1;
}
Ok(CpuStats {
cycles: self.cycles,
memory_access_score: self.memory_access_score,
})
}
fn get_access_score(&self, address: u32) -> usize {
if address >= self.l1_start && address < self.l1_start + self.l1_size {
self.l1_score_multiplier
} else if address >= self.l2_start && address < self.l2_start + self.l2_size {
self.l2_score_multiplier
} else if address >= self.l3_start && address < self.l3_start + self.l3_size {
self.l3_score_multiplier
} else {
self.memory_score_multiplier
}
}
fn valid_address(&self, address: u32) -> Result<(), ExecutionError> {
if (address as usize) < self.memory.len() {
Ok(())
} else {
Err(ExecutionError::InvalidMemoryLocation)
}
}
fn read_memory(&mut self, address: u32) -> Result<u32, ExecutionError> {
self.valid_address(address)?;
self.memory_access_score += self.get_access_score(address);
Ok(self.memory[address as usize])
}
fn write_memory(&mut self, address: u32, value: u32) -> Result<(), ExecutionError> {
self.valid_address(address)?;
self.memory[address as usize] = value;
Ok(())
}
// fn push_to_stack(&mut self, value: u32) -> Result<(), ExecutionError> {
// self.valid_address(self.sp)?;
// self.sp -= 1;
// self.memory[self.sp as usize] = value;
// Ok(())
// }
// fn pop_from_stack(&mut self) -> Result<u32, ExecutionError> {
// self.valid_address(self.sp)?;
// let value = self.memory[self.sp as usize];
// self.sp += 1;
// Ok(value)
// }
// fn call_function(&mut self, function_address: u32) {
// self.call_stack.push(self.pc); // Save current PC to call stack
// self.pc = function_address; // Jump to the function's address
// }
// fn return_from_function(&mut self) {
// if let Some(return_address) = self.call_stack.pop() {
// self.pc = return_address; // Restore PC from the call stack
// } else {
// panic!("Call stack underflow: No return address");
// }
// }
}

View File

@ -1,2 +0,0 @@
[build]
target = "wasm32-unknown-unknown"

2
std_cpu/.gitignore vendored
View File

@ -1,2 +0,0 @@
/target
Cargo.lock

View File

@ -1,13 +0,0 @@
[package]
name = "std_cpu"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1.3.0"
serde = { version = "1", features = ["derive"] }
common = { version = "0.1.0", path = "../common" }

View File

@ -1,28 +0,0 @@
BSD 3-Clause License
Copyright (c) 2024, Extism
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,4 +0,0 @@
# Extism Rust PDK Plugin
See more documentation at https://github.com/extism/rust-pdk and
[join us on Discord](https://extism.org/discord) for more help.

View File

@ -1,99 +0,0 @@
use extism_pdk::*;
use serde::{Deserialize, Serialize};
use common::{DebugMode, Init, RunMode, RunResult, SetMemory, SetVerbose};
#[derive(Serialize, Deserialize, ToBytes, FromBytes)]
#[encoding(Json)]
pub struct Cpu {
memory: Vec<u8>,
registers: Vec<u16>,
ip: u16,
sp: u16,
flags: u8,
verbose: bool,
}
// start with something simple
#[plugin_fn]
pub fn init(
Init {
verbose,
mem_size,
registers,
}: Init,
) -> FnResult<()> {
var::set(
"cpu",
Cpu {
memory: vec![0; mem_size],
registers: vec![0; registers],
ip: 0,
sp: 0xFFFF,
flags: 0,
verbose,
},
)?;
Ok(())
}
#[plugin_fn]
pub fn set_verbose(verbose: SetVerbose) -> FnResult<()> {
let mut cpu: Cpu = var::get("cpu")?.unwrap();
cpu.verbose = verbose.0;
var::set("cpu", cpu)?;
Ok(())
}
#[plugin_fn]
pub fn load_memory(SetMemory { offset, memory }: SetMemory) -> FnResult<()> {
let mut cpu: Cpu = var::get("cpu")?.unwrap();
cpu.memory[offset..memory.len()].copy_from_slice(&memory);
var::set("cpu", cpu)?;
Ok(())
}
#[plugin_fn]
pub fn execute(run_mode: RunMode) -> FnResult<RunResult> {
match run_mode {
RunMode::Run => run(),
RunMode::Debug(debug_mode) => debug(debug_mode),
RunMode::StepOver => step_over(),
RunMode::StepInto => step_into(),
RunMode::StepOut => step_out(),
RunMode::RunFor(cycles) => run_for(cycles),
}
}
pub fn run() -> FnResult<RunResult> {
// while self.ip < self.memory.len() as u16 {
// }
Ok(RunResult::Success)
}
pub fn debug(_debug_mode: DebugMode) -> FnResult<RunResult> {
// Implement debug functionality
Ok(RunResult::Success)
}
pub fn run_for(cycles: usize) -> FnResult<RunResult> {
for _ in 0..cycles {}
Ok(RunResult::Success)
}
pub fn step_over() -> FnResult<RunResult> {
Ok(RunResult::Success)
}
pub fn step_into() -> FnResult<RunResult> {
Ok(RunResult::Success)
}
pub fn step_out() -> FnResult<RunResult> {
Ok(RunResult::Success)
}
pub fn step() -> FnResult<RunResult> {
// Fetch and execute instructions
Ok(RunResult::Success)
}