Skip to content

Commit

Permalink
Allow reading (g)megabuf
Browse files Browse the repository at this point in the history
  • Loading branch information
captbaritone committed Apr 6, 2021
1 parent 29badc2 commit a2a5bc6
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 37 deletions.
56 changes: 54 additions & 2 deletions compiler-rs/src/builtin_functions.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
use parity_wasm::elements::{BlockType, FuncBody, Instruction, Instructions, Local, ValueType};
use parity_wasm::elements::{
BlockType, FuncBody, FunctionType, Instruction, Instructions, Local, ValueType,
};

use crate::constants::{BUFFER_SIZE, EPSILON};
use crate::utils::f64_const;
use crate::EelFunctionType;

#[derive(PartialEq, Eq, Hash)]
pub enum BuiltinFunction {
Div,
GetBufferIndex,
}

impl BuiltinFunction {
pub fn get_type(&self) -> EelFunctionType {
match self {
Self::Div => (2, 1),
Self::Div => {
FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64])
}
Self::GetBufferIndex => FunctionType::new(vec![ValueType::F64], vec![ValueType::I32]),
}
}

Expand All @@ -32,6 +40,50 @@ impl BuiltinFunction {
Instruction::End,
]),
),
// Takes a float buffer index and converts it to an int. Values out of range
// are returned as `-1`.
//
// NOTE: There's actually a subtle bug that exists in Milkdrop's Eel
// implementation, which we reproduce here.
//
// Wasm's `trunc()` rounds towards zero. This means that for index `-1` we
// will return zero, since: `roundTowardZero(-1 + EPSILON) == 0`
//
// A subsequent check handles negative indexes, so negative indexes > than
// `-1` are not affected.
Self::GetBufferIndex => FuncBody::new(
vec![Local::new(1, ValueType::F64), Local::new(1, ValueType::I32)],
Instructions::new(vec![
Instruction::F64Const(f64_const(EPSILON)),
Instruction::GetLocal(0),
Instruction::F64Add,
// STACK: [$i + EPSILON]
Instruction::TeeLocal(1), // $with_near
Instruction::I32TruncSF64,
// TODO We could probably make this a tee and get rid of the next get if we swap the final condition
Instruction::SetLocal(2),
// STACK: []
Instruction::I32Const(-1),
Instruction::GetLocal(2),
// STACK: [-1, $truncated]
Instruction::I32Const(8),
Instruction::I32Mul,
// STACK: [-1, $truncated * 8]
Instruction::GetLocal(2), // $truncated
Instruction::I32Const(0),
// STACK: [-1, $truncated * 8, $truncated, 0]
Instruction::I32LtS,
// STACK: [-1, $truncated * 8, <is index less than 0>]
Instruction::GetLocal(2), // $truncated
Instruction::I32Const(BUFFER_SIZE as i32 - 1),
Instruction::I32GtS,
// STACK: [-1, $truncated * 8, <is index less than 0>, <is index more than MAX>]
Instruction::I32Or,
// STACK: [-1, $truncated * 8, <is index out of range>]
Instruction::Select,
Instruction::End,
]),
),
}
}
}
12 changes: 12 additions & 0 deletions compiler-rs/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pub static EPSILON: f64 = 0.00001;

pub static WASM_PAGE_SIZE: u32 = 65536;

static BYTES_PER_F64: u32 = 8;
static BUFFER_COUNT: u32 = 2;

// The number of items allowed in each buffer (megabuf/gmegabuf).
// https://github.com/WACUP/vis_milk2/blob/de9625a89e724afe23ed273b96b8e48496095b6c/ns-eel2/ns-eel.h#L145
pub static BUFFER_SIZE: u32 = 65536 * 128;

pub static WASM_MEMORY_SIZE: u32 = (BUFFER_SIZE * BYTES_PER_F64 * BUFFER_COUNT) / WASM_PAGE_SIZE;
85 changes: 69 additions & 16 deletions compiler-rs/src/emitter.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
use std::collections::{HashMap, HashSet};
use std::{
collections::{HashMap, HashSet},
mem,
};

use crate::{
ast::{
Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, ExpressionBlock,
FunctionCall, UnaryExpression, UnaryOperator,
},
builtin_functions::BuiltinFunction,
constants::{BUFFER_SIZE, EPSILON, WASM_MEMORY_SIZE},
error::CompilerError,
index_store::IndexStore,
shim::Shim,
span::Span,
utils::f64_const,
EelFunctionType,
};
use parity_wasm::elements::{
BlockType, CodeSection, ExportEntry, ExportSection, External, Func, FuncBody, FunctionSection,
FunctionType, GlobalEntry, GlobalSection, GlobalType, ImportEntry, ImportSection, InitExpr,
Instruction, Instructions, Internal, Module, Section, Serialize, Type, TypeSection, ValueType,
Instruction, Instructions, Internal, Local, MemorySection, MemoryType, Module, Section,
Serialize, Type, TypeSection, ValueType,
};

type EmitterResult<T> = Result<T, CompilerError>;

static EPSILON: f64 = 0.00001;

pub fn emit(
eel_functions: Vec<(String, EelFunction, String)>,
globals_map: HashMap<String, HashSet<String>>,
Expand All @@ -37,6 +41,7 @@ struct Emitter {
builtin_functions: IndexStore<BuiltinFunction>,
function_types: IndexStore<EelFunctionType>,
builtin_offset: Option<u32>,
locals: Vec<ValueType>,
}

impl Emitter {
Expand All @@ -48,6 +53,7 @@ impl Emitter {
function_types: Default::default(),
builtin_functions: IndexStore::new(),
builtin_offset: None,
locals: Default::default(),
}
}
fn emit(
Expand Down Expand Up @@ -90,6 +96,11 @@ impl Emitter {
sections.push(Section::Import(import_section));
}
sections.push(Section::Function(self.emit_function_section(funcs)));

sections.push(Section::Memory(MemorySection::with_entries(vec![
MemoryType::new(WASM_MEMORY_SIZE, Some(WASM_MEMORY_SIZE)),
])));

if let Some(global_section) = self.emit_global_section() {
sections.push(Section::Global(global_section));
}
Expand All @@ -113,11 +124,12 @@ impl Emitter {
let function_types = self
.function_types
.keys()
.iter()
.map(|(args, returns)| {
.into_iter()
.map(|function_type| {
Type::Function(FunctionType::new(
vec![ValueType::F64; *args],
vec![ValueType::F64; *returns],
// TODO: This is clone with more steps. What's going on
function_type.params().to_vec(),
function_type.results().to_vec(),
))
})
.collect();
Expand Down Expand Up @@ -178,15 +190,25 @@ impl Emitter {
let mut function_bodies = Vec::new();
let mut function_definitions = Vec::new();
for (i, (name, program, pool_name)) in eel_functions.into_iter().enumerate() {
// Note: We assume self.locals has been rest during the previous run.
self.current_pool = pool_name;
exports.push(ExportEntry::new(
name,
Internal::Function(i as u32 + offset),
));
let locals = Vec::new();
function_bodies.push(FuncBody::new(locals, self.emit_program(program)?));

let function_type = self.function_types.get((0, 0));
let instructions = self.emit_program(program)?;

let local_types = mem::replace(&mut self.locals, Vec::new());

let locals = local_types
.into_iter()
.map(|type_| Local::new(1, type_))
.collect();

function_bodies.push(FuncBody::new(locals, instructions));

let function_type = self.function_types.get(FunctionType::new(vec![], vec![]));

function_definitions.push(Func::new(function_type))
}
Expand Down Expand Up @@ -334,6 +356,10 @@ impl Emitter {
self.emit_expression(alternate, instructions)?;
instructions.push(Instruction::End);
}
"megabuf" => self.emit_memory_access(&mut function_call, 0, instructions)?,
"gmegabuf" => {
self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8, instructions)?
}
shim_name if Shim::from_str(shim_name).is_some() => {
let shim = Shim::from_str(shim_name).unwrap();
assert_arity(&function_call, shim.arity())?;
Expand All @@ -353,6 +379,33 @@ impl Emitter {
Ok(())
}

fn emit_memory_access(
&mut self,
function_call: &mut FunctionCall,
memory_offset: u32,
instructions: &mut Vec<Instruction>,
) -> EmitterResult<()> {
assert_arity(&function_call, 1)?;
let index = self.resolve_local(ValueType::I32);
self.emit_expression(function_call.arguments.pop().unwrap(), instructions)?;
instructions.push(Instruction::Call(
self.resolve_builtin_function(BuiltinFunction::GetBufferIndex),
));
//
instructions.push(Instruction::TeeLocal(index));
instructions.push(Instruction::I32Const(-1));
instructions.push(Instruction::I32Ne);
// STACK: [in range]
instructions.push(Instruction::If(BlockType::Value(ValueType::F64)));
instructions.push(Instruction::GetLocal(index));
instructions.push(Instruction::F64Load(3, memory_offset));
instructions.push(Instruction::Else);
instructions.push(Instruction::F64Const(f64_const(0.0)));
instructions.push(Instruction::End);

Ok(())
}

fn resolve_variable(&mut self, name: String) -> u32 {
let pool = if variable_is_register(&name) {
None
Expand All @@ -363,6 +416,11 @@ impl Emitter {
self.globals.get((pool, name))
}

fn resolve_local(&mut self, type_: ValueType) -> u32 {
self.locals.push(type_);
return self.locals.len() as u32 - 1;
}

fn resolve_builtin_function(&mut self, builtin: BuiltinFunction) -> u32 {
self.function_types.ensure(builtin.get_type());
let offset = self
Expand All @@ -378,11 +436,6 @@ fn emit_is_not_zeroish(instructions: &mut Vec<Instruction>) {
instructions.push(Instruction::F64Gt);
}

// TODO: There's got to be a better way.
fn f64_const(value: f64) -> u64 {
u64::from_le_bytes(value.to_le_bytes())
}

fn variable_is_register(name: &str) -> bool {
let chars: Vec<_> = name.chars().collect();
// We avoided pulling in the regex crate! (But at what cost?)
Expand Down
15 changes: 0 additions & 15 deletions compiler-rs/src/index_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,3 @@ impl<T: Eq + Hash> IndexStore<T> {
self.map.keys().collect()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::EelFunctionType;
#[test]
fn tuple() {
let mut function_types: IndexStore<EelFunctionType> = IndexStore::new();
let one_arg_one_return = function_types.get((1, 1));
let no_arg_one_return = function_types.get((0, 1));

assert_eq!(one_arg_one_return, 0);
assert_eq!(no_arg_one_return, 1);
}
}
5 changes: 4 additions & 1 deletion compiler-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod ast;
mod builtin_functions;
mod constants;
mod emitter;
mod error;
mod file_chars;
Expand All @@ -9,6 +10,7 @@ mod parser;
mod shim;
mod span;
mod tokens;
mod utils;

use std::collections::{HashMap, HashSet};

Expand All @@ -17,11 +19,12 @@ use emitter::emit;
use error::CompilerError;
// Only exported for tests
pub use lexer::Lexer;
use parity_wasm::elements::FunctionType;
pub use parser::parse;
pub use tokens::Token;
pub use tokens::TokenKind;

pub type EelFunctionType = (usize, usize);
pub type EelFunctionType = FunctionType;

use wasm_bindgen::prelude::*;

Expand Down
5 changes: 4 additions & 1 deletion compiler-rs/src/shim.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::EelFunctionType;
use parity_wasm::elements::{FunctionType, ValueType};

// TODO: We could use https://docs.rs/strum_macros/0.20.1/strum_macros/index.html
#[derive(PartialEq, Eq, Hash)]
Expand All @@ -8,7 +9,9 @@ pub enum Shim {

impl Shim {
pub fn get_type(&self) -> EelFunctionType {
(self.arity(), 1)
match self {
Shim::Sin => FunctionType::new(vec![ValueType::F64], vec![ValueType::F64]),
}
}
pub fn arity(&self) -> usize {
match self {
Expand Down
4 changes: 4 additions & 0 deletions compiler-rs/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// TODO: There's got to be a better way.
pub fn f64_const(value: f64) -> u64 {
u64::from_le_bytes(value.to_le_bytes())
}
2 changes: 0 additions & 2 deletions compiler-rs/tests/compatibility_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,6 @@ fn compatibility_tests() {
"Less than or equal (false)",
"Greater than or equal (true)",
"Greater than or equal (false)",
"Megabuf access",
"Max index megabuf",
"Max index + 1 megabuf",
"Max index gmegabuf",
Expand All @@ -456,7 +455,6 @@ fn compatibility_tests() {
"Assign return value",
"EPSILON buffer indexes",
"+EPSILON & rounding -#s toward 0",
"Negative buffer index read as 0",
"Negative buffer index",
"Negative buffer index gmegabuf",
"Negative buf index execs right hand side",
Expand Down
1 change: 1 addition & 0 deletions compiler-rs/tests/fixtures/wat/div.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ g = 100 / 0
else
f64.const 0x0p+0 (;=0;)
end)
(memory (;0;) 2048 2048)
(global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;)))
(export "test" (func 1)))
1 change: 1 addition & 0 deletions compiler-rs/tests/fixtures/wat/one_plus_one.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
f64.const 0x1p+0 (;=1;)
f64.add
drop)
(memory (;0;) 2048 2048)
(export "test" (func 1)))
1 change: 1 addition & 0 deletions compiler-rs/tests/fixtures/wat/reg.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ reg00=10
global.set 0
global.get 0
drop)
(memory (;0;) 2048 2048)
(global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;)))
(export "test" (func 1)))
1 change: 1 addition & 0 deletions compiler-rs/tests/fixtures/wat/sin.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ sin(100)
f64.const 0x1.9p+6 (;=100;)
call 0
drop)
(memory (;0;) 2048 2048)
(export "test" (func 1)))

0 comments on commit a2a5bc6

Please sign in to comment.