From af69d13352c8ca6fae3176595dcbeb331e83a236 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 01/85] Stub out Rust compiler --- compiler-rs/.gitignore | 11 ++ compiler-rs/Cargo.toml | 14 ++ compiler-rs/src/ast.rs | 29 ++++ compiler-rs/src/bin/compiler.rs | 23 ++++ compiler-rs/src/emitter.rs | 101 ++++++++++++++ compiler-rs/src/file_chars.rs | 50 +++++++ compiler-rs/src/lexer.rs | 54 ++++++++ compiler-rs/src/lib.rs | 24 ++++ compiler-rs/src/ops.rs | 182 ++++++++++++++++++++++++++ compiler-rs/src/parser.rs | 153 ++++++++++++++++++++++ compiler-rs/src/span.rs | 35 +++++ compiler-rs/src/tokens.rs | 21 +++ compiler-rs/tests/intagration_test.rs | 13 ++ compiler-rs/tests/wasm_test.rs | 84 ++++++++++++ 14 files changed, 794 insertions(+) create mode 100644 compiler-rs/.gitignore create mode 100644 compiler-rs/Cargo.toml create mode 100644 compiler-rs/src/ast.rs create mode 100644 compiler-rs/src/bin/compiler.rs create mode 100644 compiler-rs/src/emitter.rs create mode 100644 compiler-rs/src/file_chars.rs create mode 100644 compiler-rs/src/lexer.rs create mode 100644 compiler-rs/src/lib.rs create mode 100644 compiler-rs/src/ops.rs create mode 100644 compiler-rs/src/parser.rs create mode 100644 compiler-rs/src/span.rs create mode 100644 compiler-rs/src/tokens.rs create mode 100644 compiler-rs/tests/intagration_test.rs create mode 100644 compiler-rs/tests/wasm_test.rs diff --git a/compiler-rs/.gitignore b/compiler-rs/.gitignore new file mode 100644 index 0000000..0eec03d --- /dev/null +++ b/compiler-rs/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk \ No newline at end of file diff --git a/compiler-rs/Cargo.toml b/compiler-rs/Cargo.toml new file mode 100644 index 0000000..ff1ddcf --- /dev/null +++ b/compiler-rs/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "eel_wasm" +version = "0.1.0" +authors = ["Jordan Eldredge "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[dev-dependencies] +wasmi = "0.8.0" +wabt = "0.9.0" +parity-wasm = "0.42.2" diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs new file mode 100644 index 0000000..bfc9e0c --- /dev/null +++ b/compiler-rs/src/ast.rs @@ -0,0 +1,29 @@ +#[derive(Debug, PartialEq)] +pub struct Program { + pub expression: Expression, +} + +#[derive(Debug, PartialEq)] +pub enum Expression { + BinaryExpression(BinaryExpression), + NumberLiteral(NumberLiteral), +} + +#[derive(Debug, PartialEq)] +pub struct BinaryExpression { + pub left: Box, + pub right: Box, + pub op: BinaryOperator, +} + +#[derive(Debug, PartialEq)] +pub struct NumberLiteral { + pub value: u64, +} + +#[derive(Debug, PartialEq)] +pub enum BinaryOperator { + Add, + // Subtract, + // Multiply, +} diff --git a/compiler-rs/src/bin/compiler.rs b/compiler-rs/src/bin/compiler.rs new file mode 100644 index 0000000..f4b04e5 --- /dev/null +++ b/compiler-rs/src/bin/compiler.rs @@ -0,0 +1,23 @@ +use eel_wasm::compile; +use std::env; +use std::fs; +use std::process; + +fn main() { + let args: Vec = env::args().collect(); + let filename = args.get(1).unwrap_or_else(|| { + eprintln!("Usage: compile INPUT"); + process::exit(1); + }); + let source = fs::read_to_string(filename).unwrap_or_else(|err| { + eprintln!("Error reading file \"{}\": {}", filename, err); + process::exit(1); + }); + + let result = compile(&source).unwrap_or_else(|err| { + eprintln!("{}", err); + process::exit(1); + }); + + println!("{:?}", result); +} diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs new file mode 100644 index 0000000..4987963 --- /dev/null +++ b/compiler-rs/src/emitter.rs @@ -0,0 +1,101 @@ +use std::io; + +use crate::{ + ast::{BinaryExpression, BinaryOperator, NumberLiteral}, + ops::opcodes, +}; + +use super::ast::{Expression, Program}; + +#[derive(Debug, Clone)] +pub enum Error { + HeapOther(String), +} + +impl From for Error { + fn from(err: io::Error) -> Self { + Error::HeapOther(format!("I/O Error: {:?}", err)) + } +} + +pub trait Serialize { + /// Serialization error produced by serialization routine. + type Error: From; + /// Serialize type to serial i/o + fn serialize(self, writer: &mut W) -> Result<(), Self::Error>; +} + +impl Serialize for Program { + type Error = Error; + fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { + self.expression.serialize(writer) + } +} + +impl Serialize for Expression { + type Error = Error; + fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { + match self { + Self::NumberLiteral(number) => number.serialize(writer), + Self::BinaryExpression(binary_expression) => binary_expression.serialize(writer), + } + } +} + +impl Serialize for NumberLiteral { + type Error = Error; + fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { + self.value.serialize(writer) + } +} + +impl Serialize for BinaryExpression { + type Error = Error; + fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { + self.left.serialize(writer)?; + self.right.serialize(writer)?; + self.op.serialize(writer)?; + + Ok(()) + } +} + +impl Serialize for BinaryOperator { + type Error = Error; + fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { + match self { + Self::Add => opcodes::F64ADD.serialize(writer), + } + } +} + +impl Serialize for u8 { + type Error = Error; + fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { + writer + .write(&[self]) + .map_err(|err| Error::HeapOther(err.to_string()))?; + Ok(()) + } +} + +impl Serialize for u64 { + type Error = Error; + fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { + let mut buf = [0u8; 1]; + let mut v = self; + loop { + buf[0] = (v & 0b0111_1111) as u8; + v >>= 7; + if v > 0 { + buf[0] |= 0b1000_0000; + } + writer.write(&buf[..])?; + if v == 0 { + break; + } + } + + Ok(()) + } +} diff --git a/compiler-rs/src/file_chars.rs b/compiler-rs/src/file_chars.rs new file mode 100644 index 0000000..c8a4fcd --- /dev/null +++ b/compiler-rs/src/file_chars.rs @@ -0,0 +1,50 @@ +use super::span::Position; +use std::mem; +use std::str::Chars; + +pub struct FileChars<'a> { + chars: Chars<'a>, + next_char: Option, + pub pos: Position, +} + +impl<'a> FileChars<'a> { + pub fn new(source: &'a str) -> Self { + let mut chars = source.chars(); + let next_char = chars.next(); + FileChars { + chars, + next_char, + pos: Position::new(), + } + } + + pub fn next(&mut self) -> Option { + if let Some(c) = self.next_char { + self.pos.byte_offset += c.len_utf8(); + if c == '\n' { + self.pos.column = 0; + self.pos.line += 1; + } else { + self.pos.column += 1 + } + } + mem::replace(&mut self.next_char, self.chars.next()) + } + + pub fn eat_while(&mut self, predicate: F) + where + F: Fn(char) -> bool, + { + while match self.next_char { + Some(c) => predicate(c), + None => false, + } { + self.next(); + } + } + + pub fn peek(&mut self) -> Option { + self.next_char + } +} diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs new file mode 100644 index 0000000..5bab787 --- /dev/null +++ b/compiler-rs/src/lexer.rs @@ -0,0 +1,54 @@ +use super::file_chars::FileChars; +use super::span::Span; +use super::tokens::{Token, TokenKind}; + +/** + * The lexer: + * - Returns an error if next token is not in the language + * - Returns EOF forever once it reaches the end + */ + +pub struct Lexer<'a> { + source: &'a str, + chars: FileChars<'a>, +} + +impl<'a> Lexer<'a> { + pub fn new(source: &'a str) -> Self { + Lexer { + source, + chars: FileChars::new(source), + } + } + + pub fn next_token(&mut self) -> Result, String> { + let start = self.chars.pos; + let kind = match self.chars.peek() { + Some(c) => match c { + _ if is_int(c) => self.read_int(), + '+' => self.read_char_as_kind(TokenKind::Plus), + _ => return Err(format!("Unexpected token {}", c)), + }, + None => TokenKind::EOF, + }; + let end = self.chars.pos; + Ok(Token::new(kind, Span::new(self.source, start, end))) + } + + fn read_char_as_kind(&mut self, kind: TokenKind) -> TokenKind { + self.chars.next(); + kind + } + + fn read_int(&mut self) -> TokenKind { + self.chars.eat_while(is_int); + TokenKind::Int + } +} + +fn is_int(c: char) -> bool { + match c { + '0'..='9' => true, + _ => false, + } +} diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs new file mode 100644 index 0000000..471806a --- /dev/null +++ b/compiler-rs/src/lib.rs @@ -0,0 +1,24 @@ +mod ast; +mod emitter; +mod file_chars; +mod lexer; +mod ops; +mod parser; +mod span; +mod tokens; + +use std::io::Write; + +use emitter::Serialize; +use parser::Parser; + +pub fn compile(source: &str) -> Result, String> { + let mut parser = Parser::new(source); + let program = parser.parse()?; + let mut binary: Vec = Vec::new(); + program + .serialize(&mut binary) + .map_err(|err| format!("{:?}", err))?; + binary.flush().map_err(|err| format!("{}", err))?; + Ok(binary) +} diff --git a/compiler-rs/src/ops.rs b/compiler-rs/src/ops.rs new file mode 100644 index 0000000..56dc924 --- /dev/null +++ b/compiler-rs/src/ops.rs @@ -0,0 +1,182 @@ +// https://github.com/paritytech/parity-wasm/blob/8caf6a8bc79a98c2f298b8fdf50857b8638e2dfc/src/elements/ops.rs#L607 +#[allow(missing_docs)] +pub mod opcodes { + pub const UNREACHABLE: u8 = 0x00; + pub const NOP: u8 = 0x01; + pub const BLOCK: u8 = 0x02; + pub const LOOP: u8 = 0x03; + pub const IF: u8 = 0x04; + pub const ELSE: u8 = 0x05; + pub const END: u8 = 0x0b; + pub const BR: u8 = 0x0c; + pub const BRIF: u8 = 0x0d; + pub const BRTABLE: u8 = 0x0e; + pub const RETURN: u8 = 0x0f; + pub const CALL: u8 = 0x10; + pub const CALLINDIRECT: u8 = 0x11; + pub const DROP: u8 = 0x1a; + pub const SELECT: u8 = 0x1b; + pub const GETLOCAL: u8 = 0x20; + pub const SETLOCAL: u8 = 0x21; + pub const TEELOCAL: u8 = 0x22; + pub const GETGLOBAL: u8 = 0x23; + pub const SETGLOBAL: u8 = 0x24; + pub const I32LOAD: u8 = 0x28; + pub const I64LOAD: u8 = 0x29; + pub const F32LOAD: u8 = 0x2a; + pub const F64LOAD: u8 = 0x2b; + pub const I32LOAD8S: u8 = 0x2c; + pub const I32LOAD8U: u8 = 0x2d; + pub const I32LOAD16S: u8 = 0x2e; + pub const I32LOAD16U: u8 = 0x2f; + pub const I64LOAD8S: u8 = 0x30; + pub const I64LOAD8U: u8 = 0x31; + pub const I64LOAD16S: u8 = 0x32; + pub const I64LOAD16U: u8 = 0x33; + pub const I64LOAD32S: u8 = 0x34; + pub const I64LOAD32U: u8 = 0x35; + pub const I32STORE: u8 = 0x36; + pub const I64STORE: u8 = 0x37; + pub const F32STORE: u8 = 0x38; + pub const F64STORE: u8 = 0x39; + pub const I32STORE8: u8 = 0x3a; + pub const I32STORE16: u8 = 0x3b; + pub const I64STORE8: u8 = 0x3c; + pub const I64STORE16: u8 = 0x3d; + pub const I64STORE32: u8 = 0x3e; + pub const CURRENTMEMORY: u8 = 0x3f; + pub const GROWMEMORY: u8 = 0x40; + pub const I32CONST: u8 = 0x41; + pub const I64CONST: u8 = 0x42; + pub const F32CONST: u8 = 0x43; + pub const F64CONST: u8 = 0x44; + pub const I32EQZ: u8 = 0x45; + pub const I32EQ: u8 = 0x46; + pub const I32NE: u8 = 0x47; + pub const I32LTS: u8 = 0x48; + pub const I32LTU: u8 = 0x49; + pub const I32GTS: u8 = 0x4a; + pub const I32GTU: u8 = 0x4b; + pub const I32LES: u8 = 0x4c; + pub const I32LEU: u8 = 0x4d; + pub const I32GES: u8 = 0x4e; + pub const I32GEU: u8 = 0x4f; + pub const I64EQZ: u8 = 0x50; + pub const I64EQ: u8 = 0x51; + pub const I64NE: u8 = 0x52; + pub const I64LTS: u8 = 0x53; + pub const I64LTU: u8 = 0x54; + pub const I64GTS: u8 = 0x55; + pub const I64GTU: u8 = 0x56; + pub const I64LES: u8 = 0x57; + pub const I64LEU: u8 = 0x58; + pub const I64GES: u8 = 0x59; + pub const I64GEU: u8 = 0x5a; + + pub const F32EQ: u8 = 0x5b; + pub const F32NE: u8 = 0x5c; + pub const F32LT: u8 = 0x5d; + pub const F32GT: u8 = 0x5e; + pub const F32LE: u8 = 0x5f; + pub const F32GE: u8 = 0x60; + + pub const F64EQ: u8 = 0x61; + pub const F64NE: u8 = 0x62; + pub const F64LT: u8 = 0x63; + pub const F64GT: u8 = 0x64; + pub const F64LE: u8 = 0x65; + pub const F64GE: u8 = 0x66; + + pub const I32CLZ: u8 = 0x67; + pub const I32CTZ: u8 = 0x68; + pub const I32POPCNT: u8 = 0x69; + pub const I32ADD: u8 = 0x6a; + pub const I32SUB: u8 = 0x6b; + pub const I32MUL: u8 = 0x6c; + pub const I32DIVS: u8 = 0x6d; + pub const I32DIVU: u8 = 0x6e; + pub const I32REMS: u8 = 0x6f; + pub const I32REMU: u8 = 0x70; + pub const I32AND: u8 = 0x71; + pub const I32OR: u8 = 0x72; + pub const I32XOR: u8 = 0x73; + pub const I32SHL: u8 = 0x74; + pub const I32SHRS: u8 = 0x75; + pub const I32SHRU: u8 = 0x76; + pub const I32ROTL: u8 = 0x77; + pub const I32ROTR: u8 = 0x78; + + pub const I64CLZ: u8 = 0x79; + pub const I64CTZ: u8 = 0x7a; + pub const I64POPCNT: u8 = 0x7b; + pub const I64ADD: u8 = 0x7c; + pub const I64SUB: u8 = 0x7d; + pub const I64MUL: u8 = 0x7e; + pub const I64DIVS: u8 = 0x7f; + pub const I64DIVU: u8 = 0x80; + pub const I64REMS: u8 = 0x81; + pub const I64REMU: u8 = 0x82; + pub const I64AND: u8 = 0x83; + pub const I64OR: u8 = 0x84; + pub const I64XOR: u8 = 0x85; + pub const I64SHL: u8 = 0x86; + pub const I64SHRS: u8 = 0x87; + pub const I64SHRU: u8 = 0x88; + pub const I64ROTL: u8 = 0x89; + pub const I64ROTR: u8 = 0x8a; + pub const F32ABS: u8 = 0x8b; + pub const F32NEG: u8 = 0x8c; + pub const F32CEIL: u8 = 0x8d; + pub const F32FLOOR: u8 = 0x8e; + pub const F32TRUNC: u8 = 0x8f; + pub const F32NEAREST: u8 = 0x90; + pub const F32SQRT: u8 = 0x91; + pub const F32ADD: u8 = 0x92; + pub const F32SUB: u8 = 0x93; + pub const F32MUL: u8 = 0x94; + pub const F32DIV: u8 = 0x95; + pub const F32MIN: u8 = 0x96; + pub const F32MAX: u8 = 0x97; + pub const F32COPYSIGN: u8 = 0x98; + pub const F64ABS: u8 = 0x99; + pub const F64NEG: u8 = 0x9a; + pub const F64CEIL: u8 = 0x9b; + pub const F64FLOOR: u8 = 0x9c; + pub const F64TRUNC: u8 = 0x9d; + pub const F64NEAREST: u8 = 0x9e; + pub const F64SQRT: u8 = 0x9f; + pub const F64ADD: u8 = 0xa0; + pub const F64SUB: u8 = 0xa1; + pub const F64MUL: u8 = 0xa2; + pub const F64DIV: u8 = 0xa3; + pub const F64MIN: u8 = 0xa4; + pub const F64MAX: u8 = 0xa5; + pub const F64COPYSIGN: u8 = 0xa6; + + pub const I32WRAPI64: u8 = 0xa7; + pub const I32TRUNCSF32: u8 = 0xa8; + pub const I32TRUNCUF32: u8 = 0xa9; + pub const I32TRUNCSF64: u8 = 0xaa; + pub const I32TRUNCUF64: u8 = 0xab; + pub const I64EXTENDSI32: u8 = 0xac; + pub const I64EXTENDUI32: u8 = 0xad; + pub const I64TRUNCSF32: u8 = 0xae; + pub const I64TRUNCUF32: u8 = 0xaf; + pub const I64TRUNCSF64: u8 = 0xb0; + pub const I64TRUNCUF64: u8 = 0xb1; + pub const F32CONVERTSI32: u8 = 0xb2; + pub const F32CONVERTUI32: u8 = 0xb3; + pub const F32CONVERTSI64: u8 = 0xb4; + pub const F32CONVERTUI64: u8 = 0xb5; + pub const F32DEMOTEF64: u8 = 0xb6; + pub const F64CONVERTSI32: u8 = 0xb7; + pub const F64CONVERTUI32: u8 = 0xb8; + pub const F64CONVERTSI64: u8 = 0xb9; + pub const F64CONVERTUI64: u8 = 0xba; + pub const F64PROMOTEF32: u8 = 0xbb; + + pub const I32REINTERPRETF32: u8 = 0xbc; + pub const I64REINTERPRETF64: u8 = 0xbd; + pub const F32REINTERPRETI32: u8 = 0xbe; + pub const F64REINTERPRETI64: u8 = 0xbf; +} diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs new file mode 100644 index 0000000..eb0bf39 --- /dev/null +++ b/compiler-rs/src/parser.rs @@ -0,0 +1,153 @@ +use crate::ast::{BinaryExpression, BinaryOperator}; + +use super::ast::{Expression, NumberLiteral, Program}; +use super::lexer::Lexer; +use super::span::Span; +use super::tokens::{Token, TokenKind}; + +static SUM_PRECEDENCE: u8 = 1; +/* +static DIFFERENCE_PRECEDENCE: u8 = 1; +static PRODUCT_PRECEDENCE: u8 = 2; +*/ + +pub struct Parser<'a> { + lexer: Lexer<'a>, + token: Token<'a>, +} + +impl<'a> Parser<'a> { + pub fn new(source: &'a str) -> Self { + Parser { + lexer: Lexer::new(source), + token: Token { + kind: TokenKind::SOF, + span: Span::start_of_file(source), + }, + } + } + + fn advance(&mut self) -> Result<(), String> { + self.token = self.lexer.next_token()?; + Ok(()) + } + + fn expect_kind(&mut self, expected: TokenKind) -> Result<(), String> { + if self.token.kind == expected { + self.advance()?; + Ok(()) + } else { + // TODO: Improve error message and improve source location. + Err(format!( + "Expected a {:?} but found {:?}", + expected, self.token.kind + )) + } + } + + pub fn parse(&mut self) -> Result { + self.expect_kind(TokenKind::SOF)?; + let program = self.parse_program()?; + self.expect_kind(TokenKind::EOF)?; + Ok(program) + } + + pub fn parse_program(&mut self) -> Result { + Ok(Program { + expression: self.parse_expression(0)?, + }) + } + + fn parse_expression(&mut self, precedence: u8) -> Result { + let left = self.parse_prefix()?; + self.maybe_parse_infix(left, precedence) + } + + fn parse_prefix(&mut self) -> Result { + match self.token.kind { + TokenKind::Int => Ok(Expression::NumberLiteral(self.parse_int()?)), + // TokenKind::OpenParen => self.parse_parenthesized_expression(), + // Once we have other prefix operators: `+-!` they will go here. + _ => Err(format!("Expected an Int but found {:?}", self.token.kind)), + } + } + + fn maybe_parse_infix( + &mut self, + left: Expression, + precedence: u8, + ) -> Result { + let mut next = left; + loop { + next = match self.token.kind { + TokenKind::Plus if precedence < SUM_PRECEDENCE => self.parse_sum(next)?, + /* + TokenKind::Minus if precedence < DIFFERENCE_PRECEDENCE => { + self.parse_difference(next)? + } + TokenKind::Asterisk if precedence < PRODUCT_PRECEDENCE => { + self.parse_product(next)? + } + */ + _ => return Ok(next), + } + } + } + + fn parse_sum(&mut self, left: Expression) -> Result { + self.expect_kind(TokenKind::Plus)?; + let right = self.parse_expression(left_associative(SUM_PRECEDENCE))?; + Ok(Expression::BinaryExpression(BinaryExpression { + left: Box::new(left), + right: Box::new(right), + op: BinaryOperator::Add, + })) + } + + fn parse_int(&mut self) -> Result { + if let TokenKind::Int = self.token.kind { + let value = self.token.span.str_from_source(); + match value.parse() { + Ok(value) => { + self.advance()?; + Ok(NumberLiteral { value }) + } + Err(_) => Err(format!("Could not parse \"{}\" to a number", value)), + } + } else { + Err(format!("Expected an Int but found {:?}", self.token.kind)) + } + } +} + +#[inline] +#[allow(dead_code)] // Save this for when we need it. +fn left_associative(precedence: u8) -> u8 { + precedence +} + +#[inline] +#[allow(dead_code)] // Save this for when we need it. +fn right_associative(precedence: u8) -> u8 { + precedence - 1 +} + +#[test] +fn can_parse_integer() { + assert_eq!( + Parser::new("1").parse(), + Ok(Program { + expression: Expression::NumberLiteral(NumberLiteral { value: 1 }) + }) + ); +} + +#[test] +fn can_parse_integer_2() { + assert_eq!( + Parser::new("2").parse(), + Ok(Program { + expression: Expression::NumberLiteral(NumberLiteral { value: 2 }) + }) + ); +} diff --git a/compiler-rs/src/span.rs b/compiler-rs/src/span.rs new file mode 100644 index 0000000..3e92beb --- /dev/null +++ b/compiler-rs/src/span.rs @@ -0,0 +1,35 @@ +# [derive (Debug, PartialEq, Clone, Copy)] +pub struct Position { + pub byte_offset: usize, + pub line: usize, + pub column: usize, +} + +impl Position { + pub fn new () -> Self { + Position { + byte_offset: 0, + line: 0, + column: 0, + } + } +} + +# [derive (Debug, PartialEq)] +pub struct Span <'a> { + source: & 'a str, + pub start: Position, + pub end: Position, +} + +impl <'a> Span <'a> { + pub fn new (source: & 'a str, start: Position, end: Position) -> Self { + Span {source, start, end} + } + pub fn start_of_file (source: & 'a str) -> Self { + Span :: new (source, Position :: new (), Position :: new ()) + } + pub fn str_from_source (& self) -> & 'a str { + & self.source [self.start.byte_offset..self.end.byte_offset] + } +} \ No newline at end of file diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs new file mode 100644 index 0000000..6bd1247 --- /dev/null +++ b/compiler-rs/src/tokens.rs @@ -0,0 +1,21 @@ +use super::span::Span; + +#[derive(Debug, PartialEq)] +pub enum TokenKind { + Int, + Plus, + EOF, + SOF, // Allows TokenKind to be non-optional in the parser +} + +#[derive(Debug, PartialEq)] +pub struct Token<'a> { + pub kind: TokenKind, + pub span: Span<'a>, +} + +impl<'a> Token<'a> { + pub fn new(kind: TokenKind, span: Span<'a>) -> Self { + Token { kind, span } + } +} diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs new file mode 100644 index 0000000..8e77efb --- /dev/null +++ b/compiler-rs/tests/intagration_test.rs @@ -0,0 +1,13 @@ +extern crate eel_wasm; + +use std::io; + +use eel_wasm::compile; + +#[test] +fn run_snapshots() -> io::Result<()> { + let output = compile("1+1").unwrap(); + + assert_eq!(output, [1, 1, 160]); + Ok(()) +} diff --git a/compiler-rs/tests/wasm_test.rs b/compiler-rs/tests/wasm_test.rs new file mode 100644 index 0000000..1ecc147 --- /dev/null +++ b/compiler-rs/tests/wasm_test.rs @@ -0,0 +1,84 @@ +extern crate wabt; +extern crate wasmi; + +use std::error::Error; + +use wasmi::{ + nan_preserving_float::F64, GlobalDescriptor, GlobalInstance, GlobalRef, ImportResolver, + ImportsBuilder, +}; +use wasmi::{ + Error as InterpreterError, ModuleImportResolver, ModuleInstance, ModuleRef, NopExternals, + RuntimeValue, +}; + +use parity_wasm::{ + builder::{from_module, module}, + elements::{ + BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, Instruction, + Instructions, MemoryType, Module, TableType, ValueType, + }, +}; + +struct SpecModule { + g: GlobalRef, +} + +impl SpecModule { + fn new() -> Self { + SpecModule { + g: GlobalInstance::alloc(RuntimeValue::F64(666.0.into()), false), + } + } +} + +impl ModuleImportResolver for SpecModule { + fn resolve_global( + &self, + field_name: &str, + _global_type: &GlobalDescriptor, + ) -> Result { + panic!("Filed name"); + Ok(GlobalInstance::alloc(RuntimeValue::F64(1.0.into()), false)) + } +} + +// #[test] +fn wasm_test() { + // Parse WAT (WebAssembly Text format) into wasm bytecode. + let wasm_binary: Vec = wabt::wat2wasm( + r#" + (module + (import "main" "foo" (global (;0;) (mut f64))) + (func (export "test") (result f64) + f64.const 69 + set_global 0 + f64.const 1 + ) + ) + "#, + ) + .expect("failed to parse wat"); + // Load wasm binary and prepare it for instantiation. + let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); + /* + + let mut imports = ImportsBuilder::default(); + let main = SpecModule::new(); + imports.push_resolver("main", &main); + + // Instantiate a module with empty imports and + // assert that there is no `start` function. + let instance = ModuleInstance::new(&module, &imports) + .expect("failed to instantiate wasm module") + .assert_no_start(); + + // Finally, invoke the exported function "test" with no parameters + // and empty external function executor. + assert_eq!( + instance + .invoke_export("test", &[], &mut NopExternals,) + .expect("failed to execute export"), + Some(RuntimeValue::F64(1.0.into())), + ); */ +} From a51d274c37f7857c484a052f2bbfe54e51947be8 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 02/85] Subtraction --- compiler-rs/src/ast.rs | 2 +- compiler-rs/src/emitter.rs | 1 + compiler-rs/src/lexer.rs | 1 + compiler-rs/src/parser.rs | 14 ++++++++++++-- compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/intagration_test.rs | 5 ++--- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index bfc9e0c..1020ae4 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -24,6 +24,6 @@ pub struct NumberLiteral { #[derive(Debug, PartialEq)] pub enum BinaryOperator { Add, - // Subtract, + Subtract, // Multiply, } diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 4987963..75193f4 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -65,6 +65,7 @@ impl Serialize for BinaryOperator { fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { match self { Self::Add => opcodes::F64ADD.serialize(writer), + Self::Subtract => opcodes::F64SUB.serialize(writer), } } } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 5bab787..80bc03a 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -27,6 +27,7 @@ impl<'a> Lexer<'a> { Some(c) => match c { _ if is_int(c) => self.read_int(), '+' => self.read_char_as_kind(TokenKind::Plus), + '-' => self.read_char_as_kind(TokenKind::Minus), _ => return Err(format!("Unexpected token {}", c)), }, None => TokenKind::EOF, diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index eb0bf39..9f3736d 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -6,8 +6,8 @@ use super::span::Span; use super::tokens::{Token, TokenKind}; static SUM_PRECEDENCE: u8 = 1; -/* static DIFFERENCE_PRECEDENCE: u8 = 1; +/* static PRODUCT_PRECEDENCE: u8 = 2; */ @@ -81,10 +81,10 @@ impl<'a> Parser<'a> { loop { next = match self.token.kind { TokenKind::Plus if precedence < SUM_PRECEDENCE => self.parse_sum(next)?, - /* TokenKind::Minus if precedence < DIFFERENCE_PRECEDENCE => { self.parse_difference(next)? } + /* TokenKind::Asterisk if precedence < PRODUCT_PRECEDENCE => { self.parse_product(next)? } @@ -104,6 +104,16 @@ impl<'a> Parser<'a> { })) } + fn parse_difference(&mut self, left: Expression) -> Result { + self.expect_kind(TokenKind::Minus)?; + let right = self.parse_expression(left_associative(DIFFERENCE_PRECEDENCE))?; + Ok(Expression::BinaryExpression(BinaryExpression { + left: Box::new(left), + right: Box::new(right), + op: BinaryOperator::Subtract, + })) + } + fn parse_int(&mut self) -> Result { if let TokenKind::Int = self.token.kind { let value = self.token.span.str_from_source(); diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 6bd1247..f174b03 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -4,6 +4,7 @@ use super::span::Span; pub enum TokenKind { Int, Plus, + Minus, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index 8e77efb..e85adc4 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -6,8 +6,7 @@ use eel_wasm::compile; #[test] fn run_snapshots() -> io::Result<()> { - let output = compile("1+1").unwrap(); - - assert_eq!(output, [1, 1, 160]); + assert_eq!(compile("1+1").unwrap(), [1, 1, 160]); + assert_eq!(compile("1-1").unwrap(), [1, 1, 161]); Ok(()) } From 86e08d949382f3d5e5a2d6c395aa20ee29c72f60 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 03/85] Add multiply --- compiler-rs/src/ast.rs | 2 +- compiler-rs/src/emitter.rs | 1 + compiler-rs/src/lexer.rs | 1 + compiler-rs/src/parser.rs | 14 ++++++++++---- compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/intagration_test.rs | 1 + 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 1020ae4..6b49847 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -25,5 +25,5 @@ pub struct NumberLiteral { pub enum BinaryOperator { Add, Subtract, - // Multiply, + Multiply, } diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 75193f4..52edfe7 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -66,6 +66,7 @@ impl Serialize for BinaryOperator { match self { Self::Add => opcodes::F64ADD.serialize(writer), Self::Subtract => opcodes::F64SUB.serialize(writer), + Self::Multiply => opcodes::F64MUL.serialize(writer), } } } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 80bc03a..1252d64 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -28,6 +28,7 @@ impl<'a> Lexer<'a> { _ if is_int(c) => self.read_int(), '+' => self.read_char_as_kind(TokenKind::Plus), '-' => self.read_char_as_kind(TokenKind::Minus), + '*' => self.read_char_as_kind(TokenKind::Asterisk), _ => return Err(format!("Unexpected token {}", c)), }, None => TokenKind::EOF, diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 9f3736d..c0ab1b4 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -7,9 +7,7 @@ use super::tokens::{Token, TokenKind}; static SUM_PRECEDENCE: u8 = 1; static DIFFERENCE_PRECEDENCE: u8 = 1; -/* static PRODUCT_PRECEDENCE: u8 = 2; -*/ pub struct Parser<'a> { lexer: Lexer<'a>, @@ -84,11 +82,9 @@ impl<'a> Parser<'a> { TokenKind::Minus if precedence < DIFFERENCE_PRECEDENCE => { self.parse_difference(next)? } - /* TokenKind::Asterisk if precedence < PRODUCT_PRECEDENCE => { self.parse_product(next)? } - */ _ => return Ok(next), } } @@ -114,6 +110,16 @@ impl<'a> Parser<'a> { })) } + fn parse_product(&mut self, left: Expression) -> Result { + self.expect_kind(TokenKind::Asterisk)?; + let right = self.parse_expression(left_associative(PRODUCT_PRECEDENCE))?; + Ok(Expression::BinaryExpression(BinaryExpression { + left: Box::new(left), + right: Box::new(right), + op: BinaryOperator::Multiply, + })) + } + fn parse_int(&mut self) -> Result { if let TokenKind::Int = self.token.kind { let value = self.token.span.str_from_source(); diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index f174b03..c1ff48e 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -5,6 +5,7 @@ pub enum TokenKind { Int, Plus, Minus, + Asterisk, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index e85adc4..f1eb300 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -8,5 +8,6 @@ use eel_wasm::compile; fn run_snapshots() -> io::Result<()> { assert_eq!(compile("1+1").unwrap(), [1, 1, 160]); assert_eq!(compile("1-1").unwrap(), [1, 1, 161]); + assert_eq!(compile("1*1").unwrap(), [1, 1, 162]); Ok(()) } From 4b3e1922326e35687ae90fb0cf2b07f495f4e17d Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 04/85] Support division --- compiler-rs/src/ast.rs | 1 + compiler-rs/src/emitter.rs | 1 + compiler-rs/src/lexer.rs | 1 + compiler-rs/src/parser.rs | 14 ++++++++++++++ compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/intagration_test.rs | 1 + 6 files changed, 19 insertions(+) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 6b49847..e758e76 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -26,4 +26,5 @@ pub enum BinaryOperator { Add, Subtract, Multiply, + Divide, } diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 52edfe7..a73fcdc 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -67,6 +67,7 @@ impl Serialize for BinaryOperator { Self::Add => opcodes::F64ADD.serialize(writer), Self::Subtract => opcodes::F64SUB.serialize(writer), Self::Multiply => opcodes::F64MUL.serialize(writer), + Self::Divide => opcodes::F64DIV.serialize(writer), } } } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 1252d64..523d39c 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -29,6 +29,7 @@ impl<'a> Lexer<'a> { '+' => self.read_char_as_kind(TokenKind::Plus), '-' => self.read_char_as_kind(TokenKind::Minus), '*' => self.read_char_as_kind(TokenKind::Asterisk), + '/' => self.read_char_as_kind(TokenKind::Slash), _ => return Err(format!("Unexpected token {}", c)), }, None => TokenKind::EOF, diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index c0ab1b4..a202fdb 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -8,6 +8,7 @@ use super::tokens::{Token, TokenKind}; static SUM_PRECEDENCE: u8 = 1; static DIFFERENCE_PRECEDENCE: u8 = 1; static PRODUCT_PRECEDENCE: u8 = 2; +static QUOTIENT_PRECEDENCE: u8 = 2; pub struct Parser<'a> { lexer: Lexer<'a>, @@ -85,6 +86,9 @@ impl<'a> Parser<'a> { TokenKind::Asterisk if precedence < PRODUCT_PRECEDENCE => { self.parse_product(next)? } + TokenKind::Slash if precedence < QUOTIENT_PRECEDENCE => { + self.parse_quotient(next)? + } _ => return Ok(next), } } @@ -120,6 +124,16 @@ impl<'a> Parser<'a> { })) } + fn parse_quotient(&mut self, left: Expression) -> Result { + self.expect_kind(TokenKind::Slash)?; + let right = self.parse_expression(left_associative(QUOTIENT_PRECEDENCE))?; + Ok(Expression::BinaryExpression(BinaryExpression { + left: Box::new(left), + right: Box::new(right), + op: BinaryOperator::Divide, + })) + } + fn parse_int(&mut self) -> Result { if let TokenKind::Int = self.token.kind { let value = self.token.span.str_from_source(); diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index c1ff48e..7b8e7eb 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -6,6 +6,7 @@ pub enum TokenKind { Plus, Minus, Asterisk, + Slash, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index f1eb300..c853b27 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -9,5 +9,6 @@ fn run_snapshots() -> io::Result<()> { assert_eq!(compile("1+1").unwrap(), [1, 1, 160]); assert_eq!(compile("1-1").unwrap(), [1, 1, 161]); assert_eq!(compile("1*1").unwrap(), [1, 1, 162]); + assert_eq!(compile("1/1").unwrap(), [1, 1, 163]); Ok(()) } From a1788c9b6aca88d85b1aeb8658b794b237483d6b Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 05/85] Stub out test that evaluates generated Wasm --- compiler-rs/src/emitter.rs | 6 ++- compiler-rs/tests/intagration_test.rs | 75 +++++++++++++++++++++++++-- compiler-rs/tests/wasm_test.rs | 63 ++++------------------ 3 files changed, 85 insertions(+), 59 deletions(-) diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index a73fcdc..fc16bc2 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -45,7 +45,11 @@ impl Serialize for Expression { impl Serialize for NumberLiteral { type Error = Error; fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - self.value.serialize(writer) + opcodes::F64CONST.serialize(writer)?; + writer + .write(&[0, 0, 0, 0, 0, 0, 240, 63]) + .map_err(|err| Error::HeapOther(err.to_string()))?; + Ok(()) } } diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index c853b27..c312e8f 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -3,12 +3,77 @@ extern crate eel_wasm; use std::io; use eel_wasm::compile; +use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue}; + +fn build(body: &[u8]) -> Vec { + let prefix = &[ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, 115, + 116, 0, 0, 10, // + // These bits need to reflect the code len + 13, 1, 11, 0, + ]; + let suffix = &[11]; + + let mut result = Vec::with_capacity(prefix.len() + body.len() + suffix.len()); + result.extend_from_slice(prefix); + result.extend_from_slice(body); + result.extend_from_slice(suffix); + result +} + +fn run(body: &[u8]) -> f64 { + let wasm_binary = build(body); + let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); + let instance = ModuleInstance::new(&module, &ImportsBuilder::default()) + .expect("failed to instantiate wasm module") + .assert_no_start(); + + // Finally, invoke the exported function "test" with no parameters + // and empty external function executor. + match instance + .invoke_export("test", &[], &mut NopExternals) + .expect("failed to execute export") + { + Some(RuntimeValue::F64(val)) => val.into(), + None => 0.0, + _ => 0.0, + } +} + +#[test] +fn compile_one() -> io::Result<()> { + assert_eq!(&compile("1").unwrap(), &[68, 0, 0, 0, 0, 0, 0, 240, 63,]); + Ok(()) +} + +#[test] +fn build_one() -> io::Result<()> { + assert_eq!( + &build(&compile("1").unwrap()), + &[ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, + 115, 116, 0, 0, 10, 13, 1, 11, 0, 68, 0, 0, 0, 0, 0, 0, 240, 63, 11, + ] + ); + Ok(()) +} + +/* +#[test] +fn build_one_plus_one() -> io::Result<()> { + assert_eq!( + &build(&compile("1+1").unwrap()), + &[ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, + 115, 116, 0, 0, 10, 23, 1, 21, 0, 68, 0, 0, 0, 0, 0, 0, 240, 63, 68, 0, 0, 0, 0, 0, 0, + 240, 63, 160, 11 + ] + ); + Ok(()) +} */ #[test] -fn run_snapshots() -> io::Result<()> { - assert_eq!(compile("1+1").unwrap(), [1, 1, 160]); - assert_eq!(compile("1-1").unwrap(), [1, 1, 161]); - assert_eq!(compile("1*1").unwrap(), [1, 1, 162]); - assert_eq!(compile("1/1").unwrap(), [1, 1, 163]); +fn execute_one() -> io::Result<()> { + assert_eq!(run(&compile("1").unwrap()), 1.0); Ok(()) } diff --git a/compiler-rs/tests/wasm_test.rs b/compiler-rs/tests/wasm_test.rs index 1ecc147..36afef8 100644 --- a/compiler-rs/tests/wasm_test.rs +++ b/compiler-rs/tests/wasm_test.rs @@ -1,72 +1,29 @@ extern crate wabt; extern crate wasmi; -use std::error::Error; +use wasmi::ImportsBuilder; +use wasmi::{ModuleInstance, NopExternals, RuntimeValue}; -use wasmi::{ - nan_preserving_float::F64, GlobalDescriptor, GlobalInstance, GlobalRef, ImportResolver, - ImportsBuilder, -}; -use wasmi::{ - Error as InterpreterError, ModuleImportResolver, ModuleInstance, ModuleRef, NopExternals, - RuntimeValue, -}; - -use parity_wasm::{ - builder::{from_module, module}, - elements::{ - BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, Instruction, - Instructions, MemoryType, Module, TableType, ValueType, - }, -}; - -struct SpecModule { - g: GlobalRef, -} - -impl SpecModule { - fn new() -> Self { - SpecModule { - g: GlobalInstance::alloc(RuntimeValue::F64(666.0.into()), false), - } - } -} - -impl ModuleImportResolver for SpecModule { - fn resolve_global( - &self, - field_name: &str, - _global_type: &GlobalDescriptor, - ) -> Result { - panic!("Filed name"); - Ok(GlobalInstance::alloc(RuntimeValue::F64(1.0.into()), false)) - } -} - -// #[test] +#[test] fn wasm_test() { // Parse WAT (WebAssembly Text format) into wasm bytecode. let wasm_binary: Vec = wabt::wat2wasm( r#" (module - (import "main" "foo" (global (;0;) (mut f64))) (func (export "test") (result f64) - f64.const 69 - set_global 0 f64.const 1 + f64.const 1 + f64.add ) ) "#, ) .expect("failed to parse wat"); + + // println!("{:?}", wasm_binary); // Load wasm binary and prepare it for instantiation. let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); - /* - - let mut imports = ImportsBuilder::default(); - let main = SpecModule::new(); - imports.push_resolver("main", &main); - + let imports = ImportsBuilder::default(); // Instantiate a module with empty imports and // assert that there is no `start` function. let instance = ModuleInstance::new(&module, &imports) @@ -79,6 +36,6 @@ fn wasm_test() { instance .invoke_export("test", &[], &mut NopExternals,) .expect("failed to execute export"), - Some(RuntimeValue::F64(1.0.into())), - ); */ + Some(RuntimeValue::F64(2.0.into())), + ); } From 827e6e8ece4b7fcc2b746cced67986913d218f10 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 06/85] Allow dead opcodes --- compiler-rs/src/ops.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler-rs/src/ops.rs b/compiler-rs/src/ops.rs index 56dc924..a08e2ac 100644 --- a/compiler-rs/src/ops.rs +++ b/compiler-rs/src/ops.rs @@ -1,5 +1,5 @@ // https://github.com/paritytech/parity-wasm/blob/8caf6a8bc79a98c2f298b8fdf50857b8638e2dfc/src/elements/ops.rs#L607 -#[allow(missing_docs)] +#[allow(missing_docs, dead_code)] pub mod opcodes { pub const UNREACHABLE: u8 = 0x00; pub const NOP: u8 = 0x01; From 1ce622eaa420278f2ce6b6f01467fedb673c00ad Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 07/85] Compile using parity-wasm --- compiler-rs/Cargo.toml | 2 +- compiler-rs/src/ast.rs | 2 +- compiler-rs/src/emitter.rs | 146 +++++++++++------- compiler-rs/src/file_chars.rs | 33 ++-- compiler-rs/src/lexer.rs | 18 +-- compiler-rs/src/lib.rs | 11 +- compiler-rs/src/parser.rs | 7 +- compiler-rs/tests/intagration_test.rs | 62 +++----- .../compiler/src/__tests__/encoding.test.ts | 4 + 9 files changed, 144 insertions(+), 141 deletions(-) create mode 100644 packages/compiler/src/__tests__/encoding.test.ts diff --git a/compiler-rs/Cargo.toml b/compiler-rs/Cargo.toml index ff1ddcf..15fbcfe 100644 --- a/compiler-rs/Cargo.toml +++ b/compiler-rs/Cargo.toml @@ -7,8 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +parity-wasm = "0.42.2" [dev-dependencies] wasmi = "0.8.0" wabt = "0.9.0" -parity-wasm = "0.42.2" diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index e758e76..88d51f6 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -18,7 +18,7 @@ pub struct BinaryExpression { #[derive(Debug, PartialEq)] pub struct NumberLiteral { - pub value: u64, + pub value: f64, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index fc16bc2..d240399 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,30 +1,93 @@ -use std::io; - -use crate::{ - ast::{BinaryExpression, BinaryOperator, NumberLiteral}, - ops::opcodes, +use parity_wasm::elements::{ + CodeSection, ExportEntry, ExportSection, Func, FuncBody, FunctionSection, FunctionType, + Internal, Module, Section, Serialize, Type, TypeSection, ValueType, }; +use parity_wasm::elements::{Instruction, Instructions}; + +use crate::ast::{BinaryExpression, BinaryOperator}; use super::ast::{Expression, Program}; -#[derive(Debug, Clone)] -pub enum Error { - HeapOther(String), +pub fn emit(program: Program) -> Result, String> { + let emitter = Emitter {}; + emitter.emit(program) } -impl From for Error { - fn from(err: io::Error) -> Self { - Error::HeapOther(format!("I/O Error: {:?}", err)) +struct Emitter {} + +impl Emitter { + fn emit(&self, program: Program) -> Result, String> { + let mut binary: Vec = Vec::new(); + let locals = vec![]; + let instructions = self.emit_program(program)?; + let func_body = FuncBody::new(locals, instructions); + let code_section = CodeSection::with_bodies(vec![func_body]); + + let export_section = ExportSection::with_entries(vec![ExportEntry::new( + "test".to_string(), + Internal::Function(0), + )]); + + let params = vec![]; + let results = vec![ValueType::F64]; + + let type_section = + TypeSection::with_types(vec![Type::Function(FunctionType::new(params, results))]); + let function_section = FunctionSection::with_entries(vec![Func::new(0)]); + + let module = Module::new(vec![ + Section::Type(type_section), + Section::Function(function_section), + Section::Export(export_section), + Section::Code(code_section), + ]); + module + .serialize(&mut binary) + .map_err(|err| format!("Serialization Error: {}", err))?; + + Ok(binary) } -} -pub trait Serialize { - /// Serialization error produced by serialization routine. - type Error: From; - /// Serialize type to serial i/o - fn serialize(self, writer: &mut W) -> Result<(), Self::Error>; + fn emit_program(&self, program: Program) -> Result { + let expression_instructions = self.emit_expression(program.expression)?; + let mut new = Vec::with_capacity(expression_instructions.len() + 1); + new.extend_from_slice(&expression_instructions); + new.push(Instruction::End); + Ok(Instructions::new(new)) + } + + fn emit_expression(&self, expression: Expression) -> Result, String> { + match expression { + Expression::BinaryExpression(binary_expression) => { + self.emit_binary_expression(binary_expression) + } + Expression::NumberLiteral(number_literal) => Ok(vec![Instruction::F64Const( + u64::from_le_bytes(number_literal.value.to_le_bytes()), + )]), + } + } + + fn emit_binary_expression( + &self, + binary_expression: BinaryExpression, + ) -> Result, String> { + let left = self.emit_expression(*binary_expression.left)?; + let right = self.emit_expression(*binary_expression.right)?; + let op = match binary_expression.op { + BinaryOperator::Add => Instruction::F64Add, + BinaryOperator::Subtract => Instruction::F64Sub, + BinaryOperator::Multiply => Instruction::F64Mul, + BinaryOperator::Divide => Instruction::F64Div, + }; + let mut instructions = Vec::with_capacity(left.len() + right.len() + 1); + instructions.extend_from_slice(&left); + instructions.extend_from_slice(&right); + instructions.push(op); + Ok(instructions) + } } +/* impl Serialize for Program { type Error = Error; fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { @@ -45,10 +108,9 @@ impl Serialize for Expression { impl Serialize for NumberLiteral { type Error = Error; fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - opcodes::F64CONST.serialize(writer)?; - writer - .write(&[0, 0, 0, 0, 0, 0, 240, 63]) - .map_err(|err| Error::HeapOther(err.to_string()))?; + Instruction::F64Const(self.value) + .serialize(writer) + .map_err(|err| format!("{}", err))?; Ok(()) } } @@ -65,44 +127,14 @@ impl Serialize for BinaryExpression { } impl Serialize for BinaryOperator { - type Error = Error; + type Error = SerializationError; fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { match self { - Self::Add => opcodes::F64ADD.serialize(writer), - Self::Subtract => opcodes::F64SUB.serialize(writer), - Self::Multiply => opcodes::F64MUL.serialize(writer), - Self::Divide => opcodes::F64DIV.serialize(writer), + Self::Add => Instruction::F64Add.serialize(writer), + Self::Subtract => Instruction::F64Sub.serialize(writer), + Self::Multiply => Instruction::F64Mul.serialize(writer), + Self::Divide => Instruction::F64Div.serialize(writer), } } } - -impl Serialize for u8 { - type Error = Error; - fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - writer - .write(&[self]) - .map_err(|err| Error::HeapOther(err.to_string()))?; - Ok(()) - } -} - -impl Serialize for u64 { - type Error = Error; - fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - let mut buf = [0u8; 1]; - let mut v = self; - loop { - buf[0] = (v & 0b0111_1111) as u8; - v >>= 7; - if v > 0 { - buf[0] |= 0b1000_0000; - } - writer.write(&buf[..])?; - if v == 0 { - break; - } - } - - Ok(()) - } -} +*/ diff --git a/compiler-rs/src/file_chars.rs b/compiler-rs/src/file_chars.rs index c8a4fcd..7486cc1 100644 --- a/compiler-rs/src/file_chars.rs +++ b/compiler-rs/src/file_chars.rs @@ -2,16 +2,18 @@ use super::span::Position; use std::mem; use std::str::Chars; +pub const NULL: char = '!'; + pub struct FileChars<'a> { chars: Chars<'a>, - next_char: Option, + next_char: char, pub pos: Position, } impl<'a> FileChars<'a> { pub fn new(source: &'a str) -> Self { let mut chars = source.chars(); - let next_char = chars.next(); + let next_char = chars.next().unwrap_or(NULL); FileChars { chars, next_char, @@ -19,32 +21,29 @@ impl<'a> FileChars<'a> { } } - pub fn next(&mut self) -> Option { - if let Some(c) = self.next_char { - self.pos.byte_offset += c.len_utf8(); - if c == '\n' { - self.pos.column = 0; - self.pos.line += 1; - } else { - self.pos.column += 1 - } + pub fn next(&mut self) -> char { + let c = self.next_char; + self.pos.byte_offset += c.len_utf8(); + if c == '\n' { + self.pos.column = 0; + self.pos.line += 1; + } else { + self.pos.column += 1 } - mem::replace(&mut self.next_char, self.chars.next()) + + mem::replace(&mut self.next_char, self.chars.next().unwrap_or(NULL)) } pub fn eat_while(&mut self, predicate: F) where F: Fn(char) -> bool, { - while match self.next_char { - Some(c) => predicate(c), - None => false, - } { + while predicate(self.next_char) { self.next(); } } - pub fn peek(&mut self) -> Option { + pub fn peek(&mut self) -> char { self.next_char } } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 523d39c..3454dea 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -1,4 +1,4 @@ -use super::file_chars::FileChars; +use super::file_chars::{FileChars, NULL}; use super::span::Span; use super::tokens::{Token, TokenKind}; @@ -24,15 +24,13 @@ impl<'a> Lexer<'a> { pub fn next_token(&mut self) -> Result, String> { let start = self.chars.pos; let kind = match self.chars.peek() { - Some(c) => match c { - _ if is_int(c) => self.read_int(), - '+' => self.read_char_as_kind(TokenKind::Plus), - '-' => self.read_char_as_kind(TokenKind::Minus), - '*' => self.read_char_as_kind(TokenKind::Asterisk), - '/' => self.read_char_as_kind(TokenKind::Slash), - _ => return Err(format!("Unexpected token {}", c)), - }, - None => TokenKind::EOF, + c if is_int(c) => self.read_int(), + '+' => self.read_char_as_kind(TokenKind::Plus), + '-' => self.read_char_as_kind(TokenKind::Minus), + '*' => self.read_char_as_kind(TokenKind::Asterisk), + '/' => self.read_char_as_kind(TokenKind::Slash), + NULL => TokenKind::EOF, + c => return Err(format!("Unexpected token {}", c)), }; let end = self.chars.pos; Ok(Token::new(kind, Span::new(self.source, start, end))) diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 471806a..0ce8232 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -7,18 +7,11 @@ mod parser; mod span; mod tokens; -use std::io::Write; - -use emitter::Serialize; +use emitter::emit; use parser::Parser; pub fn compile(source: &str) -> Result, String> { let mut parser = Parser::new(source); let program = parser.parse()?; - let mut binary: Vec = Vec::new(); - program - .serialize(&mut binary) - .map_err(|err| format!("{:?}", err))?; - binary.flush().map_err(|err| format!("{}", err))?; - Ok(binary) + emit(program) } diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index a202fdb..2c17bbd 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -137,9 +137,10 @@ impl<'a> Parser<'a> { fn parse_int(&mut self) -> Result { if let TokenKind::Int = self.token.kind { let value = self.token.span.str_from_source(); - match value.parse() { + match value.parse::() { Ok(value) => { self.advance()?; + // TODO: This is not quite right Ok(NumberLiteral { value }) } Err(_) => Err(format!("Could not parse \"{}\" to a number", value)), @@ -167,7 +168,7 @@ fn can_parse_integer() { assert_eq!( Parser::new("1").parse(), Ok(Program { - expression: Expression::NumberLiteral(NumberLiteral { value: 1 }) + expression: Expression::NumberLiteral(NumberLiteral { value: 1.0 }) }) ); } @@ -177,7 +178,7 @@ fn can_parse_integer_2() { assert_eq!( Parser::new("2").parse(), Ok(Program { - expression: Expression::NumberLiteral(NumberLiteral { value: 2 }) + expression: Expression::NumberLiteral(NumberLiteral { value: 2.0 }) }) ); } diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index c312e8f..f4c2b51 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -5,24 +5,8 @@ use std::io; use eel_wasm::compile; use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue}; -fn build(body: &[u8]) -> Vec { - let prefix = &[ - 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, 115, - 116, 0, 0, 10, // - // These bits need to reflect the code len - 13, 1, 11, 0, - ]; - let suffix = &[11]; - - let mut result = Vec::with_capacity(prefix.len() + body.len() + suffix.len()); - result.extend_from_slice(prefix); - result.extend_from_slice(body); - result.extend_from_slice(suffix); - result -} - -fn run(body: &[u8]) -> f64 { - let wasm_binary = build(body); +fn run(body: &[u8]) -> Result { + let wasm_binary = body; let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); let instance = ModuleInstance::new(&module, &ImportsBuilder::default()) .expect("failed to instantiate wasm module") @@ -34,22 +18,23 @@ fn run(body: &[u8]) -> f64 { .invoke_export("test", &[], &mut NopExternals) .expect("failed to execute export") { - Some(RuntimeValue::F64(val)) => val.into(), - None => 0.0, - _ => 0.0, + Some(RuntimeValue::F64(val)) => Ok(val.into()), + Some(val) => Err(format!("Unexpected return type: {:?}", val)), + None => Err("No Result".to_string()), } } -#[test] -fn compile_one() -> io::Result<()> { - assert_eq!(&compile("1").unwrap(), &[68, 0, 0, 0, 0, 0, 0, 240, 63,]); - Ok(()) +fn test_run(program: &str, expected_output: f64) { + assert_eq!( + run(&compile(program).unwrap()).expect("Run Error"), + expected_output + ); } #[test] fn build_one() -> io::Result<()> { assert_eq!( - &build(&compile("1").unwrap()), + &compile("1").unwrap(), &[ 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, 115, 116, 0, 0, 10, 13, 1, 11, 0, 68, 0, 0, 0, 0, 0, 0, 240, 63, 11, @@ -58,22 +43,13 @@ fn build_one() -> io::Result<()> { Ok(()) } -/* -#[test] -fn build_one_plus_one() -> io::Result<()> { - assert_eq!( - &build(&compile("1+1").unwrap()), - &[ - 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, - 115, 116, 0, 0, 10, 23, 1, 21, 0, 68, 0, 0, 0, 0, 0, 0, 240, 63, 68, 0, 0, 0, 0, 0, 0, - 240, 63, 160, 11 - ] - ); - Ok(()) -} */ - #[test] -fn execute_one() -> io::Result<()> { - assert_eq!(run(&compile("1").unwrap()), 1.0); - Ok(()) +fn execute_one() { + test_run("1", 1.0); + test_run("1+1", 2.0); + test_run("1-1", 0.0); + test_run("2*2", 4.0); + test_run("2/2", 1.0); + + test_run("1+1*2", 3.0); } diff --git a/packages/compiler/src/__tests__/encoding.test.ts b/packages/compiler/src/__tests__/encoding.test.ts new file mode 100644 index 0000000..c36c72a --- /dev/null +++ b/packages/compiler/src/__tests__/encoding.test.ts @@ -0,0 +1,4 @@ +import { encodef64 } from "../encoding"; +test("float", () => { + expect(encodef64(1)).toEqual(new Uint8Array([0, 0, 0, 0, 0, 0, 240, 63])); +}); From 1bbb7458d4252f07afc635234249bcb94a54350b Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 08/85] Clean up some unused code --- compiler-rs/src/emitter.rs | 52 ----------- compiler-rs/src/lib.rs | 1 - compiler-rs/src/ops.rs | 182 ------------------------------------- 3 files changed, 235 deletions(-) delete mode 100644 compiler-rs/src/ops.rs diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index d240399..bd822c3 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -86,55 +86,3 @@ impl Emitter { Ok(instructions) } } - -/* -impl Serialize for Program { - type Error = Error; - fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - self.expression.serialize(writer) - } -} - -impl Serialize for Expression { - type Error = Error; - fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - match self { - Self::NumberLiteral(number) => number.serialize(writer), - Self::BinaryExpression(binary_expression) => binary_expression.serialize(writer), - } - } -} - -impl Serialize for NumberLiteral { - type Error = Error; - fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - Instruction::F64Const(self.value) - .serialize(writer) - .map_err(|err| format!("{}", err))?; - Ok(()) - } -} - -impl Serialize for BinaryExpression { - type Error = Error; - fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - self.left.serialize(writer)?; - self.right.serialize(writer)?; - self.op.serialize(writer)?; - - Ok(()) - } -} - -impl Serialize for BinaryOperator { - type Error = SerializationError; - fn serialize(self, writer: &mut W) -> Result<(), Self::Error> { - match self { - Self::Add => Instruction::F64Add.serialize(writer), - Self::Subtract => Instruction::F64Sub.serialize(writer), - Self::Multiply => Instruction::F64Mul.serialize(writer), - Self::Divide => Instruction::F64Div.serialize(writer), - } - } -} -*/ diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 0ce8232..3946f4c 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -2,7 +2,6 @@ mod ast; mod emitter; mod file_chars; mod lexer; -mod ops; mod parser; mod span; mod tokens; diff --git a/compiler-rs/src/ops.rs b/compiler-rs/src/ops.rs deleted file mode 100644 index a08e2ac..0000000 --- a/compiler-rs/src/ops.rs +++ /dev/null @@ -1,182 +0,0 @@ -// https://github.com/paritytech/parity-wasm/blob/8caf6a8bc79a98c2f298b8fdf50857b8638e2dfc/src/elements/ops.rs#L607 -#[allow(missing_docs, dead_code)] -pub mod opcodes { - pub const UNREACHABLE: u8 = 0x00; - pub const NOP: u8 = 0x01; - pub const BLOCK: u8 = 0x02; - pub const LOOP: u8 = 0x03; - pub const IF: u8 = 0x04; - pub const ELSE: u8 = 0x05; - pub const END: u8 = 0x0b; - pub const BR: u8 = 0x0c; - pub const BRIF: u8 = 0x0d; - pub const BRTABLE: u8 = 0x0e; - pub const RETURN: u8 = 0x0f; - pub const CALL: u8 = 0x10; - pub const CALLINDIRECT: u8 = 0x11; - pub const DROP: u8 = 0x1a; - pub const SELECT: u8 = 0x1b; - pub const GETLOCAL: u8 = 0x20; - pub const SETLOCAL: u8 = 0x21; - pub const TEELOCAL: u8 = 0x22; - pub const GETGLOBAL: u8 = 0x23; - pub const SETGLOBAL: u8 = 0x24; - pub const I32LOAD: u8 = 0x28; - pub const I64LOAD: u8 = 0x29; - pub const F32LOAD: u8 = 0x2a; - pub const F64LOAD: u8 = 0x2b; - pub const I32LOAD8S: u8 = 0x2c; - pub const I32LOAD8U: u8 = 0x2d; - pub const I32LOAD16S: u8 = 0x2e; - pub const I32LOAD16U: u8 = 0x2f; - pub const I64LOAD8S: u8 = 0x30; - pub const I64LOAD8U: u8 = 0x31; - pub const I64LOAD16S: u8 = 0x32; - pub const I64LOAD16U: u8 = 0x33; - pub const I64LOAD32S: u8 = 0x34; - pub const I64LOAD32U: u8 = 0x35; - pub const I32STORE: u8 = 0x36; - pub const I64STORE: u8 = 0x37; - pub const F32STORE: u8 = 0x38; - pub const F64STORE: u8 = 0x39; - pub const I32STORE8: u8 = 0x3a; - pub const I32STORE16: u8 = 0x3b; - pub const I64STORE8: u8 = 0x3c; - pub const I64STORE16: u8 = 0x3d; - pub const I64STORE32: u8 = 0x3e; - pub const CURRENTMEMORY: u8 = 0x3f; - pub const GROWMEMORY: u8 = 0x40; - pub const I32CONST: u8 = 0x41; - pub const I64CONST: u8 = 0x42; - pub const F32CONST: u8 = 0x43; - pub const F64CONST: u8 = 0x44; - pub const I32EQZ: u8 = 0x45; - pub const I32EQ: u8 = 0x46; - pub const I32NE: u8 = 0x47; - pub const I32LTS: u8 = 0x48; - pub const I32LTU: u8 = 0x49; - pub const I32GTS: u8 = 0x4a; - pub const I32GTU: u8 = 0x4b; - pub const I32LES: u8 = 0x4c; - pub const I32LEU: u8 = 0x4d; - pub const I32GES: u8 = 0x4e; - pub const I32GEU: u8 = 0x4f; - pub const I64EQZ: u8 = 0x50; - pub const I64EQ: u8 = 0x51; - pub const I64NE: u8 = 0x52; - pub const I64LTS: u8 = 0x53; - pub const I64LTU: u8 = 0x54; - pub const I64GTS: u8 = 0x55; - pub const I64GTU: u8 = 0x56; - pub const I64LES: u8 = 0x57; - pub const I64LEU: u8 = 0x58; - pub const I64GES: u8 = 0x59; - pub const I64GEU: u8 = 0x5a; - - pub const F32EQ: u8 = 0x5b; - pub const F32NE: u8 = 0x5c; - pub const F32LT: u8 = 0x5d; - pub const F32GT: u8 = 0x5e; - pub const F32LE: u8 = 0x5f; - pub const F32GE: u8 = 0x60; - - pub const F64EQ: u8 = 0x61; - pub const F64NE: u8 = 0x62; - pub const F64LT: u8 = 0x63; - pub const F64GT: u8 = 0x64; - pub const F64LE: u8 = 0x65; - pub const F64GE: u8 = 0x66; - - pub const I32CLZ: u8 = 0x67; - pub const I32CTZ: u8 = 0x68; - pub const I32POPCNT: u8 = 0x69; - pub const I32ADD: u8 = 0x6a; - pub const I32SUB: u8 = 0x6b; - pub const I32MUL: u8 = 0x6c; - pub const I32DIVS: u8 = 0x6d; - pub const I32DIVU: u8 = 0x6e; - pub const I32REMS: u8 = 0x6f; - pub const I32REMU: u8 = 0x70; - pub const I32AND: u8 = 0x71; - pub const I32OR: u8 = 0x72; - pub const I32XOR: u8 = 0x73; - pub const I32SHL: u8 = 0x74; - pub const I32SHRS: u8 = 0x75; - pub const I32SHRU: u8 = 0x76; - pub const I32ROTL: u8 = 0x77; - pub const I32ROTR: u8 = 0x78; - - pub const I64CLZ: u8 = 0x79; - pub const I64CTZ: u8 = 0x7a; - pub const I64POPCNT: u8 = 0x7b; - pub const I64ADD: u8 = 0x7c; - pub const I64SUB: u8 = 0x7d; - pub const I64MUL: u8 = 0x7e; - pub const I64DIVS: u8 = 0x7f; - pub const I64DIVU: u8 = 0x80; - pub const I64REMS: u8 = 0x81; - pub const I64REMU: u8 = 0x82; - pub const I64AND: u8 = 0x83; - pub const I64OR: u8 = 0x84; - pub const I64XOR: u8 = 0x85; - pub const I64SHL: u8 = 0x86; - pub const I64SHRS: u8 = 0x87; - pub const I64SHRU: u8 = 0x88; - pub const I64ROTL: u8 = 0x89; - pub const I64ROTR: u8 = 0x8a; - pub const F32ABS: u8 = 0x8b; - pub const F32NEG: u8 = 0x8c; - pub const F32CEIL: u8 = 0x8d; - pub const F32FLOOR: u8 = 0x8e; - pub const F32TRUNC: u8 = 0x8f; - pub const F32NEAREST: u8 = 0x90; - pub const F32SQRT: u8 = 0x91; - pub const F32ADD: u8 = 0x92; - pub const F32SUB: u8 = 0x93; - pub const F32MUL: u8 = 0x94; - pub const F32DIV: u8 = 0x95; - pub const F32MIN: u8 = 0x96; - pub const F32MAX: u8 = 0x97; - pub const F32COPYSIGN: u8 = 0x98; - pub const F64ABS: u8 = 0x99; - pub const F64NEG: u8 = 0x9a; - pub const F64CEIL: u8 = 0x9b; - pub const F64FLOOR: u8 = 0x9c; - pub const F64TRUNC: u8 = 0x9d; - pub const F64NEAREST: u8 = 0x9e; - pub const F64SQRT: u8 = 0x9f; - pub const F64ADD: u8 = 0xa0; - pub const F64SUB: u8 = 0xa1; - pub const F64MUL: u8 = 0xa2; - pub const F64DIV: u8 = 0xa3; - pub const F64MIN: u8 = 0xa4; - pub const F64MAX: u8 = 0xa5; - pub const F64COPYSIGN: u8 = 0xa6; - - pub const I32WRAPI64: u8 = 0xa7; - pub const I32TRUNCSF32: u8 = 0xa8; - pub const I32TRUNCUF32: u8 = 0xa9; - pub const I32TRUNCSF64: u8 = 0xaa; - pub const I32TRUNCUF64: u8 = 0xab; - pub const I64EXTENDSI32: u8 = 0xac; - pub const I64EXTENDUI32: u8 = 0xad; - pub const I64TRUNCSF32: u8 = 0xae; - pub const I64TRUNCUF32: u8 = 0xaf; - pub const I64TRUNCSF64: u8 = 0xb0; - pub const I64TRUNCUF64: u8 = 0xb1; - pub const F32CONVERTSI32: u8 = 0xb2; - pub const F32CONVERTUI32: u8 = 0xb3; - pub const F32CONVERTSI64: u8 = 0xb4; - pub const F32CONVERTUI64: u8 = 0xb5; - pub const F32DEMOTEF64: u8 = 0xb6; - pub const F64CONVERTSI32: u8 = 0xb7; - pub const F64CONVERTUI32: u8 = 0xb8; - pub const F64CONVERTSI64: u8 = 0xb9; - pub const F64CONVERTUI64: u8 = 0xba; - pub const F64PROMOTEF32: u8 = 0xbb; - - pub const I32REINTERPRETF32: u8 = 0xbc; - pub const I64REINTERPRETF64: u8 = 0xbd; - pub const F32REINTERPRETI32: u8 = 0xbe; - pub const F64REINTERPRETI64: u8 = 0xbf; -} From 32144b62de04085e46b569afe76389c0a75b70f7 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 09/85] wasm-bindgen and compatibility test suite --- compiler-rs/Cargo.toml | 27 ++ compiler-rs/README.md | 8 + compiler-rs/src/lib.rs | 13 + compiler-rs/tests/compatibility_test.rs | 553 ++++++++++++++++++++++++ 4 files changed, 601 insertions(+) create mode 100644 compiler-rs/README.md create mode 100644 compiler-rs/tests/compatibility_test.rs diff --git a/compiler-rs/Cargo.toml b/compiler-rs/Cargo.toml index 15fbcfe..b6100a0 100644 --- a/compiler-rs/Cargo.toml +++ b/compiler-rs/Cargo.toml @@ -5,10 +5,37 @@ authors = ["Jordan Eldredge "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] [dependencies] +wasm-bindgen = "0.2.63" parity-wasm = "0.42.2" +structopt = "0.3.21" + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.6", optional = true } + +# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size +# compared to the default allocator's ~10K. It is slower than the default +# allocator, however. +# +# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. +wee_alloc = { version = "0.4.5", optional = true } + [dev-dependencies] wasmi = "0.8.0" wabt = "0.9.0" +wasm-bindgen-test = "0.3.13" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" + diff --git a/compiler-rs/README.md b/compiler-rs/README.md new file mode 100644 index 0000000..d7d107d --- /dev/null +++ b/compiler-rs/README.md @@ -0,0 +1,8 @@ +## Build + +To build the Wasm module: +```bash +wasm-pack build +``` + +You can find the output in `pkg/`. \ No newline at end of file diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 3946f4c..93cd833 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -9,6 +9,19 @@ mod tokens; use emitter::emit; use parser::Parser; +use wasm_bindgen::prelude::*; + +// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global +// allocator. +#[cfg(feature = "wee_alloc")] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +#[wasm_bindgen] +pub fn assert_compile(source: &str) -> Vec { + compile(source).expect("Don't screw it up") +} + pub fn compile(source: &str) -> Result, String> { let mut parser = Parser::new(source); let program = parser.parse()?; diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs new file mode 100644 index 0000000..c3510a5 --- /dev/null +++ b/compiler-rs/tests/compatibility_test.rs @@ -0,0 +1,553 @@ +use eel_wasm::compile; +use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue}; + +fn run(body: &[u8]) -> Result { + let wasm_binary = body; + let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); + let instance = ModuleInstance::new(&module, &ImportsBuilder::default()) + .expect("failed to instantiate wasm module") + .assert_no_start(); + + // Finally, invoke the exported function "test" with no parameters + // and empty external function executor. + match instance + .invoke_export("test", &[], &mut NopExternals) + .expect("failed to execute export") + { + Some(RuntimeValue::F64(val)) => Ok(val.into()), + Some(val) => Err(format!("Unexpected return type: {:?}", val)), + None => Err("No Result".to_string()), + } +} + +#[test] +fn compatibility_tests() { + let test_cases: &[(&'static str, &'static str, f64)] = &[ + ("[REMOVE] Integer", "1", 2.0), + ("Expressions", "g = ((6- -7.0)+ 3.0);", 16.0), + ("Number", "g = 5;", 5.0), + ("Number with decimal", "g = 5.5;", 5.5), + ("Number with decimal and no leading whole", "g = .5;", 0.5), + ("Number with decimal and no trailing dec", "g = 5.;", 5.0), + ("Number with no digits", "g = .;", 0.0), + ("Optional final semi", "(g = 5; g = 10.0);", 10.0), + ("Unary negeation", "g = -10;", -10.0), + ("Unary plus", "g = +10;", 10.0), + ("Unary not true", "g = !10;", 0.0), + ("Unary not false", "g = !0;", 1.0), + ("Unary not 0.1", "g = !0.1;", 0.0), + ("Unary not < epsilon", "g = !0.000009;", 1.0), + ("Multiply", "g = 10 * 10;", 100.0), + ("Divide", "g = 10 / 10;", 1.0), + ("Mod", "g = 5 % 2;", 1.0), + ("Mod zero", "g = 5 % 0;", 0.0), + ("Bitwise and", "g = 3 & 5;", 1.0), + ("Bitwise or", "g = 3 | 5;", 7.0), + ("To the power", "g = 5 ^ 2;", 25.0), + ("Order of operations (+ and *)", "g = 1 + 1 * 10;", 11.0), + ("Order of operations (+ and /)", "g = 1 + 1 / 10;", 1.1), + ("Order of operations (unary - and +)", "g = -1 + 1;", 0.0), + ("Parens", "g = (1 + 1.0) * 10;", 20.0), + ("Absolute value negative", "g = abs(-10);", 10.0), + ("Absolute value positive", "g = abs(10);", 10.0), + ("Function used as expression", "g = 1 + abs(-10);", 11.0), + ("Min", "g = min(2, 10.0);", 2.0), + ("Min reversed", "g = min(10, 2.0);", 2.0), + ("Max", "g = max(2, 10.0);", 10.0), + ("Max reversed", "g = max(10, 2.0);", 10.0), + ("Sqrt", "g = sqrt(4);", 2.0), + ("Sqrt (negative)", "g = sqrt(-4);", 2.0), + ("Sqr", "g = sqr(10);", 100.0), + ("Int", "g = int(4.5);", 4.0), + ("Sin", "g = sin(10);", 10.0_f64.cos()), + ("Cos", "g = cos(10);", 10.0_f64.cos()), + ("Tan", "g = tan(10);", 10.0_f64.tan()), + ("Asin", "g = asin(0.5);", 0.5_f64.asin()), + ("Acos", "g = acos(0.5);", 0.5_f64.acos()), + ("Atan", "g = atan(0.5);", 0.5_f64.atan()), + ("Atan2", "g = atan2(1, 1.0);", 1_f64.atan2(1.0)), + ("Assign to globals", "g = 10;", 10.0), + ("Read globals", "g = x;", 10.0), + ("Multiple statements", "g = 10; g = 20;", 20.0), + ("Multiple statements expression", "(g = 10; g = 20;);", 20.0), + ( + "Multiple statements expression implcit return", + "g = (0; 20 + 5;);", + 25.0, + ), + ("if", "g = if(0, 20, 10.0);", 10.0), + ("if", "g = if(0, 20, 10.0);", 10.0), + ( + "if does short-circit (consiquent)", + "if(0, (g = 10;), 10.0);", + 0.0, + ), + ( + "if does short-circit (alternate)", + "if(1, (10), (g = 10;));", + 0.0, + ), + ("above (true)", "g = above(10, 4.0);", 1.0), + ("above (false)", "g = above(4, 10.0);", 0.0), + ("below (true)", "g = below(4, 10.0);", 1.0), + ("below (false)", "g = below(10, 4.0);", 0.0), + ("Line comments", "g = 10; // g = 20;", 10.0), + ("Line comments (\\\\)", "g = 10; \\\\ g = 20;", 10.0), + ("Equal (false)", "g = equal(10, 5.0);", 0.0), + ("Equal (true)", "g = equal(10, 10.0);", 1.0), + ("Pow", "g = pow(2, 10.0);", 1024.0), + ("Log", "g = log(10);", 10_f64.log(std::f64::consts::E)), + ("Log10", "g = log10(10);", 10_f64.log10()), + ("Sign (10)", "g = sign(10);", 1.0), + ("Sign (-10)", "g = sign(-10);", -1.0), + ("Sign (0)", "g = sign(0);", 0.0), + ("Sign (-0)", "g = sign(-0);", 0.0), + ("Local variables", "a = 10; g = a * a;", 100.0), + ( + "Local variable assignment (implicit return)", + "g = a = 10;", + 10.0, + ), + ("Bor (true, false)", "g = bor(10, 0.0);", 1.0), + ("Bor (false, true)", "g = bor(0, 2.0);", 1.0), + ("Bor (true, true)", "g = bor(1, 7.0);", 1.0), + ("Bor (false, false)", "g = bor(0, 0.0);", 0.0), + ("Bor does not shortcircut", "bor(1, g = 10.0);", 10.0), + ("Bor respects epsilon", "g = bor(0.000009, 0.000009);", 0.0), + ("Band (true, false)", "g = band(10, 0.0);", 0.0), + ("Band (false, true)", "g = band(0, 2.0);", 0.0), + ("Band (true, true)", "g = band(1, 7.0);", 1.0), + ("Band (false, false)", "g = band(0, 0.0);", 0.0), + ("Band does not shortcircut", "band(0, g = 10.0);", 10.0), + ( + "Band respects epsilon", + "g = band(0.000009, 0.000009);", + 0.0, + ), + ("Bnot (true)", "g = bnot(10);", 0.0), + ("Bnot (false)", "g = bnot(0);", 1.0), + ("Bnot 0.1", "g = bnot(0.1);", 0.0), + ("Bnot < epsilon", "g = bnot(0.000009);", 1.0), + ("Plus equals", "g = 5; g += 5;", 10.0), + ("Plus equals (local var)", "a = 5; a += 5; g = a;", 10.0), + ("Plus equals (megabuf)", "g = megabuf(0) += 5;", 5.0), + ("Minus equals", "g = 5; g -= 4;", 1.0), + ("Minus equals (local var)", "a = 5; a -= 4; g = a;", 1.0), + ("Minus equals (megabuf)", "g = megabuf(0) -= 5;", -5.0), + ("Times equals", "g = 5; g *= 4;", 20.0), + ("Times equals (local var)", "a = 5; a *= 4; g = a;", 20.0), + ( + "Times equals (megabuf)", + "g = (megabuf(0) = 9; megabuf(0) *= 2.0);", + 18.0, + ), + ("Divide equals", "g = 5; g /= 2;", 2.5), + ("Divide equals (local var)", "a = 5; a /= 2; g = a;", 2.5), + ( + "Divide equals (megabuf)", + "g = (megabuf(0) = 8; megabuf(0) /= 2.0);", + 4.0, + ), + ("Mod equals", "g = 5; g %= 2;", 1.0), + ("Mod equals (local var)", "a = 5; a %= 2; g = a;", 1.0), + ( + "Mod equals (megabuf)", + "g = (megabuf(0) = 5; megabuf(0) %= 2.0);", + 1.0, + ), + ( + "Statement block as argument", + "g = int(g = 5; g + 10.5;);", + 15.0, + ), + ("Logical and (both true)", "g = 10 && 2;", 1.0), + ( + "Logical and does not run the left twice", + "(g = g + 1; 0;) && 10;", + 1.0, + ), + ("Logical and (first value false)", "g = 0 && 2;", 0.0), + ("Logical and (second value false)", "g = 2 && 0;", 0.0), + ("Logical or (both true)", "g = 10 || 2;", 1.0), + ("Logical or (first value false)", "g = 0 || 2;", 1.0), + ("Logical and shortcircuts", "0 && g = 10;", 0.0), + ("Logical or shortcircuts", "1 || g = 10;", 0.0), + ("Exec2", "g = exec2(x = 5, x * 3.0);", 15.0), + ("Exec3", "g = exec3(x = 5, x = x * 3, x + 1.0);", 16.0), + ("While", "while(exec2(g = g + 1, g - 10.0));", 10.0), + ("Loop", "loop(10, g = g + 1.0);", 10.0), + ("Loop fractional times", "loop(1.5, g = g + 1.0);", 1.0), + ("Loop zero times", "loop(0, g = g + 1.0);", 0.0), + ("Loop negative times", "loop(-2, g = g + 1.0);", 0.0), + ( + "Loop negative fractional times", + "loop(-0.2, g = g + 1.0);", + 0.0, + ), + ("Equality (true)", "g = 1 == 1;", 1.0), + ("Equality epsilon", "g = 0 == 0.000009;", 1.0), + ("!Equality (true)", "g = 1 != 0;", 1.0), + ("!Equality (false)", "g = 1 != 1;", 0.0), + ("!Equality epsilon", "g = 0 != 0.000009;", 0.0), + ("Equality (false)", "g = 1 == 0;", 0.0), + ("Less than (true)", "g = 1 < 2;", 1.0), + ("Less than (false)", "g = 2 < 1;", 0.0), + ("Greater than (true)", "g = 2 > 1;", 1.0), + ("Greater than (false)", "g = 1 > 2;", 0.0), + ("Less than or equal (true)", "g = 1 <= 2;", 1.0), + ("Less than or equal (false)", "g = 2 <= 1;", 0.0), + ("Greater than or equal (true)", "g = 2 >= 1;", 1.0), + ("Greater than or equal (false)", "g = 1 >= 2;", 0.0), + ("Script without trailing semi", "g = 1", 1.0), + ("Megabuf access", "g = megabuf(1);", 0.0), + ( + "Max index megabuf", + "megabuf(8388607) = 10; g = megabuf(8388607);", + 10.0, + ), + ( + "Max index + 1 megabuf", + "megabuf(8388608) = 10; g = megabuf(8388608);", + 0.0, + ), + ( + "Max index gmegabuf", + "gmegabuf(8388607) = 10; g = gmegabuf(8388607);", + 10.0, + ), + ( + "Max index+1 gmegabuf", + "gmegabuf(8388608) = 10; g = gmegabuf(8388608);", + 0.0, + ), + ( + "Megabuf assignment", + "megabuf(1) = 10; g = megabuf(1);", + 10.0, + ), + ( + "Megabuf assignment (idx 100.0)", + "megabuf(100) = 10; g = megabuf(100);", + 10.0, + ), + ("Megabuf (float)", "megabuf(0) = 1.2; g = megabuf(0);", 1.2), + ("Gmegabuf", "gmegabuf(0) = 1.2; g = gmegabuf(0);", 1.2), + ( + "Megabuf != Gmegabuf", + "gmegabuf(0) = 1.2; g = megabuf(0);", + 0.0, + ), + ( + "Gmegabuf != Megabuf", + "megabuf(0) = 1.2; g = gmegabuf(0);", + 0.0, + ), + ("Case insensitive vars", "G = 10;", 10.0), + ("Case insensitive funcs", "g = InT(10);", 10.0), + ("Consecutive semis", "g = 10;;; ;g = 20;;", 20.0), + ("Equality (< epsilon)", "g = 0.000009 == 0;", 1.0), + ("Equality (< -epsilon)", "g = -0.000009 == 0;", 1.0), + ("Variables don't collide", "g = 1; not_g = 2;", 1.0), + ("Block comment", "g = 1; /* g = 10 */ g = g * 2;", 2.0), + ("Sigmoid 1, 2", "g = sigmoid(1, 2.0);", 0.8807970779778823), + ("Sigmoid 2, 1", "g = sigmoid(2, 1.0);", 0.8807970779778823), + ("Sigmoid 0, 0", "g = sigmoid(0, 0.0);", 0.5), + ("Sigmoid 10, 10", "g = sigmoid(10, 10.0);", 1.0), + ("Exp", "g = exp(10);", 10_f64.exp()), + ("Floor", "g = floor(10.9);", 10.0), + ("Floor", "g = floor(-10.9);", -11.0), + ("Ceil", "g = ceil(9.1);", 10.0), + ("Ceil", "g = ceil(-9.9);", -9.0), + ("Assign", "assign(g, 10.0);", 10.0), + ("Assign return value", "g = assign(x, 10.0);", 10.0), + ( + "EPSILON buffer indexes", + "megabuf(9.99999) = 10; g = megabuf(10)", + 10.0, + ), + ( + "+EPSILON & rounding -#s toward 0", + "megabuf(-1) = 10; g = megabuf(0)", + 10.0, + ), + ("Negative buffer index read as 0", "g = megabuf(-2);", 0.0), + ("Negative buffer index", "g = (megabuf(-2) = 20.0);", 0.0), + ( + "Negative buffer index gmegabuf", + "g = (gmegabuf(-2) = 20.0);", + 0.0, + ), + ( + "Negative buf index execs right hand side", + "megabuf(-2) = (g = 10.0);", + 10.0, + ), + ("Negative buf index +=", "g = megabuf(-2) += 10;", 10.0), + ("Negative buf index -=", "g = megabuf(-2) -= 10;", -10.0), + ("Negative buf index *=", "g = megabuf(-2) *= 10;", 0.0), + ("Negative buf index /=", "g = megabuf(-2) /= 10;", 0.0), + ("Negative buf index %=", "g = megabuf(-2) %= 10;", 0.0), + ( + "Buff += mutates", + "megabuf(100) += 10; g = megabuf(100)", + 10.0, + ), + ( + "Buffers don't collide", + "megabuf(100) = 10; g = gmegabuf(100)", + 0.0, + ), + ( + "gmegabuf does not write megabuf", + "i = 100; loop(10000,gmegabuf(i) = 10; i += 1.0); g = megabuf(100)", + 0.0, + ), + ( + "megabuf does not write gmegabuf", + "i = 100; loop(10000,megabuf(i) = 10; i += 1.0); g = gmegabuf(100)", + 0.0, + ), + ( + "Adjacent buf indicies don't collide", + "megabuf(99) = 10; megabuf(100) = 1; g = megabuf(99)", + 10.0, + ), + ("Exponentiation associativity", "g = 2 ^ 2 ^ 4", 256.0), + ( + "^ has lower precedence than * (left)", + "g = 2 ^ 2 * 4", + 16.0, + ), + ( + "^ has lower precedence than * (right)", + "g = 2 * 2 ^ 4", + 32.0, + ), + ( + "% has lower precedence than * (right)", + "g = 2 * 5 % 2", + 2.0, + ), + ("% has lower precedence than * (left)", "g = 2 % 5 * 2", 4.0), + ( + "% and ^ have the same precedence (% first)", + "g = 2 % 5 ^ 2", + 4.0, + ), + ( + "% and ^ have the same precedence (^ first)", + "g = 2 ^ 5 % 2", + 0.0, + ), + ("Loop limit", "g = 0; while(g = g + 1.0)", 1048576.0), + ("Divide by zero", "g = 100 / 0", 0.0), + ( + "Divide by less than epsilon", + "g = 100 / 0.000001", + 100000000.0, + ), + ]; + + let mut failing: Vec<&str> = Vec::new(); + + for (name, code, expected) in test_cases { + match compile(code) { + Ok(binary) => { + let actual = run(&binary).expect("to run"); + assert_eq!(&actual, expected) + } + Err(_) => failing.push(name), + } + } + + let expected_failing: Vec<&str> = vec![ + "Expressions", + "Number", + "Number with decimal", + "Number with decimal and no leading whole", + "Number with decimal and no trailing dec", + "Number with no digits", + "Optional final semi", + "Unary negeation", + "Unary plus", + "Unary not true", + "Unary not false", + "Unary not 0.1", + "Unary not < epsilon", + "Multiply", + "Divide", + "Mod", + "Mod zero", + "Bitwise and", + "Bitwise or", + "To the power", + "Order of operations (+ and *)", + "Order of operations (+ and /)", + "Order of operations (unary - and +)", + "Parens", + "Absolute value negative", + "Absolute value positive", + "Function used as expression", + "Min", + "Min reversed", + "Max", + "Max reversed", + "Sqrt", + "Sqrt (negative)", + "Sqr", + "Int", + "Sin", + "Cos", + "Tan", + "Asin", + "Acos", + "Atan", + "Atan2", + "Assign to globals", + "Read globals", + "Multiple statements", + "Multiple statements expression", + "Multiple statements expression implcit return", + "if", + "if", + "if does short-circit (consiquent)", + "if does short-circit (alternate)", + "above (true)", + "above (false)", + "below (true)", + "below (false)", + "Line comments", + "Line comments (\\\\)", + "Equal (false)", + "Equal (true)", + "Pow", + "Log", + "Log10", + "Sign (10)", + "Sign (-10)", + "Sign (0)", + "Sign (-0)", + "Local variables", + "Local variable assignment (implicit return)", + "Bor (true, false)", + "Bor (false, true)", + "Bor (true, true)", + "Bor (false, false)", + "Bor does not shortcircut", + "Bor respects epsilon", + "Band (true, false)", + "Band (false, true)", + "Band (true, true)", + "Band (false, false)", + "Band does not shortcircut", + "Band respects epsilon", + "Bnot (true)", + "Bnot (false)", + "Bnot 0.1", + "Bnot < epsilon", + "Plus equals", + "Plus equals (local var)", + "Plus equals (megabuf)", + "Minus equals", + "Minus equals (local var)", + "Minus equals (megabuf)", + "Times equals", + "Times equals (local var)", + "Times equals (megabuf)", + "Divide equals", + "Divide equals (local var)", + "Divide equals (megabuf)", + "Mod equals", + "Mod equals (local var)", + "Mod equals (megabuf)", + "Statement block as argument", + "Logical and (both true)", + "Logical and does not run the left twice", + "Logical and (first value false)", + "Logical and (second value false)", + "Logical or (both true)", + "Logical or (first value false)", + "Logical and shortcircuts", + "Logical or shortcircuts", + "Exec2", + "Exec3", + "While", + "Loop", + "Loop fractional times", + "Loop zero times", + "Loop negative times", + "Loop negative fractional times", + "Equality (true)", + "Equality epsilon", + "!Equality (true)", + "!Equality (false)", + "!Equality epsilon", + "Equality (false)", + "Less than (true)", + "Less than (false)", + "Greater than (true)", + "Greater than (false)", + "Less than or equal (true)", + "Less than or equal (false)", + "Greater than or equal (true)", + "Greater than or equal (false)", + "Script without trailing semi", + "Megabuf access", + "Max index megabuf", + "Max index + 1 megabuf", + "Max index gmegabuf", + "Max index+1 gmegabuf", + "Megabuf assignment", + "Megabuf assignment (idx 100.0)", + "Megabuf (float)", + "Gmegabuf", + "Megabuf != Gmegabuf", + "Gmegabuf != Megabuf", + "Case insensitive vars", + "Case insensitive funcs", + "Consecutive semis", + "Equality (< epsilon)", + "Equality (< -epsilon)", + "Variables don\'t collide", + "Block comment", + "Sigmoid 1, 2", + "Sigmoid 2, 1", + "Sigmoid 0, 0", + "Sigmoid 10, 10", + "Exp", + "Floor", + "Floor", + "Ceil", + "Ceil", + "Assign", + "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", + "Negative buf index +=", + "Negative buf index -=", + "Negative buf index *=", + "Negative buf index /=", + "Negative buf index %=", + "Buff += mutates", + "Buffers don\'t collide", + "gmegabuf does not write megabuf", + "megabuf does not write gmegabuf", + "Adjacent buf indicies don\'t collide", + "Exponentiation associativity", + "^ has lower precedence than * (left)", + "^ has lower precedence than * (right)", + "% has lower precedence than * (right)", + "% has lower precedence than * (left)", + "% and ^ have the same precedence (% first)", + "% and ^ have the same precedence (^ first)", + "Loop limit", + "Divide by zero", + "Divide by less than epsilon", + ]; + + assert_eq!(failing, expected_failing); +} From 02c8c74adc15efae47c1fe97a0530468a65a89b2 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 10/85] Hack in assignment --- compiler-rs/src/ast.rs | 20 +++- compiler-rs/src/bin/compiler.rs | 32 +++-- compiler-rs/src/emitter.rs | 150 ++++++++++++++++++------ compiler-rs/src/lexer.rs | 65 +++++++++- compiler-rs/src/parser.rs | 62 ++++++++-- compiler-rs/src/tokens.rs | 2 + compiler-rs/tests/compatibility_test.rs | 35 +++--- 7 files changed, 302 insertions(+), 64 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 88d51f6..50864f8 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -1,12 +1,13 @@ #[derive(Debug, PartialEq)] pub struct Program { - pub expression: Expression, + pub expressions: Vec, } #[derive(Debug, PartialEq)] pub enum Expression { BinaryExpression(BinaryExpression), NumberLiteral(NumberLiteral), + Assignment(Assignment), } #[derive(Debug, PartialEq)] @@ -28,3 +29,20 @@ pub enum BinaryOperator { Multiply, Divide, } + +#[derive(Debug, PartialEq)] +pub struct Identifier { + pub name: String, +} + +#[derive(Debug, PartialEq)] +pub enum AssignmentOperator { + Equal, +} + +#[derive(Debug, PartialEq)] +pub struct Assignment { + pub left: Identifier, + pub operator: AssignmentOperator, + pub right: Box, +} diff --git a/compiler-rs/src/bin/compiler.rs b/compiler-rs/src/bin/compiler.rs index f4b04e5..7ce629b 100644 --- a/compiler-rs/src/bin/compiler.rs +++ b/compiler-rs/src/bin/compiler.rs @@ -1,16 +1,27 @@ use eel_wasm::compile; -use std::env; use std::fs; use std::process; +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "eel-wasm", about = "Compile Eel code to WebAssembly.")] +struct Opt { + /// Input file + #[structopt(parse(from_os_str))] + input: PathBuf, + + /// Output file, stdout if not present + #[structopt(parse(from_os_str))] + output: PathBuf, +} + fn main() { - let args: Vec = env::args().collect(); - let filename = args.get(1).unwrap_or_else(|| { - eprintln!("Usage: compile INPUT"); - process::exit(1); - }); + let opt = Opt::from_args(); + let filename = opt.input; let source = fs::read_to_string(filename).unwrap_or_else(|err| { - eprintln!("Error reading file \"{}\": {}", filename, err); + eprintln!("Error reading file: {}", err); process::exit(1); }); @@ -19,5 +30,10 @@ fn main() { process::exit(1); }); - println!("{:?}", result); + fs::write(opt.output, result).unwrap_or_else(|err| { + eprintln!("Error writing output: {}", err); + process::exit(1); + }); + + println!("Done."); } diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index bd822c3..c9a3892 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,66 +1,111 @@ +use std::collections::{hash_map::Entry, HashMap}; + use parity_wasm::elements::{ CodeSection, ExportEntry, ExportSection, Func, FuncBody, FunctionSection, FunctionType, - Internal, Module, Section, Serialize, Type, TypeSection, ValueType, + GlobalEntry, GlobalSection, GlobalType, InitExpr, Internal, Module, Section, Serialize, Type, + TypeSection, ValueType, }; use parity_wasm::elements::{Instruction, Instructions}; -use crate::ast::{BinaryExpression, BinaryOperator}; +use crate::ast::{Assignment, BinaryExpression, BinaryOperator}; use super::ast::{Expression, Program}; pub fn emit(program: Program) -> Result, String> { - let emitter = Emitter {}; + let mut emitter = Emitter::new(); emitter.emit(program) } -struct Emitter {} +struct Emitter { + globals: HashMap, +} impl Emitter { - fn emit(&self, program: Program) -> Result, String> { - let mut binary: Vec = Vec::new(); - let locals = vec![]; + fn new() -> Self { + Emitter { + globals: HashMap::default(), + } + } + fn emit(&mut self, program: Program) -> Result, String> { let instructions = self.emit_program(program)?; - let func_body = FuncBody::new(locals, instructions); - let code_section = CodeSection::with_bodies(vec![func_body]); - let export_section = ExportSection::with_entries(vec![ExportEntry::new( - "test".to_string(), - Internal::Function(0), - )]); + let mut sections = vec![]; + sections.push(Section::Type(self.emit_type_section())); + sections.push(Section::Function(self.emit_function_section())); + if let Some(global_section) = self.emit_global_section() { + sections.push(Section::Global(global_section)); + } + sections.push(Section::Export(self.emit_export_section())); + sections.push(Section::Code(self.emit_code_section(instructions))); + let mut binary: Vec = Vec::new(); + Module::new(sections) + .serialize(&mut binary) + .map_err(|err| format!("Module serialization error: {}", err))?; + + Ok(binary) + } + + fn emit_type_section(&self) -> TypeSection { let params = vec![]; let results = vec![ValueType::F64]; + TypeSection::with_types(vec![Type::Function(FunctionType::new(params, results))]) + } - let type_section = - TypeSection::with_types(vec![Type::Function(FunctionType::new(params, results))]); - let function_section = FunctionSection::with_entries(vec![Func::new(0)]); - - let module = Module::new(vec![ - Section::Type(type_section), - Section::Function(function_section), - Section::Export(export_section), - Section::Code(code_section), - ]); - module - .serialize(&mut binary) - .map_err(|err| format!("Serialization Error: {}", err))?; + fn emit_function_section(&self) -> FunctionSection { + FunctionSection::with_entries(vec![Func::new(0)]) + } - Ok(binary) + fn emit_global_section(&self) -> Option { + // TODO: Derive this from seen globals + if self.globals.len() == 0 { + None + } else { + let mut globals = vec![]; + let mut globals_left = self.globals.len(); + while globals_left > 0 { + globals.push(make_empty_global()); + globals_left -= 1; + } + + Some(GlobalSection::with_entries(globals)) + } } - fn emit_program(&self, program: Program) -> Result { - let expression_instructions = self.emit_expression(program.expression)?; - let mut new = Vec::with_capacity(expression_instructions.len() + 1); - new.extend_from_slice(&expression_instructions); + fn emit_export_section(&self) -> ExportSection { + ExportSection::with_entries(vec![ExportEntry::new( + "test".to_string(), + Internal::Function(0), + )]) + } + + fn emit_code_section(&self, instructions: Instructions) -> CodeSection { + let locals = vec![]; + let func_body = FuncBody::new(locals, instructions); + CodeSection::with_bodies(vec![func_body]) + } + + fn emit_program(&mut self, program: Program) -> Result { + let mut instructions: Vec = Vec::new(); + for expression in program.expressions { + let expression_instructions = self.emit_expression(expression)?; + instructions.extend_from_slice(&expression_instructions); + // TODO: Consider that we might need to drop the implicit return. + } + let mut new = Vec::with_capacity(instructions.len() + 1); + new.extend_from_slice(&instructions); new.push(Instruction::End); Ok(Instructions::new(new)) } - fn emit_expression(&self, expression: Expression) -> Result, String> { + fn emit_expression(&mut self, expression: Expression) -> Result, String> { match expression { Expression::BinaryExpression(binary_expression) => { self.emit_binary_expression(binary_expression) } + Expression::Assignment(assignment_expression) => { + self.emit_assignment(assignment_expression) + } Expression::NumberLiteral(number_literal) => Ok(vec![Instruction::F64Const( u64::from_le_bytes(number_literal.value.to_le_bytes()), )]), @@ -68,7 +113,7 @@ impl Emitter { } fn emit_binary_expression( - &self, + &mut self, binary_expression: BinaryExpression, ) -> Result, String> { let left = self.emit_expression(*binary_expression.left)?; @@ -85,4 +130,43 @@ impl Emitter { instructions.push(op); Ok(instructions) } + + fn emit_assignment( + &mut self, + assignment_expression: Assignment, + ) -> Result, String> { + let mut instructions: Vec = Vec::new(); + let resolved_name = self.resolve_variable(assignment_expression.left.name); + let right_expression = self.emit_expression(*assignment_expression.right)?; + + instructions.extend_from_slice(&right_expression); + + instructions.push(Instruction::SetGlobal(resolved_name)); + instructions.push(Instruction::GetGlobal(resolved_name)); + Ok(instructions) + } + + fn resolve_variable(&mut self, name: String) -> u32 { + let next = self.globals.len() as u32; + match self.globals.entry(name) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => { + entry.insert(next); + next + } + } + } +} + +fn make_empty_global() -> GlobalEntry { + GlobalEntry::new( + GlobalType::new(ValueType::F64, true), + InitExpr::new(vec![ + Instruction::F64Const( + // TODO: Get the correct bits here + 0, + ), + Instruction::End, + ]), + ) } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 3454dea..f203f57 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -25,12 +25,14 @@ impl<'a> Lexer<'a> { let start = self.chars.pos; let kind = match self.chars.peek() { c if is_int(c) => self.read_int(), + c if is_identifier_head(c) => self.read_identifier(), '+' => self.read_char_as_kind(TokenKind::Plus), '-' => self.read_char_as_kind(TokenKind::Minus), '*' => self.read_char_as_kind(TokenKind::Asterisk), '/' => self.read_char_as_kind(TokenKind::Slash), + '=' => self.read_char_as_kind(TokenKind::Equal), NULL => TokenKind::EOF, - c => return Err(format!("Unexpected token {}", c)), + c => return Err(format!("Unexpected character {}", c)), }; let end = self.chars.pos; Ok(Token::new(kind, Span::new(self.source, start, end))) @@ -42,9 +44,32 @@ impl<'a> Lexer<'a> { } fn read_int(&mut self) -> TokenKind { + self.chars.next(); self.chars.eat_while(is_int); TokenKind::Int } + + fn read_identifier(&mut self) -> TokenKind { + self.chars.next(); + self.chars.eat_while(is_identifier_tail); + TokenKind::Identifier + } +} + +// https://github.com/justinfrankel/WDL/blob/63943fbac273b847b733aceecdb16703679967b9/WDL/eel2/eel2.l#L93 +fn is_identifier_head(c: char) -> bool { + match c { + 'a'..='z' | 'A'..='Z' | '_' => true, + _ => false, + } +} + +// https://github.com/justinfrankel/WDL/blob/63943fbac273b847b733aceecdb16703679967b9/WDL/eel2/eel2.l#L93 +fn is_identifier_tail(c: char) -> bool { + match c { + 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => true, + _ => false, + } } fn is_int(c: char) -> bool { @@ -53,3 +78,41 @@ fn is_int(c: char) -> bool { _ => false, } } + +#[test] +fn can_lex_number() { + let mut lexer = Lexer::new("1"); + let mut token_kinds: Vec = vec![]; + loop { + let token = lexer.next_token().expect("token"); + let done = token.kind == TokenKind::EOF; + token_kinds.push(token.kind); + if done { + break; + } + } + assert_eq!(token_kinds, vec![TokenKind::Int, TokenKind::EOF]); +} + +#[test] +fn can_lex_assignment() { + let mut lexer = Lexer::new("g=1"); + let mut token_kinds: Vec = vec![]; + loop { + let token = lexer.next_token().expect("token"); + let done = token.kind == TokenKind::EOF; + token_kinds.push(token.kind); + if done { + break; + } + } + assert_eq!( + token_kinds, + vec![ + TokenKind::Identifier, + TokenKind::Equal, + TokenKind::Int, + TokenKind::EOF + ] + ); +} diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 2c17bbd..5d8e08f 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -1,4 +1,4 @@ -use crate::ast::{BinaryExpression, BinaryOperator}; +use crate::ast::{Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, Identifier}; use super::ast::{Expression, NumberLiteral, Program}; use super::lexer::Lexer; @@ -32,7 +32,8 @@ impl<'a> Parser<'a> { } fn expect_kind(&mut self, expected: TokenKind) -> Result<(), String> { - if self.token.kind == expected { + let token = self.peek(); + if token.kind == expected { self.advance()?; Ok(()) } else { @@ -53,13 +54,41 @@ impl<'a> Parser<'a> { pub fn parse_program(&mut self) -> Result { Ok(Program { - expression: self.parse_expression(0)?, + expressions: self.parse_expression_block()?, }) } + pub fn parse_expression_block(&mut self) -> Result, String> { + let mut expressions = vec![]; + while self.peek_expression() { + expressions.push(self.parse_expression(0)?); + // TODO: Eat a semicolon? + } + Ok(expressions) + } + + fn peek_expression(&self) -> bool { + let token = self.peek(); + match token.kind { + TokenKind::Int => true, + TokenKind::Identifier => true, + _ => false, + } + } + fn parse_expression(&mut self, precedence: u8) -> Result { - let left = self.parse_prefix()?; - self.maybe_parse_infix(left, precedence) + match self.peek().kind { + // TODO: Handle unary + TokenKind::Int => { + let left = self.parse_prefix()?; + self.maybe_parse_infix(left, precedence) + } + TokenKind::Identifier => self.parse_assignment(), + _ => Err(format!( + "Expected Int or Identifier but got {:?}", + self.token.kind + )), + } } fn parse_prefix(&mut self) -> Result { @@ -149,6 +178,25 @@ impl<'a> Parser<'a> { Err(format!("Expected an Int but found {:?}", self.token.kind)) } } + + fn parse_assignment(&mut self) -> Result { + self.expect_kind(TokenKind::Identifier)?; + // TODO: Support other operator types + let _operator_token = self.expect_kind(TokenKind::Equal)?; + let right = self.parse_expression(0)?; + Ok(Expression::Assignment(Assignment { + left: Identifier { + // TODO: Derive name from token + name: "g".to_string(), + }, + operator: AssignmentOperator::Equal, + right: Box::new(right), + })) + } + + fn peek(&self) -> &Token { + &self.token + } } #[inline] @@ -168,7 +216,7 @@ fn can_parse_integer() { assert_eq!( Parser::new("1").parse(), Ok(Program { - expression: Expression::NumberLiteral(NumberLiteral { value: 1.0 }) + expressions: vec![Expression::NumberLiteral(NumberLiteral { value: 1.0 })] }) ); } @@ -178,7 +226,7 @@ fn can_parse_integer_2() { assert_eq!( Parser::new("2").parse(), Ok(Program { - expression: Expression::NumberLiteral(NumberLiteral { value: 2.0 }) + expressions: vec![Expression::NumberLiteral(NumberLiteral { value: 2.0 })] }) ); } diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 7b8e7eb..9060853 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -7,6 +7,8 @@ pub enum TokenKind { Minus, Asterisk, Slash, + Equal, + Identifier, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index c3510a5..93911da 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -23,7 +23,8 @@ fn run(body: &[u8]) -> Result { #[test] fn compatibility_tests() { let test_cases: &[(&'static str, &'static str, f64)] = &[ - ("[REMOVE] Integer", "1", 2.0), + ("[REMOVE] Integer", "1", 1.0), + ("[REMOVE] Assignment", "g=1", 1.0), ("Expressions", "g = ((6- -7.0)+ 3.0);", 16.0), ("Number", "g = 5;", 5.0), ("Number with decimal", "g = 5.5;", 5.5), @@ -348,18 +349,6 @@ fn compatibility_tests() { ), ]; - let mut failing: Vec<&str> = Vec::new(); - - for (name, code, expected) in test_cases { - match compile(code) { - Ok(binary) => { - let actual = run(&binary).expect("to run"); - assert_eq!(&actual, expected) - } - Err(_) => failing.push(name), - } - } - let expected_failing: Vec<&str> = vec![ "Expressions", "Number", @@ -549,5 +538,23 @@ fn compatibility_tests() { "Divide by less than epsilon", ]; - assert_eq!(failing, expected_failing); + for (name, code, expected) in test_cases { + match compile(code) { + Ok(binary) => { + if expected_failing.contains(name) { + panic!(format!("Expected {} to fail, but it passed!", name)); + } + let actual = run(&binary).expect("to run"); + assert_eq!(&actual, expected) + } + Err(err) => { + if !expected_failing.contains(name) { + panic!(format!( + "Didn't expect \"{}\" to fail. Failed with {}", + name, err + )); + } + } + } + } } From 7dfb6ad3faadcb1b3ef42c1d43aafd9f4be3d864 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 11/85] Proper globals --- compiler-rs/src/parser.rs | 12 +++++++----- compiler-rs/src/tokens.rs | 4 ++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 5d8e08f..08e2190 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -1,3 +1,5 @@ +use std::process::id; + use crate::ast::{Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, Identifier}; use super::ast::{Expression, NumberLiteral, Program}; @@ -31,7 +33,7 @@ impl<'a> Parser<'a> { Ok(()) } - fn expect_kind(&mut self, expected: TokenKind) -> Result<(), String> { + fn expect_kind(&mut self, expected: TokenKind) -> Result<(0), String> { let token = self.peek(); if token.kind == expected { self.advance()?; @@ -180,15 +182,15 @@ impl<'a> Parser<'a> { } fn parse_assignment(&mut self) -> Result { + // TODO: A little odd that we get the identifier before we check the + // kind. (lifetimes...) + let identifier = self.token.text().to_string(); self.expect_kind(TokenKind::Identifier)?; // TODO: Support other operator types let _operator_token = self.expect_kind(TokenKind::Equal)?; let right = self.parse_expression(0)?; Ok(Expression::Assignment(Assignment { - left: Identifier { - // TODO: Derive name from token - name: "g".to_string(), - }, + left: Identifier { name: identifier }, operator: AssignmentOperator::Equal, right: Box::new(right), })) diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 9060853..c9eae90 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -23,4 +23,8 @@ impl<'a> Token<'a> { pub fn new(kind: TokenKind, span: Span<'a>) -> Self { Token { kind, span } } + + pub fn text(&self) -> &str { + self.span.str_from_source() + } } From 5efc6a5f10d2aa8ede427f999d339573a1785691 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 12/85] Start to play with imported values --- compiler-rs/src/bin/compiler.rs | 2 +- compiler-rs/src/emitter.rs | 47 ++++++++++++++++++---- compiler-rs/src/lib.rs | 6 +-- compiler-rs/src/parser.rs | 4 +- compiler-rs/tests/compatibility_test.rs | 2 +- compiler-rs/tests/intagration_test.rs | 53 +++++++++++++++++++++++-- 6 files changed, 95 insertions(+), 19 deletions(-) diff --git a/compiler-rs/src/bin/compiler.rs b/compiler-rs/src/bin/compiler.rs index 7ce629b..61cd339 100644 --- a/compiler-rs/src/bin/compiler.rs +++ b/compiler-rs/src/bin/compiler.rs @@ -25,7 +25,7 @@ fn main() { process::exit(1); }); - let result = compile(&source).unwrap_or_else(|err| { + let result = compile(&source, vec![]).unwrap_or_else(|err| { eprintln!("{}", err); process::exit(1); }); diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index c9a3892..fa89bc0 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -2,35 +2,49 @@ use std::collections::{hash_map::Entry, HashMap}; use parity_wasm::elements::{ CodeSection, ExportEntry, ExportSection, Func, FuncBody, FunctionSection, FunctionType, - GlobalEntry, GlobalSection, GlobalType, InitExpr, Internal, Module, Section, Serialize, Type, - TypeSection, ValueType, + GlobalEntry, GlobalSection, GlobalType, ImportEntry, ImportSection, InitExpr, Internal, + Section, Serialize, Type, TypeSection, ValueType, }; -use parity_wasm::elements::{Instruction, Instructions}; +use parity_wasm::elements::{External, Instruction, Instructions}; +use parity_wasm::{builder::ModuleBuilder, elements::Module}; use crate::ast::{Assignment, BinaryExpression, BinaryOperator}; use super::ast::{Expression, Program}; -pub fn emit(program: Program) -> Result, String> { +pub fn emit(program: Program, globals: Vec) -> Result, String> { let mut emitter = Emitter::new(); - emitter.emit(program) + emitter.emit(program, globals) } struct Emitter { + current_pool: String, globals: HashMap, } impl Emitter { fn new() -> Self { Emitter { + current_pool: "".to_string(), // TODO: Is this okay to be empty? globals: HashMap::default(), } } - fn emit(&mut self, program: Program) -> Result, String> { + fn emit(&mut self, program: Program, globals: Vec) -> Result, String> { + let mut imports = Vec::new(); + + self.current_pool = "pool".to_string(); + for global in &globals { + // TODO: Lots of clones. + self.resolve_variable(global.clone()); + imports.push(make_import_entry(self.current_pool.clone(), global.clone())); + } let instructions = self.emit_program(program)?; let mut sections = vec![]; sections.push(Section::Type(self.emit_type_section())); + if let Some(import_section) = self.emit_import_section(imports) { + sections.push(Section::Import(import_section)); + } sections.push(Section::Function(self.emit_function_section())); if let Some(global_section) = self.emit_global_section() { sections.push(Section::Global(global_section)); @@ -52,6 +66,14 @@ impl Emitter { TypeSection::with_types(vec![Type::Function(FunctionType::new(params, results))]) } + fn emit_import_section(&self, imports: Vec) -> Option { + if imports.len() > 0 { + Some(ImportSection::with_entries(imports)) + } else { + None + } + } + fn emit_function_section(&self) -> FunctionSection { FunctionSection::with_entries(vec![Func::new(0)]) } @@ -148,7 +170,10 @@ impl Emitter { fn resolve_variable(&mut self, name: String) -> u32 { let next = self.globals.len() as u32; - match self.globals.entry(name) { + match self + .globals + .entry(format!("{}::{}", self.current_pool, &name)) // TODO: Can we avoid this format? + { Entry::Occupied(entry) => entry.get().clone(), Entry::Vacant(entry) => { entry.insert(next); @@ -170,3 +195,11 @@ fn make_empty_global() -> GlobalEntry { ]), ) } + +fn make_import_entry(module_str: String, field_str: String) -> ImportEntry { + ImportEntry::new( + module_str, + field_str, + External::Global(GlobalType::new(ValueType::F64, true)), + ) +} diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 93cd833..90b8cfb 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -19,11 +19,11 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[wasm_bindgen] pub fn assert_compile(source: &str) -> Vec { - compile(source).expect("Don't screw it up") + compile(source, vec![]).expect("Don't screw it up") } -pub fn compile(source: &str) -> Result, String> { +pub fn compile(source: &str, globals: Vec) -> Result, String> { let mut parser = Parser::new(source); let program = parser.parse()?; - emit(program) + emit(program, globals) } diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 08e2190..e041f47 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -1,5 +1,3 @@ -use std::process::id; - use crate::ast::{Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, Identifier}; use super::ast::{Expression, NumberLiteral, Program}; @@ -33,7 +31,7 @@ impl<'a> Parser<'a> { Ok(()) } - fn expect_kind(&mut self, expected: TokenKind) -> Result<(0), String> { + fn expect_kind(&mut self, expected: TokenKind) -> Result<(), String> { let token = self.peek(); if token.kind == expected { self.advance()?; diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 93911da..5df0145 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -539,7 +539,7 @@ fn compatibility_tests() { ]; for (name, code, expected) in test_cases { - match compile(code) { + match compile(code, vec![]) { Ok(binary) => { if expected_failing.contains(name) { panic!(format!("Expected {} to fail, but it passed!", name)); diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index f4c2b51..f358cad 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -1,9 +1,12 @@ extern crate eel_wasm; -use std::io; +use std::{collections::HashMap, io, ops::Deref, rc::Rc}; use eel_wasm::compile; -use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue}; +use wasmi::{ + nan_preserving_float::F64, Error as WasmiError, GlobalDescriptor, GlobalInstance, GlobalRef, + ImportsBuilder, ModuleImportResolver, ModuleInstance, NopExternals, RuntimeValue, +}; fn run(body: &[u8]) -> Result { let wasm_binary = body; @@ -26,7 +29,7 @@ fn run(body: &[u8]) -> Result { fn test_run(program: &str, expected_output: f64) { assert_eq!( - run(&compile(program).unwrap()).expect("Run Error"), + run(&compile(program, vec![]).unwrap()).expect("Run Error"), expected_output ); } @@ -34,7 +37,7 @@ fn test_run(program: &str, expected_output: f64) { #[test] fn build_one() -> io::Result<()> { assert_eq!( - &compile("1").unwrap(), + &compile("1", vec![]).unwrap(), &[ 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, 115, 116, 0, 0, 10, 13, 1, 11, 0, 68, 0, 0, 0, 0, 0, 0, 240, 63, 11, @@ -53,3 +56,45 @@ fn execute_one() { test_run("1+1*2", 3.0); } + +struct GlobalPool { + globals: HashMap, +} + +impl ModuleImportResolver for GlobalPool { + fn resolve_global( + &self, + field_name: &str, + _global_type: &GlobalDescriptor, + ) -> Result { + let global = GlobalInstance::alloc(RuntimeValue::F64(F64::from_float(0.0)), true); + Ok(global) + } +} + +#[test] +#[ignore] +fn with_global() { + let global_imports = GlobalPool { + globals: HashMap::default(), + }; + let wasm_binary = compile("g=1", vec!["g".to_string()]).expect("Expect to compile"); + // TODO: This will fail becuase wasmi 0.8.0 depends upon wasmi-validaiton + // 0.3.0 which does not include https://github.com/paritytech/wasmi/pull/228 + // which allows mutable globals. + // 0.3.1 has the PR, but wasmi has not shipped a new version that includes it. + // parity-wasm already depends upon 0.3.1 (I _think_) + let module = wasmi::Module::from_buffer(&wasm_binary).expect("No validation errors"); + let mut imports = ImportsBuilder::default(); + imports.push_resolver("pool", &global_imports); + let instance = ModuleInstance::new(&module, &imports) + .expect("failed to instantiate wasm module") + .assert_no_start(); + + // Finally, invoke the exported function "test" with no parameters + // and empty external function executor. + instance + .invoke_export("test", &[], &mut NopExternals) + .expect("failed to execute export") + .expect("Ran"); +} From 51b520c2aa5a9380321f58ec107891249a0e5c47 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 13/85] Multiple user defined functions --- compiler-rs/src/bin/compiler.rs | 2 +- compiler-rs/src/emitter.rs | 66 +++++++++++++++---------- compiler-rs/src/lib.rs | 16 ++++-- compiler-rs/tests/compatibility_test.rs | 2 +- compiler-rs/tests/intagration_test.rs | 37 ++++++++++++-- 5 files changed, 87 insertions(+), 36 deletions(-) diff --git a/compiler-rs/src/bin/compiler.rs b/compiler-rs/src/bin/compiler.rs index 61cd339..dee58ca 100644 --- a/compiler-rs/src/bin/compiler.rs +++ b/compiler-rs/src/bin/compiler.rs @@ -25,7 +25,7 @@ fn main() { process::exit(1); }); - let result = compile(&source, vec![]).unwrap_or_else(|err| { + let result = compile(vec![("test".to_string(), &source)], vec![]).unwrap_or_else(|err| { eprintln!("{}", err); process::exit(1); }); diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index fa89bc0..6812d68 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,20 +1,20 @@ use std::collections::{hash_map::Entry, HashMap}; +use parity_wasm::elements::Module; use parity_wasm::elements::{ CodeSection, ExportEntry, ExportSection, Func, FuncBody, FunctionSection, FunctionType, GlobalEntry, GlobalSection, GlobalType, ImportEntry, ImportSection, InitExpr, Internal, Section, Serialize, Type, TypeSection, ValueType, }; use parity_wasm::elements::{External, Instruction, Instructions}; -use parity_wasm::{builder::ModuleBuilder, elements::Module}; use crate::ast::{Assignment, BinaryExpression, BinaryOperator}; use super::ast::{Expression, Program}; -pub fn emit(program: Program, globals: Vec) -> Result, String> { +pub fn emit(programs: Vec<(String, Program)>, globals: Vec) -> Result, String> { let mut emitter = Emitter::new(); - emitter.emit(program, globals) + emitter.emit(programs, globals) } struct Emitter { @@ -29,7 +29,11 @@ impl Emitter { globals: HashMap::default(), } } - fn emit(&mut self, program: Program, globals: Vec) -> Result, String> { + fn emit( + &mut self, + programs: Vec<(String, Program)>, + globals: Vec, + ) -> Result, String> { let mut imports = Vec::new(); self.current_pool = "pool".to_string(); @@ -38,19 +42,20 @@ impl Emitter { self.resolve_variable(global.clone()); imports.push(make_import_entry(self.current_pool.clone(), global.clone())); } - let instructions = self.emit_program(program)?; + + let (function_exports, function_bodies, funcs) = self.emit_programs(programs)?; let mut sections = vec![]; sections.push(Section::Type(self.emit_type_section())); if let Some(import_section) = self.emit_import_section(imports) { sections.push(Section::Import(import_section)); } - sections.push(Section::Function(self.emit_function_section())); + sections.push(Section::Function(self.emit_function_section(funcs))); if let Some(global_section) = self.emit_global_section() { sections.push(Section::Global(global_section)); } - sections.push(Section::Export(self.emit_export_section())); - sections.push(Section::Code(self.emit_code_section(instructions))); + sections.push(Section::Export(self.emit_export_section(function_exports))); + sections.push(Section::Code(self.emit_code_section(function_bodies))); let mut binary: Vec = Vec::new(); Module::new(sections) @@ -74,8 +79,8 @@ impl Emitter { } } - fn emit_function_section(&self) -> FunctionSection { - FunctionSection::with_entries(vec![Func::new(0)]) + fn emit_function_section(&self, funcs: Vec) -> FunctionSection { + FunctionSection::with_entries(funcs) } fn emit_global_section(&self) -> Option { @@ -83,28 +88,39 @@ impl Emitter { if self.globals.len() == 0 { None } else { - let mut globals = vec![]; - let mut globals_left = self.globals.len(); - while globals_left > 0 { - globals.push(make_empty_global()); - globals_left -= 1; - } + let globals = [0..self.globals.len()] + .iter() + .map(|_| make_empty_global()) + .collect(); Some(GlobalSection::with_entries(globals)) } } - fn emit_export_section(&self) -> ExportSection { - ExportSection::with_entries(vec![ExportEntry::new( - "test".to_string(), - Internal::Function(0), - )]) + fn emit_export_section(&self, function_exports: Vec) -> ExportSection { + ExportSection::with_entries(function_exports) } - fn emit_code_section(&self, instructions: Instructions) -> CodeSection { - let locals = vec![]; - let func_body = FuncBody::new(locals, instructions); - CodeSection::with_bodies(vec![func_body]) + fn emit_code_section(&self, function_bodies: Vec) -> CodeSection { + CodeSection::with_bodies(function_bodies) + } + + fn emit_programs( + &mut self, + programs: Vec<(String, Program)>, + ) -> Result<(Vec, Vec, Vec), String> { + let mut names = Vec::new(); + let mut instructions = Vec::new(); + let mut funcs = Vec::new(); + for (i, (name, program)) in programs.into_iter().enumerate() { + names.push(ExportEntry::new(name, Internal::Function(i as u32))); + let locals = Vec::new(); + let func_body = FuncBody::new(locals, self.emit_program(program)?); + instructions.push(func_body); + + funcs.push(Func::new(0)) + } + Ok((names, instructions, funcs)) } fn emit_program(&mut self, program: Program) -> Result { diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 90b8cfb..74ee62b 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -6,6 +6,7 @@ mod parser; mod span; mod tokens; +use ast::Program; use emitter::emit; use parser::Parser; @@ -19,11 +20,16 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[wasm_bindgen] pub fn assert_compile(source: &str) -> Vec { - compile(source, vec![]).expect("Don't screw it up") + compile(vec![("test".to_string(), source)], vec![]).expect("Don't screw it up") } -pub fn compile(source: &str, globals: Vec) -> Result, String> { - let mut parser = Parser::new(source); - let program = parser.parse()?; - emit(program, globals) +pub fn compile(sources: Vec<(String, &str)>, globals: Vec) -> Result, String> { + let programs: Result, String> = sources + .into_iter() + .map(|(name, source)| { + let mut parser = Parser::new(&source); + Ok((name, parser.parse()?)) + }) + .collect(); + emit(programs?, globals) } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 5df0145..87fec27 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -539,7 +539,7 @@ fn compatibility_tests() { ]; for (name, code, expected) in test_cases { - match compile(code, vec![]) { + match compile(vec![("test".to_string(), code)], vec![]) { Ok(binary) => { if expected_failing.contains(name) { panic!(format!("Expected {} to fail, but it passed!", name)); diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index f358cad..0cbec58 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -1,6 +1,6 @@ extern crate eel_wasm; -use std::{collections::HashMap, io, ops::Deref, rc::Rc}; +use std::{collections::HashMap, io}; use eel_wasm::compile; use wasmi::{ @@ -29,7 +29,7 @@ fn run(body: &[u8]) -> Result { fn test_run(program: &str, expected_output: f64) { assert_eq!( - run(&compile(program, vec![]).unwrap()).expect("Run Error"), + run(&compile(vec![("test".to_string(), program)], vec![]).unwrap()).expect("Run Error"), expected_output ); } @@ -37,7 +37,7 @@ fn test_run(program: &str, expected_output: f64) { #[test] fn build_one() -> io::Result<()> { assert_eq!( - &compile("1", vec![]).unwrap(), + &compile(vec![("test".to_string(), "1")], vec![]).unwrap(), &[ 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, 115, 116, 0, 0, 10, 13, 1, 11, 0, 68, 0, 0, 0, 0, 0, 0, 240, 63, 11, @@ -78,7 +78,8 @@ fn with_global() { let global_imports = GlobalPool { globals: HashMap::default(), }; - let wasm_binary = compile("g=1", vec!["g".to_string()]).expect("Expect to compile"); + let wasm_binary = compile(vec![("test".to_string(), "g=1")], vec!["g".to_string()]) + .expect("Expect to compile"); // TODO: This will fail becuase wasmi 0.8.0 depends upon wasmi-validaiton // 0.3.0 which does not include https://github.com/paritytech/wasmi/pull/228 // which allows mutable globals. @@ -98,3 +99,31 @@ fn with_global() { .expect("failed to execute export") .expect("Ran"); } + +#[test] +fn multiple_functions() { + let wasm_binary = compile( + vec![("one".to_string(), "1"), ("two".to_string(), "2")], + vec![], + ) + .expect("Expect to compile"); + // TODO: This will fail becuase wasmi 0.8.0 depends upon wasmi-validaiton + // 0.3.0 which does not include https://github.com/paritytech/wasmi/pull/228 + // which allows mutable globals. + // 0.3.1 has the PR, but wasmi has not shipped a new version that includes it. + // parity-wasm already depends upon 0.3.1 (I _think_) + let module = wasmi::Module::from_buffer(&wasm_binary).expect("No validation errors"); + let instance = ModuleInstance::new(&module, &ImportsBuilder::default()) + .expect("failed to instantiate wasm module") + .assert_no_start(); + + instance + .invoke_export("one", &[], &mut NopExternals) + .expect("failed to execute export") + .expect("Ran"); + + instance + .invoke_export("two", &[], &mut NopExternals) + .expect("failed to execute export") + .expect("Ran"); +} From 8e033cfb6a8be795b0633509f8adb0d61cf40263 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 14/85] Add function call int() --- compiler-rs/src/ast.rs | 7 ++++ compiler-rs/src/emitter.rs | 20 ++++++++++- compiler-rs/src/lexer.rs | 3 ++ compiler-rs/src/parser.rs | 47 +++++++++++++++++++------ compiler-rs/src/tokens.rs | 3 ++ compiler-rs/tests/compatibility_test.rs | 1 + 6 files changed, 70 insertions(+), 11 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 50864f8..1310390 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -8,6 +8,7 @@ pub enum Expression { BinaryExpression(BinaryExpression), NumberLiteral(NumberLiteral), Assignment(Assignment), + FunctionCall(FunctionCall), } #[derive(Debug, PartialEq)] @@ -46,3 +47,9 @@ pub struct Assignment { pub operator: AssignmentOperator, pub right: Box, } + +#[derive(Debug, PartialEq)] +pub struct FunctionCall { + pub name: Identifier, + pub arguments: Vec, +} diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 6812d68..0621c8b 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -8,7 +8,7 @@ use parity_wasm::elements::{ }; use parity_wasm::elements::{External, Instruction, Instructions}; -use crate::ast::{Assignment, BinaryExpression, BinaryOperator}; +use crate::ast::{Assignment, BinaryExpression, BinaryOperator, FunctionCall}; use super::ast::{Expression, Program}; @@ -147,6 +147,7 @@ impl Emitter { Expression::NumberLiteral(number_literal) => Ok(vec![Instruction::F64Const( u64::from_le_bytes(number_literal.value.to_le_bytes()), )]), + Expression::FunctionCall(function_call) => self.emit_function_call(function_call), } } @@ -184,6 +185,23 @@ impl Emitter { Ok(instructions) } + fn emit_function_call( + &mut self, + function_call: FunctionCall, + ) -> Result, String> { + match &function_call.name.name[..] { + "int" => { + let mut instructions = vec![]; + for arg in function_call.arguments { + instructions.extend_from_slice(&self.emit_expression(arg)?); + } + instructions.push(Instruction::F64Floor); + Ok(instructions) + } + _ => Err(format!("Unknown function `{}`", function_call.name.name)), + } + } + fn resolve_variable(&mut self, name: String) -> u32 { let next = self.globals.len() as u32; match self diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index f203f57..69e9e5c 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -31,6 +31,9 @@ impl<'a> Lexer<'a> { '*' => self.read_char_as_kind(TokenKind::Asterisk), '/' => self.read_char_as_kind(TokenKind::Slash), '=' => self.read_char_as_kind(TokenKind::Equal), + '(' => self.read_char_as_kind(TokenKind::OpenParen), + ')' => self.read_char_as_kind(TokenKind::CloseParen), + ',' => self.read_char_as_kind(TokenKind::Comma), NULL => TokenKind::EOF, c => return Err(format!("Unexpected character {}", c)), }; diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index e041f47..891a45b 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -1,4 +1,6 @@ -use crate::ast::{Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, Identifier}; +use crate::ast::{ + Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, FunctionCall, Identifier, +}; use super::ast::{Expression, NumberLiteral, Program}; use super::lexer::Lexer; @@ -83,7 +85,7 @@ impl<'a> Parser<'a> { let left = self.parse_prefix()?; self.maybe_parse_infix(left, precedence) } - TokenKind::Identifier => self.parse_assignment(), + TokenKind::Identifier => self.parse_identifier(), _ => Err(format!( "Expected Int or Identifier but got {:?}", self.token.kind @@ -179,19 +181,44 @@ impl<'a> Parser<'a> { } } - fn parse_assignment(&mut self) -> Result { + fn parse_identifier(&mut self) -> Result { // TODO: A little odd that we get the identifier before we check the // kind. (lifetimes...) let identifier = self.token.text().to_string(); self.expect_kind(TokenKind::Identifier)?; + + match self.peek().kind { + TokenKind::Equal => { + let _operator_token = self.expect_kind(TokenKind::Equal)?; + let right = self.parse_expression(0)?; + Ok(Expression::Assignment(Assignment { + left: Identifier { name: identifier }, + operator: AssignmentOperator::Equal, + right: Box::new(right), + })) + } + TokenKind::OpenParen => { + self.advance()?; + let mut arguments = vec![]; + while self.peek_expression() { + arguments.push(self.parse_expression(0)?); + match self.peek().kind { + TokenKind::Comma => self.advance()?, + TokenKind::CloseParen => { + self.advance()?; + break; + } + _ => return Err("Expected , or )".to_string()), + } + } + Ok(Expression::FunctionCall(FunctionCall { + name: Identifier { name: identifier }, + arguments, + })) + } + _ => Err(format!("Expected = or (")), + } // TODO: Support other operator types - let _operator_token = self.expect_kind(TokenKind::Equal)?; - let right = self.parse_expression(0)?; - Ok(Expression::Assignment(Assignment { - left: Identifier { name: identifier }, - operator: AssignmentOperator::Equal, - right: Box::new(right), - })) } fn peek(&self) -> &Token { diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index c9eae90..40630f7 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -9,6 +9,9 @@ pub enum TokenKind { Slash, Equal, Identifier, + OpenParen, + CloseParen, + Comma, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 87fec27..fdd9cde 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -25,6 +25,7 @@ fn compatibility_tests() { let test_cases: &[(&'static str, &'static str, f64)] = &[ ("[REMOVE] Integer", "1", 1.0), ("[REMOVE] Assignment", "g=1", 1.0), + ("[REMOVE] Call", "int(4)", 4.0), ("Expressions", "g = ((6- -7.0)+ 3.0);", 16.0), ("Number", "g = 5;", 5.0), ("Number with decimal", "g = 5.5;", 5.5), From e9d7012c770f368a654d6c90a16b9e0fe9d040a7 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:16 -0700 Subject: [PATCH 15/85] Custom result types --- compiler-rs/src/emitter.rs | 23 +++++++++++------------ compiler-rs/src/lexer.rs | 4 +++- compiler-rs/src/parser.rs | 34 ++++++++++++++++------------------ 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 0621c8b..4589911 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,5 +1,5 @@ -use std::collections::{hash_map::Entry, HashMap}; - +use super::ast::{Expression, Program}; +use crate::ast::{Assignment, BinaryExpression, BinaryOperator, FunctionCall}; use parity_wasm::elements::Module; use parity_wasm::elements::{ CodeSection, ExportEntry, ExportSection, Func, FuncBody, FunctionSection, FunctionType, @@ -7,10 +7,9 @@ use parity_wasm::elements::{ Section, Serialize, Type, TypeSection, ValueType, }; use parity_wasm::elements::{External, Instruction, Instructions}; +use std::collections::{hash_map::Entry, HashMap}; -use crate::ast::{Assignment, BinaryExpression, BinaryOperator, FunctionCall}; - -use super::ast::{Expression, Program}; +type EmitterResult = Result; pub fn emit(programs: Vec<(String, Program)>, globals: Vec) -> Result, String> { let mut emitter = Emitter::new(); @@ -33,7 +32,7 @@ impl Emitter { &mut self, programs: Vec<(String, Program)>, globals: Vec, - ) -> Result, String> { + ) -> EmitterResult> { let mut imports = Vec::new(); self.current_pool = "pool".to_string(); @@ -108,7 +107,7 @@ impl Emitter { fn emit_programs( &mut self, programs: Vec<(String, Program)>, - ) -> Result<(Vec, Vec, Vec), String> { + ) -> EmitterResult<(Vec, Vec, Vec)> { let mut names = Vec::new(); let mut instructions = Vec::new(); let mut funcs = Vec::new(); @@ -123,7 +122,7 @@ impl Emitter { Ok((names, instructions, funcs)) } - fn emit_program(&mut self, program: Program) -> Result { + fn emit_program(&mut self, program: Program) -> EmitterResult { let mut instructions: Vec = Vec::new(); for expression in program.expressions { let expression_instructions = self.emit_expression(expression)?; @@ -136,7 +135,7 @@ impl Emitter { Ok(Instructions::new(new)) } - fn emit_expression(&mut self, expression: Expression) -> Result, String> { + fn emit_expression(&mut self, expression: Expression) -> EmitterResult> { match expression { Expression::BinaryExpression(binary_expression) => { self.emit_binary_expression(binary_expression) @@ -154,7 +153,7 @@ impl Emitter { fn emit_binary_expression( &mut self, binary_expression: BinaryExpression, - ) -> Result, String> { + ) -> EmitterResult> { let left = self.emit_expression(*binary_expression.left)?; let right = self.emit_expression(*binary_expression.right)?; let op = match binary_expression.op { @@ -173,7 +172,7 @@ impl Emitter { fn emit_assignment( &mut self, assignment_expression: Assignment, - ) -> Result, String> { + ) -> EmitterResult> { let mut instructions: Vec = Vec::new(); let resolved_name = self.resolve_variable(assignment_expression.left.name); let right_expression = self.emit_expression(*assignment_expression.right)?; @@ -188,7 +187,7 @@ impl Emitter { fn emit_function_call( &mut self, function_call: FunctionCall, - ) -> Result, String> { + ) -> EmitterResult> { match &function_call.name.name[..] { "int" => { let mut instructions = vec![]; diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 69e9e5c..2f9d9b7 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -8,6 +8,8 @@ use super::tokens::{Token, TokenKind}; * - Returns EOF forever once it reaches the end */ +type LexerResult = Result; + pub struct Lexer<'a> { source: &'a str, chars: FileChars<'a>, @@ -21,7 +23,7 @@ impl<'a> Lexer<'a> { } } - pub fn next_token(&mut self) -> Result, String> { + pub fn next_token(&mut self) -> LexerResult> { let start = self.chars.pos; let kind = match self.chars.peek() { c if is_int(c) => self.read_int(), diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 891a45b..6cc8294 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -17,6 +17,8 @@ pub struct Parser<'a> { token: Token<'a>, } +type ParseResult = Result; + impl<'a> Parser<'a> { pub fn new(source: &'a str) -> Self { Parser { @@ -28,12 +30,12 @@ impl<'a> Parser<'a> { } } - fn advance(&mut self) -> Result<(), String> { + fn advance(&mut self) -> ParseResult<()> { self.token = self.lexer.next_token()?; Ok(()) } - fn expect_kind(&mut self, expected: TokenKind) -> Result<(), String> { + fn expect_kind(&mut self, expected: TokenKind) -> ParseResult<()> { let token = self.peek(); if token.kind == expected { self.advance()?; @@ -47,20 +49,20 @@ impl<'a> Parser<'a> { } } - pub fn parse(&mut self) -> Result { + pub fn parse(&mut self) -> ParseResult { self.expect_kind(TokenKind::SOF)?; let program = self.parse_program()?; self.expect_kind(TokenKind::EOF)?; Ok(program) } - pub fn parse_program(&mut self) -> Result { + pub fn parse_program(&mut self) -> ParseResult { Ok(Program { expressions: self.parse_expression_block()?, }) } - pub fn parse_expression_block(&mut self) -> Result, String> { + pub fn parse_expression_block(&mut self) -> ParseResult> { let mut expressions = vec![]; while self.peek_expression() { expressions.push(self.parse_expression(0)?); @@ -78,7 +80,7 @@ impl<'a> Parser<'a> { } } - fn parse_expression(&mut self, precedence: u8) -> Result { + fn parse_expression(&mut self, precedence: u8) -> ParseResult { match self.peek().kind { // TODO: Handle unary TokenKind::Int => { @@ -93,7 +95,7 @@ impl<'a> Parser<'a> { } } - fn parse_prefix(&mut self) -> Result { + fn parse_prefix(&mut self) -> ParseResult { match self.token.kind { TokenKind::Int => Ok(Expression::NumberLiteral(self.parse_int()?)), // TokenKind::OpenParen => self.parse_parenthesized_expression(), @@ -102,11 +104,7 @@ impl<'a> Parser<'a> { } } - fn maybe_parse_infix( - &mut self, - left: Expression, - precedence: u8, - ) -> Result { + fn maybe_parse_infix(&mut self, left: Expression, precedence: u8) -> ParseResult { let mut next = left; loop { next = match self.token.kind { @@ -125,7 +123,7 @@ impl<'a> Parser<'a> { } } - fn parse_sum(&mut self, left: Expression) -> Result { + fn parse_sum(&mut self, left: Expression) -> ParseResult { self.expect_kind(TokenKind::Plus)?; let right = self.parse_expression(left_associative(SUM_PRECEDENCE))?; Ok(Expression::BinaryExpression(BinaryExpression { @@ -135,7 +133,7 @@ impl<'a> Parser<'a> { })) } - fn parse_difference(&mut self, left: Expression) -> Result { + fn parse_difference(&mut self, left: Expression) -> ParseResult { self.expect_kind(TokenKind::Minus)?; let right = self.parse_expression(left_associative(DIFFERENCE_PRECEDENCE))?; Ok(Expression::BinaryExpression(BinaryExpression { @@ -145,7 +143,7 @@ impl<'a> Parser<'a> { })) } - fn parse_product(&mut self, left: Expression) -> Result { + fn parse_product(&mut self, left: Expression) -> ParseResult { self.expect_kind(TokenKind::Asterisk)?; let right = self.parse_expression(left_associative(PRODUCT_PRECEDENCE))?; Ok(Expression::BinaryExpression(BinaryExpression { @@ -155,7 +153,7 @@ impl<'a> Parser<'a> { })) } - fn parse_quotient(&mut self, left: Expression) -> Result { + fn parse_quotient(&mut self, left: Expression) -> ParseResult { self.expect_kind(TokenKind::Slash)?; let right = self.parse_expression(left_associative(QUOTIENT_PRECEDENCE))?; Ok(Expression::BinaryExpression(BinaryExpression { @@ -165,7 +163,7 @@ impl<'a> Parser<'a> { })) } - fn parse_int(&mut self) -> Result { + fn parse_int(&mut self) -> ParseResult { if let TokenKind::Int = self.token.kind { let value = self.token.span.str_from_source(); match value.parse::() { @@ -181,7 +179,7 @@ impl<'a> Parser<'a> { } } - fn parse_identifier(&mut self) -> Result { + fn parse_identifier(&mut self) -> ParseResult { // TODO: A little odd that we get the identifier before we check the // kind. (lifetimes...) let identifier = self.token.text().to_string(); From d4540f410a3bbd09f84cf9baf9f7b4c5f8ef709c Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 16/85] Improve error reporting --- compiler-rs/src/ast.rs | 3 + compiler-rs/src/bin/compiler.rs | 2 +- compiler-rs/src/emitter.rs | 24 ++++++-- compiler-rs/src/error.rs | 16 ++++++ compiler-rs/src/file_chars.rs | 14 +---- compiler-rs/src/lexer.rs | 19 +++++-- compiler-rs/src/lib.rs | 9 ++- compiler-rs/src/parser.rs | 74 +++++++++++++++++-------- compiler-rs/src/span.rs | 41 ++++---------- compiler-rs/src/tokens.rs | 12 ++-- compiler-rs/tests/compatibility_test.rs | 2 +- 11 files changed, 128 insertions(+), 88 deletions(-) create mode 100644 compiler-rs/src/error.rs diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 1310390..ad78a10 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -1,3 +1,5 @@ +use crate::span::Span; + #[derive(Debug, PartialEq)] pub struct Program { pub expressions: Vec, @@ -34,6 +36,7 @@ pub enum BinaryOperator { #[derive(Debug, PartialEq)] pub struct Identifier { pub name: String, + pub span: Span, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/bin/compiler.rs b/compiler-rs/src/bin/compiler.rs index dee58ca..0b237e1 100644 --- a/compiler-rs/src/bin/compiler.rs +++ b/compiler-rs/src/bin/compiler.rs @@ -26,7 +26,7 @@ fn main() { }); let result = compile(vec![("test".to_string(), &source)], vec![]).unwrap_or_else(|err| { - eprintln!("{}", err); + eprintln!("{:?}", err); process::exit(1); }); diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 4589911..571bda3 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,5 +1,9 @@ -use super::ast::{Expression, Program}; -use crate::ast::{Assignment, BinaryExpression, BinaryOperator, FunctionCall}; +use crate::ast::{Expression, Program}; +use crate::span::Span; +use crate::{ + ast::{Assignment, BinaryExpression, BinaryOperator, FunctionCall}, + error::CompilerError, +}; use parity_wasm::elements::Module; use parity_wasm::elements::{ CodeSection, ExportEntry, ExportSection, Func, FuncBody, FunctionSection, FunctionType, @@ -9,9 +13,9 @@ use parity_wasm::elements::{ use parity_wasm::elements::{External, Instruction, Instructions}; use std::collections::{hash_map::Entry, HashMap}; -type EmitterResult = Result; +type EmitterResult = Result; -pub fn emit(programs: Vec<(String, Program)>, globals: Vec) -> Result, String> { +pub fn emit(programs: Vec<(String, Program)>, globals: Vec) -> EmitterResult> { let mut emitter = Emitter::new(); emitter.emit(programs, globals) } @@ -59,7 +63,12 @@ impl Emitter { let mut binary: Vec = Vec::new(); Module::new(sections) .serialize(&mut binary) - .map_err(|err| format!("Module serialization error: {}", err))?; + .map_err(|err| { + CompilerError::new( + format!("Module serialization error: {}", err), + Span::empty(), + ) + })?; Ok(binary) } @@ -197,7 +206,10 @@ impl Emitter { instructions.push(Instruction::F64Floor); Ok(instructions) } - _ => Err(format!("Unknown function `{}`", function_call.name.name)), + _ => Err(CompilerError::new( + format!("Unknown function `{}`", function_call.name.name), + function_call.name.span, + )), } } diff --git a/compiler-rs/src/error.rs b/compiler-rs/src/error.rs new file mode 100644 index 0000000..9f116fb --- /dev/null +++ b/compiler-rs/src/error.rs @@ -0,0 +1,16 @@ +use crate::span::Span; + +#[derive(Debug, PartialEq)] +pub struct CompilerError { + message: String, + span: Span, +} + +impl CompilerError { + pub fn new(message: String, span: Span) -> Self { + Self { + span, + message: message.into(), + } + } +} diff --git a/compiler-rs/src/file_chars.rs b/compiler-rs/src/file_chars.rs index 7486cc1..539c16c 100644 --- a/compiler-rs/src/file_chars.rs +++ b/compiler-rs/src/file_chars.rs @@ -1,4 +1,3 @@ -use super::span::Position; use std::mem; use std::str::Chars; @@ -7,7 +6,7 @@ pub const NULL: char = '!'; pub struct FileChars<'a> { chars: Chars<'a>, next_char: char, - pub pos: Position, + pub pos: u32, } impl<'a> FileChars<'a> { @@ -17,20 +16,13 @@ impl<'a> FileChars<'a> { FileChars { chars, next_char, - pos: Position::new(), + pos: 0, } } pub fn next(&mut self) -> char { let c = self.next_char; - self.pos.byte_offset += c.len_utf8(); - if c == '\n' { - self.pos.column = 0; - self.pos.line += 1; - } else { - self.pos.column += 1 - } - + self.pos += c.len_utf8() as u32; mem::replace(&mut self.next_char, self.chars.next().unwrap_or(NULL)) } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 2f9d9b7..fc15edd 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -1,3 +1,5 @@ +use crate::error::CompilerError; + use super::file_chars::{FileChars, NULL}; use super::span::Span; use super::tokens::{Token, TokenKind}; @@ -8,7 +10,7 @@ use super::tokens::{Token, TokenKind}; * - Returns EOF forever once it reaches the end */ -type LexerResult = Result; +type LexerResult = Result; pub struct Lexer<'a> { source: &'a str, @@ -23,7 +25,7 @@ impl<'a> Lexer<'a> { } } - pub fn next_token(&mut self) -> LexerResult> { + pub fn next_token(&mut self) -> LexerResult { let start = self.chars.pos; let kind = match self.chars.peek() { c if is_int(c) => self.read_int(), @@ -37,10 +39,15 @@ impl<'a> Lexer<'a> { ')' => self.read_char_as_kind(TokenKind::CloseParen), ',' => self.read_char_as_kind(TokenKind::Comma), NULL => TokenKind::EOF, - c => return Err(format!("Unexpected character {}", c)), + c => { + return Err(CompilerError::new( + format!("Parse Error: Unexpected character '{}'.", c), + Span::new(start, start), + )) + } }; let end = self.chars.pos; - Ok(Token::new(kind, Span::new(self.source, start, end))) + Ok(Token::new(kind, Span::new(start, end))) } fn read_char_as_kind(&mut self, kind: TokenKind) -> TokenKind { @@ -59,6 +66,10 @@ impl<'a> Lexer<'a> { self.chars.eat_while(is_identifier_tail); TokenKind::Identifier } + + pub fn source(&self, span: Span) -> &str { + &self.source[span.start as usize..span.end as usize] + } } // https://github.com/justinfrankel/WDL/blob/63943fbac273b847b733aceecdb16703679967b9/WDL/eel2/eel2.l#L93 diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 74ee62b..094557f 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -1,5 +1,6 @@ mod ast; mod emitter; +mod error; mod file_chars; mod lexer; mod parser; @@ -8,6 +9,7 @@ mod tokens; use ast::Program; use emitter::emit; +use error::CompilerError; use parser::Parser; use wasm_bindgen::prelude::*; @@ -23,8 +25,11 @@ pub fn assert_compile(source: &str) -> Vec { compile(vec![("test".to_string(), source)], vec![]).expect("Don't screw it up") } -pub fn compile(sources: Vec<(String, &str)>, globals: Vec) -> Result, String> { - let programs: Result, String> = sources +pub fn compile( + sources: Vec<(String, &str)>, + globals: Vec, +) -> Result, CompilerError> { + let programs: Result, CompilerError> = sources .into_iter() .map(|(name, source)| { let mut parser = Parser::new(&source); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 6cc8294..969c431 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -3,6 +3,7 @@ use crate::ast::{ }; use super::ast::{Expression, NumberLiteral, Program}; +use super::error::CompilerError; use super::lexer::Lexer; use super::span::Span; use super::tokens::{Token, TokenKind}; @@ -14,10 +15,10 @@ static QUOTIENT_PRECEDENCE: u8 = 2; pub struct Parser<'a> { lexer: Lexer<'a>, - token: Token<'a>, + token: Token, } -type ParseResult = Result; +type ParseResult = Result; impl<'a> Parser<'a> { pub fn new(source: &'a str) -> Self { @@ -25,7 +26,7 @@ impl<'a> Parser<'a> { lexer: Lexer::new(source), token: Token { kind: TokenKind::SOF, - span: Span::start_of_file(source), + span: Span::empty(), }, } } @@ -41,10 +42,9 @@ impl<'a> Parser<'a> { self.advance()?; Ok(()) } else { - // TODO: Improve error message and improve source location. - Err(format!( - "Expected a {:?} but found {:?}", - expected, self.token.kind + Err(CompilerError::new( + format!("Expected a {:?} but found {:?}", expected, self.token.kind), + token.span, )) } } @@ -81,16 +81,17 @@ impl<'a> Parser<'a> { } fn parse_expression(&mut self, precedence: u8) -> ParseResult { - match self.peek().kind { + let token = self.peek(); + match &token.kind { // TODO: Handle unary TokenKind::Int => { let left = self.parse_prefix()?; self.maybe_parse_infix(left, precedence) } - TokenKind::Identifier => self.parse_identifier(), - _ => Err(format!( - "Expected Int or Identifier but got {:?}", - self.token.kind + TokenKind::Identifier => self.parse_identifier_expression(), + kind => Err(CompilerError::new( + format!("Expected Int or Identifier but got {:?}", kind), + token.span, )), } } @@ -100,7 +101,10 @@ impl<'a> Parser<'a> { TokenKind::Int => Ok(Expression::NumberLiteral(self.parse_int()?)), // TokenKind::OpenParen => self.parse_parenthesized_expression(), // Once we have other prefix operators: `+-!` they will go here. - _ => Err(format!("Expected an Int but found {:?}", self.token.kind)), + _ => Err(CompilerError::new( + format!("Expected Int but got {:?}", self.token.kind), + self.token.span, + )), } } @@ -165,32 +169,46 @@ impl<'a> Parser<'a> { fn parse_int(&mut self) -> ParseResult { if let TokenKind::Int = self.token.kind { - let value = self.token.span.str_from_source(); + let value = self.lexer.source(self.token.span); match value.parse::() { Ok(value) => { self.advance()?; // TODO: This is not quite right Ok(NumberLiteral { value }) } - Err(_) => Err(format!("Could not parse \"{}\" to a number", value)), + Err(_) => Err(CompilerError::new( + format!("Could not parse \"{}\" to a number", value), + self.token.span, + )), } } else { - Err(format!("Expected an Int but found {:?}", self.token.kind)) + Err(CompilerError::new( + format!("Expected an Int but found {:?}", self.token.kind), + self.token.span, + )) } } - fn parse_identifier(&mut self) -> ParseResult { + fn parse_identifier(&mut self) -> ParseResult { + let span = self.token.span; + self.expect_kind(TokenKind::Identifier)?; + Ok(Identifier { + name: self.lexer.source(span).to_string(), + span, + }) + } + + fn parse_identifier_expression(&mut self) -> ParseResult { // TODO: A little odd that we get the identifier before we check the // kind. (lifetimes...) - let identifier = self.token.text().to_string(); - self.expect_kind(TokenKind::Identifier)?; + let identifier = self.parse_identifier()?; - match self.peek().kind { + match self.token.kind { TokenKind::Equal => { let _operator_token = self.expect_kind(TokenKind::Equal)?; let right = self.parse_expression(0)?; Ok(Expression::Assignment(Assignment { - left: Identifier { name: identifier }, + left: identifier, operator: AssignmentOperator::Equal, right: Box::new(right), })) @@ -206,15 +224,23 @@ impl<'a> Parser<'a> { self.advance()?; break; } - _ => return Err("Expected , or )".to_string()), + _ => { + return Err(CompilerError::new( + "Expected , or )".to_string(), + self.token.span, + )) + } } } Ok(Expression::FunctionCall(FunctionCall { - name: Identifier { name: identifier }, + name: identifier, arguments, })) } - _ => Err(format!("Expected = or (")), + _ => Err(CompilerError::new( + "Expected = or (".to_string(), + self.token.span, + )), } // TODO: Support other operator types } diff --git a/compiler-rs/src/span.rs b/compiler-rs/src/span.rs index 3e92beb..f91e2d2 100644 --- a/compiler-rs/src/span.rs +++ b/compiler-rs/src/span.rs @@ -1,35 +1,14 @@ -# [derive (Debug, PartialEq, Clone, Copy)] -pub struct Position { - pub byte_offset: usize, - pub line: usize, - pub column: usize, +#[derive(Debug, PartialEq, Copy, Clone, Eq)] +pub struct Span { + pub start: u32, + pub end: u32, } -impl Position { - pub fn new () -> Self { - Position { - byte_offset: 0, - line: 0, - column: 0, - } +impl Span { + pub fn new(start: u32, end: u32) -> Self { + Span { start, end } } -} - -# [derive (Debug, PartialEq)] -pub struct Span <'a> { - source: & 'a str, - pub start: Position, - pub end: Position, -} - -impl <'a> Span <'a> { - pub fn new (source: & 'a str, start: Position, end: Position) -> Self { - Span {source, start, end} + pub fn empty() -> Self { + Span { start: 0, end: 0 } } - pub fn start_of_file (source: & 'a str) -> Self { - Span :: new (source, Position :: new (), Position :: new ()) - } - pub fn str_from_source (& self) -> & 'a str { - & self.source [self.start.byte_offset..self.end.byte_offset] - } -} \ No newline at end of file +} diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 40630f7..9413fe9 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -17,17 +17,13 @@ pub enum TokenKind { } #[derive(Debug, PartialEq)] -pub struct Token<'a> { +pub struct Token { pub kind: TokenKind, - pub span: Span<'a>, + pub span: Span, } -impl<'a> Token<'a> { - pub fn new(kind: TokenKind, span: Span<'a>) -> Self { +impl Token { + pub fn new(kind: TokenKind, span: Span) -> Self { Token { kind, span } } - - pub fn text(&self) -> &str { - self.span.str_from_source() - } } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index fdd9cde..95cdde2 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -551,7 +551,7 @@ fn compatibility_tests() { Err(err) => { if !expected_failing.contains(name) { panic!(format!( - "Didn't expect \"{}\" to fail. Failed with {}", + "Didn't expect \"{}\" to fail. Failed with {:?}", name, err )); } From 7f422dcc30b12aa8c4a2fbc76d8a57a5385d1103 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 17/85] Add snapshot tests --- compiler-rs/Cargo.toml | 1 + compiler-rs/src/error.rs | 5 ++ .../fixtures/one_plus_nothing.invalid.eel | 1 + .../one_plus_nothing.invalid.snapshot | 3 + compiler-rs/tests/fixtures/one_plus_one.eel | 1 + .../tests/fixtures/one_plus_one.snapshot | 9 +++ compiler-rs/tests/snapshots_test.rs | 66 +++++++++++++++++++ 7 files changed, 86 insertions(+) create mode 100644 compiler-rs/tests/fixtures/one_plus_nothing.invalid.eel create mode 100644 compiler-rs/tests/fixtures/one_plus_nothing.invalid.snapshot create mode 100644 compiler-rs/tests/fixtures/one_plus_one.eel create mode 100644 compiler-rs/tests/fixtures/one_plus_one.snapshot create mode 100644 compiler-rs/tests/snapshots_test.rs diff --git a/compiler-rs/Cargo.toml b/compiler-rs/Cargo.toml index b6100a0..fd74a2a 100644 --- a/compiler-rs/Cargo.toml +++ b/compiler-rs/Cargo.toml @@ -34,6 +34,7 @@ wee_alloc = { version = "0.4.5", optional = true } wasmi = "0.8.0" wabt = "0.9.0" wasm-bindgen-test = "0.3.13" +wasmprinter = "0.2.0" [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/compiler-rs/src/error.rs b/compiler-rs/src/error.rs index 9f116fb..1b70880 100644 --- a/compiler-rs/src/error.rs +++ b/compiler-rs/src/error.rs @@ -13,4 +13,9 @@ impl CompilerError { message: message.into(), } } + + // TODO: Print a code frame + pub fn pretty_print(&self, _source: &str) -> String { + self.message.clone() + } } diff --git a/compiler-rs/tests/fixtures/one_plus_nothing.invalid.eel b/compiler-rs/tests/fixtures/one_plus_nothing.invalid.eel new file mode 100644 index 0000000..63b166a --- /dev/null +++ b/compiler-rs/tests/fixtures/one_plus_nothing.invalid.eel @@ -0,0 +1 @@ +1+ \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/one_plus_nothing.invalid.snapshot b/compiler-rs/tests/fixtures/one_plus_nothing.invalid.snapshot new file mode 100644 index 0000000..48de112 --- /dev/null +++ b/compiler-rs/tests/fixtures/one_plus_nothing.invalid.snapshot @@ -0,0 +1,3 @@ +1+ +======================================================================== +Expected Int or Identifier but got EOF diff --git a/compiler-rs/tests/fixtures/one_plus_one.eel b/compiler-rs/tests/fixtures/one_plus_one.eel new file mode 100644 index 0000000..07d91dc --- /dev/null +++ b/compiler-rs/tests/fixtures/one_plus_one.eel @@ -0,0 +1 @@ +1+1 \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/one_plus_one.snapshot b/compiler-rs/tests/fixtures/one_plus_one.snapshot new file mode 100644 index 0000000..b9bda59 --- /dev/null +++ b/compiler-rs/tests/fixtures/one_plus_one.snapshot @@ -0,0 +1,9 @@ +1+1 +======================================================================== +(module + (type (;0;) (func (result f64))) + (func (;0;) (type 0) (result f64) + f64.const 0x1p+0 (;=1;) + f64.const 0x1p+0 (;=1;) + f64.add) + (export "test" (func 0))) diff --git a/compiler-rs/tests/snapshots_test.rs b/compiler-rs/tests/snapshots_test.rs new file mode 100644 index 0000000..8dcee41 --- /dev/null +++ b/compiler-rs/tests/snapshots_test.rs @@ -0,0 +1,66 @@ +extern crate eel_wasm; + +use std::env; +use std::fs; +use std::io; +use std::path::PathBuf; + +use eel_wasm::compile; + +fn get_fixture_dir_path() -> io::Result { + let mut fixture_dir = env::current_dir()?; + fixture_dir.push("tests/fixtures"); + Ok(fixture_dir) +} + +#[test] +fn run_snapshots() -> io::Result<()> { + let line = "========================================================================"; + for entry in fs::read_dir(get_fixture_dir_path()?)? { + let entry = entry?; + let path = &entry.path(); + if let Some(ext) = path.extension() { + if ext == "snapshot" { + // TODO: We could assert that this snapshot file has a matching + // source file and clean up any extranious files. + continue; + } + } + let source_path = path.file_name().unwrap().to_string_lossy(); + let expected_invalid = source_path.ends_with(".invalid.eel"); + let snapshot_file_path = path.with_extension("snapshot"); + + let source = fs::read_to_string(path)?; + + let output = compile(vec![("test".to_string(), &source)], vec![]); + + let actual_invaid = output.is_err(); + + let output_str: String = match output { + Ok(binary) => wasmprinter::print_bytes(&binary).unwrap(), + Err(err) => err.pretty_print(&source), + }; + + let snapshot = format!("{}\n{}\n{}\n", source, line, output_str); + + if !snapshot_file_path.exists() || env::var("UPDATE_SNAPSHOTS").is_ok() { + fs::write(snapshot_file_path, snapshot)?; + } else { + let actual_snapshot = fs::read_to_string(snapshot_file_path)?; + // TODO: We could improve the diff output + // TODO: We could inform the user that they can set the UPDATE_SNAPSHOTS environment variable to update. + assert_eq!(snapshot, actual_snapshot, "Expected snapshot for {} to match, but it did not.\nRerun with `UPDATE_SNAPSHOTS=1 to update.`", source_path); + } + + let actual_str = if actual_invaid { "invalid" } else { "valid" }; + let expected_str = if expected_invalid { "invalid" } else { "valid" }; + + assert_eq!( + actual_invaid, expected_invalid, + "Expected file \"{}\" to be {} but it was {}", + source_path, actual_str, expected_str + ); + } + + Ok(()) +} From e31466e519005f8c0f665edd3fba5267191ab600 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 18/85] Add parser snapshot tests --- compiler-rs/src/lib.rs | 6 +-- compiler-rs/src/parser.rs | 7 ++- .../tests/fixtures/{ => ast}/one_plus_one.eel | 0 .../tests/fixtures/ast/one_plus_one.snapshot | 21 ++++++++ .../{ => wat}/one_plus_nothing.invalid.eel | 0 .../one_plus_nothing.invalid.snapshot | 0 .../tests/fixtures/wat/one_plus_one.eel | 1 + .../fixtures/{ => wat}/one_plus_one.snapshot | 0 compiler-rs/tests/snapshots_test.rs | 53 ++++++++++++++----- 9 files changed, 71 insertions(+), 17 deletions(-) rename compiler-rs/tests/fixtures/{ => ast}/one_plus_one.eel (100%) create mode 100644 compiler-rs/tests/fixtures/ast/one_plus_one.snapshot rename compiler-rs/tests/fixtures/{ => wat}/one_plus_nothing.invalid.eel (100%) rename compiler-rs/tests/fixtures/{ => wat}/one_plus_nothing.invalid.snapshot (100%) create mode 100644 compiler-rs/tests/fixtures/wat/one_plus_one.eel rename compiler-rs/tests/fixtures/{ => wat}/one_plus_one.snapshot (100%) diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 094557f..a049c31 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -10,7 +10,7 @@ mod tokens; use ast::Program; use emitter::emit; use error::CompilerError; -use parser::Parser; +pub use parser::parse; use wasm_bindgen::prelude::*; @@ -32,8 +32,8 @@ pub fn compile( let programs: Result, CompilerError> = sources .into_iter() .map(|(name, source)| { - let mut parser = Parser::new(&source); - Ok((name, parser.parse()?)) + let program = parse(&source)?; + Ok((name, program)) }) .collect(); emit(programs?, globals) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 969c431..589374b 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -13,13 +13,18 @@ static DIFFERENCE_PRECEDENCE: u8 = 1; static PRODUCT_PRECEDENCE: u8 = 2; static QUOTIENT_PRECEDENCE: u8 = 2; -pub struct Parser<'a> { +struct Parser<'a> { lexer: Lexer<'a>, token: Token, } type ParseResult = Result; +pub fn parse(src: &str) -> ParseResult { + let mut parser = Parser::new(&src); + parser.parse() +} + impl<'a> Parser<'a> { pub fn new(source: &'a str) -> Self { Parser { diff --git a/compiler-rs/tests/fixtures/one_plus_one.eel b/compiler-rs/tests/fixtures/ast/one_plus_one.eel similarity index 100% rename from compiler-rs/tests/fixtures/one_plus_one.eel rename to compiler-rs/tests/fixtures/ast/one_plus_one.eel diff --git a/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot b/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot new file mode 100644 index 0000000..13dc741 --- /dev/null +++ b/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot @@ -0,0 +1,21 @@ +1+1 +======================================================================== +Program { + expressions: [ + BinaryExpression( + BinaryExpression { + left: NumberLiteral( + NumberLiteral { + value: 1.0, + }, + ), + right: NumberLiteral( + NumberLiteral { + value: 1.0, + }, + ), + op: Add, + }, + ), + ], +} diff --git a/compiler-rs/tests/fixtures/one_plus_nothing.invalid.eel b/compiler-rs/tests/fixtures/wat/one_plus_nothing.invalid.eel similarity index 100% rename from compiler-rs/tests/fixtures/one_plus_nothing.invalid.eel rename to compiler-rs/tests/fixtures/wat/one_plus_nothing.invalid.eel diff --git a/compiler-rs/tests/fixtures/one_plus_nothing.invalid.snapshot b/compiler-rs/tests/fixtures/wat/one_plus_nothing.invalid.snapshot similarity index 100% rename from compiler-rs/tests/fixtures/one_plus_nothing.invalid.snapshot rename to compiler-rs/tests/fixtures/wat/one_plus_nothing.invalid.snapshot diff --git a/compiler-rs/tests/fixtures/wat/one_plus_one.eel b/compiler-rs/tests/fixtures/wat/one_plus_one.eel new file mode 100644 index 0000000..07d91dc --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/one_plus_one.eel @@ -0,0 +1 @@ +1+1 \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/one_plus_one.snapshot b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot similarity index 100% rename from compiler-rs/tests/fixtures/one_plus_one.snapshot rename to compiler-rs/tests/fixtures/wat/one_plus_one.snapshot diff --git a/compiler-rs/tests/snapshots_test.rs b/compiler-rs/tests/snapshots_test.rs index 8dcee41..c800cc1 100644 --- a/compiler-rs/tests/snapshots_test.rs +++ b/compiler-rs/tests/snapshots_test.rs @@ -6,17 +6,27 @@ use std::io; use std::path::PathBuf; use eel_wasm::compile; +use eel_wasm::parse; -fn get_fixture_dir_path() -> io::Result { +fn get_fixture_dir_path(dir: &str) -> io::Result { let mut fixture_dir = env::current_dir()?; - fixture_dir.push("tests/fixtures"); + fixture_dir.push(dir); Ok(fixture_dir) } #[test] fn run_snapshots() -> io::Result<()> { + run_snapshots_impl("tests/fixtures/wat", compiler_transform)?; + run_snapshots_impl("tests/fixtures/ast", ast_transform)?; + Ok(()) +} + +fn run_snapshots_impl(dir: &str, transform: F) -> io::Result<()> +where + F: Fn(&str) -> (String, bool), +{ let line = "========================================================================"; - for entry in fs::read_dir(get_fixture_dir_path()?)? { + for entry in fs::read_dir(get_fixture_dir_path(dir)?)? { let entry = entry?; let path = &entry.path(); if let Some(ext) = path.extension() { @@ -32,14 +42,7 @@ fn run_snapshots() -> io::Result<()> { let source = fs::read_to_string(path)?; - let output = compile(vec![("test".to_string(), &source)], vec![]); - - let actual_invaid = output.is_err(); - - let output_str: String = match output { - Ok(binary) => wasmprinter::print_bytes(&binary).unwrap(), - Err(err) => err.pretty_print(&source), - }; + let (output_str, actual_invalid) = transform(&source); let snapshot = format!("{}\n{}\n{}\n", source, line, output_str); @@ -52,11 +55,11 @@ fn run_snapshots() -> io::Result<()> { assert_eq!(snapshot, actual_snapshot, "Expected snapshot for {} to match, but it did not.\nRerun with `UPDATE_SNAPSHOTS=1 to update.`", source_path); } - let actual_str = if actual_invaid { "invalid" } else { "valid" }; + let actual_str = if actual_invalid { "invalid" } else { "valid" }; let expected_str = if expected_invalid { "invalid" } else { "valid" }; assert_eq!( - actual_invaid, expected_invalid, + actual_invalid, expected_invalid, "Expected file \"{}\" to be {} but it was {}", source_path, actual_str, expected_str ); @@ -64,3 +67,27 @@ fn run_snapshots() -> io::Result<()> { Ok(()) } + +fn compiler_transform(src: &str) -> (String, bool) { + let output = compile(vec![("test".to_string(), src)], vec![]); + + let actual_invaid = output.is_err(); + + let output_str: String = match output { + Ok(binary) => wasmprinter::print_bytes(&binary).unwrap(), + Err(err) => err.pretty_print(src), + }; + (output_str, actual_invaid) +} + +fn ast_transform(src: &str) -> (String, bool) { + let output = parse(src); + + let actual_invaid = output.is_err(); + + let output_str: String = match output { + Ok(ast) => format!("{:#?}", ast), + Err(err) => err.pretty_print(src), + }; + (output_str, actual_invaid) +} From 3fa446e8dc2233aeb95500f661c07c4636fe5450 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 19/85] Clean up comment --- compiler-rs/src/parser.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 589374b..8a0378d 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -204,8 +204,6 @@ impl<'a> Parser<'a> { } fn parse_identifier_expression(&mut self) -> ParseResult { - // TODO: A little odd that we get the identifier before we check the - // kind. (lifetimes...) let identifier = self.parse_identifier()?; match self.token.kind { From 551b91316c8aa7291a1cb115c027babda0ddd869 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 20/85] Add pools to programs --- compiler-rs/src/bin/compiler.rs | 6 +++++- compiler-rs/src/emitter.rs | 17 +++++++++++------ compiler-rs/src/lib.rs | 16 ++++++++++------ compiler-rs/tests/compatibility_test.rs | 2 +- compiler-rs/tests/intagration_test.rs | 21 ++++++++++++++++----- compiler-rs/tests/snapshots_test.rs | 2 +- 6 files changed, 44 insertions(+), 20 deletions(-) diff --git a/compiler-rs/src/bin/compiler.rs b/compiler-rs/src/bin/compiler.rs index 0b237e1..2b6144c 100644 --- a/compiler-rs/src/bin/compiler.rs +++ b/compiler-rs/src/bin/compiler.rs @@ -25,7 +25,11 @@ fn main() { process::exit(1); }); - let result = compile(vec![("test".to_string(), &source)], vec![]).unwrap_or_else(|err| { + let result = compile( + vec![("test".to_string(), &source, "pool".to_string())], + vec![], + ) + .unwrap_or_else(|err| { eprintln!("{:?}", err); process::exit(1); }); diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 571bda3..4ba8edc 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -15,7 +15,10 @@ use std::collections::{hash_map::Entry, HashMap}; type EmitterResult = Result; -pub fn emit(programs: Vec<(String, Program)>, globals: Vec) -> EmitterResult> { +pub fn emit( + programs: Vec<(String, Program, String)>, + globals: Vec<(String, String)>, +) -> EmitterResult> { let mut emitter = Emitter::new(); emitter.emit(programs, globals) } @@ -34,13 +37,14 @@ impl Emitter { } fn emit( &mut self, - programs: Vec<(String, Program)>, - globals: Vec, + programs: Vec<(String, Program, String)>, + globals: Vec<(String, String)>, // (Pool name, global) ) -> EmitterResult> { let mut imports = Vec::new(); self.current_pool = "pool".to_string(); - for global in &globals { + for (pool_name, global) in globals { + self.current_pool = pool_name; // TODO: Lots of clones. self.resolve_variable(global.clone()); imports.push(make_import_entry(self.current_pool.clone(), global.clone())); @@ -115,12 +119,13 @@ impl Emitter { fn emit_programs( &mut self, - programs: Vec<(String, Program)>, + programs: Vec<(String, Program, String)>, ) -> EmitterResult<(Vec, Vec, Vec)> { let mut names = Vec::new(); let mut instructions = Vec::new(); let mut funcs = Vec::new(); - for (i, (name, program)) in programs.into_iter().enumerate() { + for (i, (name, program, pool_name)) in programs.into_iter().enumerate() { + self.current_pool = pool_name; names.push(ExportEntry::new(name, Internal::Function(i as u32))); let locals = Vec::new(); let func_body = FuncBody::new(locals, self.emit_program(program)?); diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index a049c31..4c36eda 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -22,18 +22,22 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[wasm_bindgen] pub fn assert_compile(source: &str) -> Vec { - compile(vec![("test".to_string(), source)], vec![]).expect("Don't screw it up") + compile( + vec![("test".to_string(), source, "pool".to_string())], + vec![], + ) + .expect("Don't screw it up") } pub fn compile( - sources: Vec<(String, &str)>, - globals: Vec, + sources: Vec<(String, &str, String)>, + globals: Vec<(String, String)>, ) -> Result, CompilerError> { - let programs: Result, CompilerError> = sources + let programs: Result, CompilerError> = sources .into_iter() - .map(|(name, source)| { + .map(|(name, source, pool)| { let program = parse(&source)?; - Ok((name, program)) + Ok((name, program, pool)) }) .collect(); emit(programs?, globals) diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 95cdde2..2e1b2e7 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -540,7 +540,7 @@ fn compatibility_tests() { ]; for (name, code, expected) in test_cases { - match compile(vec![("test".to_string(), code)], vec![]) { + match compile(vec![("test".to_string(), code, "pool".to_string())], vec![]) { Ok(binary) => { if expected_failing.contains(name) { panic!(format!("Expected {} to fail, but it passed!", name)); diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index 0cbec58..d7880f9 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -29,7 +29,12 @@ fn run(body: &[u8]) -> Result { fn test_run(program: &str, expected_output: f64) { assert_eq!( - run(&compile(vec![("test".to_string(), program)], vec![]).unwrap()).expect("Run Error"), + run(&compile( + vec![("test".to_string(), program, "pool".to_string())], + vec![] + ) + .unwrap()) + .expect("Run Error"), expected_output ); } @@ -37,7 +42,7 @@ fn test_run(program: &str, expected_output: f64) { #[test] fn build_one() -> io::Result<()> { assert_eq!( - &compile(vec![("test".to_string(), "1")], vec![]).unwrap(), + &compile(vec![("test".to_string(), "1", "pool".to_string())], vec![]).unwrap(), &[ 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, 115, 116, 0, 0, 10, 13, 1, 11, 0, 68, 0, 0, 0, 0, 0, 0, 240, 63, 11, @@ -78,8 +83,11 @@ fn with_global() { let global_imports = GlobalPool { globals: HashMap::default(), }; - let wasm_binary = compile(vec![("test".to_string(), "g=1")], vec!["g".to_string()]) - .expect("Expect to compile"); + let wasm_binary = compile( + vec![("test".to_string(), "g=1", "pool".to_string())], + vec![("g".to_string(), "pool".to_string())], + ) + .expect("Expect to compile"); // TODO: This will fail becuase wasmi 0.8.0 depends upon wasmi-validaiton // 0.3.0 which does not include https://github.com/paritytech/wasmi/pull/228 // which allows mutable globals. @@ -103,7 +111,10 @@ fn with_global() { #[test] fn multiple_functions() { let wasm_binary = compile( - vec![("one".to_string(), "1"), ("two".to_string(), "2")], + vec![ + ("one".to_string(), "1", "pool".to_string()), + ("two".to_string(), "2", "pool".to_string()), + ], vec![], ) .expect("Expect to compile"); diff --git a/compiler-rs/tests/snapshots_test.rs b/compiler-rs/tests/snapshots_test.rs index c800cc1..6c9731a 100644 --- a/compiler-rs/tests/snapshots_test.rs +++ b/compiler-rs/tests/snapshots_test.rs @@ -69,7 +69,7 @@ where } fn compiler_transform(src: &str) -> (String, bool) { - let output = compile(vec![("test".to_string(), src)], vec![]); + let output = compile(vec![("test".to_string(), src, "pool".to_string())], vec![]); let actual_invaid = output.is_err(); From 298a64e9565167ee07e6f2963e220a40eea2f8e1 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 21/85] Add token snapshot tests --- compiler-rs/src/lexer.rs | 1 + compiler-rs/src/lib.rs | 4 +++ .../tokens/one_backslash_one.invalid.eel | 1 + .../tokens/one_backslash_one.invalid.snapshot | 3 ++ .../tests/fixtures/tokens/one_plus_one.eel | 1 + .../fixtures/tokens/one_plus_one.snapshot | 7 +++++ compiler-rs/tests/snapshots_test.rs | 31 +++++++++++++++++-- 7 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.eel create mode 100644 compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.snapshot create mode 100644 compiler-rs/tests/fixtures/tokens/one_plus_one.eel create mode 100644 compiler-rs/tests/fixtures/tokens/one_plus_one.snapshot diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index fc15edd..3a795d4 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -17,6 +17,7 @@ pub struct Lexer<'a> { chars: FileChars<'a>, } +// TODO: Consider https://github.com/maciejhirsz/logos impl<'a> Lexer<'a> { pub fn new(source: &'a str) -> Self { Lexer { diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 4c36eda..4cd47e8 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -10,7 +10,11 @@ mod tokens; use ast::Program; use emitter::emit; use error::CompilerError; +// Only exported for tests +pub use lexer::Lexer; pub use parser::parse; +pub use tokens::Token; +pub use tokens::TokenKind; use wasm_bindgen::prelude::*; diff --git a/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.eel b/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.eel new file mode 100644 index 0000000..d928b17 --- /dev/null +++ b/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.eel @@ -0,0 +1 @@ +1\1 \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.snapshot b/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.snapshot new file mode 100644 index 0000000..5bd6126 --- /dev/null +++ b/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.snapshot @@ -0,0 +1,3 @@ +1\1 +======================================================================== +Parse Error: Unexpected character '\'. diff --git a/compiler-rs/tests/fixtures/tokens/one_plus_one.eel b/compiler-rs/tests/fixtures/tokens/one_plus_one.eel new file mode 100644 index 0000000..07d91dc --- /dev/null +++ b/compiler-rs/tests/fixtures/tokens/one_plus_one.eel @@ -0,0 +1 @@ +1+1 \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/tokens/one_plus_one.snapshot b/compiler-rs/tests/fixtures/tokens/one_plus_one.snapshot new file mode 100644 index 0000000..f80db65 --- /dev/null +++ b/compiler-rs/tests/fixtures/tokens/one_plus_one.snapshot @@ -0,0 +1,7 @@ +1+1 +======================================================================== +[ + Int, + Plus, + Int, +] diff --git a/compiler-rs/tests/snapshots_test.rs b/compiler-rs/tests/snapshots_test.rs index 6c9731a..7633641 100644 --- a/compiler-rs/tests/snapshots_test.rs +++ b/compiler-rs/tests/snapshots_test.rs @@ -5,8 +5,8 @@ use std::fs; use std::io; use std::path::PathBuf; -use eel_wasm::compile; -use eel_wasm::parse; +use eel_wasm::{compile, Lexer, TokenKind}; +use eel_wasm::{parse, Token}; fn get_fixture_dir_path(dir: &str) -> io::Result { let mut fixture_dir = env::current_dir()?; @@ -18,6 +18,7 @@ fn get_fixture_dir_path(dir: &str) -> io::Result { fn run_snapshots() -> io::Result<()> { run_snapshots_impl("tests/fixtures/wat", compiler_transform)?; run_snapshots_impl("tests/fixtures/ast", ast_transform)?; + run_snapshots_impl("tests/fixtures/tokens", lexer_transform)?; Ok(()) } @@ -91,3 +92,29 @@ fn ast_transform(src: &str) -> (String, bool) { }; (output_str, actual_invaid) } + +fn lexer_transform(src: &str) -> (String, bool) { + let mut lexer = Lexer::new(src); + let mut tokens = vec![]; + let mut error = None; + loop { + match lexer.next_token() { + Err(err) => { + error = Some(err); + break; + } + Ok(Token { + kind: TokenKind::EOF, + .. + }) => break, + Ok(Token { kind, .. }) => tokens.push(kind), + } + } + + let actual_invalid = error.is_some(); + let output_str = match error { + Some(err) => err.pretty_print(src), + None => format!("{:#?}", tokens), + }; + (output_str, actual_invalid) +} From 2ac029739a62a441f92626c4ddda920b0e76dd37 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 22/85] Add Cargo tests to GitHub actions --- .github/workflows/rust.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..94404bd --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,24 @@ +name: Rust + +on: + push: + branches: [ master, ci-execution ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cargo build --verbose + - name: Ensure formatted + run: cargo fmt --all -- --check + - name: Run Cargo tests + run: cargo test \ No newline at end of file From 8d442b69ab1f4bd9f7216821727f05bf5d7272be Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 23/85] Set CI working directory --- .github/workflows/rust.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 94404bd..b8a12ed 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,10 +10,11 @@ env: CARGO_TERM_COLOR: always jobs: - build: + build: + env: + working-directory: ./compiler-rs runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Build From 10f54af67ba6843ad3efa7c0b429692f29dc6901 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 24/85] Another attempt at working directory --- .github/workflows/rust.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b8a12ed..bceddd5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,11 +9,12 @@ on: env: CARGO_TERM_COLOR: always -jobs: +defaults: + run: + working-directory: ./compiler-rs +jobs: build: - env: - working-directory: ./compiler-rs runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From d1a01a7a3f07d4f315e6b8d8cb1f3aadd0f1e7d1 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 25/85] Shims --- compiler-rs/Cargo.toml | 1 + compiler-rs/README.md | 7 +- compiler-rs/src/emitter.rs | 112 ++++++++++++------ compiler-rs/src/index_store.rs | 31 +++++ compiler-rs/src/lib.rs | 4 + compiler-rs/src/ordered_ | 0 compiler-rs/src/shim.rs | 29 +++++ .../fixtures/wat/shims_wrong_args.invalid.eel | 1 + .../wat/shims_wrong_args.invalid.snapshot | 3 + compiler-rs/tests/fixtures/wat/sin.eel | 1 + compiler-rs/tests/fixtures/wat/sin.snapshot | 10 ++ 11 files changed, 160 insertions(+), 39 deletions(-) create mode 100644 compiler-rs/src/index_store.rs create mode 100644 compiler-rs/src/ordered_ create mode 100644 compiler-rs/src/shim.rs create mode 100644 compiler-rs/tests/fixtures/wat/shims_wrong_args.invalid.eel create mode 100644 compiler-rs/tests/fixtures/wat/shims_wrong_args.invalid.snapshot create mode 100644 compiler-rs/tests/fixtures/wat/sin.eel create mode 100644 compiler-rs/tests/fixtures/wat/sin.snapshot diff --git a/compiler-rs/Cargo.toml b/compiler-rs/Cargo.toml index fd74a2a..1b6ad3b 100644 --- a/compiler-rs/Cargo.toml +++ b/compiler-rs/Cargo.toml @@ -15,6 +15,7 @@ default = ["console_error_panic_hook"] wasm-bindgen = "0.2.63" parity-wasm = "0.42.2" structopt = "0.3.21" +indexmap = "1.6.2" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/compiler-rs/README.md b/compiler-rs/README.md index d7d107d..5010544 100644 --- a/compiler-rs/README.md +++ b/compiler-rs/README.md @@ -5,4 +5,9 @@ To build the Wasm module: wasm-pack build ``` -You can find the output in `pkg/`. \ No newline at end of file +You can find the output in `pkg/`. + +## TODO + +- [ ] Add AST node for arguments list so that we can show it as the error node when arg count is wrong. +- [ ] Run `wasm-pack build` in CI. \ No newline at end of file diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 4ba8edc..4f4f7c1 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,8 +1,10 @@ -use crate::ast::{Expression, Program}; -use crate::span::Span; use crate::{ - ast::{Assignment, BinaryExpression, BinaryOperator, FunctionCall}, + ast::{Assignment, BinaryExpression, BinaryOperator, Expression, FunctionCall, Program}, error::CompilerError, + index_store::IndexStore, + shim::Shim, + span::Span, + EelFunctionType, }; use parity_wasm::elements::Module; use parity_wasm::elements::{ @@ -11,7 +13,6 @@ use parity_wasm::elements::{ Section, Serialize, Type, TypeSection, ValueType, }; use parity_wasm::elements::{External, Instruction, Instructions}; -use std::collections::{hash_map::Entry, HashMap}; type EmitterResult = Result; @@ -25,14 +26,18 @@ pub fn emit( struct Emitter { current_pool: String, - globals: HashMap, + globals: IndexStore, + shims: IndexStore, + function_types: IndexStore, } impl Emitter { fn new() -> Self { Emitter { current_pool: "".to_string(), // TODO: Is this okay to be empty? - globals: HashMap::default(), + globals: Default::default(), + function_types: Default::default(), + shims: IndexStore::new(), } } fn emit( @@ -42,7 +47,6 @@ impl Emitter { ) -> EmitterResult> { let mut imports = Vec::new(); - self.current_pool = "pool".to_string(); for (pool_name, global) in globals { self.current_pool = pool_name; // TODO: Lots of clones. @@ -52,6 +56,15 @@ impl Emitter { let (function_exports, function_bodies, funcs) = self.emit_programs(programs)?; + for shim in self.shims.keys() { + let type_index = self.function_types.get(shim.get_type()); + imports.push(ImportEntry::new( + "shims".to_string(), + shim.as_str().to_string(), + External::Function(type_index), + )) + } + let mut sections = vec![]; sections.push(Section::Type(self.emit_type_section())); if let Some(import_section) = self.emit_import_section(imports) { @@ -78,9 +91,18 @@ impl Emitter { } fn emit_type_section(&self) -> TypeSection { - let params = vec![]; - let results = vec![ValueType::F64]; - TypeSection::with_types(vec![Type::Function(FunctionType::new(params, results))]) + let function_types = self + .function_types + .keys() + .iter() + .map(|(args, returns)| { + Type::Function(FunctionType::new( + vec![ValueType::F64; *args], + vec![ValueType::F64; *returns], + )) + }) + .collect(); + TypeSection::with_types(function_types) } fn emit_import_section(&self, imports: Vec) -> Option { @@ -96,15 +118,16 @@ impl Emitter { } fn emit_global_section(&self) -> Option { - // TODO: Derive this from seen globals - if self.globals.len() == 0 { + let globals: Vec = self + .globals + .keys() + .iter() + .map(|_| make_empty_global()) + .collect(); + + if globals.len() == 0 { None } else { - let globals = [0..self.globals.len()] - .iter() - .map(|_| make_empty_global()) - .collect(); - Some(GlobalSection::with_entries(globals)) } } @@ -131,7 +154,10 @@ impl Emitter { let func_body = FuncBody::new(locals, self.emit_program(program)?); instructions.push(func_body); - funcs.push(Func::new(0)) + // TODO: In the future functions should not return any values + let function_type = self.function_types.get((0, 1)); + + funcs.push(Func::new(function_type)) } Ok((names, instructions, funcs)) } @@ -202,34 +228,44 @@ impl Emitter { &mut self, function_call: FunctionCall, ) -> EmitterResult> { + let mut instructions = vec![]; + let arity = function_call.arguments.len(); + for arg in function_call.arguments { + instructions.extend_from_slice(&self.emit_expression(arg)?); + } + // TODO: Assert arrity match &function_call.name.name[..] { "int" => { - let mut instructions = vec![]; - for arg in function_call.arguments { - instructions.extend_from_slice(&self.emit_expression(arg)?); - } instructions.push(Instruction::F64Floor); - Ok(instructions) } - _ => Err(CompilerError::new( - format!("Unknown function `{}`", function_call.name.name), - function_call.name.span, - )), + shim_name if Shim::from_str(shim_name).is_some() => { + let shim = Shim::from_str(shim_name).unwrap(); + if arity != shim.arity() { + return Err(CompilerError::new( + format!( + "Incorrect argument count for function `{}`. Expected {} but got {}.", + shim_name, + shim.arity(), + arity + ), + // TODO: Better to underline the argument list + function_call.name.span, + )); + } + instructions.push(Instruction::Call(self.shims.get(shim))); + } + _ => { + return Err(CompilerError::new( + format!("Unknown function `{}`", function_call.name.name), + function_call.name.span, + )) + } } + Ok(instructions) } fn resolve_variable(&mut self, name: String) -> u32 { - let next = self.globals.len() as u32; - match self - .globals - .entry(format!("{}::{}", self.current_pool, &name)) // TODO: Can we avoid this format? - { - Entry::Occupied(entry) => entry.get().clone(), - Entry::Vacant(entry) => { - entry.insert(next); - next - } - } + self.globals.get(name) } } diff --git a/compiler-rs/src/index_store.rs b/compiler-rs/src/index_store.rs new file mode 100644 index 0000000..f1210ec --- /dev/null +++ b/compiler-rs/src/index_store.rs @@ -0,0 +1,31 @@ +use indexmap::map::Entry; +use indexmap::IndexMap; +use std::hash::Hash; + +#[derive(Default)] +pub struct IndexStore { + map: IndexMap, +} + +impl IndexStore { + pub fn new() -> Self { + IndexStore { + map: IndexMap::new(), + } + } + pub fn get(&mut self, key: T) -> u32 { + let next = self.map.len() as u32; + match self.map.entry(key) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => { + entry.insert(next); + next + } + } + } + + // TODO: Return iter? + pub fn keys(&self) -> Vec<&T> { + self.map.keys().collect() + } +} diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 4cd47e8..d84d7c2 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -2,8 +2,10 @@ mod ast; mod emitter; mod error; mod file_chars; +mod index_store; mod lexer; mod parser; +mod shim; mod span; mod tokens; @@ -16,6 +18,8 @@ pub use parser::parse; pub use tokens::Token; pub use tokens::TokenKind; +pub type EelFunctionType = (usize, usize); + use wasm_bindgen::prelude::*; // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global diff --git a/compiler-rs/src/ordered_ b/compiler-rs/src/ordered_ new file mode 100644 index 0000000..e69de29 diff --git a/compiler-rs/src/shim.rs b/compiler-rs/src/shim.rs new file mode 100644 index 0000000..f44c80b --- /dev/null +++ b/compiler-rs/src/shim.rs @@ -0,0 +1,29 @@ +use crate::EelFunctionType; + +// TODO: We could use https://docs.rs/strum_macros/0.20.1/strum_macros/index.html +#[derive(PartialEq, Eq, Hash)] +pub enum Shim { + Sin, +} + +impl Shim { + pub fn get_type(&self) -> EelFunctionType { + (self.arity(), 1) + } + pub fn arity(&self) -> usize { + match self { + Shim::Sin => 1, + } + } + pub fn as_str(&self) -> &str { + match self { + Shim::Sin => "sin", + } + } + pub fn from_str(name: &str) -> Option { + match name { + "sin" => Some(Shim::Sin), + _ => None, + } + } +} diff --git a/compiler-rs/tests/fixtures/wat/shims_wrong_args.invalid.eel b/compiler-rs/tests/fixtures/wat/shims_wrong_args.invalid.eel new file mode 100644 index 0000000..052b158 --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/shims_wrong_args.invalid.eel @@ -0,0 +1 @@ +sin(100,200) \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/wat/shims_wrong_args.invalid.snapshot b/compiler-rs/tests/fixtures/wat/shims_wrong_args.invalid.snapshot new file mode 100644 index 0000000..f5696af --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/shims_wrong_args.invalid.snapshot @@ -0,0 +1,3 @@ +sin(100,200) +======================================================================== +Incorrect argument count for function `sin`. Expected 1 but got 2. diff --git a/compiler-rs/tests/fixtures/wat/sin.eel b/compiler-rs/tests/fixtures/wat/sin.eel new file mode 100644 index 0000000..5204d29 --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/sin.eel @@ -0,0 +1 @@ +sin(100) \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/wat/sin.snapshot b/compiler-rs/tests/fixtures/wat/sin.snapshot new file mode 100644 index 0000000..45fc18a --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/sin.snapshot @@ -0,0 +1,10 @@ +sin(100) +======================================================================== +(module + (type (;0;) (func (result f64))) + (type (;1;) (func (param f64) (result f64))) + (import "shims" "sin" (func (;0;) (type 1))) + (func (;1;) (type 0) (result f64) + f64.const 0x1.9p+6 (;=100;) + call 0) + (export "test" (func 0))) From c0fba27a6fa614e447be5f6234255e5ee5fccb5f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 26/85] Add support for reg/d/d --- compiler-rs/README.md | 3 ++- compiler-rs/src/emitter.rs | 16 ++++++++++++++-- compiler-rs/src/ordered_ | 0 compiler-rs/tests/fixtures/wat/reg.eel | 1 + compiler-rs/tests/fixtures/wat/reg.snapshot | 10 ++++++++++ 5 files changed, 27 insertions(+), 3 deletions(-) delete mode 100644 compiler-rs/src/ordered_ create mode 100644 compiler-rs/tests/fixtures/wat/reg.eel create mode 100644 compiler-rs/tests/fixtures/wat/reg.snapshot diff --git a/compiler-rs/README.md b/compiler-rs/README.md index 5010544..4d75601 100644 --- a/compiler-rs/README.md +++ b/compiler-rs/README.md @@ -10,4 +10,5 @@ You can find the output in `pkg/`. ## TODO - [ ] Add AST node for arguments list so that we can show it as the error node when arg count is wrong. -- [ ] Run `wasm-pack build` in CI. \ No newline at end of file +- [ ] Run `wasm-pack build` in CI. +- [ ] Should the magicness of reg10 values be case insensitive? (It is in the JS version) \ No newline at end of file diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 4f4f7c1..6dc1cf0 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -26,7 +26,7 @@ pub fn emit( struct Emitter { current_pool: String, - globals: IndexStore, + globals: IndexStore<(Option, String)>, shims: IndexStore, function_types: IndexStore, } @@ -265,10 +265,22 @@ impl Emitter { } fn resolve_variable(&mut self, name: String) -> u32 { - self.globals.get(name) + let pool = if variable_is_register(&name) { + None + } else { + Some(self.current_pool.clone()) + }; + + self.globals.get((pool, name)) } } +fn variable_is_register(name: &str) -> bool { + let chars: Vec<_> = name.chars().collect(); + // We avoided pulling in the regex crate! (But at what cost?) + matches!(chars.as_slice(), ['r', 'e', 'g', '0'..='9', '0'..='9']) +} + fn make_empty_global() -> GlobalEntry { GlobalEntry::new( GlobalType::new(ValueType::F64, true), diff --git a/compiler-rs/src/ordered_ b/compiler-rs/src/ordered_ deleted file mode 100644 index e69de29..0000000 diff --git a/compiler-rs/tests/fixtures/wat/reg.eel b/compiler-rs/tests/fixtures/wat/reg.eel new file mode 100644 index 0000000..c707df0 --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/reg.eel @@ -0,0 +1 @@ +reg00=10 \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/wat/reg.snapshot b/compiler-rs/tests/fixtures/wat/reg.snapshot new file mode 100644 index 0000000..07a020a --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/reg.snapshot @@ -0,0 +1,10 @@ +reg00=10 +======================================================================== +(module + (type (;0;) (func (result f64))) + (func (;0;) (type 0) (result f64) + f64.const 0x1.4p+3 (;=10;) + global.set 0 + global.get 0) + (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) + (export "test" (func 0))) From b5add55349650bff0ed4c7cc42a318fbeea789cc Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 27/85] Build up a single instruction vec per program --- compiler-rs/src/emitter.rs | 62 ++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 6dc1cf0..61dae17 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -165,73 +165,75 @@ impl Emitter { fn emit_program(&mut self, program: Program) -> EmitterResult { let mut instructions: Vec = Vec::new(); for expression in program.expressions { - let expression_instructions = self.emit_expression(expression)?; - instructions.extend_from_slice(&expression_instructions); + self.emit_expression(expression, &mut instructions)?; // TODO: Consider that we might need to drop the implicit return. } - let mut new = Vec::with_capacity(instructions.len() + 1); - new.extend_from_slice(&instructions); - new.push(Instruction::End); - Ok(Instructions::new(new)) + instructions.push(Instruction::End); + Ok(Instructions::new(instructions)) } - fn emit_expression(&mut self, expression: Expression) -> EmitterResult> { + fn emit_expression( + &mut self, + expression: Expression, + instructions: &mut Vec, + ) -> EmitterResult<()> { match expression { Expression::BinaryExpression(binary_expression) => { - self.emit_binary_expression(binary_expression) + self.emit_binary_expression(binary_expression, instructions) } Expression::Assignment(assignment_expression) => { - self.emit_assignment(assignment_expression) + self.emit_assignment(assignment_expression, instructions) + } + Expression::NumberLiteral(number_literal) => { + instructions.push(Instruction::F64Const(u64::from_le_bytes( + number_literal.value.to_le_bytes(), + ))); + Ok(()) + } + Expression::FunctionCall(function_call) => { + self.emit_function_call(function_call, instructions) } - Expression::NumberLiteral(number_literal) => Ok(vec![Instruction::F64Const( - u64::from_le_bytes(number_literal.value.to_le_bytes()), - )]), - Expression::FunctionCall(function_call) => self.emit_function_call(function_call), } } fn emit_binary_expression( &mut self, binary_expression: BinaryExpression, - ) -> EmitterResult> { - let left = self.emit_expression(*binary_expression.left)?; - let right = self.emit_expression(*binary_expression.right)?; + instructions: &mut Vec, + ) -> EmitterResult<()> { + self.emit_expression(*binary_expression.left, instructions)?; + self.emit_expression(*binary_expression.right, instructions)?; let op = match binary_expression.op { BinaryOperator::Add => Instruction::F64Add, BinaryOperator::Subtract => Instruction::F64Sub, BinaryOperator::Multiply => Instruction::F64Mul, BinaryOperator::Divide => Instruction::F64Div, }; - let mut instructions = Vec::with_capacity(left.len() + right.len() + 1); - instructions.extend_from_slice(&left); - instructions.extend_from_slice(&right); instructions.push(op); - Ok(instructions) + Ok(()) } fn emit_assignment( &mut self, assignment_expression: Assignment, - ) -> EmitterResult> { - let mut instructions: Vec = Vec::new(); + instructions: &mut Vec, + ) -> EmitterResult<()> { let resolved_name = self.resolve_variable(assignment_expression.left.name); - let right_expression = self.emit_expression(*assignment_expression.right)?; - - instructions.extend_from_slice(&right_expression); + self.emit_expression(*assignment_expression.right, instructions)?; instructions.push(Instruction::SetGlobal(resolved_name)); instructions.push(Instruction::GetGlobal(resolved_name)); - Ok(instructions) + Ok(()) } fn emit_function_call( &mut self, function_call: FunctionCall, - ) -> EmitterResult> { - let mut instructions = vec![]; + instructions: &mut Vec, + ) -> EmitterResult<()> { let arity = function_call.arguments.len(); for arg in function_call.arguments { - instructions.extend_from_slice(&self.emit_expression(arg)?); + &self.emit_expression(arg, instructions)?; } // TODO: Assert arrity match &function_call.name.name[..] { @@ -261,7 +263,7 @@ impl Emitter { )) } } - Ok(instructions) + Ok(()) } fn resolve_variable(&mut self, name: String) -> u32 { From d4cb2792d84821905ef034717b79295bb416358c Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 28/85] Improve naming --- compiler-rs/src/emitter.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 61dae17..3f3a361 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -144,22 +144,21 @@ impl Emitter { &mut self, programs: Vec<(String, Program, String)>, ) -> EmitterResult<(Vec, Vec, Vec)> { - let mut names = Vec::new(); - let mut instructions = Vec::new(); - let mut funcs = Vec::new(); + let mut exports = Vec::new(); + let mut function_bodies = Vec::new(); + let mut function_definitions = Vec::new(); for (i, (name, program, pool_name)) in programs.into_iter().enumerate() { self.current_pool = pool_name; - names.push(ExportEntry::new(name, Internal::Function(i as u32))); + exports.push(ExportEntry::new(name, Internal::Function(i as u32))); let locals = Vec::new(); - let func_body = FuncBody::new(locals, self.emit_program(program)?); - instructions.push(func_body); + function_bodies.push(FuncBody::new(locals, self.emit_program(program)?)); // TODO: In the future functions should not return any values let function_type = self.function_types.get((0, 1)); - funcs.push(Func::new(function_type)) + function_definitions.push(Func::new(function_type)) } - Ok((names, instructions, funcs)) + Ok((exports, function_bodies, function_definitions)) } fn emit_program(&mut self, program: Program) -> EmitterResult { From d019ab7ec2d1d01add273125b0a9c46253ada575 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 29/85] Work toward getting shims working in tests --- compiler-rs/tests/intagration_test.rs | 99 ++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index d7880f9..5d816e0 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -3,9 +3,12 @@ extern crate eel_wasm; use std::{collections::HashMap, io}; use eel_wasm::compile; +use wasmi::nan_preserving_float::F64; +use wasmi::RuntimeValue; use wasmi::{ - nan_preserving_float::F64, Error as WasmiError, GlobalDescriptor, GlobalInstance, GlobalRef, - ImportsBuilder, ModuleImportResolver, ModuleInstance, NopExternals, RuntimeValue, + Error as WasmiError, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, + GlobalRef, ImportsBuilder, ModuleImportResolver, ModuleInstance, NopExternals, RuntimeArgs, + Signature, Trap, ValueType, }; fn run(body: &[u8]) -> Result { @@ -75,6 +78,66 @@ impl ModuleImportResolver for GlobalPool { let global = GlobalInstance::alloc(RuntimeValue::F64(F64::from_float(0.0)), true); Ok(global) } + fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { + println!("Calling function: {}", field_name); + let index = match field_name { + "sin" => SIN_FUNC_INDEX, + _ => { + return Err(WasmiError::Instantiation(format!( + "Export {} not found", + field_name + ))) + } + }; + + if !self.check_signature(index, signature) { + return Err(WasmiError::Instantiation(format!( + "Export {} has a bad signature", + field_name + ))); + } + + println!("PAssed sig check: {}", field_name); + + Ok(FuncInstance::alloc_host( + Signature::new(&[ValueType::F64][..], Some(ValueType::F64)), + index, + )) + } +} + +const SIN_FUNC_INDEX: usize = 0; + +impl Externals for GlobalPool { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + println!("Calling index {}", index); + match index { + SIN_FUNC_INDEX => { + let a: F64 = args.nth_checked(0)?; + println!("First arg was {:?}", a); + + let result = a * 2; + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + _ => panic!("Unimplemented function at {}", index), + } + } +} + +impl GlobalPool { + fn check_signature(&self, index: usize, signature: &Signature) -> bool { + println!("Checking sig of {}", index); + let (params, ret_ty): (&[ValueType], Option) = match index { + SIN_FUNC_INDEX => (&[ValueType::F64], Some(ValueType::F64)), + _ => return false, + }; + signature.params() == params && signature.return_type() == ret_ty + } } #[test] @@ -108,6 +171,38 @@ fn with_global() { .expect("Ran"); } +#[test] +#[ignore] +fn with_shims() { + let global_imports = GlobalPool { + globals: HashMap::default(), + }; + let wasm_binary = compile( + vec![("test".to_string(), "sin(10)", "pool".to_string())], + vec![], + ) + .expect("Expect to compile"); + // TODO: This will fail becuase wasmi 0.8.0 depends upon wasmi-validaiton + // 0.3.0 which does not include https://github.com/paritytech/wasmi/pull/228 + // which allows mutable globals. + // 0.3.1 has the PR, but wasmi has not shipped a new version that includes it. + // parity-wasm already depends upon 0.3.1 (I _think_) + let module = wasmi::Module::from_buffer(&wasm_binary).expect("No validation errors"); + let mut imports = ImportsBuilder::default(); + imports.push_resolver("pool", &global_imports); + imports.push_resolver("shims", &global_imports); + let instance = ModuleInstance::new(&module, &imports) + .expect("failed to instantiate wasm module") + .assert_no_start(); + + // Finally, invoke the exported function "test" with no parameters + // and empty external function executor. + instance + .invoke_export("test", &[], &mut NopExternals) + .expect("failed to execute export") + .expect("Ran"); +} + #[test] fn multiple_functions() { let wasm_binary = compile( From bf74d5cfff580ad1b0db8b9f9bd572b0466fa710 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 30/85] Get tests working for globals and shims --- compiler-rs/Cargo.toml | 3 +- compiler-rs/src/emitter.rs | 10 +- compiler-rs/src/index_store.rs | 15 ++ compiler-rs/tests/common/mod.rs | 75 ++++++++ compiler-rs/tests/compatibility_test.rs | 17 +- .../tests/fixtures/wat/one_plus_one.snapshot | 6 +- compiler-rs/tests/fixtures/wat/reg.snapshot | 6 +- compiler-rs/tests/fixtures/wat/sin.snapshot | 2 +- compiler-rs/tests/intagration_test.rs | 178 ++---------------- 9 files changed, 142 insertions(+), 170 deletions(-) create mode 100644 compiler-rs/tests/common/mod.rs diff --git a/compiler-rs/Cargo.toml b/compiler-rs/Cargo.toml index 1b6ad3b..ceaf8f6 100644 --- a/compiler-rs/Cargo.toml +++ b/compiler-rs/Cargo.toml @@ -32,7 +32,8 @@ wee_alloc = { version = "0.4.5", optional = true } [dev-dependencies] -wasmi = "0.8.0" +# https://github.com/paritytech/wasmi/issues/252 +wasmi = { git = "https://github.com/paritytech/wasmi", branch = "master" } wabt = "0.9.0" wasm-bindgen-test = "0.3.13" wasmprinter = "0.2.0" diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 3f3a361..9892dcc 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -54,7 +54,9 @@ impl Emitter { imports.push(make_import_entry(self.current_pool.clone(), global.clone())); } - let (function_exports, function_bodies, funcs) = self.emit_programs(programs)?; + self.shims.get(Shim::Sin); + + let (function_exports, function_bodies, funcs) = self.emit_programs(programs, 1)?; for shim in self.shims.keys() { let type_index = self.function_types.get(shim.get_type()); @@ -143,13 +145,17 @@ impl Emitter { fn emit_programs( &mut self, programs: Vec<(String, Program, String)>, + offset: u32, ) -> EmitterResult<(Vec, Vec, Vec)> { let mut exports = Vec::new(); let mut function_bodies = Vec::new(); let mut function_definitions = Vec::new(); for (i, (name, program, pool_name)) in programs.into_iter().enumerate() { self.current_pool = pool_name; - exports.push(ExportEntry::new(name, Internal::Function(i as u32))); + 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)?)); diff --git a/compiler-rs/src/index_store.rs b/compiler-rs/src/index_store.rs index f1210ec..91ca716 100644 --- a/compiler-rs/src/index_store.rs +++ b/compiler-rs/src/index_store.rs @@ -29,3 +29,18 @@ impl IndexStore { self.map.keys().collect() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::EelFunctionType; + #[test] + fn tuple() { + let mut function_types: IndexStore = 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); + } +} diff --git a/compiler-rs/tests/common/mod.rs b/compiler-rs/tests/common/mod.rs new file mode 100644 index 0000000..9f23b82 --- /dev/null +++ b/compiler-rs/tests/common/mod.rs @@ -0,0 +1,75 @@ +extern crate eel_wasm; + +use wasmi::nan_preserving_float::F64; +use wasmi::RuntimeValue; +use wasmi::{ + Error as WasmiError, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, + GlobalRef, ModuleImportResolver, RuntimeArgs, Signature, Trap, ValueType, +}; + +pub struct GlobalPool {} + +impl GlobalPool { + fn check_signature(&self, index: usize, signature: &Signature) -> bool { + let (params, ret_ty): (&[ValueType], Option) = match index { + SIN_FUNC_INDEX => (&[ValueType::F64], Some(ValueType::F64)), + _ => return false, + }; + signature.params() == params && signature.return_type() == ret_ty + } +} + +impl ModuleImportResolver for GlobalPool { + fn resolve_global( + &self, + _field_name: &str, + _global_type: &GlobalDescriptor, + ) -> Result { + let global = GlobalInstance::alloc(RuntimeValue::F64(F64::from_float(0.0)), true); + Ok(global) + } + fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { + let index = match field_name { + "sin" => SIN_FUNC_INDEX, + _ => { + return Err(WasmiError::Instantiation(format!( + "Export {} not found", + field_name + ))) + } + }; + + if !self.check_signature(index, signature) { + return Err(WasmiError::Instantiation(format!( + "Export {} has a bad signature", + field_name + ))); + } + + Ok(FuncInstance::alloc_host( + Signature::new(&[ValueType::F64][..], Some(ValueType::F64)), + index, + )) + } +} + +const SIN_FUNC_INDEX: usize = 0; + +impl Externals for GlobalPool { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + match index { + SIN_FUNC_INDEX => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().sin(); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + _ => panic!("Unimplemented function at {}", index), + } + } +} diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 2e1b2e7..0b5cc49 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -1,17 +1,23 @@ +use common::GlobalPool; use eel_wasm::compile; -use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue}; +use wasmi::{ImportsBuilder, ModuleInstance, RuntimeValue}; +mod common; fn run(body: &[u8]) -> Result { let wasm_binary = body; let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); - let instance = ModuleInstance::new(&module, &ImportsBuilder::default()) + let mut global_imports = GlobalPool {}; + let mut imports = ImportsBuilder::default(); + imports.push_resolver("pool", &global_imports); + imports.push_resolver("shims", &global_imports); + let instance = ModuleInstance::new(&module, &imports) .expect("failed to instantiate wasm module") .assert_no_start(); // Finally, invoke the exported function "test" with no parameters // and empty external function executor. match instance - .invoke_export("test", &[], &mut NopExternals) + .invoke_export("test", &[], &mut global_imports) .expect("failed to execute export") { Some(RuntimeValue::F64(val)) => Ok(val.into()), @@ -540,7 +546,10 @@ fn compatibility_tests() { ]; for (name, code, expected) in test_cases { - match compile(vec![("test".to_string(), code, "pool".to_string())], vec![]) { + match compile( + vec![("test".to_string(), code, "pool".to_string())], + vec![("pool".to_string(), "g".to_string())], + ) { Ok(binary) => { if expected_failing.contains(name) { panic!(format!("Expected {} to fail, but it passed!", name)); diff --git a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot index b9bda59..6b8c32e 100644 --- a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot +++ b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot @@ -2,8 +2,10 @@ ======================================================================== (module (type (;0;) (func (result f64))) - (func (;0;) (type 0) (result f64) + (type (;1;) (func (param f64) (result f64))) + (import "shims" "sin" (func (;0;) (type 1))) + (func (;1;) (type 0) (result f64) f64.const 0x1p+0 (;=1;) f64.const 0x1p+0 (;=1;) f64.add) - (export "test" (func 0))) + (export "test" (func 1))) diff --git a/compiler-rs/tests/fixtures/wat/reg.snapshot b/compiler-rs/tests/fixtures/wat/reg.snapshot index 07a020a..d31da34 100644 --- a/compiler-rs/tests/fixtures/wat/reg.snapshot +++ b/compiler-rs/tests/fixtures/wat/reg.snapshot @@ -2,9 +2,11 @@ reg00=10 ======================================================================== (module (type (;0;) (func (result f64))) - (func (;0;) (type 0) (result f64) + (type (;1;) (func (param f64) (result f64))) + (import "shims" "sin" (func (;0;) (type 1))) + (func (;1;) (type 0) (result f64) f64.const 0x1.4p+3 (;=10;) global.set 0 global.get 0) (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) - (export "test" (func 0))) + (export "test" (func 1))) diff --git a/compiler-rs/tests/fixtures/wat/sin.snapshot b/compiler-rs/tests/fixtures/wat/sin.snapshot index 45fc18a..325090a 100644 --- a/compiler-rs/tests/fixtures/wat/sin.snapshot +++ b/compiler-rs/tests/fixtures/wat/sin.snapshot @@ -7,4 +7,4 @@ sin(100) (func (;1;) (type 0) (result f64) f64.const 0x1.9p+6 (;=100;) call 0) - (export "test" (func 0))) + (export "test" (func 1))) diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index 5d816e0..bf68dae 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -1,27 +1,24 @@ extern crate eel_wasm; +mod common; -use std::{collections::HashMap, io}; - +use common::GlobalPool; use eel_wasm::compile; -use wasmi::nan_preserving_float::F64; -use wasmi::RuntimeValue; -use wasmi::{ - Error as WasmiError, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, - GlobalRef, ImportsBuilder, ModuleImportResolver, ModuleInstance, NopExternals, RuntimeArgs, - Signature, Trap, ValueType, -}; +use wasmi::{ImportsBuilder, ModuleInstance, RuntimeValue}; fn run(body: &[u8]) -> Result { let wasm_binary = body; let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); - let instance = ModuleInstance::new(&module, &ImportsBuilder::default()) + + let mut global_imports = GlobalPool {}; + let mut imports = ImportsBuilder::default(); + imports.push_resolver("pool", &global_imports); + imports.push_resolver("shims", &global_imports); + let instance = ModuleInstance::new(&module, &imports) .expect("failed to instantiate wasm module") .assert_no_start(); - // Finally, invoke the exported function "test" with no parameters - // and empty external function executor. match instance - .invoke_export("test", &[], &mut NopExternals) + .invoke_export("test", &[], &mut global_imports) .expect("failed to execute export") { Some(RuntimeValue::F64(val)) => Ok(val.into()), @@ -42,18 +39,6 @@ fn test_run(program: &str, expected_output: f64) { ); } -#[test] -fn build_one() -> io::Result<()> { - assert_eq!( - &compile(vec![("test".to_string(), "1", "pool".to_string())], vec![]).unwrap(), - &[ - 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 124, 3, 2, 1, 0, 7, 8, 1, 4, 116, 101, - 115, 116, 0, 0, 10, 13, 1, 11, 0, 68, 0, 0, 0, 0, 0, 0, 240, 63, 11, - ] - ); - Ok(()) -} - #[test] fn execute_one() { test_run("1", 1.0); @@ -65,142 +50,14 @@ fn execute_one() { test_run("1+1*2", 3.0); } -struct GlobalPool { - globals: HashMap, -} - -impl ModuleImportResolver for GlobalPool { - fn resolve_global( - &self, - field_name: &str, - _global_type: &GlobalDescriptor, - ) -> Result { - let global = GlobalInstance::alloc(RuntimeValue::F64(F64::from_float(0.0)), true); - Ok(global) - } - fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { - println!("Calling function: {}", field_name); - let index = match field_name { - "sin" => SIN_FUNC_INDEX, - _ => { - return Err(WasmiError::Instantiation(format!( - "Export {} not found", - field_name - ))) - } - }; - - if !self.check_signature(index, signature) { - return Err(WasmiError::Instantiation(format!( - "Export {} has a bad signature", - field_name - ))); - } - - println!("PAssed sig check: {}", field_name); - - Ok(FuncInstance::alloc_host( - Signature::new(&[ValueType::F64][..], Some(ValueType::F64)), - index, - )) - } -} - -const SIN_FUNC_INDEX: usize = 0; - -impl Externals for GlobalPool { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> Result, Trap> { - println!("Calling index {}", index); - match index { - SIN_FUNC_INDEX => { - let a: F64 = args.nth_checked(0)?; - println!("First arg was {:?}", a); - - let result = a * 2; - - Ok(Some(RuntimeValue::F64(F64::from(result)))) - } - _ => panic!("Unimplemented function at {}", index), - } - } -} - -impl GlobalPool { - fn check_signature(&self, index: usize, signature: &Signature) -> bool { - println!("Checking sig of {}", index); - let (params, ret_ty): (&[ValueType], Option) = match index { - SIN_FUNC_INDEX => (&[ValueType::F64], Some(ValueType::F64)), - _ => return false, - }; - signature.params() == params && signature.return_type() == ret_ty - } -} - #[test] -#[ignore] fn with_global() { - let global_imports = GlobalPool { - globals: HashMap::default(), - }; - let wasm_binary = compile( - vec![("test".to_string(), "g=1", "pool".to_string())], - vec![("g".to_string(), "pool".to_string())], - ) - .expect("Expect to compile"); - // TODO: This will fail becuase wasmi 0.8.0 depends upon wasmi-validaiton - // 0.3.0 which does not include https://github.com/paritytech/wasmi/pull/228 - // which allows mutable globals. - // 0.3.1 has the PR, but wasmi has not shipped a new version that includes it. - // parity-wasm already depends upon 0.3.1 (I _think_) - let module = wasmi::Module::from_buffer(&wasm_binary).expect("No validation errors"); - let mut imports = ImportsBuilder::default(); - imports.push_resolver("pool", &global_imports); - let instance = ModuleInstance::new(&module, &imports) - .expect("failed to instantiate wasm module") - .assert_no_start(); - - // Finally, invoke the exported function "test" with no parameters - // and empty external function executor. - instance - .invoke_export("test", &[], &mut NopExternals) - .expect("failed to execute export") - .expect("Ran"); + test_run("g=1", 1.0); } #[test] -#[ignore] fn with_shims() { - let global_imports = GlobalPool { - globals: HashMap::default(), - }; - let wasm_binary = compile( - vec![("test".to_string(), "sin(10)", "pool".to_string())], - vec![], - ) - .expect("Expect to compile"); - // TODO: This will fail becuase wasmi 0.8.0 depends upon wasmi-validaiton - // 0.3.0 which does not include https://github.com/paritytech/wasmi/pull/228 - // which allows mutable globals. - // 0.3.1 has the PR, but wasmi has not shipped a new version that includes it. - // parity-wasm already depends upon 0.3.1 (I _think_) - let module = wasmi::Module::from_buffer(&wasm_binary).expect("No validation errors"); - let mut imports = ImportsBuilder::default(); - imports.push_resolver("pool", &global_imports); - imports.push_resolver("shims", &global_imports); - let instance = ModuleInstance::new(&module, &imports) - .expect("failed to instantiate wasm module") - .assert_no_start(); - - // Finally, invoke the exported function "test" with no parameters - // and empty external function executor. - instance - .invoke_export("test", &[], &mut NopExternals) - .expect("failed to execute export") - .expect("Ran"); + test_run("sin(10)", 10.0_f64.sin()); } #[test] @@ -219,17 +76,22 @@ fn multiple_functions() { // 0.3.1 has the PR, but wasmi has not shipped a new version that includes it. // parity-wasm already depends upon 0.3.1 (I _think_) let module = wasmi::Module::from_buffer(&wasm_binary).expect("No validation errors"); - let instance = ModuleInstance::new(&module, &ImportsBuilder::default()) + let mut global_imports = GlobalPool {}; + let mut imports = ImportsBuilder::default(); + imports.push_resolver("pool", &global_imports); + imports.push_resolver("shims", &global_imports); + + let instance = ModuleInstance::new(&module, &imports) .expect("failed to instantiate wasm module") .assert_no_start(); instance - .invoke_export("one", &[], &mut NopExternals) + .invoke_export("one", &[], &mut global_imports) .expect("failed to execute export") .expect("Ran"); instance - .invoke_export("two", &[], &mut NopExternals) + .invoke_export("two", &[], &mut global_imports) .expect("failed to execute export") .expect("Ran"); } From f64de0eda37c0db38958bee4421a98debefe975a Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 31/85] Abstract test util and improved signature for globals --- compiler-rs/src/bin/compiler.rs | 4 +-- compiler-rs/src/emitter.rs | 18 ++++++---- compiler-rs/src/lib.rs | 6 ++-- compiler-rs/tests/common/mod.rs | 44 +++++++++++++++++++++++-- compiler-rs/tests/compatibility_test.rs | 36 ++++---------------- compiler-rs/tests/intagration_test.rs | 43 ++++++------------------ compiler-rs/tests/snapshots_test.rs | 7 ++-- 7 files changed, 81 insertions(+), 77 deletions(-) diff --git a/compiler-rs/src/bin/compiler.rs b/compiler-rs/src/bin/compiler.rs index 2b6144c..4fd84e0 100644 --- a/compiler-rs/src/bin/compiler.rs +++ b/compiler-rs/src/bin/compiler.rs @@ -1,6 +1,6 @@ use eel_wasm::compile; -use std::fs; use std::process; +use std::{collections::HashMap, fs}; use std::path::PathBuf; use structopt::StructOpt; @@ -27,7 +27,7 @@ fn main() { let result = compile( vec![("test".to_string(), &source, "pool".to_string())], - vec![], + HashMap::default(), ) .unwrap_or_else(|err| { eprintln!("{:?}", err); diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 9892dcc..2b6bf8b 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,3 +1,5 @@ +use std::collections::{HashMap, HashSet}; + use crate::{ ast::{Assignment, BinaryExpression, BinaryOperator, Expression, FunctionCall, Program}, error::CompilerError, @@ -18,10 +20,10 @@ type EmitterResult = Result; pub fn emit( programs: Vec<(String, Program, String)>, - globals: Vec<(String, String)>, + globals_map: HashMap>, ) -> EmitterResult> { let mut emitter = Emitter::new(); - emitter.emit(programs, globals) + emitter.emit(programs, globals_map) } struct Emitter { @@ -43,15 +45,17 @@ impl Emitter { fn emit( &mut self, programs: Vec<(String, Program, String)>, - globals: Vec<(String, String)>, // (Pool name, global) + globals_map: HashMap>, // HahsMap> ) -> EmitterResult> { let mut imports = Vec::new(); - for (pool_name, global) in globals { + for (pool_name, globals) in globals_map { self.current_pool = pool_name; - // TODO: Lots of clones. - self.resolve_variable(global.clone()); - imports.push(make_import_entry(self.current_pool.clone(), global.clone())); + for global in globals { + // TODO: Lots of clones. + self.resolve_variable(global.clone()); + imports.push(make_import_entry(self.current_pool.clone(), global.clone())); + } } self.shims.get(Shim::Sin); diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index d84d7c2..9b88bc4 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -9,6 +9,8 @@ mod shim; mod span; mod tokens; +use std::collections::{HashMap, HashSet}; + use ast::Program; use emitter::emit; use error::CompilerError; @@ -32,14 +34,14 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; pub fn assert_compile(source: &str) -> Vec { compile( vec![("test".to_string(), source, "pool".to_string())], - vec![], + HashMap::default(), ) .expect("Don't screw it up") } pub fn compile( sources: Vec<(String, &str, String)>, - globals: Vec<(String, String)>, + globals: HashMap>, ) -> Result, CompilerError> { let programs: Result, CompilerError> = sources .into_iter() diff --git a/compiler-rs/tests/common/mod.rs b/compiler-rs/tests/common/mod.rs index 9f23b82..7f15507 100644 --- a/compiler-rs/tests/common/mod.rs +++ b/compiler-rs/tests/common/mod.rs @@ -1,11 +1,14 @@ extern crate eel_wasm; -use wasmi::nan_preserving_float::F64; -use wasmi::RuntimeValue; +use std::collections::{HashMap, HashSet}; + +use eel_wasm::compile; +use wasmi::{nan_preserving_float::F64, ImportsBuilder}; use wasmi::{ Error as WasmiError, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, GlobalRef, ModuleImportResolver, RuntimeArgs, Signature, Trap, ValueType, }; +use wasmi::{ModuleInstance, RuntimeValue}; pub struct GlobalPool {} @@ -73,3 +76,40 @@ impl Externals for GlobalPool { } } } + +pub fn eval_eel( + sources: Vec<(String, &str, String)>, + globals_map: HashMap>, + function_to_run: &str, +) -> Result { + // TODO: Avoid having to clone globals + let wasm_binary = compile(sources, globals_map.clone()) + .map_err(|err| format!("Compiler Error: {:?}", err))?; + + let module = wasmi::Module::from_buffer(&wasm_binary) + .map_err(|err| format!("Error parsing binary Wasm: {}", err))?; + + let mut global_imports = GlobalPool {}; + let mut imports = ImportsBuilder::default(); + + for (pool, _) in globals_map { + // TODO: Only make defined globals resolvable + imports.push_resolver(pool, &global_imports); + } + + imports.push_resolver("shims", &global_imports); + let instance = ModuleInstance::new(&module, &imports) + .map_err(|err| format!("Error instantiating Wasm module: {}", err))? + .assert_no_start(); + + // TODO: Instead of returning return value, return value of globals + match instance.invoke_export(function_to_run, &[], &mut global_imports) { + Ok(Some(RuntimeValue::F64(val))) => Ok(val.into()), + Ok(Some(val)) => Err(format!("Unexpected return type: {:?}", val)), + Ok(None) => Err("No Result".to_string()), + Err(err) => Err(format!( + "Error invoking exported function {}: {}", + function_to_run, err + )), + } +} diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 0b5cc49..7ec252d 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -1,30 +1,8 @@ -use common::GlobalPool; -use eel_wasm::compile; -use wasmi::{ImportsBuilder, ModuleInstance, RuntimeValue}; -mod common; +use std::collections::HashMap; -fn run(body: &[u8]) -> Result { - let wasm_binary = body; - let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); - let mut global_imports = GlobalPool {}; - let mut imports = ImportsBuilder::default(); - imports.push_resolver("pool", &global_imports); - imports.push_resolver("shims", &global_imports); - let instance = ModuleInstance::new(&module, &imports) - .expect("failed to instantiate wasm module") - .assert_no_start(); +use common::eval_eel; - // Finally, invoke the exported function "test" with no parameters - // and empty external function executor. - match instance - .invoke_export("test", &[], &mut global_imports) - .expect("failed to execute export") - { - Some(RuntimeValue::F64(val)) => Ok(val.into()), - Some(val) => Err(format!("Unexpected return type: {:?}", val)), - None => Err("No Result".to_string()), - } -} +mod common; #[test] fn compatibility_tests() { @@ -546,15 +524,15 @@ fn compatibility_tests() { ]; for (name, code, expected) in test_cases { - match compile( + match eval_eel( vec![("test".to_string(), code, "pool".to_string())], - vec![("pool".to_string(), "g".to_string())], + HashMap::new(), + "test", ) { - Ok(binary) => { + Ok(actual) => { if expected_failing.contains(name) { panic!(format!("Expected {} to fail, but it passed!", name)); } - let actual = run(&binary).expect("to run"); assert_eq!(&actual, expected) } Err(err) => { diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/intagration_test.rs index bf68dae..a17463f 100644 --- a/compiler-rs/tests/intagration_test.rs +++ b/compiler-rs/tests/intagration_test.rs @@ -1,42 +1,19 @@ extern crate eel_wasm; mod common; -use common::GlobalPool; -use eel_wasm::compile; -use wasmi::{ImportsBuilder, ModuleInstance, RuntimeValue}; - -fn run(body: &[u8]) -> Result { - let wasm_binary = body; - let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); +use std::collections::HashMap; - let mut global_imports = GlobalPool {}; - let mut imports = ImportsBuilder::default(); - imports.push_resolver("pool", &global_imports); - imports.push_resolver("shims", &global_imports); - let instance = ModuleInstance::new(&module, &imports) - .expect("failed to instantiate wasm module") - .assert_no_start(); - - match instance - .invoke_export("test", &[], &mut global_imports) - .expect("failed to execute export") - { - Some(RuntimeValue::F64(val)) => Ok(val.into()), - Some(val) => Err(format!("Unexpected return type: {:?}", val)), - None => Err("No Result".to_string()), - } -} +use common::{eval_eel, GlobalPool}; +use eel_wasm::compile; +use wasmi::{ImportsBuilder, ModuleInstance}; fn test_run(program: &str, expected_output: f64) { - assert_eq!( - run(&compile( - vec![("test".to_string(), program, "pool".to_string())], - vec![] - ) - .unwrap()) - .expect("Run Error"), - expected_output + let result = eval_eel( + vec![("test".to_string(), program, "pool".to_string())], + HashMap::default(), + "test", ); + assert_eq!(result, Ok(expected_output)); } #[test] @@ -67,7 +44,7 @@ fn multiple_functions() { ("one".to_string(), "1", "pool".to_string()), ("two".to_string(), "2", "pool".to_string()), ], - vec![], + HashMap::default(), ) .expect("Expect to compile"); // TODO: This will fail becuase wasmi 0.8.0 depends upon wasmi-validaiton diff --git a/compiler-rs/tests/snapshots_test.rs b/compiler-rs/tests/snapshots_test.rs index 7633641..03a3bd0 100644 --- a/compiler-rs/tests/snapshots_test.rs +++ b/compiler-rs/tests/snapshots_test.rs @@ -1,9 +1,9 @@ extern crate eel_wasm; -use std::env; use std::fs; use std::io; use std::path::PathBuf; +use std::{collections::HashMap, env}; use eel_wasm::{compile, Lexer, TokenKind}; use eel_wasm::{parse, Token}; @@ -70,7 +70,10 @@ where } fn compiler_transform(src: &str) -> (String, bool) { - let output = compile(vec![("test".to_string(), src, "pool".to_string())], vec![]); + let output = compile( + vec![("test".to_string(), src, "pool".to_string())], + HashMap::default(), + ); let actual_invaid = output.is_err(); From 210dc60700caad78963cfe13e125b245872134f8 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 32/85] Support whitespace --- compiler-rs/src/file_chars.rs | 2 +- compiler-rs/src/lexer.rs | 8 ++++++++ compiler-rs/tests/compatibility_test.rs | 11 ++++++++--- compiler-rs/tests/fixtures/tokens/whitespace.eel | 5 +++++ .../tests/fixtures/tokens/whitespace.snapshot | 15 +++++++++++++++ 5 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 compiler-rs/tests/fixtures/tokens/whitespace.eel create mode 100644 compiler-rs/tests/fixtures/tokens/whitespace.snapshot diff --git a/compiler-rs/src/file_chars.rs b/compiler-rs/src/file_chars.rs index 539c16c..327a18b 100644 --- a/compiler-rs/src/file_chars.rs +++ b/compiler-rs/src/file_chars.rs @@ -1,7 +1,7 @@ use std::mem; use std::str::Chars; -pub const NULL: char = '!'; +pub const NULL: char = '\0'; pub struct FileChars<'a> { chars: Chars<'a>, diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 3a795d4..43a1b9a 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -39,6 +39,10 @@ impl<'a> Lexer<'a> { '(' => self.read_char_as_kind(TokenKind::OpenParen), ')' => self.read_char_as_kind(TokenKind::CloseParen), ',' => self.read_char_as_kind(TokenKind::Comma), + c if is_whitepsace(c) => { + self.chars.eat_while(is_whitepsace); + return self.next_token(); + } NULL => TokenKind::EOF, c => { return Err(CompilerError::new( @@ -96,6 +100,10 @@ fn is_int(c: char) -> bool { } } +fn is_whitepsace(c: char) -> bool { + c.is_whitespace() +} + #[test] fn can_lex_number() { let mut lexer = Lexer::new("1"); diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 7ec252d..2cc1d53 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -326,7 +326,7 @@ fn compatibility_tests() { 0.0, ), ("Loop limit", "g = 0; while(g = g + 1.0)", 1048576.0), - ("Divide by zero", "g = 100 / 0", 0.0), + // ("Divide by zero", "g = 100 / 0", 0.0), ( "Divide by less than epsilon", "g = 100 / 0.000001", @@ -465,7 +465,7 @@ fn compatibility_tests() { "Less than or equal (false)", "Greater than or equal (true)", "Greater than or equal (false)", - "Script without trailing semi", + // "Script without trailing semi", "Megabuf access", "Max index megabuf", "Max index + 1 megabuf", @@ -533,7 +533,12 @@ fn compatibility_tests() { if expected_failing.contains(name) { panic!(format!("Expected {} to fail, but it passed!", name)); } - assert_eq!(&actual, expected) + if &actual != expected { + panic!(format!( + "Bad result for {}. Expected {}, but got {}.", + name, expected, actual + )); + } } Err(err) => { if !expected_failing.contains(name) { diff --git a/compiler-rs/tests/fixtures/tokens/whitespace.eel b/compiler-rs/tests/fixtures/tokens/whitespace.eel new file mode 100644 index 0000000..567126e --- /dev/null +++ b/compiler-rs/tests/fixtures/tokens/whitespace.eel @@ -0,0 +1,5 @@ +1 + 2 +- 100 + + +- 1 \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/tokens/whitespace.snapshot b/compiler-rs/tests/fixtures/tokens/whitespace.snapshot new file mode 100644 index 0000000..b399ad2 --- /dev/null +++ b/compiler-rs/tests/fixtures/tokens/whitespace.snapshot @@ -0,0 +1,15 @@ +1 + 2 +- 100 + + +- 1 +======================================================================== +[ + Int, + Plus, + Int, + Minus, + Int, + Minus, + Int, +] From fbc044afe0edb6aadbe7968eda8536cf8edd2758 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 33/85] Support builtin functions (div) --- compiler-rs/Cargo.toml | 1 + compiler-rs/src/builtin_functions.rs | 37 +++++++++++ compiler-rs/src/emitter.rs | 65 +++++++++++++------ compiler-rs/src/index_store.rs | 7 ++ compiler-rs/src/lib.rs | 1 + compiler-rs/tests/compatibility_test.rs | 3 +- compiler-rs/tests/fixtures/wat/div.eel | 1 + compiler-rs/tests/fixtures/wat/div.snapshot | 27 ++++++++ .../tests/fixtures/wat/one_plus_one.snapshot | 8 +-- compiler-rs/tests/fixtures/wat/reg.snapshot | 8 +-- compiler-rs/tests/fixtures/wat/sin.snapshot | 8 +-- ...ntagration_test.rs => integration_test.rs} | 0 12 files changed, 133 insertions(+), 33 deletions(-) create mode 100644 compiler-rs/src/builtin_functions.rs create mode 100644 compiler-rs/tests/fixtures/wat/div.eel create mode 100644 compiler-rs/tests/fixtures/wat/div.snapshot rename compiler-rs/tests/{intagration_test.rs => integration_test.rs} (100%) diff --git a/compiler-rs/Cargo.toml b/compiler-rs/Cargo.toml index ceaf8f6..01cda2f 100644 --- a/compiler-rs/Cargo.toml +++ b/compiler-rs/Cargo.toml @@ -33,6 +33,7 @@ wee_alloc = { version = "0.4.5", optional = true } [dev-dependencies] # https://github.com/paritytech/wasmi/issues/252 +# wasmi = { path = "../../wasmi"} wasmi = { git = "https://github.com/paritytech/wasmi", branch = "master" } wabt = "0.9.0" wasm-bindgen-test = "0.3.13" diff --git a/compiler-rs/src/builtin_functions.rs b/compiler-rs/src/builtin_functions.rs new file mode 100644 index 0000000..9d1b2d8 --- /dev/null +++ b/compiler-rs/src/builtin_functions.rs @@ -0,0 +1,37 @@ +use parity_wasm::elements::{BlockType, FuncBody, Instruction, Instructions, Local, ValueType}; + +use crate::EelFunctionType; + +#[derive(PartialEq, Eq, Hash)] +pub enum BuiltinFunction { + Div, +} + +impl BuiltinFunction { + pub fn get_type(&self) -> EelFunctionType { + match self { + Self::Div => (2, 1), + } + } + + pub fn func_body(&self) -> FuncBody { + match self { + Self::Div => FuncBody::new( + vec![Local::new(1, ValueType::I32)], + Instructions::new(vec![ + Instruction::GetLocal(1), + Instruction::F64Const(0), + Instruction::F64Ne, + Instruction::If(BlockType::Value(ValueType::F64)), + Instruction::GetLocal(0), + Instruction::GetLocal(1), + Instruction::F64Div, + Instruction::Else, + Instruction::F64Const(0), + Instruction::End, + Instruction::End, + ]), + ), + } + } +} diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 2b6bf8b..72f53e4 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -2,19 +2,18 @@ use std::collections::{HashMap, HashSet}; use crate::{ ast::{Assignment, BinaryExpression, BinaryOperator, Expression, FunctionCall, Program}, + builtin_functions::BuiltinFunction, error::CompilerError, index_store::IndexStore, shim::Shim, span::Span, EelFunctionType, }; -use parity_wasm::elements::Module; use parity_wasm::elements::{ - CodeSection, ExportEntry, ExportSection, Func, FuncBody, FunctionSection, FunctionType, - GlobalEntry, GlobalSection, GlobalType, ImportEntry, ImportSection, InitExpr, Internal, - Section, Serialize, Type, TypeSection, ValueType, + CodeSection, ExportEntry, ExportSection, External, Func, FuncBody, FunctionSection, + FunctionType, GlobalEntry, GlobalSection, GlobalType, ImportEntry, ImportSection, InitExpr, + Instruction, Instructions, Internal, Module, Section, Serialize, Type, TypeSection, ValueType, }; -use parity_wasm::elements::{External, Instruction, Instructions}; type EmitterResult = Result; @@ -30,7 +29,9 @@ struct Emitter { current_pool: String, globals: IndexStore<(Option, String)>, shims: IndexStore, + builtin_functions: IndexStore, function_types: IndexStore, + builtin_offset: Option, } impl Emitter { @@ -38,8 +39,10 @@ impl Emitter { Emitter { current_pool: "".to_string(), // TODO: Is this okay to be empty? globals: Default::default(), - function_types: Default::default(), shims: IndexStore::new(), + function_types: Default::default(), + builtin_functions: IndexStore::new(), + builtin_offset: None, } } fn emit( @@ -58,19 +61,23 @@ impl Emitter { } } - self.shims.get(Shim::Sin); - - let (function_exports, function_bodies, funcs) = self.emit_programs(programs, 1)?; - - for shim in self.shims.keys() { - let type_index = self.function_types.get(shim.get_type()); + let shims: Vec = vec![Shim::Sin]; + for shim in shims { + let field_str = shim.as_str().to_string(); + let type_ = shim.get_type(); + self.shims.ensure(shim); imports.push(ImportEntry::new( "shims".to_string(), - shim.as_str().to_string(), - External::Function(type_index), - )) + field_str, + External::Function(self.function_types.get(type_)), + )); } + self.builtin_offset = Some(programs.len() as u32 + imports.len() as u32); + + let (function_exports, function_bodies, funcs) = + self.emit_programs(programs, imports.len() as u32)?; + let mut sections = vec![]; sections.push(Section::Type(self.emit_type_section())); if let Some(import_section) = self.emit_import_section(imports) { @@ -119,8 +126,13 @@ impl Emitter { } } - fn emit_function_section(&self, funcs: Vec) -> FunctionSection { - FunctionSection::with_entries(funcs) + fn emit_function_section(&mut self, funcs: Vec) -> FunctionSection { + let mut entries = funcs.clone(); + for builtin in self.builtin_functions.keys() { + let type_idx = self.function_types.get(builtin.get_type()); + entries.push(Func::new(type_idx)); + } + FunctionSection::with_entries(entries) } fn emit_global_section(&self) -> Option { @@ -143,7 +155,12 @@ impl Emitter { } fn emit_code_section(&self, function_bodies: Vec) -> CodeSection { - CodeSection::with_bodies(function_bodies) + // TODO: Avoid this clone + let mut bodies = function_bodies.clone(); + for builtin in self.builtin_functions.keys() { + bodies.push(builtin.func_body()); + } + CodeSection::with_bodies(bodies) } fn emit_programs( @@ -216,7 +233,9 @@ impl Emitter { BinaryOperator::Add => Instruction::F64Add, BinaryOperator::Subtract => Instruction::F64Sub, BinaryOperator::Multiply => Instruction::F64Mul, - BinaryOperator::Divide => Instruction::F64Div, + BinaryOperator::Divide => { + Instruction::Call(self.resolve_builtin_function(BuiltinFunction::Div)) + } }; instructions.push(op); Ok(()) @@ -284,6 +303,14 @@ impl Emitter { self.globals.get((pool, name)) } + + fn resolve_builtin_function(&mut self, builtin: BuiltinFunction) -> u32 { + self.function_types.ensure(builtin.get_type()); + let offset = self + .builtin_offset + .expect("Tried to compute builtin index before setting offset."); + self.builtin_functions.get(builtin) + offset + } } fn variable_is_register(name: &str) -> bool { diff --git a/compiler-rs/src/index_store.rs b/compiler-rs/src/index_store.rs index 91ca716..e8f1143 100644 --- a/compiler-rs/src/index_store.rs +++ b/compiler-rs/src/index_store.rs @@ -24,6 +24,13 @@ impl IndexStore { } } + pub fn ensure(&mut self, key: T) { + let next = self.map.len() as u32; + if let Entry::Vacant(entry) = self.map.entry(key) { + entry.insert(next); + } + } + // TODO: Return iter? pub fn keys(&self) -> Vec<&T> { self.map.keys().collect() diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 9b88bc4..d7c1df9 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -1,4 +1,5 @@ mod ast; +mod builtin_functions; mod emitter; mod error; mod file_chars; diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 2cc1d53..d71cb6b 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -326,7 +326,7 @@ fn compatibility_tests() { 0.0, ), ("Loop limit", "g = 0; while(g = g + 1.0)", 1048576.0), - // ("Divide by zero", "g = 100 / 0", 0.0), + ("Divide by zero", "g = 100 / 0", 0.0), ( "Divide by less than epsilon", "g = 100 / 0.000001", @@ -519,7 +519,6 @@ fn compatibility_tests() { "% and ^ have the same precedence (% first)", "% and ^ have the same precedence (^ first)", "Loop limit", - "Divide by zero", "Divide by less than epsilon", ]; diff --git a/compiler-rs/tests/fixtures/wat/div.eel b/compiler-rs/tests/fixtures/wat/div.eel new file mode 100644 index 0000000..f2e4433 --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/div.eel @@ -0,0 +1 @@ +g = 100 / 0 \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/wat/div.snapshot b/compiler-rs/tests/fixtures/wat/div.snapshot new file mode 100644 index 0000000..7481fb4 --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/div.snapshot @@ -0,0 +1,27 @@ +g = 100 / 0 +======================================================================== +(module + (type (;0;) (func (param f64) (result f64))) + (type (;1;) (func (param f64 f64) (result f64))) + (type (;2;) (func (result f64))) + (import "shims" "sin" (func (;0;) (type 0))) + (func (;1;) (type 2) (result f64) + f64.const 0x1.9p+6 (;=100;) + f64.const 0x0p+0 (;=0;) + call 2 + global.set 0 + global.get 0) + (func (;2;) (type 1) (param f64 f64) (result f64) + (local i32) + local.get 1 + f64.const 0x0p+0 (;=0;) + f64.ne + if (result f64) ;; label = @1 + local.get 0 + local.get 1 + f64.div + else + f64.const 0x0p+0 (;=0;) + end) + (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) + (export "test" (func 1))) diff --git a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot index 6b8c32e..ea0a6b7 100644 --- a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot +++ b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot @@ -1,10 +1,10 @@ 1+1 ======================================================================== (module - (type (;0;) (func (result f64))) - (type (;1;) (func (param f64) (result f64))) - (import "shims" "sin" (func (;0;) (type 1))) - (func (;1;) (type 0) (result f64) + (type (;0;) (func (param f64) (result f64))) + (type (;1;) (func (result f64))) + (import "shims" "sin" (func (;0;) (type 0))) + (func (;1;) (type 1) (result f64) f64.const 0x1p+0 (;=1;) f64.const 0x1p+0 (;=1;) f64.add) diff --git a/compiler-rs/tests/fixtures/wat/reg.snapshot b/compiler-rs/tests/fixtures/wat/reg.snapshot index d31da34..3708b12 100644 --- a/compiler-rs/tests/fixtures/wat/reg.snapshot +++ b/compiler-rs/tests/fixtures/wat/reg.snapshot @@ -1,10 +1,10 @@ reg00=10 ======================================================================== (module - (type (;0;) (func (result f64))) - (type (;1;) (func (param f64) (result f64))) - (import "shims" "sin" (func (;0;) (type 1))) - (func (;1;) (type 0) (result f64) + (type (;0;) (func (param f64) (result f64))) + (type (;1;) (func (result f64))) + (import "shims" "sin" (func (;0;) (type 0))) + (func (;1;) (type 1) (result f64) f64.const 0x1.4p+3 (;=10;) global.set 0 global.get 0) diff --git a/compiler-rs/tests/fixtures/wat/sin.snapshot b/compiler-rs/tests/fixtures/wat/sin.snapshot index 325090a..09dcd2b 100644 --- a/compiler-rs/tests/fixtures/wat/sin.snapshot +++ b/compiler-rs/tests/fixtures/wat/sin.snapshot @@ -1,10 +1,10 @@ sin(100) ======================================================================== (module - (type (;0;) (func (result f64))) - (type (;1;) (func (param f64) (result f64))) - (import "shims" "sin" (func (;0;) (type 1))) - (func (;1;) (type 0) (result f64) + (type (;0;) (func (param f64) (result f64))) + (type (;1;) (func (result f64))) + (import "shims" "sin" (func (;0;) (type 0))) + (func (;1;) (type 1) (result f64) f64.const 0x1.9p+6 (;=100;) call 0) (export "test" (func 1))) diff --git a/compiler-rs/tests/intagration_test.rs b/compiler-rs/tests/integration_test.rs similarity index 100% rename from compiler-rs/tests/intagration_test.rs rename to compiler-rs/tests/integration_test.rs From d24fef127defce5ca25e93c0a8005440d5fa41c5 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 34/85] Improve naming --- compiler-rs/src/ast.rs | 2 +- compiler-rs/src/emitter.rs | 20 +++++++++---------- compiler-rs/src/lib.rs | 6 +++--- compiler-rs/src/parser.rs | 14 ++++++------- .../tests/fixtures/ast/one_plus_one.snapshot | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index ad78a10..71052d7 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -1,7 +1,7 @@ use crate::span::Span; #[derive(Debug, PartialEq)] -pub struct Program { +pub struct EelFunction { pub expressions: Vec, } diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 72f53e4..d9af7b7 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use crate::{ - ast::{Assignment, BinaryExpression, BinaryOperator, Expression, FunctionCall, Program}, + ast::{Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, FunctionCall}, builtin_functions::BuiltinFunction, error::CompilerError, index_store::IndexStore, @@ -18,11 +18,11 @@ use parity_wasm::elements::{ type EmitterResult = Result; pub fn emit( - programs: Vec<(String, Program, String)>, + eel_functions: Vec<(String, EelFunction, String)>, globals_map: HashMap>, ) -> EmitterResult> { let mut emitter = Emitter::new(); - emitter.emit(programs, globals_map) + emitter.emit(eel_functions, globals_map) } struct Emitter { @@ -47,7 +47,7 @@ impl Emitter { } fn emit( &mut self, - programs: Vec<(String, Program, String)>, + eel_functions: Vec<(String, EelFunction, String)>, globals_map: HashMap>, // HahsMap> ) -> EmitterResult> { let mut imports = Vec::new(); @@ -73,10 +73,10 @@ impl Emitter { )); } - self.builtin_offset = Some(programs.len() as u32 + imports.len() as u32); + self.builtin_offset = Some(eel_functions.len() as u32 + imports.len() as u32); let (function_exports, function_bodies, funcs) = - self.emit_programs(programs, imports.len() as u32)?; + self.emit_eel_functions(eel_functions, imports.len() as u32)?; let mut sections = vec![]; sections.push(Section::Type(self.emit_type_section())); @@ -163,15 +163,15 @@ impl Emitter { CodeSection::with_bodies(bodies) } - fn emit_programs( + fn emit_eel_functions( &mut self, - programs: Vec<(String, Program, String)>, + eel_functions: Vec<(String, EelFunction, String)>, offset: u32, ) -> EmitterResult<(Vec, Vec, Vec)> { let mut exports = Vec::new(); let mut function_bodies = Vec::new(); let mut function_definitions = Vec::new(); - for (i, (name, program, pool_name)) in programs.into_iter().enumerate() { + for (i, (name, program, pool_name)) in eel_functions.into_iter().enumerate() { self.current_pool = pool_name; exports.push(ExportEntry::new( name, @@ -188,7 +188,7 @@ impl Emitter { Ok((exports, function_bodies, function_definitions)) } - fn emit_program(&mut self, program: Program) -> EmitterResult { + fn emit_program(&mut self, program: EelFunction) -> EmitterResult { let mut instructions: Vec = Vec::new(); for expression in program.expressions { self.emit_expression(expression, &mut instructions)?; diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index d7c1df9..cfd3792 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -12,7 +12,7 @@ mod tokens; use std::collections::{HashMap, HashSet}; -use ast::Program; +use ast::EelFunction; use emitter::emit; use error::CompilerError; // Only exported for tests @@ -44,12 +44,12 @@ pub fn compile( sources: Vec<(String, &str, String)>, globals: HashMap>, ) -> Result, CompilerError> { - let programs: Result, CompilerError> = sources + let eel_functions: Result, CompilerError> = sources .into_iter() .map(|(name, source, pool)| { let program = parse(&source)?; Ok((name, program, pool)) }) .collect(); - emit(programs?, globals) + emit(eel_functions?, globals) } diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 8a0378d..35cee74 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -2,7 +2,7 @@ use crate::ast::{ Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, FunctionCall, Identifier, }; -use super::ast::{Expression, NumberLiteral, Program}; +use super::ast::{EelFunction, Expression, NumberLiteral}; use super::error::CompilerError; use super::lexer::Lexer; use super::span::Span; @@ -20,7 +20,7 @@ struct Parser<'a> { type ParseResult = Result; -pub fn parse(src: &str) -> ParseResult { +pub fn parse(src: &str) -> ParseResult { let mut parser = Parser::new(&src); parser.parse() } @@ -54,15 +54,15 @@ impl<'a> Parser<'a> { } } - pub fn parse(&mut self) -> ParseResult { + pub fn parse(&mut self) -> ParseResult { self.expect_kind(TokenKind::SOF)?; let program = self.parse_program()?; self.expect_kind(TokenKind::EOF)?; Ok(program) } - pub fn parse_program(&mut self) -> ParseResult { - Ok(Program { + pub fn parse_program(&mut self) -> ParseResult { + Ok(EelFunction { expressions: self.parse_expression_block()?, }) } @@ -269,7 +269,7 @@ fn right_associative(precedence: u8) -> u8 { fn can_parse_integer() { assert_eq!( Parser::new("1").parse(), - Ok(Program { + Ok(EelFunction { expressions: vec![Expression::NumberLiteral(NumberLiteral { value: 1.0 })] }) ); @@ -279,7 +279,7 @@ fn can_parse_integer() { fn can_parse_integer_2() { assert_eq!( Parser::new("2").parse(), - Ok(Program { + Ok(EelFunction { expressions: vec![Expression::NumberLiteral(NumberLiteral { value: 2.0 })] }) ); diff --git a/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot b/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot index 13dc741..93e67a7 100644 --- a/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot +++ b/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot @@ -1,6 +1,6 @@ 1+1 ======================================================================== -Program { +EelFunction { expressions: [ BinaryExpression( BinaryExpression { From bf0949baf5824ba21ef4a03e0364745de8bf50d0 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 35/85] Move variable access to prefix parsing --- compiler-rs/src/parser.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 35cee74..672f546 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -77,6 +77,15 @@ impl<'a> Parser<'a> { } fn peek_expression(&self) -> bool { + self.peek_prefix() + } + + fn parse_expression(&mut self, precedence: u8) -> ParseResult { + let left = self.parse_prefix()?; + self.maybe_parse_infix(left, precedence) + } + + fn peek_prefix(&self) -> bool { let token = self.peek(); match token.kind { TokenKind::Int => true, @@ -85,29 +94,14 @@ impl<'a> Parser<'a> { } } - fn parse_expression(&mut self, precedence: u8) -> ParseResult { - let token = self.peek(); - match &token.kind { - // TODO: Handle unary - TokenKind::Int => { - let left = self.parse_prefix()?; - self.maybe_parse_infix(left, precedence) - } - TokenKind::Identifier => self.parse_identifier_expression(), - kind => Err(CompilerError::new( - format!("Expected Int or Identifier but got {:?}", kind), - token.span, - )), - } - } - fn parse_prefix(&mut self) -> ParseResult { match self.token.kind { TokenKind::Int => Ok(Expression::NumberLiteral(self.parse_int()?)), // TokenKind::OpenParen => self.parse_parenthesized_expression(), // Once we have other prefix operators: `+-!` they will go here. + TokenKind::Identifier => self.parse_identifier_expression(), _ => Err(CompilerError::new( - format!("Expected Int but got {:?}", self.token.kind), + format!("Expected Int or Identifier but got {:?}", self.token.kind), self.token.span, )), } From 74e161201d8753ff893bd7af49a468ac98fcd287 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 36/85] Unary expressions --- compiler-rs/src/ast.rs | 14 ++++++++++++ compiler-rs/src/emitter.rs | 44 ++++++++++++++++++++++++++++++++++---- compiler-rs/src/parser.rs | 22 +++++++++++++++++++ compiler-rs/src/tokens.rs | 1 + 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 71052d7..86397ed 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -8,11 +8,18 @@ pub struct EelFunction { #[derive(Debug, PartialEq)] pub enum Expression { BinaryExpression(BinaryExpression), + UnaryExpression(UnaryExpression), NumberLiteral(NumberLiteral), Assignment(Assignment), FunctionCall(FunctionCall), } +#[derive(Debug, PartialEq)] +pub struct UnaryExpression { + pub right: Box, + pub op: UnaryOperator, +} + #[derive(Debug, PartialEq)] pub struct BinaryExpression { pub left: Box, @@ -33,6 +40,13 @@ pub enum BinaryOperator { Divide, } +#[derive(Debug, PartialEq)] +pub enum UnaryOperator { + Plus, + Minus, + Not, +} + #[derive(Debug, PartialEq)] pub struct Identifier { pub name: String, diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index d9af7b7..b1233d7 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,7 +1,10 @@ use std::collections::{HashMap, HashSet}; use crate::{ - ast::{Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, FunctionCall}, + ast::{ + Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, FunctionCall, + UnaryExpression, UnaryOperator, + }, builtin_functions::BuiltinFunction, error::CompilerError, index_store::IndexStore, @@ -17,6 +20,8 @@ use parity_wasm::elements::{ type EmitterResult = Result; +static EPSILON: f64 = 0.00001; + pub fn emit( eel_functions: Vec<(String, EelFunction, String)>, globals_map: HashMap>, @@ -204,6 +209,9 @@ impl Emitter { instructions: &mut Vec, ) -> EmitterResult<()> { match expression { + Expression::UnaryExpression(unary_expression) => { + self.emit_unary_expression(unary_expression, instructions) + } Expression::BinaryExpression(binary_expression) => { self.emit_binary_expression(binary_expression, instructions) } @@ -211,9 +219,7 @@ impl Emitter { self.emit_assignment(assignment_expression, instructions) } Expression::NumberLiteral(number_literal) => { - instructions.push(Instruction::F64Const(u64::from_le_bytes( - number_literal.value.to_le_bytes(), - ))); + instructions.push(Instruction::F64Const(f64_const(number_literal.value))); Ok(()) } Expression::FunctionCall(function_call) => { @@ -222,6 +228,31 @@ impl Emitter { } } + fn emit_unary_expression( + &mut self, + unary_expression: UnaryExpression, + instructions: &mut Vec, + ) -> EmitterResult<()> { + match unary_expression.op { + UnaryOperator::Plus => self.emit_expression(*unary_expression.right, instructions), + UnaryOperator::Minus => { + self.emit_expression(*unary_expression.right, instructions)?; + instructions.push(Instruction::F64Neg); + Ok(()) + } + UnaryOperator::Not => { + self.emit_expression(*unary_expression.right, instructions)?; + instructions.extend(vec![ + Instruction::F32Abs, + Instruction::F64Const(f64_const(EPSILON)), + Instruction::F64Lt, + ]); + instructions.push(Instruction::F64ConvertSI32); + Ok(()) + } + } + } + fn emit_binary_expression( &mut self, binary_expression: BinaryExpression, @@ -313,6 +344,11 @@ impl Emitter { } } +// 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?) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 672f546..827c32e 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -1,5 +1,6 @@ use crate::ast::{ Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, FunctionCall, Identifier, + UnaryExpression, UnaryOperator, }; use super::ast::{EelFunction, Expression, NumberLiteral}; @@ -97,6 +98,27 @@ impl<'a> Parser<'a> { fn parse_prefix(&mut self) -> ParseResult { match self.token.kind { TokenKind::Int => Ok(Expression::NumberLiteral(self.parse_int()?)), + TokenKind::Plus => { + self.advance()?; + Ok(Expression::UnaryExpression(UnaryExpression { + right: Box::new(self.parse_expression(0)?), + op: UnaryOperator::Plus, + })) + } + TokenKind::Minus => { + self.advance()?; + Ok(Expression::UnaryExpression(UnaryExpression { + right: Box::new(self.parse_expression(0)?), + op: UnaryOperator::Minus, + })) + } + TokenKind::Bang => { + self.advance()?; + Ok(Expression::UnaryExpression(UnaryExpression { + right: Box::new(self.parse_expression(0)?), + op: UnaryOperator::Not, + })) + } // TokenKind::OpenParen => self.parse_parenthesized_expression(), // Once we have other prefix operators: `+-!` they will go here. TokenKind::Identifier => self.parse_identifier_expression(), diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 9413fe9..023456c 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -5,6 +5,7 @@ pub enum TokenKind { Int, Plus, Minus, + Bang, Asterisk, Slash, Equal, From 94680e209123e73af99fce97cf8fe9e504984244 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 37/85] Read global g in tests --- compiler-rs/src/emitter.rs | 10 ++++---- compiler-rs/tests/common/mod.rs | 31 ++++++++++++++++++------- compiler-rs/tests/compatibility_test.rs | 11 +++++---- compiler-rs/tests/integration_test.rs | 25 ++++++++++++-------- 4 files changed, 48 insertions(+), 29 deletions(-) diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index b1233d7..933be98 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -67,6 +67,7 @@ impl Emitter { } let shims: Vec = vec![Shim::Sin]; + let shim_offset = shims.len() as u32; for shim in shims { let field_str = shim.as_str().to_string(); let type_ = shim.get_type(); @@ -78,10 +79,10 @@ impl Emitter { )); } - self.builtin_offset = Some(eel_functions.len() as u32 + imports.len() as u32); + self.builtin_offset = Some(eel_functions.len() as u32 + shim_offset); let (function_exports, function_bodies, funcs) = - self.emit_eel_functions(eel_functions, imports.len() as u32)?; + self.emit_eel_functions(eel_functions, shim_offset)?; let mut sections = vec![]; sections.push(Section::Type(self.emit_type_section())); @@ -359,10 +360,7 @@ fn make_empty_global() -> GlobalEntry { GlobalEntry::new( GlobalType::new(ValueType::F64, true), InitExpr::new(vec![ - Instruction::F64Const( - // TODO: Get the correct bits here - 0, - ), + Instruction::F64Const(f64_const(0.0)), Instruction::End, ]), ) diff --git a/compiler-rs/tests/common/mod.rs b/compiler-rs/tests/common/mod.rs index 7f15507..91f0156 100644 --- a/compiler-rs/tests/common/mod.rs +++ b/compiler-rs/tests/common/mod.rs @@ -10,9 +10,16 @@ use wasmi::{ }; use wasmi::{ModuleInstance, RuntimeValue}; -pub struct GlobalPool {} +pub struct GlobalPool { + g: GlobalRef, +} impl GlobalPool { + pub fn new() -> Self { + Self { + g: GlobalInstance::alloc(RuntimeValue::F64(0.0.into()), true), + } + } fn check_signature(&self, index: usize, signature: &Signature) -> bool { let (params, ret_ty): (&[ValueType], Option) = match index { SIN_FUNC_INDEX => (&[ValueType::F64], Some(ValueType::F64)), @@ -25,10 +32,13 @@ impl GlobalPool { impl ModuleImportResolver for GlobalPool { fn resolve_global( &self, - _field_name: &str, + field_name: &str, _global_type: &GlobalDescriptor, ) -> Result { - let global = GlobalInstance::alloc(RuntimeValue::F64(F64::from_float(0.0)), true); + let global = match field_name { + "g" => self.g.clone(), + _ => GlobalInstance::alloc(RuntimeValue::F64(F64::from_float(0.0)), true), + }; Ok(global) } fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { @@ -86,10 +96,13 @@ pub fn eval_eel( let wasm_binary = compile(sources, globals_map.clone()) .map_err(|err| format!("Compiler Error: {:?}", err))?; - let module = wasmi::Module::from_buffer(&wasm_binary) - .map_err(|err| format!("Error parsing binary Wasm: {}", err))?; + let module = wasmi::Module::from_buffer(&wasm_binary).map_err(|err| { + // TODO: Print out the wat? + println!("Wat: {}", wasmprinter::print_bytes(&wasm_binary).unwrap()); + format!("Error parsing binary Wasm: {}", err) + })?; - let mut global_imports = GlobalPool {}; + let mut global_imports = GlobalPool::new(); let mut imports = ImportsBuilder::default(); for (pool, _) in globals_map { @@ -104,12 +117,14 @@ pub fn eval_eel( // TODO: Instead of returning return value, return value of globals match instance.invoke_export(function_to_run, &[], &mut global_imports) { - Ok(Some(RuntimeValue::F64(val))) => Ok(val.into()), + Ok(Some(RuntimeValue::F64(_val))) => Ok(()), Ok(Some(val)) => Err(format!("Unexpected return type: {:?}", val)), Ok(None) => Err("No Result".to_string()), Err(err) => Err(format!( "Error invoking exported function {}: {}", function_to_run, err )), - } + }?; + let g = global_imports.g.get().try_into::().unwrap(); + Ok(g.into()) } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index d71cb6b..9655c80 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use common::eval_eel; @@ -7,9 +7,6 @@ mod common; #[test] fn compatibility_tests() { let test_cases: &[(&'static str, &'static str, f64)] = &[ - ("[REMOVE] Integer", "1", 1.0), - ("[REMOVE] Assignment", "g=1", 1.0), - ("[REMOVE] Call", "int(4)", 4.0), ("Expressions", "g = ((6- -7.0)+ 3.0);", 16.0), ("Number", "g = 5;", 5.0), ("Number with decimal", "g = 5.5;", 5.5), @@ -523,9 +520,13 @@ fn compatibility_tests() { ]; for (name, code, expected) in test_cases { + let mut globals = HashMap::default(); + let mut pool_globals = HashSet::new(); + pool_globals.insert("g".to_string()); + globals.insert("pool".to_string(), pool_globals); match eval_eel( vec![("test".to_string(), code, "pool".to_string())], - HashMap::new(), + globals, "test", ) { Ok(actual) => { diff --git a/compiler-rs/tests/integration_test.rs b/compiler-rs/tests/integration_test.rs index a17463f..60ab2e2 100644 --- a/compiler-rs/tests/integration_test.rs +++ b/compiler-rs/tests/integration_test.rs @@ -1,16 +1,21 @@ extern crate eel_wasm; mod common; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use common::{eval_eel, GlobalPool}; use eel_wasm::compile; use wasmi::{ImportsBuilder, ModuleInstance}; fn test_run(program: &str, expected_output: f64) { + let mut globals = HashMap::default(); + let mut pool_globals = HashSet::new(); + pool_globals.insert("g".to_string()); + globals.insert("pool".to_string(), pool_globals); + let result = eval_eel( vec![("test".to_string(), program, "pool".to_string())], - HashMap::default(), + globals, "test", ); assert_eq!(result, Ok(expected_output)); @@ -18,13 +23,13 @@ fn test_run(program: &str, expected_output: f64) { #[test] fn execute_one() { - test_run("1", 1.0); - test_run("1+1", 2.0); - test_run("1-1", 0.0); - test_run("2*2", 4.0); - test_run("2/2", 1.0); + test_run("g=1", 1.0); + test_run("g=1+1", 2.0); + test_run("g=1-1", 0.0); + test_run("g=2*2", 4.0); + test_run("g=2/2", 1.0); - test_run("1+1*2", 3.0); + test_run("g=1+1*2", 3.0); } #[test] @@ -34,7 +39,7 @@ fn with_global() { #[test] fn with_shims() { - test_run("sin(10)", 10.0_f64.sin()); + test_run("g=sin(10)", 10.0_f64.sin()); } #[test] @@ -53,7 +58,7 @@ fn multiple_functions() { // 0.3.1 has the PR, but wasmi has not shipped a new version that includes it. // parity-wasm already depends upon 0.3.1 (I _think_) let module = wasmi::Module::from_buffer(&wasm_binary).expect("No validation errors"); - let mut global_imports = GlobalPool {}; + let mut global_imports = GlobalPool::new(); let mut imports = ImportsBuilder::default(); imports.push_resolver("pool", &global_imports); imports.push_resolver("shims", &global_imports); From 668b07a6bd694dcc5a4dd00f3927ea634ca36f28 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 38/85] Add basic semicolon support --- compiler-rs/src/lexer.rs | 1 + compiler-rs/src/parser.rs | 5 ++++- compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/compatibility_test.rs | 17 ++++------------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 43a1b9a..3517112 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -39,6 +39,7 @@ impl<'a> Lexer<'a> { '(' => self.read_char_as_kind(TokenKind::OpenParen), ')' => self.read_char_as_kind(TokenKind::CloseParen), ',' => self.read_char_as_kind(TokenKind::Comma), + ';' => self.read_char_as_kind(TokenKind::Semi), c if is_whitepsace(c) => { self.chars.eat_while(is_whitepsace); return self.next_token(); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 827c32e..cad59bf 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -72,7 +72,10 @@ impl<'a> Parser<'a> { let mut expressions = vec![]; while self.peek_expression() { expressions.push(self.parse_expression(0)?); - // TODO: Eat a semicolon? + // TODO: This is probably not quite right. We should requie semis between expressions. + while self.peek().kind == TokenKind::Semi { + self.advance()?; + } } Ok(expressions) } diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 023456c..855cca6 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -13,6 +13,7 @@ pub enum TokenKind { OpenParen, CloseParen, Comma, + Semi, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 9655c80..bd88ed6 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -29,7 +29,8 @@ fn compatibility_tests() { ("To the power", "g = 5 ^ 2;", 25.0), ("Order of operations (+ and *)", "g = 1 + 1 * 10;", 11.0), ("Order of operations (+ and /)", "g = 1 + 1 / 10;", 1.1), - ("Order of operations (unary - and +)", "g = -1 + 1;", 0.0), + // TODO: Fix this + // ("Order of operations (unary - and +)", "g = -1 + 1;", 0.0), ("Parens", "g = (1 + 1.0) * 10;", 20.0), ("Absolute value negative", "g = abs(-10);", 10.0), ("Absolute value positive", "g = abs(10);", 10.0), @@ -42,7 +43,7 @@ fn compatibility_tests() { ("Sqrt (negative)", "g = sqrt(-4);", 2.0), ("Sqr", "g = sqr(10);", 100.0), ("Int", "g = int(4.5);", 4.0), - ("Sin", "g = sin(10);", 10.0_f64.cos()), + ("Sin", "g = sin(10);", 10.0_f64.sin()), ("Cos", "g = cos(10);", 10.0_f64.cos()), ("Tan", "g = tan(10);", 10.0_f64.tan()), ("Asin", "g = asin(0.5);", 0.5_f64.asin()), @@ -225,7 +226,7 @@ fn compatibility_tests() { "megabuf(0) = 1.2; g = gmegabuf(0);", 0.0, ), - ("Case insensitive vars", "G = 10;", 10.0), + // ("Case insensitive vars", "G = 10;", 10.0), ("Case insensitive funcs", "g = InT(10);", 10.0), ("Consecutive semis", "g = 10;;; ;g = 20;;", 20.0), ("Equality (< epsilon)", "g = 0.000009 == 0;", 1.0), @@ -333,27 +334,20 @@ fn compatibility_tests() { let expected_failing: Vec<&str> = vec![ "Expressions", - "Number", "Number with decimal", "Number with decimal and no leading whole", "Number with decimal and no trailing dec", "Number with no digits", "Optional final semi", - "Unary negeation", - "Unary plus", "Unary not true", "Unary not false", "Unary not 0.1", "Unary not < epsilon", - "Multiply", - "Divide", "Mod", "Mod zero", "Bitwise and", "Bitwise or", "To the power", - "Order of operations (+ and *)", - "Order of operations (+ and /)", "Order of operations (unary - and +)", "Parens", "Absolute value negative", @@ -367,14 +361,12 @@ fn compatibility_tests() { "Sqrt (negative)", "Sqr", "Int", - "Sin", "Cos", "Tan", "Asin", "Acos", "Atan", "Atan2", - "Assign to globals", "Read globals", "Multiple statements", "Multiple statements expression", @@ -399,7 +391,6 @@ fn compatibility_tests() { "Sign (0)", "Sign (-0)", "Local variables", - "Local variable assignment (implicit return)", "Bor (true, false)", "Bor (false, true)", "Bor (true, true)", From d94ddca28bf2e524d25f09aec6ee1cdcdb482b35 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 39/85] Spelling --- compiler-rs/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index cad59bf..40b6372 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -72,7 +72,7 @@ impl<'a> Parser<'a> { let mut expressions = vec![]; while self.peek_expression() { expressions.push(self.parse_expression(0)?); - // TODO: This is probably not quite right. We should requie semis between expressions. + // TODO: This is probably not quite right. We should require semis between expressions. while self.peek().kind == TokenKind::Semi { self.advance()?; } From ca2b2510973005071711ec4823088526bf8d96f4 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 40/85] Fix unary precedence --- compiler-rs/src/parser.rs | 7 ++++--- compiler-rs/tests/compatibility_test.rs | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 40b6372..296367b 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -13,6 +13,7 @@ static SUM_PRECEDENCE: u8 = 1; static DIFFERENCE_PRECEDENCE: u8 = 1; static PRODUCT_PRECEDENCE: u8 = 2; static QUOTIENT_PRECEDENCE: u8 = 2; +static PREFIX_PRECEDENCE: u8 = 6; struct Parser<'a> { lexer: Lexer<'a>, @@ -104,21 +105,21 @@ impl<'a> Parser<'a> { TokenKind::Plus => { self.advance()?; Ok(Expression::UnaryExpression(UnaryExpression { - right: Box::new(self.parse_expression(0)?), + right: Box::new(self.parse_expression(PREFIX_PRECEDENCE)?), op: UnaryOperator::Plus, })) } TokenKind::Minus => { self.advance()?; Ok(Expression::UnaryExpression(UnaryExpression { - right: Box::new(self.parse_expression(0)?), + right: Box::new(self.parse_expression(PREFIX_PRECEDENCE)?), op: UnaryOperator::Minus, })) } TokenKind::Bang => { self.advance()?; Ok(Expression::UnaryExpression(UnaryExpression { - right: Box::new(self.parse_expression(0)?), + right: Box::new(self.parse_expression(PREFIX_PRECEDENCE)?), op: UnaryOperator::Not, })) } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index bd88ed6..bec1682 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -29,8 +29,7 @@ fn compatibility_tests() { ("To the power", "g = 5 ^ 2;", 25.0), ("Order of operations (+ and *)", "g = 1 + 1 * 10;", 11.0), ("Order of operations (+ and /)", "g = 1 + 1 / 10;", 1.1), - // TODO: Fix this - // ("Order of operations (unary - and +)", "g = -1 + 1;", 0.0), + ("Order of operations (unary - and +)", "g = -1 + 1;", 0.0), ("Parens", "g = (1 + 1.0) * 10;", 20.0), ("Absolute value negative", "g = abs(-10);", 10.0), ("Absolute value positive", "g = abs(10);", 10.0), @@ -348,7 +347,6 @@ fn compatibility_tests() { "Bitwise and", "Bitwise or", "To the power", - "Order of operations (unary - and +)", "Parens", "Absolute value negative", "Absolute value positive", From d9b67f910334d6489983c0388729ff4889fa9d72 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 41/85] Make variables case insensitive --- compiler-rs/src/parser.rs | 2 +- compiler-rs/tests/compatibility_test.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 296367b..1044984 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -218,7 +218,7 @@ impl<'a> Parser<'a> { let span = self.token.span; self.expect_kind(TokenKind::Identifier)?; Ok(Identifier { - name: self.lexer.source(span).to_string(), + name: self.lexer.source(span).to_lowercase(), span, }) } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index bec1682..7d15010 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -225,7 +225,7 @@ fn compatibility_tests() { "megabuf(0) = 1.2; g = gmegabuf(0);", 0.0, ), - // ("Case insensitive vars", "G = 10;", 10.0), + ("Case insensitive vars", "G = 10;", 10.0), ("Case insensitive funcs", "g = InT(10);", 10.0), ("Consecutive semis", "g = 10;;; ;g = 20;;", 20.0), ("Equality (< epsilon)", "g = 0.000009 == 0;", 1.0), @@ -451,7 +451,6 @@ fn compatibility_tests() { "Less than or equal (false)", "Greater than or equal (true)", "Greater than or equal (false)", - // "Script without trailing semi", "Megabuf access", "Max index megabuf", "Max index + 1 megabuf", @@ -463,8 +462,6 @@ fn compatibility_tests() { "Gmegabuf", "Megabuf != Gmegabuf", "Gmegabuf != Megabuf", - "Case insensitive vars", - "Case insensitive funcs", "Consecutive semis", "Equality (< epsilon)", "Equality (< -epsilon)", From e91ffd3e7d159ab1d786000e5cefc275298a14a5 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 42/85] Fix peek_prefix --- compiler-rs/src/parser.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 1044984..8799c19 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -94,6 +94,9 @@ impl<'a> Parser<'a> { let token = self.peek(); match token.kind { TokenKind::Int => true, + TokenKind::Plus => true, + TokenKind::Minus => true, + TokenKind::Bang => true, TokenKind::Identifier => true, _ => false, } From 80b7a7592b66f3de3ea3a586b323c626a806a16f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 43/85] Decimals and parens --- compiler-rs/src/lexer.rs | 15 +++++++++++---- compiler-rs/src/parser.rs | 7 +++++++ compiler-rs/tests/compatibility_test.rs | 7 ------- compiler-rs/tests/snapshots_test.rs | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 3517112..1968044 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -29,7 +29,8 @@ impl<'a> Lexer<'a> { pub fn next_token(&mut self) -> LexerResult { let start = self.chars.pos; let kind = match self.chars.peek() { - c if is_int(c) => self.read_int(), + c if is_int(c) => self.read_number(), + '.' => self.read_number(), c if is_identifier_head(c) => self.read_identifier(), '+' => self.read_char_as_kind(TokenKind::Plus), '-' => self.read_char_as_kind(TokenKind::Minus), @@ -61,9 +62,15 @@ impl<'a> Lexer<'a> { kind } - fn read_int(&mut self) -> TokenKind { - self.chars.next(); - self.chars.eat_while(is_int); + fn read_number(&mut self) -> TokenKind { + if is_int(self.chars.peek()) { + self.chars.next(); + self.chars.eat_while(is_int); + } + if self.chars.peek() == '.' { + self.chars.next(); + self.chars.eat_while(is_int); + } TokenKind::Int } diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 8799c19..4c5c4d9 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -93,6 +93,7 @@ impl<'a> Parser<'a> { fn peek_prefix(&self) -> bool { let token = self.peek(); match token.kind { + TokenKind::OpenParen => true, TokenKind::Int => true, TokenKind::Plus => true, TokenKind::Minus => true, @@ -104,6 +105,12 @@ impl<'a> Parser<'a> { fn parse_prefix(&mut self) -> ParseResult { match self.token.kind { + TokenKind::OpenParen => { + self.advance()?; + let expression = self.parse_expression(0)?; + self.expect_kind(TokenKind::CloseParen)?; + Ok(expression) + } TokenKind::Int => Ok(Expression::NumberLiteral(self.parse_int()?)), TokenKind::Plus => { self.advance()?; diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 7d15010..f5d6c68 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -332,10 +332,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Expressions", - "Number with decimal", - "Number with decimal and no leading whole", - "Number with decimal and no trailing dec", "Number with no digits", "Optional final semi", "Unary not true", @@ -347,7 +343,6 @@ fn compatibility_tests() { "Bitwise and", "Bitwise or", "To the power", - "Parens", "Absolute value negative", "Absolute value positive", "Function used as expression", @@ -358,7 +353,6 @@ fn compatibility_tests() { "Sqrt", "Sqrt (negative)", "Sqr", - "Int", "Cos", "Tan", "Asin", @@ -502,7 +496,6 @@ fn compatibility_tests() { "% and ^ have the same precedence (% first)", "% and ^ have the same precedence (^ first)", "Loop limit", - "Divide by less than epsilon", ]; for (name, code, expected) in test_cases { diff --git a/compiler-rs/tests/snapshots_test.rs b/compiler-rs/tests/snapshots_test.rs index 03a3bd0..2a769ff 100644 --- a/compiler-rs/tests/snapshots_test.rs +++ b/compiler-rs/tests/snapshots_test.rs @@ -62,7 +62,7 @@ where assert_eq!( actual_invalid, expected_invalid, "Expected file \"{}\" to be {} but it was {}", - source_path, actual_str, expected_str + source_path, expected_str, actual_str, ); } From a57659b6aa2909fb7a0eabc87b9503537b3b35b8 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 44/85] Support numbers on either side of decimal point --- compiler-rs/src/parser.rs | 13 +++++++++++-- compiler-rs/tests/compatibility_test.rs | 1 - 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 4c5c4d9..9ad34c0 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -1,3 +1,5 @@ +use std::num::ParseFloatError; + use crate::ast::{ Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, FunctionCall, Identifier, UnaryExpression, UnaryOperator, @@ -205,10 +207,9 @@ impl<'a> Parser<'a> { fn parse_int(&mut self) -> ParseResult { if let TokenKind::Int = self.token.kind { let value = self.lexer.source(self.token.span); - match value.parse::() { + match parse_number(value) { Ok(value) => { self.advance()?; - // TODO: This is not quite right Ok(NumberLiteral { value }) } Err(_) => Err(CompilerError::new( @@ -283,6 +284,14 @@ impl<'a> Parser<'a> { } } +fn parse_number(raw: &str) -> Result { + if raw.starts_with('.') { + format!("0{}", raw).parse::() + } else { + raw.parse::() + } +} + #[inline] #[allow(dead_code)] // Save this for when we need it. fn left_associative(precedence: u8) -> u8 { diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index f5d6c68..0960d63 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -332,7 +332,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Number with no digits", "Optional final semi", "Unary not true", "Unary not false", From cebbda58cd174849f1b096cbc1fb6ecafbc14618 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 45/85] Fix unary ! --- compiler-rs/src/emitter.rs | 2 +- compiler-rs/src/lexer.rs | 1 + compiler-rs/tests/compatibility_test.rs | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 933be98..7cd22d3 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -244,7 +244,7 @@ impl Emitter { UnaryOperator::Not => { self.emit_expression(*unary_expression.right, instructions)?; instructions.extend(vec![ - Instruction::F32Abs, + Instruction::F64Abs, Instruction::F64Const(f64_const(EPSILON)), Instruction::F64Lt, ]); diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 1968044..69e722a 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -41,6 +41,7 @@ impl<'a> Lexer<'a> { ')' => self.read_char_as_kind(TokenKind::CloseParen), ',' => self.read_char_as_kind(TokenKind::Comma), ';' => self.read_char_as_kind(TokenKind::Semi), + '!' => self.read_char_as_kind(TokenKind::Bang), c if is_whitepsace(c) => { self.chars.eat_while(is_whitepsace); return self.next_token(); diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 0960d63..4edc741 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -333,10 +333,6 @@ fn compatibility_tests() { let expected_failing: Vec<&str> = vec![ "Optional final semi", - "Unary not true", - "Unary not false", - "Unary not 0.1", - "Unary not < epsilon", "Mod", "Mod zero", "Bitwise and", From 892bd3b39cf8236db34cec8f8f996717d3bbb78d Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 46/85] Commets and lexer cleanup --- compiler-rs/src/file_chars.rs | 16 +++----- compiler-rs/src/lexer.rs | 53 +++++++++++++++++++++++-- compiler-rs/tests/compatibility_test.rs | 3 +- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/compiler-rs/src/file_chars.rs b/compiler-rs/src/file_chars.rs index 327a18b..28bfe18 100644 --- a/compiler-rs/src/file_chars.rs +++ b/compiler-rs/src/file_chars.rs @@ -5,37 +5,33 @@ pub const NULL: char = '\0'; pub struct FileChars<'a> { chars: Chars<'a>, - next_char: char, + pub next: char, pub pos: u32, } impl<'a> FileChars<'a> { pub fn new(source: &'a str) -> Self { let mut chars = source.chars(); - let next_char = chars.next().unwrap_or(NULL); + let next = chars.next().unwrap_or(NULL); FileChars { chars, - next_char, + next, pos: 0, } } pub fn next(&mut self) -> char { - let c = self.next_char; + let c = self.next; self.pos += c.len_utf8() as u32; - mem::replace(&mut self.next_char, self.chars.next().unwrap_or(NULL)) + mem::replace(&mut self.next, self.chars.next().unwrap_or(NULL)) } pub fn eat_while(&mut self, predicate: F) where F: Fn(char) -> bool, { - while predicate(self.next_char) { + while predicate(self.next) && self.next != NULL { self.next(); } } - - pub fn peek(&mut self) -> char { - self.next_char - } } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 69e722a..e494c5c 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -28,14 +28,29 @@ impl<'a> Lexer<'a> { pub fn next_token(&mut self) -> LexerResult { let start = self.chars.pos; - let kind = match self.chars.peek() { + let kind = match self.chars.next { c if is_int(c) => self.read_number(), '.' => self.read_number(), c if is_identifier_head(c) => self.read_identifier(), '+' => self.read_char_as_kind(TokenKind::Plus), '-' => self.read_char_as_kind(TokenKind::Minus), '*' => self.read_char_as_kind(TokenKind::Asterisk), - '/' => self.read_char_as_kind(TokenKind::Slash), + '/' => { + self.chars.next(); + match self.chars.next { + '/' => { + self.chars.next(); + self.eat_inline_comment_tail(); + return self.next_token(); + } + '*' => { + self.chars.next(); + self.eat_block_comment_tail(start)?; + return self.next_token(); + } + _ => TokenKind::Slash, + } + } '=' => self.read_char_as_kind(TokenKind::Equal), '(' => self.read_char_as_kind(TokenKind::OpenParen), ')' => self.read_char_as_kind(TokenKind::CloseParen), @@ -64,11 +79,11 @@ impl<'a> Lexer<'a> { } fn read_number(&mut self) -> TokenKind { - if is_int(self.chars.peek()) { + if is_int(self.chars.next) { self.chars.next(); self.chars.eat_while(is_int); } - if self.chars.peek() == '.' { + if self.chars.next == '.' { self.chars.next(); self.chars.eat_while(is_int); } @@ -81,6 +96,28 @@ impl<'a> Lexer<'a> { TokenKind::Identifier } + fn eat_block_comment_tail(&mut self, start: u32) -> LexerResult<()> { + loop { + self.chars.eat_while(|c| c != '*'); + self.chars.next(); + if self.chars.next == NULL { + Err(CompilerError::new( + format!("Unclosed block comment."), + Span::new(start, self.chars.pos), + ))?; + } else if self.chars.next == '/' { + self.chars.next(); + break; + }; + } + Ok(()) + } + + fn eat_inline_comment_tail(&mut self) { + self.chars.eat_while(not_line_break); + self.chars.next(); + } + pub fn source(&self, span: Span) -> &str { &self.source[span.start as usize..span.end as usize] } @@ -113,6 +150,14 @@ fn is_whitepsace(c: char) -> bool { c.is_whitespace() } +fn not_line_break(c: char) -> bool { + match c { + // TODO: Windows line endings? + '\n' => false, + _ => true, + } +} + #[test] fn can_lex_number() { let mut lexer = Lexer::new("1"); diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 4edc741..afaebab 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -231,6 +231,7 @@ fn compatibility_tests() { ("Equality (< epsilon)", "g = 0.000009 == 0;", 1.0), ("Equality (< -epsilon)", "g = -0.000009 == 0;", 1.0), ("Variables don't collide", "g = 1; not_g = 2;", 1.0), + ("Simple block comment", "g = 1; /* g = 10 */", 1.0), ("Block comment", "g = 1; /* g = 10 */ g = g * 2;", 2.0), ("Sigmoid 1, 2", "g = sigmoid(1, 2.0);", 0.8807970779778823), ("Sigmoid 2, 1", "g = sigmoid(2, 1.0);", 0.8807970779778823), @@ -366,7 +367,6 @@ fn compatibility_tests() { "above (false)", "below (true)", "below (false)", - "Line comments", "Line comments (\\\\)", "Equal (false)", "Equal (true)", @@ -497,6 +497,7 @@ fn compatibility_tests() { let mut globals = HashMap::default(); let mut pool_globals = HashSet::new(); pool_globals.insert("g".to_string()); + // TODO: We should set x = 10 globals.insert("pool".to_string(), pool_globals); match eval_eel( vec![("test".to_string(), code, "pool".to_string())], From 21a8450694d9b698c6603144b92f3210839b77b2 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 47/85] Add expression blocks --- compiler-rs/src/ast.rs | 6 ++++ compiler-rs/src/emitter.rs | 26 ++++++++++---- compiler-rs/src/parser.rs | 20 ++++++----- .../tests/fixtures/ast/one_plus_one.snapshot | 36 ++++++++++--------- 4 files changed, 56 insertions(+), 32 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 86397ed..ee58e3a 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -2,6 +2,11 @@ use crate::span::Span; #[derive(Debug, PartialEq)] pub struct EelFunction { + pub expressions: ExpressionBlock, +} + +#[derive(Debug, PartialEq)] +pub struct ExpressionBlock { pub expressions: Vec, } @@ -12,6 +17,7 @@ pub enum Expression { NumberLiteral(NumberLiteral), Assignment(Assignment), FunctionCall(FunctionCall), + ExpressionBlock(ExpressionBlock), } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 7cd22d3..cf416de 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -2,8 +2,8 @@ use std::collections::{HashMap, HashSet}; use crate::{ ast::{ - Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, FunctionCall, - UnaryExpression, UnaryOperator, + Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, ExpressionBlock, + FunctionCall, UnaryExpression, UnaryOperator, }, builtin_functions::BuiltinFunction, error::CompilerError, @@ -194,16 +194,25 @@ impl Emitter { Ok((exports, function_bodies, function_definitions)) } - fn emit_program(&mut self, program: EelFunction) -> EmitterResult { + fn emit_program(&mut self, eel_function: EelFunction) -> EmitterResult { let mut instructions: Vec = Vec::new(); - for expression in program.expressions { - self.emit_expression(expression, &mut instructions)?; - // TODO: Consider that we might need to drop the implicit return. - } + self.emit_expression_block(eel_function.expressions, &mut instructions)?; instructions.push(Instruction::End); Ok(Instructions::new(instructions)) } + fn emit_expression_block( + &mut self, + block: ExpressionBlock, + instructions: &mut Vec, + ) -> EmitterResult<()> { + for expression in block.expressions { + self.emit_expression(expression, instructions)?; + // TODO: Consider that we might need to drop the implicit return. + } + Ok(()) + } + fn emit_expression( &mut self, expression: Expression, @@ -226,6 +235,9 @@ impl Emitter { Expression::FunctionCall(function_call) => { self.emit_function_call(function_call, instructions) } + Expression::ExpressionBlock(expression_block) => { + self.emit_expression_block(expression_block, instructions) + } } } diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 9ad34c0..27aa108 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -1,8 +1,8 @@ use std::num::ParseFloatError; use crate::ast::{ - Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, FunctionCall, Identifier, - UnaryExpression, UnaryOperator, + Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, ExpressionBlock, + FunctionCall, Identifier, UnaryExpression, UnaryOperator, }; use super::ast::{EelFunction, Expression, NumberLiteral}; @@ -71,7 +71,7 @@ impl<'a> Parser<'a> { }) } - pub fn parse_expression_block(&mut self) -> ParseResult> { + pub fn parse_expression_block(&mut self) -> ParseResult { let mut expressions = vec![]; while self.peek_expression() { expressions.push(self.parse_expression(0)?); @@ -80,7 +80,7 @@ impl<'a> Parser<'a> { self.advance()?; } } - Ok(expressions) + Ok(ExpressionBlock { expressions }) } fn peek_expression(&self) -> bool { @@ -109,9 +109,9 @@ impl<'a> Parser<'a> { match self.token.kind { TokenKind::OpenParen => { self.advance()?; - let expression = self.parse_expression(0)?; + let expression_block = self.parse_expression_block()?; self.expect_kind(TokenKind::CloseParen)?; - Ok(expression) + Ok(Expression::ExpressionBlock(expression_block)) } TokenKind::Int => Ok(Expression::NumberLiteral(self.parse_int()?)), TokenKind::Plus => { @@ -309,7 +309,9 @@ fn can_parse_integer() { assert_eq!( Parser::new("1").parse(), Ok(EelFunction { - expressions: vec![Expression::NumberLiteral(NumberLiteral { value: 1.0 })] + expressions: ExpressionBlock { + expressions: vec![Expression::NumberLiteral(NumberLiteral { value: 1.0 })] + } }) ); } @@ -319,7 +321,9 @@ fn can_parse_integer_2() { assert_eq!( Parser::new("2").parse(), Ok(EelFunction { - expressions: vec![Expression::NumberLiteral(NumberLiteral { value: 2.0 })] + expressions: ExpressionBlock { + expressions: vec![Expression::NumberLiteral(NumberLiteral { value: 2.0 })] + } }) ); } diff --git a/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot b/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot index 93e67a7..cd0821f 100644 --- a/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot +++ b/compiler-rs/tests/fixtures/ast/one_plus_one.snapshot @@ -1,21 +1,23 @@ 1+1 ======================================================================== EelFunction { - expressions: [ - BinaryExpression( - BinaryExpression { - left: NumberLiteral( - NumberLiteral { - value: 1.0, - }, - ), - right: NumberLiteral( - NumberLiteral { - value: 1.0, - }, - ), - op: Add, - }, - ), - ], + expressions: ExpressionBlock { + expressions: [ + BinaryExpression( + BinaryExpression { + left: NumberLiteral( + NumberLiteral { + value: 1.0, + }, + ), + right: NumberLiteral( + NumberLiteral { + value: 1.0, + }, + ), + op: Add, + }, + ), + ], + }, } From 3b111345504679b4e5950046c7afc702e9604895 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 48/85] Don't return from eel functions --- compiler-rs/src/emitter.rs | 11 +++++++---- compiler-rs/tests/common/mod.rs | 5 ++--- compiler-rs/tests/compatibility_test.rs | 6 ------ compiler-rs/tests/fixtures/wat/div.snapshot | 7 ++++--- compiler-rs/tests/fixtures/wat/one_plus_one.snapshot | 7 ++++--- compiler-rs/tests/fixtures/wat/reg.snapshot | 7 ++++--- compiler-rs/tests/fixtures/wat/sin.snapshot | 7 ++++--- compiler-rs/tests/integration_test.rs | 6 ++---- 8 files changed, 27 insertions(+), 29 deletions(-) diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index cf416de..24a867a 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -186,8 +186,7 @@ impl Emitter { let locals = Vec::new(); function_bodies.push(FuncBody::new(locals, self.emit_program(program)?)); - // TODO: In the future functions should not return any values - let function_type = self.function_types.get((0, 1)); + let function_type = self.function_types.get((0, 0)); function_definitions.push(Func::new(function_type)) } @@ -197,6 +196,7 @@ impl Emitter { fn emit_program(&mut self, eel_function: EelFunction) -> EmitterResult { let mut instructions: Vec = Vec::new(); self.emit_expression_block(eel_function.expressions, &mut instructions)?; + instructions.push(Instruction::Drop); instructions.push(Instruction::End); Ok(Instructions::new(instructions)) } @@ -206,9 +206,12 @@ impl Emitter { block: ExpressionBlock, instructions: &mut Vec, ) -> EmitterResult<()> { - for expression in block.expressions { + let last_index = block.expressions.len() - 1; + for (i, expression) in block.expressions.into_iter().enumerate() { self.emit_expression(expression, instructions)?; - // TODO: Consider that we might need to drop the implicit return. + if i != last_index { + instructions.push(Instruction::Drop) + } } Ok(()) } diff --git a/compiler-rs/tests/common/mod.rs b/compiler-rs/tests/common/mod.rs index 91f0156..99ee94d 100644 --- a/compiler-rs/tests/common/mod.rs +++ b/compiler-rs/tests/common/mod.rs @@ -117,9 +117,8 @@ pub fn eval_eel( // TODO: Instead of returning return value, return value of globals match instance.invoke_export(function_to_run, &[], &mut global_imports) { - Ok(Some(RuntimeValue::F64(_val))) => Ok(()), - Ok(Some(val)) => Err(format!("Unexpected return type: {:?}", val)), - Ok(None) => Err("No Result".to_string()), + Ok(Some(_)) => Err("Did not expect to get return from eel function".to_string()), + Ok(None) => Ok(()), Err(err) => Err(format!( "Error invoking exported function {}: {}", function_to_run, err diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index afaebab..846be82 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -333,7 +333,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Optional final semi", "Mod", "Mod zero", "Bitwise and", @@ -356,9 +355,6 @@ fn compatibility_tests() { "Atan", "Atan2", "Read globals", - "Multiple statements", - "Multiple statements expression", - "Multiple statements expression implcit return", "if", "if", "if does short-circit (consiquent)", @@ -451,10 +447,8 @@ fn compatibility_tests() { "Gmegabuf", "Megabuf != Gmegabuf", "Gmegabuf != Megabuf", - "Consecutive semis", "Equality (< epsilon)", "Equality (< -epsilon)", - "Variables don\'t collide", "Block comment", "Sigmoid 1, 2", "Sigmoid 2, 1", diff --git a/compiler-rs/tests/fixtures/wat/div.snapshot b/compiler-rs/tests/fixtures/wat/div.snapshot index 7481fb4..fd2a674 100644 --- a/compiler-rs/tests/fixtures/wat/div.snapshot +++ b/compiler-rs/tests/fixtures/wat/div.snapshot @@ -3,14 +3,15 @@ g = 100 / 0 (module (type (;0;) (func (param f64) (result f64))) (type (;1;) (func (param f64 f64) (result f64))) - (type (;2;) (func (result f64))) + (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) - (func (;1;) (type 2) (result f64) + (func (;1;) (type 2) f64.const 0x1.9p+6 (;=100;) f64.const 0x0p+0 (;=0;) call 2 global.set 0 - global.get 0) + global.get 0 + drop) (func (;2;) (type 1) (param f64 f64) (result f64) (local i32) local.get 1 diff --git a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot index ea0a6b7..bb2c848 100644 --- a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot +++ b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot @@ -2,10 +2,11 @@ ======================================================================== (module (type (;0;) (func (param f64) (result f64))) - (type (;1;) (func (result f64))) + (type (;1;) (func)) (import "shims" "sin" (func (;0;) (type 0))) - (func (;1;) (type 1) (result f64) + (func (;1;) (type 1) f64.const 0x1p+0 (;=1;) f64.const 0x1p+0 (;=1;) - f64.add) + f64.add + drop) (export "test" (func 1))) diff --git a/compiler-rs/tests/fixtures/wat/reg.snapshot b/compiler-rs/tests/fixtures/wat/reg.snapshot index 3708b12..fea95b7 100644 --- a/compiler-rs/tests/fixtures/wat/reg.snapshot +++ b/compiler-rs/tests/fixtures/wat/reg.snapshot @@ -2,11 +2,12 @@ reg00=10 ======================================================================== (module (type (;0;) (func (param f64) (result f64))) - (type (;1;) (func (result f64))) + (type (;1;) (func)) (import "shims" "sin" (func (;0;) (type 0))) - (func (;1;) (type 1) (result f64) + (func (;1;) (type 1) f64.const 0x1.4p+3 (;=10;) global.set 0 - global.get 0) + global.get 0 + drop) (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) (export "test" (func 1))) diff --git a/compiler-rs/tests/fixtures/wat/sin.snapshot b/compiler-rs/tests/fixtures/wat/sin.snapshot index 09dcd2b..4bd127d 100644 --- a/compiler-rs/tests/fixtures/wat/sin.snapshot +++ b/compiler-rs/tests/fixtures/wat/sin.snapshot @@ -2,9 +2,10 @@ sin(100) ======================================================================== (module (type (;0;) (func (param f64) (result f64))) - (type (;1;) (func (result f64))) + (type (;1;) (func)) (import "shims" "sin" (func (;0;) (type 0))) - (func (;1;) (type 1) (result f64) + (func (;1;) (type 1) f64.const 0x1.9p+6 (;=100;) - call 0) + call 0 + drop) (export "test" (func 1))) diff --git a/compiler-rs/tests/integration_test.rs b/compiler-rs/tests/integration_test.rs index 60ab2e2..0c6351a 100644 --- a/compiler-rs/tests/integration_test.rs +++ b/compiler-rs/tests/integration_test.rs @@ -69,11 +69,9 @@ fn multiple_functions() { instance .invoke_export("one", &[], &mut global_imports) - .expect("failed to execute export") - .expect("Ran"); + .expect("failed to execute export"); instance .invoke_export("two", &[], &mut global_imports) - .expect("failed to execute export") - .expect("Ran"); + .expect("failed to execute export"); } From 85117add907087f02cd21ccc06478943f32855aa Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 49/85] Variable access --- compiler-rs/src/ast.rs | 1 + compiler-rs/src/emitter.rs | 5 +++++ compiler-rs/src/parser.rs | 9 +++------ compiler-rs/tests/compatibility_test.rs | 5 +---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index ee58e3a..ad24f21 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -18,6 +18,7 @@ pub enum Expression { Assignment(Assignment), FunctionCall(FunctionCall), ExpressionBlock(ExpressionBlock), + Identifier(Identifier), } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 24a867a..2e5d290 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -241,6 +241,11 @@ impl Emitter { Expression::ExpressionBlock(expression_block) => { self.emit_expression_block(expression_block, instructions) } + Expression::Identifier(identifier) => { + let index = self.resolve_variable(identifier.name); + instructions.push(Instruction::GetGlobal(index)); + Ok(()) + } } } diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 27aa108..60f5e83 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -237,9 +237,9 @@ impl<'a> Parser<'a> { fn parse_identifier_expression(&mut self) -> ParseResult { let identifier = self.parse_identifier()?; - match self.token.kind { + match &self.token.kind { TokenKind::Equal => { - let _operator_token = self.expect_kind(TokenKind::Equal)?; + self.advance()?; let right = self.parse_expression(0)?; Ok(Expression::Assignment(Assignment { left: identifier, @@ -271,10 +271,7 @@ impl<'a> Parser<'a> { arguments, })) } - _ => Err(CompilerError::new( - "Expected = or (".to_string(), - self.token.span, - )), + _ => Ok(Expression::Identifier(identifier)), } // TODO: Support other operator types } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 846be82..4420da2 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -50,7 +50,7 @@ fn compatibility_tests() { ("Atan", "g = atan(0.5);", 0.5_f64.atan()), ("Atan2", "g = atan2(1, 1.0);", 1_f64.atan2(1.0)), ("Assign to globals", "g = 10;", 10.0), - ("Read globals", "g = x;", 10.0), + // ("Read globals", "g = x;", 10.0), ("Multiple statements", "g = 10; g = 20;", 20.0), ("Multiple statements expression", "(g = 10; g = 20;);", 20.0), ( @@ -354,7 +354,6 @@ fn compatibility_tests() { "Acos", "Atan", "Atan2", - "Read globals", "if", "if", "if does short-circit (consiquent)", @@ -373,7 +372,6 @@ fn compatibility_tests() { "Sign (-10)", "Sign (0)", "Sign (-0)", - "Local variables", "Bor (true, false)", "Bor (false, true)", "Bor (true, true)", @@ -449,7 +447,6 @@ fn compatibility_tests() { "Gmegabuf != Megabuf", "Equality (< epsilon)", "Equality (< -epsilon)", - "Block comment", "Sigmoid 1, 2", "Sigmoid 2, 1", "Sigmoid 0, 0", From e8e60f1e0ea84bb63b6d259adfa02b87bd9c504a Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 50/85] If function --- compiler-rs/src/emitter.rs | 66 ++++++++++++++----- compiler-rs/tests/compatibility_test.rs | 4 -- .../fixtures/wat/wrong_arity_if.invalid.eel | 1 + .../wat/wrong_arity_if.invalid.snapshot | 3 + 4 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 compiler-rs/tests/fixtures/wat/wrong_arity_if.invalid.eel create mode 100644 compiler-rs/tests/fixtures/wat/wrong_arity_if.invalid.snapshot diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 2e5d290..49eca6c 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -13,7 +13,7 @@ use crate::{ EelFunctionType, }; use parity_wasm::elements::{ - CodeSection, ExportEntry, ExportSection, External, Func, FuncBody, FunctionSection, + BlockType, CodeSection, ExportEntry, ExportSection, External, Func, FuncBody, FunctionSection, FunctionType, GlobalEntry, GlobalSection, GlobalType, ImportEntry, ImportSection, InitExpr, Instruction, Instructions, Internal, Module, Section, Serialize, Type, TypeSection, ValueType, }; @@ -308,31 +308,38 @@ impl Emitter { fn emit_function_call( &mut self, - function_call: FunctionCall, + mut function_call: FunctionCall, instructions: &mut Vec, ) -> EmitterResult<()> { - let arity = function_call.arguments.len(); - for arg in function_call.arguments { - &self.emit_expression(arg, instructions)?; - } - // TODO: Assert arrity match &function_call.name.name[..] { "int" => { + assert_arity(&function_call, 1)?; + for arg in function_call.arguments { + self.emit_expression(arg, instructions)?; + } instructions.push(Instruction::F64Floor); } + "if" => { + assert_arity(&function_call, 3)?; + + let alternate = function_call.arguments.pop().unwrap(); + let consiquent = function_call.arguments.pop().unwrap(); + let test = function_call.arguments.pop().unwrap(); + + self.emit_expression(test, instructions)?; + emit_is_not_zeroish(instructions); + instructions.push(Instruction::If(BlockType::Value(ValueType::F64))); + self.emit_expression(consiquent, instructions)?; + instructions.push(Instruction::Else); + self.emit_expression(alternate, instructions)?; + instructions.push(Instruction::End); + } shim_name if Shim::from_str(shim_name).is_some() => { let shim = Shim::from_str(shim_name).unwrap(); - if arity != shim.arity() { - return Err(CompilerError::new( - format!( - "Incorrect argument count for function `{}`. Expected {} but got {}.", - shim_name, - shim.arity(), - arity - ), - // TODO: Better to underline the argument list - function_call.name.span, - )); + assert_arity(&function_call, shim.arity())?; + + for arg in function_call.arguments { + self.emit_expression(arg, instructions)?; } instructions.push(Instruction::Call(self.shims.get(shim))); } @@ -365,6 +372,12 @@ impl Emitter { } } +fn emit_is_not_zeroish(instructions: &mut Vec) { + instructions.push(Instruction::F64Abs); + instructions.push(Instruction::F64Const(f64_const(EPSILON))); + 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()) @@ -393,3 +406,20 @@ fn make_import_entry(module_str: String, field_str: String) -> ImportEntry { External::Global(GlobalType::new(ValueType::F64, true)), ) } + +fn assert_arity(function_call: &FunctionCall, arity: usize) -> EmitterResult<()> { + if function_call.arguments.len() != arity { + Err(CompilerError::new( + format!( + "Incorrect argument count for function `{}`. Expected {} but got {}.", + function_call.name.name, + arity, + function_call.arguments.len() + ), + // TODO: Better to underline the argument list + function_call.name.span, + )) + } else { + Ok(()) + } +} diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 4420da2..7d73c04 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -354,10 +354,6 @@ fn compatibility_tests() { "Acos", "Atan", "Atan2", - "if", - "if", - "if does short-circit (consiquent)", - "if does short-circit (alternate)", "above (true)", "above (false)", "below (true)", diff --git a/compiler-rs/tests/fixtures/wat/wrong_arity_if.invalid.eel b/compiler-rs/tests/fixtures/wat/wrong_arity_if.invalid.eel new file mode 100644 index 0000000..b83a49a --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/wrong_arity_if.invalid.eel @@ -0,0 +1 @@ +if(1,2) \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/wat/wrong_arity_if.invalid.snapshot b/compiler-rs/tests/fixtures/wat/wrong_arity_if.invalid.snapshot new file mode 100644 index 0000000..cb188fd --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/wrong_arity_if.invalid.snapshot @@ -0,0 +1,3 @@ +if(1,2) +======================================================================== +Incorrect argument count for function `if`. Expected 3 but got 2. From 3a487b4283ca515e7f79dbc2b4e2127a1c8bdffc Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 51/85] Allow reading (g)megabuf --- compiler-rs/src/builtin_functions.rs | 56 +++++++++++- compiler-rs/src/constants.rs | 12 +++ compiler-rs/src/emitter.rs | 85 +++++++++++++++---- compiler-rs/src/index_store.rs | 15 ---- compiler-rs/src/lib.rs | 5 +- compiler-rs/src/shim.rs | 5 +- compiler-rs/src/utils.rs | 4 + compiler-rs/tests/compatibility_test.rs | 2 - compiler-rs/tests/fixtures/wat/div.snapshot | 1 + .../tests/fixtures/wat/one_plus_one.snapshot | 1 + compiler-rs/tests/fixtures/wat/reg.snapshot | 1 + compiler-rs/tests/fixtures/wat/sin.snapshot | 1 + 12 files changed, 151 insertions(+), 37 deletions(-) create mode 100644 compiler-rs/src/constants.rs create mode 100644 compiler-rs/src/utils.rs diff --git a/compiler-rs/src/builtin_functions.rs b/compiler-rs/src/builtin_functions.rs index 9d1b2d8..1506965 100644 --- a/compiler-rs/src/builtin_functions.rs +++ b/compiler-rs/src/builtin_functions.rs @@ -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]), } } @@ -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, ] + Instruction::GetLocal(2), // $truncated + Instruction::I32Const(BUFFER_SIZE as i32 - 1), + Instruction::I32GtS, + // STACK: [-1, $truncated * 8, , ] + Instruction::I32Or, + // STACK: [-1, $truncated * 8, ] + Instruction::Select, + Instruction::End, + ]), + ), } } } diff --git a/compiler-rs/src/constants.rs b/compiler-rs/src/constants.rs new file mode 100644 index 0000000..5e20758 --- /dev/null +++ b/compiler-rs/src/constants.rs @@ -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; diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs index 49eca6c..04c2ad8 100644 --- a/compiler-rs/src/emitter.rs +++ b/compiler-rs/src/emitter.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + mem, +}; use crate::{ ast::{ @@ -6,22 +9,23 @@ use crate::{ 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 = Result; -static EPSILON: f64 = 0.00001; - pub fn emit( eel_functions: Vec<(String, EelFunction, String)>, globals_map: HashMap>, @@ -37,6 +41,7 @@ struct Emitter { builtin_functions: IndexStore, function_types: IndexStore, builtin_offset: Option, + locals: Vec, } impl Emitter { @@ -48,6 +53,7 @@ impl Emitter { function_types: Default::default(), builtin_functions: IndexStore::new(), builtin_offset: None, + locals: Default::default(), } } fn emit( @@ -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)); } @@ -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(); @@ -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)) } @@ -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())?; @@ -353,6 +379,33 @@ impl Emitter { Ok(()) } + fn emit_memory_access( + &mut self, + function_call: &mut FunctionCall, + memory_offset: u32, + instructions: &mut Vec, + ) -> 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 @@ -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 @@ -378,11 +436,6 @@ fn emit_is_not_zeroish(instructions: &mut Vec) { 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?) diff --git a/compiler-rs/src/index_store.rs b/compiler-rs/src/index_store.rs index e8f1143..3778984 100644 --- a/compiler-rs/src/index_store.rs +++ b/compiler-rs/src/index_store.rs @@ -36,18 +36,3 @@ impl IndexStore { self.map.keys().collect() } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::EelFunctionType; - #[test] - fn tuple() { - let mut function_types: IndexStore = 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); - } -} diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index cfd3792..15a32a6 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -1,5 +1,6 @@ mod ast; mod builtin_functions; +mod constants; mod emitter; mod error; mod file_chars; @@ -9,6 +10,7 @@ mod parser; mod shim; mod span; mod tokens; +mod utils; use std::collections::{HashMap, HashSet}; @@ -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::*; diff --git a/compiler-rs/src/shim.rs b/compiler-rs/src/shim.rs index f44c80b..a79c57c 100644 --- a/compiler-rs/src/shim.rs +++ b/compiler-rs/src/shim.rs @@ -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)] @@ -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 { diff --git a/compiler-rs/src/utils.rs b/compiler-rs/src/utils.rs new file mode 100644 index 0000000..165e944 --- /dev/null +++ b/compiler-rs/src/utils.rs @@ -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()) +} diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 7d73c04..4927cf6 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -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", @@ -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", diff --git a/compiler-rs/tests/fixtures/wat/div.snapshot b/compiler-rs/tests/fixtures/wat/div.snapshot index fd2a674..5976b69 100644 --- a/compiler-rs/tests/fixtures/wat/div.snapshot +++ b/compiler-rs/tests/fixtures/wat/div.snapshot @@ -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))) diff --git a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot index bb2c848..c0acc27 100644 --- a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot +++ b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot @@ -9,4 +9,5 @@ f64.const 0x1p+0 (;=1;) f64.add drop) + (memory (;0;) 2048 2048) (export "test" (func 1))) diff --git a/compiler-rs/tests/fixtures/wat/reg.snapshot b/compiler-rs/tests/fixtures/wat/reg.snapshot index fea95b7..f3260a8 100644 --- a/compiler-rs/tests/fixtures/wat/reg.snapshot +++ b/compiler-rs/tests/fixtures/wat/reg.snapshot @@ -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))) diff --git a/compiler-rs/tests/fixtures/wat/sin.snapshot b/compiler-rs/tests/fixtures/wat/sin.snapshot index 4bd127d..45851fa 100644 --- a/compiler-rs/tests/fixtures/wat/sin.snapshot +++ b/compiler-rs/tests/fixtures/wat/sin.snapshot @@ -8,4 +8,5 @@ sin(100) f64.const 0x1.9p+6 (;=100;) call 0 drop) + (memory (;0;) 2048 2048) (export "test" (func 1))) From 27ceca3021405eac55adba740a403dd5e96a82f5 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 52/85] Split emitter in two --- compiler-rs/src/emitter.rs | 478 ---------------------------- compiler-rs/src/error.rs | 2 + compiler-rs/src/function_emitter.rs | 292 +++++++++++++++++ compiler-rs/src/lib.rs | 7 +- compiler-rs/src/module_emitter.rs | 241 ++++++++++++++ 5 files changed, 539 insertions(+), 481 deletions(-) delete mode 100644 compiler-rs/src/emitter.rs create mode 100644 compiler-rs/src/function_emitter.rs create mode 100644 compiler-rs/src/module_emitter.rs diff --git a/compiler-rs/src/emitter.rs b/compiler-rs/src/emitter.rs deleted file mode 100644 index 04c2ad8..0000000 --- a/compiler-rs/src/emitter.rs +++ /dev/null @@ -1,478 +0,0 @@ -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, Local, MemorySection, MemoryType, Module, Section, - Serialize, Type, TypeSection, ValueType, -}; - -type EmitterResult = Result; - -pub fn emit( - eel_functions: Vec<(String, EelFunction, String)>, - globals_map: HashMap>, -) -> EmitterResult> { - let mut emitter = Emitter::new(); - emitter.emit(eel_functions, globals_map) -} - -struct Emitter { - current_pool: String, - globals: IndexStore<(Option, String)>, - shims: IndexStore, - builtin_functions: IndexStore, - function_types: IndexStore, - builtin_offset: Option, - locals: Vec, -} - -impl Emitter { - fn new() -> Self { - Emitter { - current_pool: "".to_string(), // TODO: Is this okay to be empty? - globals: Default::default(), - shims: IndexStore::new(), - function_types: Default::default(), - builtin_functions: IndexStore::new(), - builtin_offset: None, - locals: Default::default(), - } - } - fn emit( - &mut self, - eel_functions: Vec<(String, EelFunction, String)>, - globals_map: HashMap>, // HahsMap> - ) -> EmitterResult> { - let mut imports = Vec::new(); - - for (pool_name, globals) in globals_map { - self.current_pool = pool_name; - for global in globals { - // TODO: Lots of clones. - self.resolve_variable(global.clone()); - imports.push(make_import_entry(self.current_pool.clone(), global.clone())); - } - } - - let shims: Vec = vec![Shim::Sin]; - let shim_offset = shims.len() as u32; - for shim in shims { - let field_str = shim.as_str().to_string(); - let type_ = shim.get_type(); - self.shims.ensure(shim); - imports.push(ImportEntry::new( - "shims".to_string(), - field_str, - External::Function(self.function_types.get(type_)), - )); - } - - self.builtin_offset = Some(eel_functions.len() as u32 + shim_offset); - - let (function_exports, function_bodies, funcs) = - self.emit_eel_functions(eel_functions, shim_offset)?; - - let mut sections = vec![]; - sections.push(Section::Type(self.emit_type_section())); - if let Some(import_section) = self.emit_import_section(imports) { - 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)); - } - sections.push(Section::Export(self.emit_export_section(function_exports))); - sections.push(Section::Code(self.emit_code_section(function_bodies))); - - let mut binary: Vec = Vec::new(); - Module::new(sections) - .serialize(&mut binary) - .map_err(|err| { - CompilerError::new( - format!("Module serialization error: {}", err), - Span::empty(), - ) - })?; - - Ok(binary) - } - - fn emit_type_section(&self) -> TypeSection { - let function_types = self - .function_types - .keys() - .into_iter() - .map(|function_type| { - Type::Function(FunctionType::new( - // TODO: This is clone with more steps. What's going on - function_type.params().to_vec(), - function_type.results().to_vec(), - )) - }) - .collect(); - TypeSection::with_types(function_types) - } - - fn emit_import_section(&self, imports: Vec) -> Option { - if imports.len() > 0 { - Some(ImportSection::with_entries(imports)) - } else { - None - } - } - - fn emit_function_section(&mut self, funcs: Vec) -> FunctionSection { - let mut entries = funcs.clone(); - for builtin in self.builtin_functions.keys() { - let type_idx = self.function_types.get(builtin.get_type()); - entries.push(Func::new(type_idx)); - } - FunctionSection::with_entries(entries) - } - - fn emit_global_section(&self) -> Option { - let globals: Vec = self - .globals - .keys() - .iter() - .map(|_| make_empty_global()) - .collect(); - - if globals.len() == 0 { - None - } else { - Some(GlobalSection::with_entries(globals)) - } - } - - fn emit_export_section(&self, function_exports: Vec) -> ExportSection { - ExportSection::with_entries(function_exports) - } - - fn emit_code_section(&self, function_bodies: Vec) -> CodeSection { - // TODO: Avoid this clone - let mut bodies = function_bodies.clone(); - for builtin in self.builtin_functions.keys() { - bodies.push(builtin.func_body()); - } - CodeSection::with_bodies(bodies) - } - - fn emit_eel_functions( - &mut self, - eel_functions: Vec<(String, EelFunction, String)>, - offset: u32, - ) -> EmitterResult<(Vec, Vec, Vec)> { - let mut exports = Vec::new(); - 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 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)) - } - Ok((exports, function_bodies, function_definitions)) - } - - fn emit_program(&mut self, eel_function: EelFunction) -> EmitterResult { - let mut instructions: Vec = Vec::new(); - self.emit_expression_block(eel_function.expressions, &mut instructions)?; - instructions.push(Instruction::Drop); - instructions.push(Instruction::End); - Ok(Instructions::new(instructions)) - } - - fn emit_expression_block( - &mut self, - block: ExpressionBlock, - instructions: &mut Vec, - ) -> EmitterResult<()> { - let last_index = block.expressions.len() - 1; - for (i, expression) in block.expressions.into_iter().enumerate() { - self.emit_expression(expression, instructions)?; - if i != last_index { - instructions.push(Instruction::Drop) - } - } - Ok(()) - } - - fn emit_expression( - &mut self, - expression: Expression, - instructions: &mut Vec, - ) -> EmitterResult<()> { - match expression { - Expression::UnaryExpression(unary_expression) => { - self.emit_unary_expression(unary_expression, instructions) - } - Expression::BinaryExpression(binary_expression) => { - self.emit_binary_expression(binary_expression, instructions) - } - Expression::Assignment(assignment_expression) => { - self.emit_assignment(assignment_expression, instructions) - } - Expression::NumberLiteral(number_literal) => { - instructions.push(Instruction::F64Const(f64_const(number_literal.value))); - Ok(()) - } - Expression::FunctionCall(function_call) => { - self.emit_function_call(function_call, instructions) - } - Expression::ExpressionBlock(expression_block) => { - self.emit_expression_block(expression_block, instructions) - } - Expression::Identifier(identifier) => { - let index = self.resolve_variable(identifier.name); - instructions.push(Instruction::GetGlobal(index)); - Ok(()) - } - } - } - - fn emit_unary_expression( - &mut self, - unary_expression: UnaryExpression, - instructions: &mut Vec, - ) -> EmitterResult<()> { - match unary_expression.op { - UnaryOperator::Plus => self.emit_expression(*unary_expression.right, instructions), - UnaryOperator::Minus => { - self.emit_expression(*unary_expression.right, instructions)?; - instructions.push(Instruction::F64Neg); - Ok(()) - } - UnaryOperator::Not => { - self.emit_expression(*unary_expression.right, instructions)?; - instructions.extend(vec![ - Instruction::F64Abs, - Instruction::F64Const(f64_const(EPSILON)), - Instruction::F64Lt, - ]); - instructions.push(Instruction::F64ConvertSI32); - Ok(()) - } - } - } - - fn emit_binary_expression( - &mut self, - binary_expression: BinaryExpression, - instructions: &mut Vec, - ) -> EmitterResult<()> { - self.emit_expression(*binary_expression.left, instructions)?; - self.emit_expression(*binary_expression.right, instructions)?; - let op = match binary_expression.op { - BinaryOperator::Add => Instruction::F64Add, - BinaryOperator::Subtract => Instruction::F64Sub, - BinaryOperator::Multiply => Instruction::F64Mul, - BinaryOperator::Divide => { - Instruction::Call(self.resolve_builtin_function(BuiltinFunction::Div)) - } - }; - instructions.push(op); - Ok(()) - } - - fn emit_assignment( - &mut self, - assignment_expression: Assignment, - instructions: &mut Vec, - ) -> EmitterResult<()> { - let resolved_name = self.resolve_variable(assignment_expression.left.name); - self.emit_expression(*assignment_expression.right, instructions)?; - - instructions.push(Instruction::SetGlobal(resolved_name)); - instructions.push(Instruction::GetGlobal(resolved_name)); - Ok(()) - } - - fn emit_function_call( - &mut self, - mut function_call: FunctionCall, - instructions: &mut Vec, - ) -> EmitterResult<()> { - match &function_call.name.name[..] { - "int" => { - assert_arity(&function_call, 1)?; - for arg in function_call.arguments { - self.emit_expression(arg, instructions)?; - } - instructions.push(Instruction::F64Floor); - } - "if" => { - assert_arity(&function_call, 3)?; - - let alternate = function_call.arguments.pop().unwrap(); - let consiquent = function_call.arguments.pop().unwrap(); - let test = function_call.arguments.pop().unwrap(); - - self.emit_expression(test, instructions)?; - emit_is_not_zeroish(instructions); - instructions.push(Instruction::If(BlockType::Value(ValueType::F64))); - self.emit_expression(consiquent, instructions)?; - instructions.push(Instruction::Else); - 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())?; - - for arg in function_call.arguments { - self.emit_expression(arg, instructions)?; - } - instructions.push(Instruction::Call(self.shims.get(shim))); - } - _ => { - return Err(CompilerError::new( - format!("Unknown function `{}`", function_call.name.name), - function_call.name.span, - )) - } - } - Ok(()) - } - - fn emit_memory_access( - &mut self, - function_call: &mut FunctionCall, - memory_offset: u32, - instructions: &mut Vec, - ) -> 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 - } else { - Some(self.current_pool.clone()) - }; - - 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 - .builtin_offset - .expect("Tried to compute builtin index before setting offset."); - self.builtin_functions.get(builtin) + offset - } -} - -fn emit_is_not_zeroish(instructions: &mut Vec) { - instructions.push(Instruction::F64Abs); - instructions.push(Instruction::F64Const(f64_const(EPSILON))); - instructions.push(Instruction::F64Gt); -} - -fn variable_is_register(name: &str) -> bool { - let chars: Vec<_> = name.chars().collect(); - // We avoided pulling in the regex crate! (But at what cost?) - matches!(chars.as_slice(), ['r', 'e', 'g', '0'..='9', '0'..='9']) -} - -fn make_empty_global() -> GlobalEntry { - GlobalEntry::new( - GlobalType::new(ValueType::F64, true), - InitExpr::new(vec![ - Instruction::F64Const(f64_const(0.0)), - Instruction::End, - ]), - ) -} - -fn make_import_entry(module_str: String, field_str: String) -> ImportEntry { - ImportEntry::new( - module_str, - field_str, - External::Global(GlobalType::new(ValueType::F64, true)), - ) -} - -fn assert_arity(function_call: &FunctionCall, arity: usize) -> EmitterResult<()> { - if function_call.arguments.len() != arity { - Err(CompilerError::new( - format!( - "Incorrect argument count for function `{}`. Expected {} but got {}.", - function_call.name.name, - arity, - function_call.arguments.len() - ), - // TODO: Better to underline the argument list - function_call.name.span, - )) - } else { - Ok(()) - } -} diff --git a/compiler-rs/src/error.rs b/compiler-rs/src/error.rs index 1b70880..b23429f 100644 --- a/compiler-rs/src/error.rs +++ b/compiler-rs/src/error.rs @@ -19,3 +19,5 @@ impl CompilerError { self.message.clone() } } + +pub type EmitterResult = Result; diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs new file mode 100644 index 0000000..da55507 --- /dev/null +++ b/compiler-rs/src/function_emitter.rs @@ -0,0 +1,292 @@ +use crate::utils::f64_const; +use crate::{ + ast::{ + Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, ExpressionBlock, + FunctionCall, UnaryExpression, UnaryOperator, + }, + builtin_functions::BuiltinFunction, + constants::BUFFER_SIZE, + error::CompilerError, + index_store::IndexStore, + shim::Shim, + EelFunctionType, +}; +use crate::{constants::EPSILON, error::EmitterResult}; +use parity_wasm::elements::{BlockType, FuncBody, Instruction, Instructions, Local, ValueType}; + +pub fn emit_function( + eel_function: EelFunction, + current_pool: String, + globals: &mut IndexStore<(Option, String)>, + shims: &mut IndexStore, + builtin_functions: &mut IndexStore, + function_types: &mut IndexStore, + builtin_offset: &Option, +) -> EmitterResult { + let mut function_emitter = FunctionEmitter::new( + current_pool, + globals, + shims, + builtin_functions, + function_types, + builtin_offset, + ); + + function_emitter.emit_expression_block(eel_function.expressions)?; + + let mut instructions: Vec = function_emitter.instructions; + instructions.push(Instruction::Drop); + instructions.push(Instruction::End); + + let locals = function_emitter + .locals + .into_iter() + .map(|type_| Local::new(1, type_)) + .collect(); + + Ok(FuncBody::new(locals, Instructions::new(instructions))) +} + +struct FunctionEmitter<'a> { + current_pool: String, + globals: &'a mut IndexStore<(Option, String)>, + shims: &'a mut IndexStore, + builtin_functions: &'a mut IndexStore, + function_types: &'a mut IndexStore, + builtin_offset: &'a Option, + locals: Vec, + instructions: Vec, +} + +impl<'a> FunctionEmitter<'a> { + fn new( + current_pool: String, + globals: &'a mut IndexStore<(Option, String)>, + shims: &'a mut IndexStore, + builtin_functions: &'a mut IndexStore, + function_types: &'a mut IndexStore, + builtin_offset: &'a Option, + ) -> Self { + Self { + current_pool, + globals, + shims, + function_types, + builtin_functions, + builtin_offset, + locals: Vec::new(), + instructions: Vec::new(), + } + } + + fn emit_expression_block(&mut self, block: ExpressionBlock) -> EmitterResult<()> { + let last_index = block.expressions.len() - 1; + for (i, expression) in block.expressions.into_iter().enumerate() { + self.emit_expression(expression)?; + if i != last_index { + self.push(Instruction::Drop) + } + } + Ok(()) + } + + fn emit_expression(&mut self, expression: Expression) -> EmitterResult<()> { + match expression { + Expression::UnaryExpression(unary_expression) => { + self.emit_unary_expression(unary_expression) + } + Expression::BinaryExpression(binary_expression) => { + self.emit_binary_expression(binary_expression) + } + Expression::Assignment(assignment_expression) => { + self.emit_assignment(assignment_expression) + } + Expression::NumberLiteral(number_literal) => { + self.push(Instruction::F64Const(f64_const(number_literal.value))); + Ok(()) + } + Expression::FunctionCall(function_call) => self.emit_function_call(function_call), + Expression::ExpressionBlock(expression_block) => { + self.emit_expression_block(expression_block) + } + Expression::Identifier(identifier) => { + let index = self.resolve_variable(identifier.name); + self.push(Instruction::GetGlobal(index)); + Ok(()) + } + } + } + + fn emit_unary_expression(&mut self, unary_expression: UnaryExpression) -> EmitterResult<()> { + match unary_expression.op { + UnaryOperator::Plus => self.emit_expression(*unary_expression.right), + UnaryOperator::Minus => { + self.emit_expression(*unary_expression.right)?; + self.push(Instruction::F64Neg); + Ok(()) + } + UnaryOperator::Not => { + self.emit_expression(*unary_expression.right)?; + self.instructions.extend(vec![ + Instruction::F64Abs, + Instruction::F64Const(f64_const(EPSILON)), + Instruction::F64Lt, + ]); + self.push(Instruction::F64ConvertSI32); + Ok(()) + } + } + } + + fn emit_binary_expression(&mut self, binary_expression: BinaryExpression) -> EmitterResult<()> { + self.emit_expression(*binary_expression.left)?; + self.emit_expression(*binary_expression.right)?; + let op = match binary_expression.op { + BinaryOperator::Add => Instruction::F64Add, + BinaryOperator::Subtract => Instruction::F64Sub, + BinaryOperator::Multiply => Instruction::F64Mul, + BinaryOperator::Divide => { + Instruction::Call(self.resolve_builtin_function(BuiltinFunction::Div)) + } + }; + self.push(op); + Ok(()) + } + + fn emit_assignment(&mut self, assignment_expression: Assignment) -> EmitterResult<()> { + let resolved_name = self.resolve_variable(assignment_expression.left.name); + self.emit_expression(*assignment_expression.right)?; + + self.push(Instruction::SetGlobal(resolved_name)); + self.push(Instruction::GetGlobal(resolved_name)); + Ok(()) + } + + fn emit_function_call(&mut self, mut function_call: FunctionCall) -> EmitterResult<()> { + match &function_call.name.name[..] { + "int" => { + assert_arity(&function_call, 1)?; + for arg in function_call.arguments { + self.emit_expression(arg)?; + } + self.push(Instruction::F64Floor); + } + "if" => { + assert_arity(&function_call, 3)?; + + let alternate = function_call.arguments.pop().unwrap(); + let consiquent = function_call.arguments.pop().unwrap(); + let test = function_call.arguments.pop().unwrap(); + + self.emit_expression(test)?; + emit_is_not_zeroish(&mut self.instructions); + self.push(Instruction::If(BlockType::Value(ValueType::F64))); + self.emit_expression(consiquent)?; + self.push(Instruction::Else); + self.emit_expression(alternate)?; + self.push(Instruction::End); + } + "megabuf" => self.emit_memory_access(&mut function_call, 0)?, + "gmegabuf" => self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8)?, + shim_name if Shim::from_str(shim_name).is_some() => { + let shim = Shim::from_str(shim_name).unwrap(); + assert_arity(&function_call, shim.arity())?; + + for arg in function_call.arguments { + self.emit_expression(arg)?; + } + let shim_index = self.shims.get(shim); + self.push(Instruction::Call(shim_index)); + } + _ => { + return Err(CompilerError::new( + format!("Unknown function `{}`", function_call.name.name), + function_call.name.span, + )) + } + } + Ok(()) + } + + fn emit_memory_access( + &mut self, + function_call: &mut FunctionCall, + memory_offset: u32, + ) -> EmitterResult<()> { + assert_arity(&function_call, 1)?; + let index = self.resolve_local(ValueType::I32); + self.emit_expression(function_call.arguments.pop().unwrap())?; + + let call_index = self.resolve_builtin_function(BuiltinFunction::GetBufferIndex); + self.push(Instruction::Call(call_index)); + // + self.push(Instruction::TeeLocal(index)); + self.push(Instruction::I32Const(-1)); + self.push(Instruction::I32Ne); + // STACK: [in range] + self.push(Instruction::If(BlockType::Value(ValueType::F64))); + self.push(Instruction::GetLocal(index)); + self.push(Instruction::F64Load(3, memory_offset)); + self.push(Instruction::Else); + self.push(Instruction::F64Const(f64_const(0.0))); + self.push(Instruction::End); + + Ok(()) + } + + fn resolve_variable(&mut self, name: String) -> u32 { + let pool = if variable_is_register(&name) { + None + } else { + Some(self.current_pool.clone()) + }; + + 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 + .builtin_offset + .expect("Tried to compute builtin index before setting offset."); + self.builtin_functions.get(builtin) + offset + } + + fn push(&mut self, instruction: Instruction) { + self.instructions.push(instruction) + } +} + +fn emit_is_not_zeroish(instructions: &mut Vec) { + instructions.push(Instruction::F64Abs); + instructions.push(Instruction::F64Const(f64_const(EPSILON))); + instructions.push(Instruction::F64Gt); +} + +fn variable_is_register(name: &str) -> bool { + let chars: Vec<_> = name.chars().collect(); + // We avoided pulling in the regex crate! (But at what cost?) + matches!(chars.as_slice(), ['r', 'e', 'g', '0'..='9', '0'..='9']) +} + +fn assert_arity(function_call: &FunctionCall, arity: usize) -> EmitterResult<()> { + if function_call.arguments.len() != arity { + Err(CompilerError::new( + format!( + "Incorrect argument count for function `{}`. Expected {} but got {}.", + function_call.name.name, + arity, + function_call.arguments.len() + ), + // TODO: Better to underline the argument list + function_call.name.span, + )) + } else { + Ok(()) + } +} diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index 15a32a6..a5a2f86 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -1,11 +1,12 @@ mod ast; mod builtin_functions; mod constants; -mod emitter; mod error; mod file_chars; +mod function_emitter; mod index_store; mod lexer; +mod module_emitter; mod parser; mod shim; mod span; @@ -15,8 +16,8 @@ mod utils; use std::collections::{HashMap, HashSet}; use ast::EelFunction; -use emitter::emit; use error::CompilerError; +use module_emitter::emit_module; // Only exported for tests pub use lexer::Lexer; use parity_wasm::elements::FunctionType; @@ -54,5 +55,5 @@ pub fn compile( Ok((name, program, pool)) }) .collect(); - emit(eel_functions?, globals) + emit_module(eel_functions?, globals) } diff --git a/compiler-rs/src/module_emitter.rs b/compiler-rs/src/module_emitter.rs new file mode 100644 index 0000000..3214024 --- /dev/null +++ b/compiler-rs/src/module_emitter.rs @@ -0,0 +1,241 @@ +use std::collections::{HashMap, HashSet}; + +use crate::{ + ast::EelFunction, + builtin_functions::BuiltinFunction, + constants::WASM_MEMORY_SIZE, + error::{CompilerError, EmitterResult}, + function_emitter::emit_function, + index_store::IndexStore, + shim::Shim, + span::Span, + utils::f64_const, + EelFunctionType, +}; +use parity_wasm::elements::{ + CodeSection, ExportEntry, ExportSection, External, Func, FuncBody, FunctionSection, + FunctionType, GlobalEntry, GlobalSection, GlobalType, ImportEntry, ImportSection, InitExpr, + Instruction, Internal, MemorySection, MemoryType, Module, Section, Serialize, Type, + TypeSection, ValueType, +}; + +pub fn emit_module( + eel_functions: Vec<(String, EelFunction, String)>, + globals_map: HashMap>, +) -> EmitterResult> { + let mut emitter = Emitter::new(); + emitter.emit(eel_functions, globals_map) +} + +struct Emitter { + current_pool: String, + globals: IndexStore<(Option, String)>, + shims: IndexStore, + builtin_functions: IndexStore, + function_types: IndexStore, + builtin_offset: Option, +} + +impl Emitter { + fn new() -> Self { + Emitter { + current_pool: "".to_string(), // TODO: Is this okay to be empty? + globals: Default::default(), + shims: IndexStore::new(), + function_types: Default::default(), + builtin_functions: IndexStore::new(), + builtin_offset: None, + } + } + fn emit( + &mut self, + eel_functions: Vec<(String, EelFunction, String)>, + globals_map: HashMap>, // HahsMap> + ) -> EmitterResult> { + let mut imports = Vec::new(); + + for (pool_name, globals) in globals_map { + self.current_pool = pool_name; + for global in globals { + // TODO: Lots of clones. + self.resolve_variable(global.clone()); + imports.push(make_import_entry(self.current_pool.clone(), global.clone())); + } + } + + let shims: Vec = vec![Shim::Sin]; + let shim_offset = shims.len() as u32; + for shim in shims { + let field_str = shim.as_str().to_string(); + let type_ = shim.get_type(); + self.shims.ensure(shim); + imports.push(ImportEntry::new( + "shims".to_string(), + field_str, + External::Function(self.function_types.get(type_)), + )); + } + + self.builtin_offset = Some(eel_functions.len() as u32 + shim_offset); + + let (function_exports, function_bodies, funcs) = + self.emit_eel_functions(eel_functions, shim_offset)?; + + let mut sections = vec![]; + sections.push(Section::Type(self.emit_type_section())); + if let Some(import_section) = self.emit_import_section(imports) { + 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)); + } + sections.push(Section::Export(self.emit_export_section(function_exports))); + sections.push(Section::Code(self.emit_code_section(function_bodies))); + + let mut binary: Vec = Vec::new(); + Module::new(sections) + .serialize(&mut binary) + .map_err(|err| { + CompilerError::new( + format!("Module serialization error: {}", err), + Span::empty(), + ) + })?; + + Ok(binary) + } + + fn emit_type_section(&self) -> TypeSection { + let function_types = self + .function_types + .keys() + .into_iter() + .map(|function_type| { + Type::Function(FunctionType::new( + // TODO: This is clone with more steps. What's going on + function_type.params().to_vec(), + function_type.results().to_vec(), + )) + }) + .collect(); + TypeSection::with_types(function_types) + } + + fn emit_import_section(&self, imports: Vec) -> Option { + if imports.len() > 0 { + Some(ImportSection::with_entries(imports)) + } else { + None + } + } + + fn emit_function_section(&mut self, funcs: Vec) -> FunctionSection { + let mut entries = funcs.clone(); + for builtin in self.builtin_functions.keys() { + let type_idx = self.function_types.get(builtin.get_type()); + entries.push(Func::new(type_idx)); + } + FunctionSection::with_entries(entries) + } + + fn emit_global_section(&self) -> Option { + let globals: Vec = self + .globals + .keys() + .iter() + .map(|_| make_empty_global()) + .collect(); + + if globals.len() == 0 { + None + } else { + Some(GlobalSection::with_entries(globals)) + } + } + + fn emit_export_section(&self, function_exports: Vec) -> ExportSection { + ExportSection::with_entries(function_exports) + } + + fn emit_code_section(&self, function_bodies: Vec) -> CodeSection { + // TODO: Avoid this clone + let mut bodies = function_bodies.clone(); + for builtin in self.builtin_functions.keys() { + bodies.push(builtin.func_body()); + } + CodeSection::with_bodies(bodies) + } + + fn emit_eel_functions( + &mut self, + eel_functions: Vec<(String, EelFunction, String)>, + offset: u32, + ) -> EmitterResult<(Vec, Vec, Vec)> { + let mut exports = Vec::new(); + let mut function_bodies = Vec::new(); + let mut function_definitions = Vec::new(); + for (i, (name, program, pool_name)) in eel_functions.into_iter().enumerate() { + let function_body = emit_function( + program, + pool_name, + &mut self.globals, + &mut self.shims, + &mut self.builtin_functions, + &mut self.function_types, + &self.builtin_offset, + )?; + + function_bodies.push(function_body); + + exports.push(ExportEntry::new( + name, + Internal::Function(i as u32 + offset), + )); + + let function_type = self.function_types.get(FunctionType::new(vec![], vec![])); + + function_definitions.push(Func::new(function_type)) + } + Ok((exports, function_bodies, function_definitions)) + } + + fn resolve_variable(&mut self, name: String) -> u32 { + let pool = if variable_is_register(&name) { + None + } else { + Some(self.current_pool.clone()) + }; + + self.globals.get((pool, name)) + } +} + +fn variable_is_register(name: &str) -> bool { + let chars: Vec<_> = name.chars().collect(); + // We avoided pulling in the regex crate! (But at what cost?) + matches!(chars.as_slice(), ['r', 'e', 'g', '0'..='9', '0'..='9']) +} + +fn make_empty_global() -> GlobalEntry { + GlobalEntry::new( + GlobalType::new(ValueType::F64, true), + InitExpr::new(vec![ + Instruction::F64Const(f64_const(0.0)), + Instruction::End, + ]), + ) +} + +fn make_import_entry(module_str: String, field_str: String) -> ImportEntry { + ImportEntry::new( + module_str, + field_str, + External::Global(GlobalType::new(ValueType::F64, true)), + ) +} From 8f9e9ef8b7f2aa566bc6023065b8d7fab13d1f2a Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 53/85] == --- compiler-rs/src/ast.rs | 1 + compiler-rs/src/function_emitter.rs | 35 ++++++++++++++++--------- compiler-rs/src/lexer.rs | 8 +++++- compiler-rs/src/parser.rs | 12 +++++++++ compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/compatibility_test.rs | 8 ++---- 6 files changed, 46 insertions(+), 19 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index ad24f21..a444e1a 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -45,6 +45,7 @@ pub enum BinaryOperator { Subtract, Multiply, Divide, + Eq, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index da55507..e9248d0 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -141,15 +141,20 @@ impl<'a> FunctionEmitter<'a> { fn emit_binary_expression(&mut self, binary_expression: BinaryExpression) -> EmitterResult<()> { self.emit_expression(*binary_expression.left)?; self.emit_expression(*binary_expression.right)?; - let op = match binary_expression.op { - BinaryOperator::Add => Instruction::F64Add, - BinaryOperator::Subtract => Instruction::F64Sub, - BinaryOperator::Multiply => Instruction::F64Mul, + match binary_expression.op { + BinaryOperator::Add => self.push(Instruction::F64Add), + BinaryOperator::Subtract => self.push(Instruction::F64Sub), + BinaryOperator::Multiply => self.push(Instruction::F64Mul), BinaryOperator::Divide => { - Instruction::Call(self.resolve_builtin_function(BuiltinFunction::Div)) + let func_index = self.resolve_builtin_function(BuiltinFunction::Div); + self.push(Instruction::Call(func_index)) + } + BinaryOperator::Eq => { + self.push(Instruction::F64Sub); + self.emit_is_zeroish(); + self.push(Instruction::F64ConvertSI32) } }; - self.push(op); Ok(()) } @@ -179,7 +184,7 @@ impl<'a> FunctionEmitter<'a> { let test = function_call.arguments.pop().unwrap(); self.emit_expression(test)?; - emit_is_not_zeroish(&mut self.instructions); + self.emit_is_not_zeroish(); self.push(Instruction::If(BlockType::Value(ValueType::F64))); self.emit_expression(consiquent)?; self.push(Instruction::Else); @@ -260,12 +265,18 @@ impl<'a> FunctionEmitter<'a> { fn push(&mut self, instruction: Instruction) { self.instructions.push(instruction) } -} -fn emit_is_not_zeroish(instructions: &mut Vec) { - instructions.push(Instruction::F64Abs); - instructions.push(Instruction::F64Const(f64_const(EPSILON))); - instructions.push(Instruction::F64Gt); + fn emit_is_not_zeroish(&mut self) { + self.push(Instruction::F64Abs); + self.push(Instruction::F64Const(f64_const(EPSILON))); + self.push(Instruction::F64Gt); + } + + fn emit_is_zeroish(&mut self) { + self.push(Instruction::F64Abs); + self.push(Instruction::F64Const(f64_const(EPSILON))); + self.push(Instruction::F64Lt); + } } fn variable_is_register(name: &str) -> bool { diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index e494c5c..512a9bd 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -51,7 +51,13 @@ impl<'a> Lexer<'a> { _ => TokenKind::Slash, } } - '=' => self.read_char_as_kind(TokenKind::Equal), + '=' => { + self.chars.next(); + match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::DoubleEqual), + _ => TokenKind::Equal, + } + } '(' => self.read_char_as_kind(TokenKind::OpenParen), ')' => self.read_char_as_kind(TokenKind::CloseParen), ',' => self.read_char_as_kind(TokenKind::Comma), diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 60f5e83..86a781c 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -159,11 +159,23 @@ impl<'a> Parser<'a> { TokenKind::Slash if precedence < QUOTIENT_PRECEDENCE => { self.parse_quotient(next)? } + TokenKind::DoubleEqual => self.parse_comparison(next)?, _ => return Ok(next), } } } + fn parse_comparison(&mut self, left: Expression) -> ParseResult { + self.expect_kind(TokenKind::DoubleEqual)?; + // TODO: What precedence? + let right = self.parse_expression(0)?; + Ok(Expression::BinaryExpression(BinaryExpression { + left: Box::new(left), + right: Box::new(right), + op: BinaryOperator::Eq, + })) + } + fn parse_sum(&mut self, left: Expression) -> ParseResult { self.expect_kind(TokenKind::Plus)?; let right = self.parse_expression(left_associative(SUM_PRECEDENCE))?; diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 855cca6..bd0c23f 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -14,6 +14,7 @@ pub enum TokenKind { CloseParen, Comma, Semi, + DoubleEqual, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 4927cf6..7681f71 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -229,7 +229,8 @@ fn compatibility_tests() { ("Case insensitive funcs", "g = InT(10);", 10.0), ("Consecutive semis", "g = 10;;; ;g = 20;;", 20.0), ("Equality (< epsilon)", "g = 0.000009 == 0;", 1.0), - ("Equality (< -epsilon)", "g = -0.000009 == 0;", 1.0), + // TODO: Fix this + // ("Equality (< -epsilon)", "g = -0.000009 == 0;", 1.0), ("Variables don't collide", "g = 1; not_g = 2;", 1.0), ("Simple block comment", "g = 1; /* g = 10 */", 1.0), ("Block comment", "g = 1; /* g = 10 */ g = g * 2;", 2.0), @@ -416,12 +417,9 @@ fn compatibility_tests() { "Loop zero times", "Loop negative times", "Loop negative fractional times", - "Equality (true)", - "Equality epsilon", "!Equality (true)", "!Equality (false)", "!Equality epsilon", - "Equality (false)", "Less than (true)", "Less than (false)", "Greater than (true)", @@ -440,8 +438,6 @@ fn compatibility_tests() { "Gmegabuf", "Megabuf != Gmegabuf", "Gmegabuf != Megabuf", - "Equality (< epsilon)", - "Equality (< -epsilon)", "Sigmoid 1, 2", "Sigmoid 2, 1", "Sigmoid 0, 0", From a3ceda7be0eb83a0b47b12751645e48260c25741 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 54/85] Support mod --- compiler-rs/src/ast.rs | 1 + compiler-rs/src/builtin_functions.rs | 23 +++++++++++++++++++++++ compiler-rs/src/function_emitter.rs | 4 ++++ compiler-rs/src/lexer.rs | 1 + compiler-rs/src/parser.rs | 12 ++++++++++++ compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/compatibility_test.rs | 4 ---- 7 files changed, 42 insertions(+), 4 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index a444e1a..aa99626 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -45,6 +45,7 @@ pub enum BinaryOperator { Subtract, Multiply, Divide, + Mod, Eq, } diff --git a/compiler-rs/src/builtin_functions.rs b/compiler-rs/src/builtin_functions.rs index 1506965..a9904e9 100644 --- a/compiler-rs/src/builtin_functions.rs +++ b/compiler-rs/src/builtin_functions.rs @@ -9,6 +9,7 @@ use crate::EelFunctionType; #[derive(PartialEq, Eq, Hash)] pub enum BuiltinFunction { Div, + Mod, GetBufferIndex, } @@ -19,6 +20,9 @@ impl BuiltinFunction { FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) } Self::GetBufferIndex => FunctionType::new(vec![ValueType::F64], vec![ValueType::I32]), + Self::Mod => { + FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) + } } } @@ -84,6 +88,25 @@ impl BuiltinFunction { Instruction::End, ]), ), + Self::Mod => FuncBody::new( + vec![], + Instructions::new(vec![ + Instruction::GetLocal(1), + Instruction::F64Const(f64_const(0.0)), + Instruction::F64Ne, + Instruction::If(BlockType::Value(ValueType::F64)), + Instruction::GetLocal(0), + Instruction::I64TruncSF64, + Instruction::GetLocal(1), + Instruction::I64TruncSF64, + Instruction::I64RemS, + Instruction::F64ConvertSI64, + Instruction::Else, + Instruction::F64Const(f64_const(0.0)), + Instruction::End, + Instruction::End, + ]), + ), } } } diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index e9248d0..dd319a9 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -149,6 +149,10 @@ impl<'a> FunctionEmitter<'a> { let func_index = self.resolve_builtin_function(BuiltinFunction::Div); self.push(Instruction::Call(func_index)) } + BinaryOperator::Mod => { + let func_index = self.resolve_builtin_function(BuiltinFunction::Mod); + self.push(Instruction::Call(func_index)) + } BinaryOperator::Eq => { self.push(Instruction::F64Sub); self.emit_is_zeroish(); diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 512a9bd..c2fcdb3 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -63,6 +63,7 @@ impl<'a> Lexer<'a> { ',' => self.read_char_as_kind(TokenKind::Comma), ';' => self.read_char_as_kind(TokenKind::Semi), '!' => self.read_char_as_kind(TokenKind::Bang), + '%' => self.read_char_as_kind(TokenKind::Percent), c if is_whitepsace(c) => { self.chars.eat_while(is_whitepsace); return self.next_token(); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 86a781c..38c89c1 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -15,6 +15,7 @@ static SUM_PRECEDENCE: u8 = 1; static DIFFERENCE_PRECEDENCE: u8 = 1; static PRODUCT_PRECEDENCE: u8 = 2; static QUOTIENT_PRECEDENCE: u8 = 2; +static MOD_PRECEDENCE: u8 = 3; // TODO: What should this be? static PREFIX_PRECEDENCE: u8 = 6; struct Parser<'a> { @@ -159,6 +160,7 @@ impl<'a> Parser<'a> { TokenKind::Slash if precedence < QUOTIENT_PRECEDENCE => { self.parse_quotient(next)? } + TokenKind::Percent if precedence < MOD_PRECEDENCE => self.parse_mod(next)?, TokenKind::DoubleEqual => self.parse_comparison(next)?, _ => return Ok(next), } @@ -216,6 +218,16 @@ impl<'a> Parser<'a> { })) } + fn parse_mod(&mut self, left: Expression) -> ParseResult { + self.expect_kind(TokenKind::Percent)?; + let right = self.parse_expression(left_associative(MOD_PRECEDENCE))?; + Ok(Expression::BinaryExpression(BinaryExpression { + left: Box::new(left), + right: Box::new(right), + op: BinaryOperator::Mod, + })) + } + fn parse_int(&mut self) -> ParseResult { if let TokenKind::Int = self.token.kind { let value = self.lexer.source(self.token.span); diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index bd0c23f..4d0d8b9 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -15,6 +15,7 @@ pub enum TokenKind { Comma, Semi, DoubleEqual, + Percent, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 7681f71..38fcf69 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -334,8 +334,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Mod", - "Mod zero", "Bitwise and", "Bitwise or", "To the power", @@ -467,8 +465,6 @@ fn compatibility_tests() { "Exponentiation associativity", "^ has lower precedence than * (left)", "^ has lower precedence than * (right)", - "% has lower precedence than * (right)", - "% has lower precedence than * (left)", "% and ^ have the same precedence (% first)", "% and ^ have the same precedence (^ first)", "Loop limit", From e8a79aeddf8255c4721d5bb02799a6670fcdf5b2 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 55/85] Bitwise operators --- compiler-rs/src/ast.rs | 2 + compiler-rs/src/builtin_functions.rs | 34 +++++++ compiler-rs/src/function_emitter.rs | 8 ++ compiler-rs/src/lexer.rs | 2 + compiler-rs/src/parser.rs | 116 ++++++++---------------- compiler-rs/src/tokens.rs | 2 + compiler-rs/tests/compatibility_test.rs | 2 - 7 files changed, 88 insertions(+), 78 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index aa99626..6ff1b46 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -47,6 +47,8 @@ pub enum BinaryOperator { Divide, Mod, Eq, + BitwiseAnd, + BitwiseOr, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/builtin_functions.rs b/compiler-rs/src/builtin_functions.rs index a9904e9..9b5f72d 100644 --- a/compiler-rs/src/builtin_functions.rs +++ b/compiler-rs/src/builtin_functions.rs @@ -11,6 +11,8 @@ pub enum BuiltinFunction { Div, Mod, GetBufferIndex, + BitwiseAnd, + BitwiseOr, } impl BuiltinFunction { @@ -23,6 +25,12 @@ impl BuiltinFunction { Self::Mod => { FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) } + Self::BitwiseAnd => { + FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) + } + Self::BitwiseOr => { + FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) + } } } @@ -107,6 +115,32 @@ impl BuiltinFunction { Instruction::End, ]), ), + // TODO: This could probably be inlined + Self::BitwiseAnd => FuncBody::new( + vec![], + Instructions::new(vec![ + Instruction::GetLocal(0), + Instruction::I64TruncSF64, + Instruction::GetLocal(1), + Instruction::I64TruncSF64, + Instruction::I64And, + Instruction::F64ConvertSI64, + Instruction::End, + ]), + ), + // TODO: This could probably be inlined + Self::BitwiseOr => FuncBody::new( + vec![], + Instructions::new(vec![ + Instruction::GetLocal(0), + Instruction::I64TruncSF64, + Instruction::GetLocal(1), + Instruction::I64TruncSF64, + Instruction::I64Or, + Instruction::F64ConvertSI64, + Instruction::End, + ]), + ), } } } diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index dd319a9..5677b25 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -158,6 +158,14 @@ impl<'a> FunctionEmitter<'a> { self.emit_is_zeroish(); self.push(Instruction::F64ConvertSI32) } + BinaryOperator::BitwiseAnd => { + let func_index = self.resolve_builtin_function(BuiltinFunction::BitwiseAnd); + self.push(Instruction::Call(func_index)) + } + BinaryOperator::BitwiseOr => { + let func_index = self.resolve_builtin_function(BuiltinFunction::BitwiseOr); + self.push(Instruction::Call(func_index)) + } }; Ok(()) } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index c2fcdb3..2408a75 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -64,6 +64,8 @@ impl<'a> Lexer<'a> { ';' => self.read_char_as_kind(TokenKind::Semi), '!' => self.read_char_as_kind(TokenKind::Bang), '%' => self.read_char_as_kind(TokenKind::Percent), + '&' => self.read_char_as_kind(TokenKind::And), + '|' => self.read_char_as_kind(TokenKind::Pipe), c if is_whitepsace(c) => { self.chars.eat_while(is_whitepsace); return self.next_token(); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 38c89c1..7be58f0 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -11,12 +11,17 @@ use super::lexer::Lexer; use super::span::Span; use super::tokens::{Token, TokenKind}; -static SUM_PRECEDENCE: u8 = 1; -static DIFFERENCE_PRECEDENCE: u8 = 1; -static PRODUCT_PRECEDENCE: u8 = 2; -static QUOTIENT_PRECEDENCE: u8 = 2; -static MOD_PRECEDENCE: u8 = 3; // TODO: What should this be? +// https://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/ +static ASSIGNMENT_PRECEDENCE: u8 = 1; +// static CONDITIONAL_PRECEDENCE: u8 = 2; +static SUM_PRECEDENCE: u8 = 3; +static DIFFERENCE_PRECEDENCE: u8 = 3; +static PRODUCT_PRECEDENCE: u8 = 4; +static QUOTIENT_PRECEDENCE: u8 = 4; +static MOD_PRECEDENCE: u8 = 5; // A little strange, in JS this would match product/quotient static PREFIX_PRECEDENCE: u8 = 6; +// static POSTFIX_PRECEDENCE: u8 = 7; +// static CALL_PRECEDENCE: u8 = 8; struct Parser<'a> { lexer: Lexer<'a>, @@ -149,83 +154,42 @@ impl<'a> Parser<'a> { fn maybe_parse_infix(&mut self, left: Expression, precedence: u8) -> ParseResult { let mut next = left; loop { - next = match self.token.kind { - TokenKind::Plus if precedence < SUM_PRECEDENCE => self.parse_sum(next)?, - TokenKind::Minus if precedence < DIFFERENCE_PRECEDENCE => { - self.parse_difference(next)? + let (precedence, op) = match self.token.kind { + TokenKind::Plus if precedence < SUM_PRECEDENCE => { + (left_associative(SUM_PRECEDENCE), BinaryOperator::Add) } - TokenKind::Asterisk if precedence < PRODUCT_PRECEDENCE => { - self.parse_product(next)? + TokenKind::Minus if precedence < DIFFERENCE_PRECEDENCE => ( + left_associative(DIFFERENCE_PRECEDENCE), + BinaryOperator::Subtract, + ), + TokenKind::Asterisk if precedence < PRODUCT_PRECEDENCE => ( + left_associative(PRODUCT_PRECEDENCE), + BinaryOperator::Multiply, + ), + TokenKind::Slash if precedence < QUOTIENT_PRECEDENCE => ( + left_associative(QUOTIENT_PRECEDENCE), + BinaryOperator::Divide, + ), + TokenKind::Percent if precedence < MOD_PRECEDENCE => { + (left_associative(MOD_PRECEDENCE), BinaryOperator::Mod) } - TokenKind::Slash if precedence < QUOTIENT_PRECEDENCE => { - self.parse_quotient(next)? + TokenKind::DoubleEqual if precedence < ASSIGNMENT_PRECEDENCE => { + (left_associative(ASSIGNMENT_PRECEDENCE), BinaryOperator::Eq) } - TokenKind::Percent if precedence < MOD_PRECEDENCE => self.parse_mod(next)?, - TokenKind::DoubleEqual => self.parse_comparison(next)?, + TokenKind::And => (0, BinaryOperator::BitwiseAnd), + TokenKind::Pipe => (0, BinaryOperator::BitwiseOr), _ => return Ok(next), - } - } - } - - fn parse_comparison(&mut self, left: Expression) -> ParseResult { - self.expect_kind(TokenKind::DoubleEqual)?; - // TODO: What precedence? - let right = self.parse_expression(0)?; - Ok(Expression::BinaryExpression(BinaryExpression { - left: Box::new(left), - right: Box::new(right), - op: BinaryOperator::Eq, - })) - } - - fn parse_sum(&mut self, left: Expression) -> ParseResult { - self.expect_kind(TokenKind::Plus)?; - let right = self.parse_expression(left_associative(SUM_PRECEDENCE))?; - Ok(Expression::BinaryExpression(BinaryExpression { - left: Box::new(left), - right: Box::new(right), - op: BinaryOperator::Add, - })) - } + }; - fn parse_difference(&mut self, left: Expression) -> ParseResult { - self.expect_kind(TokenKind::Minus)?; - let right = self.parse_expression(left_associative(DIFFERENCE_PRECEDENCE))?; - Ok(Expression::BinaryExpression(BinaryExpression { - left: Box::new(left), - right: Box::new(right), - op: BinaryOperator::Subtract, - })) - } - - fn parse_product(&mut self, left: Expression) -> ParseResult { - self.expect_kind(TokenKind::Asterisk)?; - let right = self.parse_expression(left_associative(PRODUCT_PRECEDENCE))?; - Ok(Expression::BinaryExpression(BinaryExpression { - left: Box::new(left), - right: Box::new(right), - op: BinaryOperator::Multiply, - })) - } - - fn parse_quotient(&mut self, left: Expression) -> ParseResult { - self.expect_kind(TokenKind::Slash)?; - let right = self.parse_expression(left_associative(QUOTIENT_PRECEDENCE))?; - Ok(Expression::BinaryExpression(BinaryExpression { - left: Box::new(left), - right: Box::new(right), - op: BinaryOperator::Divide, - })) - } + self.advance()?; - fn parse_mod(&mut self, left: Expression) -> ParseResult { - self.expect_kind(TokenKind::Percent)?; - let right = self.parse_expression(left_associative(MOD_PRECEDENCE))?; - Ok(Expression::BinaryExpression(BinaryExpression { - left: Box::new(left), - right: Box::new(right), - op: BinaryOperator::Mod, - })) + let right = self.parse_expression(precedence)?; + next = Expression::BinaryExpression(BinaryExpression { + left: Box::new(next), + right: Box::new(right), + op, + }); + } } fn parse_int(&mut self) -> ParseResult { diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 4d0d8b9..3eb2207 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -16,6 +16,8 @@ pub enum TokenKind { Semi, DoubleEqual, Percent, + And, + Pipe, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 38fcf69..6fc6d45 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -334,8 +334,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Bitwise and", - "Bitwise or", "To the power", "Absolute value negative", "Absolute value positive", From b3326c9fde222b08dc4f36f1d0ef98dc704e5f43 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 56/85] Pow --- compiler-rs/src/ast.rs | 1 + compiler-rs/src/function_emitter.rs | 4 ++ compiler-rs/src/lexer.rs | 1 + compiler-rs/src/lib.rs | 1 + compiler-rs/src/module_emitter.rs | 2 +- compiler-rs/src/parser.rs | 5 ++ compiler-rs/src/shim.rs | 15 +++++- compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/common/mod.rs | 49 ++++++++++--------- compiler-rs/tests/compatibility_test.rs | 7 --- compiler-rs/tests/fixtures/wat/div.snapshot | 9 ++-- .../tests/fixtures/wat/one_plus_one.snapshot | 8 +-- compiler-rs/tests/fixtures/wat/pow.eel | 1 + compiler-rs/tests/fixtures/wat/pow.snapshot | 18 +++++++ compiler-rs/tests/fixtures/wat/reg.snapshot | 8 +-- compiler-rs/tests/fixtures/wat/sin.snapshot | 8 +-- 16 files changed, 94 insertions(+), 44 deletions(-) create mode 100644 compiler-rs/tests/fixtures/wat/pow.eel create mode 100644 compiler-rs/tests/fixtures/wat/pow.snapshot diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 6ff1b46..590d2ac 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -49,6 +49,7 @@ pub enum BinaryOperator { Eq, BitwiseAnd, BitwiseOr, + Pow, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 5677b25..4206082 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -166,6 +166,10 @@ impl<'a> FunctionEmitter<'a> { let func_index = self.resolve_builtin_function(BuiltinFunction::BitwiseOr); self.push(Instruction::Call(func_index)) } + BinaryOperator::Pow => { + let shim_index = self.shims.get(Shim::Pow); + self.push(Instruction::Call(shim_index)) + } }; Ok(()) } diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 2408a75..8935466 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -66,6 +66,7 @@ impl<'a> Lexer<'a> { '%' => self.read_char_as_kind(TokenKind::Percent), '&' => self.read_char_as_kind(TokenKind::And), '|' => self.read_char_as_kind(TokenKind::Pipe), + '^' => self.read_char_as_kind(TokenKind::Caret), c if is_whitepsace(c) => { self.chars.eat_while(is_whitepsace); return self.next_token(); diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index a5a2f86..f6cb3ae 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -22,6 +22,7 @@ use module_emitter::emit_module; pub use lexer::Lexer; use parity_wasm::elements::FunctionType; pub use parser::parse; +pub use shim::Shim; pub use tokens::Token; pub use tokens::TokenKind; diff --git a/compiler-rs/src/module_emitter.rs b/compiler-rs/src/module_emitter.rs index 3214024..7594517 100644 --- a/compiler-rs/src/module_emitter.rs +++ b/compiler-rs/src/module_emitter.rs @@ -63,7 +63,7 @@ impl Emitter { } } - let shims: Vec = vec![Shim::Sin]; + let shims: Vec = vec![Shim::Sin, Shim::Pow]; let shim_offset = shims.len() as u32; for shim in shims { let field_str = shim.as_str().to_string(); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 7be58f0..da0e797 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -18,6 +18,7 @@ static SUM_PRECEDENCE: u8 = 3; static DIFFERENCE_PRECEDENCE: u8 = 3; static PRODUCT_PRECEDENCE: u8 = 4; static QUOTIENT_PRECEDENCE: u8 = 4; +static EXPONENTIATION_PRECEDENCE: u8 = 5; static MOD_PRECEDENCE: u8 = 5; // A little strange, in JS this would match product/quotient static PREFIX_PRECEDENCE: u8 = 6; // static POSTFIX_PRECEDENCE: u8 = 7; @@ -178,6 +179,10 @@ impl<'a> Parser<'a> { } TokenKind::And => (0, BinaryOperator::BitwiseAnd), TokenKind::Pipe => (0, BinaryOperator::BitwiseOr), + TokenKind::Caret if precedence < EXPONENTIATION_PRECEDENCE => ( + left_associative(EXPONENTIATION_PRECEDENCE), + BinaryOperator::Pow, + ), _ => return Ok(next), }; diff --git a/compiler-rs/src/shim.rs b/compiler-rs/src/shim.rs index a79c57c..9c49927 100644 --- a/compiler-rs/src/shim.rs +++ b/compiler-rs/src/shim.rs @@ -5,27 +5,40 @@ use parity_wasm::elements::{FunctionType, ValueType}; #[derive(PartialEq, Eq, Hash)] pub enum Shim { Sin, + Pow, } impl Shim { pub fn get_type(&self) -> EelFunctionType { + FunctionType::new(self.get_args(), self.get_return()) + } + + pub fn get_args(&self) -> Vec { + vec![ValueType::F64; self.arity()] + } + + pub fn get_return(&self) -> Vec { match self { - Shim::Sin => FunctionType::new(vec![ValueType::F64], vec![ValueType::F64]), + Shim::Sin => vec![ValueType::F64], + Shim::Pow => vec![ValueType::F64], } } pub fn arity(&self) -> usize { match self { Shim::Sin => 1, + Shim::Pow => 2, } } pub fn as_str(&self) -> &str { match self { Shim::Sin => "sin", + Shim::Pow => "pow", } } pub fn from_str(name: &str) -> Option { match name { "sin" => Some(Shim::Sin), + "pow" => Some(Shim::Pow), _ => None, } } diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 3eb2207..75f2f26 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -18,6 +18,7 @@ pub enum TokenKind { Percent, And, Pipe, + Caret, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/common/mod.rs b/compiler-rs/tests/common/mod.rs index 99ee94d..0fb022d 100644 --- a/compiler-rs/tests/common/mod.rs +++ b/compiler-rs/tests/common/mod.rs @@ -2,7 +2,7 @@ extern crate eel_wasm; use std::collections::{HashMap, HashSet}; -use eel_wasm::compile; +use eel_wasm::{compile, Shim}; use wasmi::{nan_preserving_float::F64, ImportsBuilder}; use wasmi::{ Error as WasmiError, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, @@ -20,13 +20,6 @@ impl GlobalPool { g: GlobalInstance::alloc(RuntimeValue::F64(0.0.into()), true), } } - fn check_signature(&self, index: usize, signature: &Signature) -> bool { - let (params, ret_ty): (&[ValueType], Option) = match index { - SIN_FUNC_INDEX => (&[ValueType::F64], Some(ValueType::F64)), - _ => return false, - }; - signature.params() == params && signature.return_type() == ret_ty - } } impl ModuleImportResolver for GlobalPool { @@ -42,31 +35,35 @@ impl ModuleImportResolver for GlobalPool { Ok(global) } fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { - let index = match field_name { - "sin" => SIN_FUNC_INDEX, - _ => { - return Err(WasmiError::Instantiation(format!( - "Export {} not found", - field_name - ))) - } - }; + let shim = Shim::from_str(field_name).ok_or(WasmiError::Instantiation(format!( + "Export {} not found", + field_name + )))?; + /* if !self.check_signature(index, signature) { return Err(WasmiError::Instantiation(format!( "Export {} has a bad signature", field_name ))); } - - Ok(FuncInstance::alloc_host( - Signature::new(&[ValueType::F64][..], Some(ValueType::F64)), - index, - )) + */ + + Ok(match shim { + Shim::Sin => FuncInstance::alloc_host( + Signature::new(&[ValueType::F64][..], Some(ValueType::F64)), + SIN_FUNC_INDEX, + ), + Shim::Pow => FuncInstance::alloc_host( + Signature::new(&[ValueType::F64, ValueType::F64][..], Some(ValueType::F64)), + POW_FUNC_INDEX, + ), + }) } } const SIN_FUNC_INDEX: usize = 0; +const POW_FUNC_INDEX: usize = 1; impl Externals for GlobalPool { fn invoke_index( @@ -82,6 +79,14 @@ impl Externals for GlobalPool { Ok(Some(RuntimeValue::F64(F64::from(result)))) } + POW_FUNC_INDEX => { + let a: F64 = args.nth_checked(0)?; + let b: F64 = args.nth_checked(1)?; + + let result = a.to_float().powf(b.to_float()); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } _ => panic!("Unimplemented function at {}", index), } } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 6fc6d45..1f52573 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -78,7 +78,6 @@ fn compatibility_tests() { ("Line comments (\\\\)", "g = 10; \\\\ g = 20;", 10.0), ("Equal (false)", "g = equal(10, 5.0);", 0.0), ("Equal (true)", "g = equal(10, 10.0);", 1.0), - ("Pow", "g = pow(2, 10.0);", 1024.0), ("Log", "g = log(10);", 10_f64.log(std::f64::consts::E)), ("Log10", "g = log10(10);", 10_f64.log10()), ("Sign (10)", "g = sign(10);", 1.0), @@ -334,7 +333,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "To the power", "Absolute value negative", "Absolute value positive", "Function used as expression", @@ -460,11 +458,6 @@ fn compatibility_tests() { "gmegabuf does not write megabuf", "megabuf does not write gmegabuf", "Adjacent buf indicies don\'t collide", - "Exponentiation associativity", - "^ has lower precedence than * (left)", - "^ has lower precedence than * (right)", - "% and ^ have the same precedence (% first)", - "% and ^ have the same precedence (^ first)", "Loop limit", ]; diff --git a/compiler-rs/tests/fixtures/wat/div.snapshot b/compiler-rs/tests/fixtures/wat/div.snapshot index 5976b69..675fb81 100644 --- a/compiler-rs/tests/fixtures/wat/div.snapshot +++ b/compiler-rs/tests/fixtures/wat/div.snapshot @@ -5,14 +5,15 @@ g = 100 / 0 (type (;1;) (func (param f64 f64) (result f64))) (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) - (func (;1;) (type 2) + (import "shims" "pow" (func (;1;) (type 1))) + (func (;2;) (type 2) f64.const 0x1.9p+6 (;=100;) f64.const 0x0p+0 (;=0;) - call 2 + call 3 global.set 0 global.get 0 drop) - (func (;2;) (type 1) (param f64 f64) (result f64) + (func (;3;) (type 1) (param f64 f64) (result f64) (local i32) local.get 1 f64.const 0x0p+0 (;=0;) @@ -26,4 +27,4 @@ g = 100 / 0 end) (memory (;0;) 2048 2048) (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) - (export "test" (func 1))) + (export "test" (func 2))) diff --git a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot index c0acc27..69b999a 100644 --- a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot +++ b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot @@ -2,12 +2,14 @@ ======================================================================== (module (type (;0;) (func (param f64) (result f64))) - (type (;1;) (func)) + (type (;1;) (func (param f64 f64) (result f64))) + (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) - (func (;1;) (type 1) + (import "shims" "pow" (func (;1;) (type 1))) + (func (;2;) (type 2) f64.const 0x1p+0 (;=1;) f64.const 0x1p+0 (;=1;) f64.add drop) (memory (;0;) 2048 2048) - (export "test" (func 1))) + (export "test" (func 2))) diff --git a/compiler-rs/tests/fixtures/wat/pow.eel b/compiler-rs/tests/fixtures/wat/pow.eel new file mode 100644 index 0000000..deb9412 --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/pow.eel @@ -0,0 +1 @@ +g = 5 ^ 2; \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/wat/pow.snapshot b/compiler-rs/tests/fixtures/wat/pow.snapshot new file mode 100644 index 0000000..caa0354 --- /dev/null +++ b/compiler-rs/tests/fixtures/wat/pow.snapshot @@ -0,0 +1,18 @@ +g = 5 ^ 2; +======================================================================== +(module + (type (;0;) (func (param f64) (result f64))) + (type (;1;) (func (param f64 f64) (result f64))) + (type (;2;) (func)) + (import "shims" "sin" (func (;0;) (type 0))) + (import "shims" "pow" (func (;1;) (type 1))) + (func (;2;) (type 2) + f64.const 0x1.4p+2 (;=5;) + f64.const 0x1p+1 (;=2;) + call 1 + global.set 0 + global.get 0 + drop) + (memory (;0;) 2048 2048) + (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) + (export "test" (func 2))) diff --git a/compiler-rs/tests/fixtures/wat/reg.snapshot b/compiler-rs/tests/fixtures/wat/reg.snapshot index f3260a8..ff16e2d 100644 --- a/compiler-rs/tests/fixtures/wat/reg.snapshot +++ b/compiler-rs/tests/fixtures/wat/reg.snapshot @@ -2,13 +2,15 @@ reg00=10 ======================================================================== (module (type (;0;) (func (param f64) (result f64))) - (type (;1;) (func)) + (type (;1;) (func (param f64 f64) (result f64))) + (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) - (func (;1;) (type 1) + (import "shims" "pow" (func (;1;) (type 1))) + (func (;2;) (type 2) f64.const 0x1.4p+3 (;=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))) + (export "test" (func 2))) diff --git a/compiler-rs/tests/fixtures/wat/sin.snapshot b/compiler-rs/tests/fixtures/wat/sin.snapshot index 45851fa..b91dead 100644 --- a/compiler-rs/tests/fixtures/wat/sin.snapshot +++ b/compiler-rs/tests/fixtures/wat/sin.snapshot @@ -2,11 +2,13 @@ sin(100) ======================================================================== (module (type (;0;) (func (param f64) (result f64))) - (type (;1;) (func)) + (type (;1;) (func (param f64 f64) (result f64))) + (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) - (func (;1;) (type 1) + (import "shims" "pow" (func (;1;) (type 1))) + (func (;2;) (type 2) f64.const 0x1.9p+6 (;=100;) call 0 drop) (memory (;0;) 2048 2048) - (export "test" (func 1))) + (export "test" (func 2))) From 31c8d0f73091f40067b4fc60b4e72ead4141e479 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 57/85] More inlined functions --- compiler-rs/src/function_emitter.rs | 81 +++++++++++++++++++++++-- compiler-rs/tests/compatibility_test.rs | 23 ------- 2 files changed, 75 insertions(+), 29 deletions(-) diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 4206082..0abcee6 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -187,9 +187,8 @@ impl<'a> FunctionEmitter<'a> { match &function_call.name.name[..] { "int" => { assert_arity(&function_call, 1)?; - for arg in function_call.arguments { - self.emit_expression(arg)?; - } + self.emit_function_args(function_call)?; + self.push(Instruction::F64Floor); } "if" => { @@ -207,15 +206,79 @@ impl<'a> FunctionEmitter<'a> { self.emit_expression(alternate)?; self.push(Instruction::End); } + "abs" => { + assert_arity(&function_call, 1)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Abs) + } + "sqrt" => { + assert_arity(&function_call, 1)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Abs); + self.push(Instruction::F64Sqrt) + } + "min" => { + assert_arity(&function_call, 2)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Min) + } + "max" => { + assert_arity(&function_call, 2)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Max) + } + "above" => { + assert_arity(&function_call, 2)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Gt); + self.push(Instruction::F64ConvertSI32) + } + "below" => { + assert_arity(&function_call, 2)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Lt); + self.push(Instruction::F64ConvertSI32) + } + "equal" => { + assert_arity(&function_call, 2)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Sub); + self.emit_is_zeroish(); + self.push(Instruction::F64ConvertSI32) + } + "bnot" => { + assert_arity(&function_call, 1)?; + self.emit_function_args(function_call)?; + + self.emit_is_zeroish(); + self.push(Instruction::F64ConvertSI32) + } + "floor" => { + assert_arity(&function_call, 1)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Floor) + } + "ceil" => { + assert_arity(&function_call, 1)?; + self.emit_function_args(function_call)?; + + self.push(Instruction::F64Ceil) + } "megabuf" => self.emit_memory_access(&mut function_call, 0)?, "gmegabuf" => self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8)?, shim_name if Shim::from_str(shim_name).is_some() => { let shim = Shim::from_str(shim_name).unwrap(); assert_arity(&function_call, shim.arity())?; + self.emit_function_args(function_call)?; - for arg in function_call.arguments { - self.emit_expression(arg)?; - } let shim_index = self.shims.get(shim); self.push(Instruction::Call(shim_index)); } @@ -228,6 +291,12 @@ impl<'a> FunctionEmitter<'a> { } Ok(()) } + fn emit_function_args(&mut self, function_call: FunctionCall) -> EmitterResult<()> { + for arg in function_call.arguments { + self.emit_expression(arg)?; + } + Ok(()) + } fn emit_memory_access( &mut self, diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 1f52573..1ebf44f 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -333,15 +333,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Absolute value negative", - "Absolute value positive", - "Function used as expression", - "Min", - "Min reversed", - "Max", - "Max reversed", - "Sqrt", - "Sqrt (negative)", "Sqr", "Cos", "Tan", @@ -349,13 +340,7 @@ fn compatibility_tests() { "Acos", "Atan", "Atan2", - "above (true)", - "above (false)", - "below (true)", - "below (false)", "Line comments (\\\\)", - "Equal (false)", - "Equal (true)", "Pow", "Log", "Log10", @@ -375,10 +360,6 @@ fn compatibility_tests() { "Band (false, false)", "Band does not shortcircut", "Band respects epsilon", - "Bnot (true)", - "Bnot (false)", - "Bnot 0.1", - "Bnot < epsilon", "Plus equals", "Plus equals (local var)", "Plus equals (megabuf)", @@ -437,10 +418,6 @@ fn compatibility_tests() { "Sigmoid 0, 0", "Sigmoid 10, 10", "Exp", - "Floor", - "Floor", - "Ceil", - "Ceil", "Assign", "Assign return value", "EPSILON buffer indexes", From 95f25b9aaf90b4836c78750e4cb37a9f102ab923 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Tue, 6 Apr 2021 22:28:17 -0700 Subject: [PATCH 58/85] Simplify return values --- compiler-rs/src/shim.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler-rs/src/shim.rs b/compiler-rs/src/shim.rs index 9c49927..70359a2 100644 --- a/compiler-rs/src/shim.rs +++ b/compiler-rs/src/shim.rs @@ -17,11 +17,9 @@ impl Shim { vec![ValueType::F64; self.arity()] } + // All shims return a value pub fn get_return(&self) -> Vec { - match self { - Shim::Sin => vec![ValueType::F64], - Shim::Pow => vec![ValueType::F64], - } + vec![ValueType::F64] } pub fn arity(&self) -> usize { match self { From 4885f7da66adc423b4aa50204100164ff00e4350 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 59/85] More builtin functions --- compiler-rs/src/builtin_functions.rs | 63 +++++++++++++++++++++++++ compiler-rs/src/function_emitter.rs | 29 ++++++++++++ compiler-rs/tests/compatibility_test.rs | 15 +----- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/compiler-rs/src/builtin_functions.rs b/compiler-rs/src/builtin_functions.rs index 9b5f72d..b6dbcf2 100644 --- a/compiler-rs/src/builtin_functions.rs +++ b/compiler-rs/src/builtin_functions.rs @@ -11,8 +11,11 @@ pub enum BuiltinFunction { Div, Mod, GetBufferIndex, + LogicalOr, + LogicalAnd, BitwiseAnd, BitwiseOr, + Sqr, } impl BuiltinFunction { @@ -31,6 +34,13 @@ impl BuiltinFunction { Self::BitwiseOr => { FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) } + Self::LogicalAnd => { + FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) + } + Self::LogicalOr => { + FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) + } + Self::Sqr => FunctionType::new(vec![ValueType::F64], vec![ValueType::F64]), } } @@ -141,6 +151,59 @@ impl BuiltinFunction { Instruction::End, ]), ), + Self::Sqr => FuncBody::new( + vec![], + Instructions::new(vec![ + Instruction::GetLocal(0), + Instruction::GetLocal(0), + Instruction::F64Mul, + Instruction::End, + ]), + ), + Self::LogicalAnd => FuncBody::new( + vec![], + Instructions::new(vec![ + Instruction::GetLocal(0), + // is not zeroish + Instruction::F64Abs, + Instruction::F64Const(f64_const(EPSILON)), + Instruction::F64Gt, + // end is not zeroish + Instruction::GetLocal(1), + // is not zeroish + Instruction::F64Abs, + Instruction::F64Const(f64_const(EPSILON)), + Instruction::F64Gt, + // end is not zeroish + Instruction::I32And, + Instruction::I32Const(0), + Instruction::I32Ne, + Instruction::F64ConvertSI32, + Instruction::End, + ]), + ), + Self::LogicalOr => FuncBody::new( + vec![], + Instructions::new(vec![ + Instruction::GetLocal(0), + // is not zeroish + Instruction::F64Abs, + Instruction::F64Const(f64_const(EPSILON)), + Instruction::F64Gt, + // end is not zeroish + Instruction::GetLocal(1), + // is not zeroish + Instruction::F64Abs, + Instruction::F64Const(f64_const(EPSILON)), + Instruction::F64Gt, + // end is not zeroish + Instruction::I32Or, + Instruction::I32Const(0), + Instruction::I32Ne, + Instruction::F64ConvertSI32, + Instruction::End, + ]), + ), } } } diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 0abcee6..97fc4be 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -272,6 +272,35 @@ impl<'a> FunctionEmitter<'a> { self.push(Instruction::F64Ceil) } + "sqr" => { + assert_arity(&function_call, 1)?; + self.emit_function_args(function_call)?; + let func_index = self.resolve_builtin_function(BuiltinFunction::Sqr); + + self.push(Instruction::Call(func_index)) + } + "bor" => { + assert_arity(&function_call, 2)?; + self.emit_function_args(function_call)?; + let func_index = self.resolve_builtin_function(BuiltinFunction::LogicalOr); + + self.push(Instruction::Call(func_index)) + } + "band" => { + assert_arity(&function_call, 2)?; + self.emit_function_args(function_call)?; + let func_index = self.resolve_builtin_function(BuiltinFunction::LogicalAnd); + + self.push(Instruction::Call(func_index)) + } + // TODO: Add a test for this + "mod" => { + assert_arity(&function_call, 2)?; + self.emit_function_args(function_call)?; + let func_index = self.resolve_builtin_function(BuiltinFunction::Mod); + + self.push(Instruction::Call(func_index)) + } "megabuf" => self.emit_memory_access(&mut function_call, 0)?, "gmegabuf" => self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8)?, shim_name if Shim::from_str(shim_name).is_some() => { diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 1ebf44f..7d4251b 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -333,7 +333,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Sqr", "Cos", "Tan", "Asin", @@ -348,18 +347,6 @@ fn compatibility_tests() { "Sign (-10)", "Sign (0)", "Sign (-0)", - "Bor (true, false)", - "Bor (false, true)", - "Bor (true, true)", - "Bor (false, false)", - "Bor does not shortcircut", - "Bor respects epsilon", - "Band (true, false)", - "Band (false, true)", - "Band (true, true)", - "Band (false, false)", - "Band does not shortcircut", - "Band respects epsilon", "Plus equals", "Plus equals (local var)", "Plus equals (megabuf)", @@ -438,6 +425,8 @@ fn compatibility_tests() { "Loop limit", ]; + println!("Failing: {}/{}", expected_failing.len(), test_cases.len()); + for (name, code, expected) in test_cases { let mut globals = HashMap::default(); let mut pool_globals = HashSet::new(); From 12538dfe8943383dd7cb764b81412a58612ae37c Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 60/85] Implement all shims --- compiler-rs/src/module_emitter.rs | 15 +- compiler-rs/src/shim.rs | 40 +++++ compiler-rs/tests/common/mod.rs | 148 +++++++++++++++--- compiler-rs/tests/compatibility_test.rs | 14 -- compiler-rs/tests/fixtures/wat/div.snapshot | 18 ++- .../tests/fixtures/wat/one_plus_one.snapshot | 14 +- compiler-rs/tests/fixtures/wat/pow.snapshot | 14 +- compiler-rs/tests/fixtures/wat/reg.snapshot | 14 +- compiler-rs/tests/fixtures/wat/sin.snapshot | 14 +- 9 files changed, 242 insertions(+), 49 deletions(-) diff --git a/compiler-rs/src/module_emitter.rs b/compiler-rs/src/module_emitter.rs index 7594517..c395894 100644 --- a/compiler-rs/src/module_emitter.rs +++ b/compiler-rs/src/module_emitter.rs @@ -63,7 +63,20 @@ impl Emitter { } } - let shims: Vec = vec![Shim::Sin, Shim::Pow]; + let shims: Vec = vec![ + Shim::Sin, + Shim::Pow, + Shim::Cos, + Shim::Tan, + Shim::Asin, + Shim::Acos, + Shim::Atan, + Shim::Atan2, + Shim::Log, + Shim::Log10, + Shim::Sigmoid, + Shim::Exp, + ]; let shim_offset = shims.len() as u32; for shim in shims { let field_str = shim.as_str().to_string(); diff --git a/compiler-rs/src/shim.rs b/compiler-rs/src/shim.rs index 70359a2..da097a7 100644 --- a/compiler-rs/src/shim.rs +++ b/compiler-rs/src/shim.rs @@ -6,6 +6,16 @@ use parity_wasm::elements::{FunctionType, ValueType}; pub enum Shim { Sin, Pow, + Cos, + Tan, + Asin, + Acos, + Atan, + Atan2, + Log, + Log10, + Sigmoid, + Exp, } impl Shim { @@ -25,18 +35,48 @@ impl Shim { match self { Shim::Sin => 1, Shim::Pow => 2, + Shim::Cos => 1, + Shim::Tan => 1, + Shim::Asin => 1, + Shim::Acos => 1, + Shim::Atan => 1, + Shim::Atan2 => 2, + Shim::Log => 1, + Shim::Log10 => 1, + Shim::Sigmoid => 2, + Shim::Exp => 1, } } pub fn as_str(&self) -> &str { match self { Shim::Sin => "sin", Shim::Pow => "pow", + Shim::Cos => "cos", + Shim::Tan => "tan", + Shim::Asin => "asin", + Shim::Acos => "acos", + Shim::Atan => "atan", + Shim::Atan2 => "atan2", + Shim::Log => "log", + Shim::Log10 => "log10", + Shim::Sigmoid => "sigmoid", + Shim::Exp => "exp", } } pub fn from_str(name: &str) -> Option { match name { "sin" => Some(Shim::Sin), "pow" => Some(Shim::Pow), + "cos" => Some(Shim::Cos), + "tan" => Some(Shim::Tan), + "asin" => Some(Shim::Asin), + "acos" => Some(Shim::Acos), + "atan" => Some(Shim::Atan), + "atan2" => Some(Shim::Atan2), + "log" => Some(Shim::Log), + "log10" => Some(Shim::Log10), + "sigmoid" => Some(Shim::Sigmoid), + "exp" => Some(Shim::Exp), _ => None, } } diff --git a/compiler-rs/tests/common/mod.rs b/compiler-rs/tests/common/mod.rs index 0fb022d..40026b2 100644 --- a/compiler-rs/tests/common/mod.rs +++ b/compiler-rs/tests/common/mod.rs @@ -1,6 +1,9 @@ extern crate eel_wasm; -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + f64::EPSILON, +}; use eel_wasm::{compile, Shim}; use wasmi::{nan_preserving_float::F64, ImportsBuilder}; @@ -22,6 +25,41 @@ impl GlobalPool { } } +fn get_shim_index(shim: Shim) -> usize { + match shim { + Shim::Sin => 1, + Shim::Pow => 2, + Shim::Cos => 3, + Shim::Tan => 4, + Shim::Asin => 5, + Shim::Acos => 6, + Shim::Atan => 7, + Shim::Atan2 => 8, + Shim::Log => 9, + Shim::Log10 => 10, + Shim::Sigmoid => 11, + Shim::Exp => 12, + } +} + +fn get_shim_from_index(index: usize) -> Shim { + match index { + 1 => Shim::Sin, + 2 => Shim::Pow, + 3 => Shim::Cos, + 4 => Shim::Tan, + 5 => Shim::Asin, + 6 => Shim::Acos, + 7 => Shim::Atan, + 8 => Shim::Atan2, + 9 => Shim::Log, + 10 => Shim::Log10, + 11 => Shim::Sigmoid, + 12 => Shim::Exp, + _ => panic!("Could not find shim at index"), + } +} + impl ModuleImportResolver for GlobalPool { fn resolve_global( &self, @@ -40,46 +78,37 @@ impl ModuleImportResolver for GlobalPool { field_name )))?; - /* - if !self.check_signature(index, signature) { + if signature.params().len() != shim.arity() || !signature.return_type().is_some() { return Err(WasmiError::Instantiation(format!( "Export {} has a bad signature", field_name ))); } - */ - - Ok(match shim { - Shim::Sin => FuncInstance::alloc_host( - Signature::new(&[ValueType::F64][..], Some(ValueType::F64)), - SIN_FUNC_INDEX, - ), - Shim::Pow => FuncInstance::alloc_host( - Signature::new(&[ValueType::F64, ValueType::F64][..], Some(ValueType::F64)), - POW_FUNC_INDEX, - ), - }) + + let params = vec![ValueType::F64; shim.arity()]; + + Ok(FuncInstance::alloc_host( + Signature::new(params, Some(ValueType::F64)), + get_shim_index(shim), + )) } } -const SIN_FUNC_INDEX: usize = 0; -const POW_FUNC_INDEX: usize = 1; - impl Externals for GlobalPool { fn invoke_index( &mut self, index: usize, args: RuntimeArgs, ) -> Result, Trap> { - match index { - SIN_FUNC_INDEX => { + match get_shim_from_index(index) { + Shim::Sin => { let a: F64 = args.nth_checked(0)?; let result = a.to_float().sin(); Ok(Some(RuntimeValue::F64(F64::from(result)))) } - POW_FUNC_INDEX => { + Shim::Pow => { let a: F64 = args.nth_checked(0)?; let b: F64 = args.nth_checked(1)?; @@ -87,7 +116,82 @@ impl Externals for GlobalPool { Ok(Some(RuntimeValue::F64(F64::from(result)))) } - _ => panic!("Unimplemented function at {}", index), + Shim::Cos => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().cos(); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Tan => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().tan(); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Asin => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().asin(); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Acos => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().acos(); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Atan => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().atan(); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Atan2 => { + let a: F64 = args.nth_checked(0)?; + let b: F64 = args.nth_checked(1)?; + + let result = a.to_float().atan2(b.to_float()); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Log => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().log(std::f64::consts::E); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Log10 => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().log10(); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Sigmoid => { + let a: F64 = args.nth_checked(0)?; + let b: F64 = args.nth_checked(1)?; + + let x = a.to_float(); + let y = b.to_float(); + + let t = 1.0 + (-x * y).exp(); + let result = if t.abs() > EPSILON { 1.0 / t } else { 0.0 }; + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } + Shim::Exp => { + let a: F64 = args.nth_checked(0)?; + + let result = a.to_float().exp(); + + Ok(Some(RuntimeValue::F64(F64::from(result)))) + } } } } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 7d4251b..b825bf7 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -333,16 +333,7 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Cos", - "Tan", - "Asin", - "Acos", - "Atan", - "Atan2", "Line comments (\\\\)", - "Pow", - "Log", - "Log10", "Sign (10)", "Sign (-10)", "Sign (0)", @@ -400,11 +391,6 @@ fn compatibility_tests() { "Gmegabuf", "Megabuf != Gmegabuf", "Gmegabuf != Megabuf", - "Sigmoid 1, 2", - "Sigmoid 2, 1", - "Sigmoid 0, 0", - "Sigmoid 10, 10", - "Exp", "Assign", "Assign return value", "EPSILON buffer indexes", diff --git a/compiler-rs/tests/fixtures/wat/div.snapshot b/compiler-rs/tests/fixtures/wat/div.snapshot index 675fb81..5223f01 100644 --- a/compiler-rs/tests/fixtures/wat/div.snapshot +++ b/compiler-rs/tests/fixtures/wat/div.snapshot @@ -6,14 +6,24 @@ g = 100 / 0 (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) (import "shims" "pow" (func (;1;) (type 1))) - (func (;2;) (type 2) + (import "shims" "cos" (func (;2;) (type 0))) + (import "shims" "tan" (func (;3;) (type 0))) + (import "shims" "asin" (func (;4;) (type 0))) + (import "shims" "acos" (func (;5;) (type 0))) + (import "shims" "atan" (func (;6;) (type 0))) + (import "shims" "atan2" (func (;7;) (type 1))) + (import "shims" "log" (func (;8;) (type 0))) + (import "shims" "log10" (func (;9;) (type 0))) + (import "shims" "sigmoid" (func (;10;) (type 1))) + (import "shims" "exp" (func (;11;) (type 0))) + (func (;12;) (type 2) f64.const 0x1.9p+6 (;=100;) f64.const 0x0p+0 (;=0;) - call 3 + call 13 global.set 0 global.get 0 drop) - (func (;3;) (type 1) (param f64 f64) (result f64) + (func (;13;) (type 1) (param f64 f64) (result f64) (local i32) local.get 1 f64.const 0x0p+0 (;=0;) @@ -27,4 +37,4 @@ g = 100 / 0 end) (memory (;0;) 2048 2048) (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) - (export "test" (func 2))) + (export "test" (func 12))) diff --git a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot index 69b999a..00b9083 100644 --- a/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot +++ b/compiler-rs/tests/fixtures/wat/one_plus_one.snapshot @@ -6,10 +6,20 @@ (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) (import "shims" "pow" (func (;1;) (type 1))) - (func (;2;) (type 2) + (import "shims" "cos" (func (;2;) (type 0))) + (import "shims" "tan" (func (;3;) (type 0))) + (import "shims" "asin" (func (;4;) (type 0))) + (import "shims" "acos" (func (;5;) (type 0))) + (import "shims" "atan" (func (;6;) (type 0))) + (import "shims" "atan2" (func (;7;) (type 1))) + (import "shims" "log" (func (;8;) (type 0))) + (import "shims" "log10" (func (;9;) (type 0))) + (import "shims" "sigmoid" (func (;10;) (type 1))) + (import "shims" "exp" (func (;11;) (type 0))) + (func (;12;) (type 2) f64.const 0x1p+0 (;=1;) f64.const 0x1p+0 (;=1;) f64.add drop) (memory (;0;) 2048 2048) - (export "test" (func 2))) + (export "test" (func 12))) diff --git a/compiler-rs/tests/fixtures/wat/pow.snapshot b/compiler-rs/tests/fixtures/wat/pow.snapshot index caa0354..4170373 100644 --- a/compiler-rs/tests/fixtures/wat/pow.snapshot +++ b/compiler-rs/tests/fixtures/wat/pow.snapshot @@ -6,7 +6,17 @@ g = 5 ^ 2; (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) (import "shims" "pow" (func (;1;) (type 1))) - (func (;2;) (type 2) + (import "shims" "cos" (func (;2;) (type 0))) + (import "shims" "tan" (func (;3;) (type 0))) + (import "shims" "asin" (func (;4;) (type 0))) + (import "shims" "acos" (func (;5;) (type 0))) + (import "shims" "atan" (func (;6;) (type 0))) + (import "shims" "atan2" (func (;7;) (type 1))) + (import "shims" "log" (func (;8;) (type 0))) + (import "shims" "log10" (func (;9;) (type 0))) + (import "shims" "sigmoid" (func (;10;) (type 1))) + (import "shims" "exp" (func (;11;) (type 0))) + (func (;12;) (type 2) f64.const 0x1.4p+2 (;=5;) f64.const 0x1p+1 (;=2;) call 1 @@ -15,4 +25,4 @@ g = 5 ^ 2; drop) (memory (;0;) 2048 2048) (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) - (export "test" (func 2))) + (export "test" (func 12))) diff --git a/compiler-rs/tests/fixtures/wat/reg.snapshot b/compiler-rs/tests/fixtures/wat/reg.snapshot index ff16e2d..7cd8f98 100644 --- a/compiler-rs/tests/fixtures/wat/reg.snapshot +++ b/compiler-rs/tests/fixtures/wat/reg.snapshot @@ -6,11 +6,21 @@ reg00=10 (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) (import "shims" "pow" (func (;1;) (type 1))) - (func (;2;) (type 2) + (import "shims" "cos" (func (;2;) (type 0))) + (import "shims" "tan" (func (;3;) (type 0))) + (import "shims" "asin" (func (;4;) (type 0))) + (import "shims" "acos" (func (;5;) (type 0))) + (import "shims" "atan" (func (;6;) (type 0))) + (import "shims" "atan2" (func (;7;) (type 1))) + (import "shims" "log" (func (;8;) (type 0))) + (import "shims" "log10" (func (;9;) (type 0))) + (import "shims" "sigmoid" (func (;10;) (type 1))) + (import "shims" "exp" (func (;11;) (type 0))) + (func (;12;) (type 2) f64.const 0x1.4p+3 (;=10;) global.set 0 global.get 0 drop) (memory (;0;) 2048 2048) (global (;0;) (mut f64) (f64.const 0x0p+0 (;=0;))) - (export "test" (func 2))) + (export "test" (func 12))) diff --git a/compiler-rs/tests/fixtures/wat/sin.snapshot b/compiler-rs/tests/fixtures/wat/sin.snapshot index b91dead..8ee87e2 100644 --- a/compiler-rs/tests/fixtures/wat/sin.snapshot +++ b/compiler-rs/tests/fixtures/wat/sin.snapshot @@ -6,9 +6,19 @@ sin(100) (type (;2;) (func)) (import "shims" "sin" (func (;0;) (type 0))) (import "shims" "pow" (func (;1;) (type 1))) - (func (;2;) (type 2) + (import "shims" "cos" (func (;2;) (type 0))) + (import "shims" "tan" (func (;3;) (type 0))) + (import "shims" "asin" (func (;4;) (type 0))) + (import "shims" "acos" (func (;5;) (type 0))) + (import "shims" "atan" (func (;6;) (type 0))) + (import "shims" "atan2" (func (;7;) (type 1))) + (import "shims" "log" (func (;8;) (type 0))) + (import "shims" "log10" (func (;9;) (type 0))) + (import "shims" "sigmoid" (func (;10;) (type 1))) + (import "shims" "exp" (func (;11;) (type 0))) + (func (;12;) (type 2) f64.const 0x1.9p+6 (;=100;) call 0 drop) (memory (;0;) 2048 2048) - (export "test" (func 2))) + (export "test" (func 12))) From 9becdb727b61eabc1282890a606cc59e774b6e6c Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 61/85] sign() --- compiler-rs/src/builtin_functions.rs | 16 ++++++++++++++++ compiler-rs/src/function_emitter.rs | 7 +++++++ compiler-rs/tests/compatibility_test.rs | 4 ---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/compiler-rs/src/builtin_functions.rs b/compiler-rs/src/builtin_functions.rs index b6dbcf2..0099bc9 100644 --- a/compiler-rs/src/builtin_functions.rs +++ b/compiler-rs/src/builtin_functions.rs @@ -16,6 +16,7 @@ pub enum BuiltinFunction { BitwiseAnd, BitwiseOr, Sqr, + Sign, } impl BuiltinFunction { @@ -41,11 +42,26 @@ impl BuiltinFunction { FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) } Self::Sqr => FunctionType::new(vec![ValueType::F64], vec![ValueType::F64]), + Self::Sign => FunctionType::new(vec![ValueType::F64], vec![ValueType::F64]), } } pub fn func_body(&self) -> FuncBody { match self { + Self::Sign => FuncBody::new( + vec![], + Instructions::new(vec![ + Instruction::F64Const(f64_const(0.0)), + Instruction::GetLocal(0), + Instruction::F64Lt, + Instruction::GetLocal(0), + Instruction::F64Const(f64_const(0.0)), + Instruction::F64Lt, + Instruction::I32Sub, + Instruction::F64ConvertSI32, + Instruction::End, + ]), + ), Self::Div => FuncBody::new( vec![Local::new(1, ValueType::I32)], Instructions::new(vec![ diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 97fc4be..899483d 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -301,6 +301,13 @@ impl<'a> FunctionEmitter<'a> { self.push(Instruction::Call(func_index)) } + "sign" => { + assert_arity(&function_call, 1)?; + self.emit_function_args(function_call)?; + let func_index = self.resolve_builtin_function(BuiltinFunction::Sign); + + self.push(Instruction::Call(func_index)) + } "megabuf" => self.emit_memory_access(&mut function_call, 0)?, "gmegabuf" => self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8)?, shim_name if Shim::from_str(shim_name).is_some() => { diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index b825bf7..0fc87ce 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -334,10 +334,6 @@ fn compatibility_tests() { let expected_failing: Vec<&str> = vec![ "Line comments (\\\\)", - "Sign (10)", - "Sign (-10)", - "Sign (0)", - "Sign (-0)", "Plus equals", "Plus equals (local var)", "Plus equals (megabuf)", From 3835fed7d3bab3a346eafc9590045cbbf9b3041b Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 62/85] Backslash comments --- compiler-rs/src/lexer.rs | 17 +++++++++++++++++ compiler-rs/tests/compatibility_test.rs | 1 - .../tokens/one_backslash_one.invalid.snapshot | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 8935466..3189458 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -51,6 +51,23 @@ impl<'a> Lexer<'a> { _ => TokenKind::Slash, } } + // Eel supports backslash comments! + '\\' => { + self.chars.next(); + match self.chars.next { + '\\' => { + self.chars.next(); + self.eat_inline_comment_tail(); + return self.next_token(); + } + c => { + return Err(CompilerError::new( + format!("Parse Error: Unexpected character '{}' following \\.", c), + Span::new(start, start), + )) + } + } + } '=' => { self.chars.next(); match self.chars.next { diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 0fc87ce..e7212c6 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -333,7 +333,6 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Line comments (\\\\)", "Plus equals", "Plus equals (local var)", "Plus equals (megabuf)", diff --git a/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.snapshot b/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.snapshot index 5bd6126..febbfe1 100644 --- a/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.snapshot +++ b/compiler-rs/tests/fixtures/tokens/one_backslash_one.invalid.snapshot @@ -1,3 +1,3 @@ 1\1 ======================================================================== -Parse Error: Unexpected character '\'. +Parse Error: Unexpected character '1' following \. From e3157086248248bc32d822a4e5b33e6f2fa678d3 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 63/85] Lex += --- compiler-rs/src/lexer.rs | 8 +++++++- compiler-rs/src/tokens.rs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 3189458..4c5ceb7 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -32,7 +32,13 @@ impl<'a> Lexer<'a> { c if is_int(c) => self.read_number(), '.' => self.read_number(), c if is_identifier_head(c) => self.read_identifier(), - '+' => self.read_char_as_kind(TokenKind::Plus), + '+' => { + self.chars.next(); + match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::PlusEqual), + _ => TokenKind::Plus, + } + } '-' => self.read_char_as_kind(TokenKind::Minus), '*' => self.read_char_as_kind(TokenKind::Asterisk), '/' => { diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 75f2f26..ed7d3d9 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -19,6 +19,7 @@ pub enum TokenKind { And, Pipe, Caret, + PlusEqual, EOF, SOF, // Allows TokenKind to be non-optional in the parser } From 609b5dcfa1d87c8b4975d3e2d60628fa7623ab2f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 64/85] Document how to get gzipped size of wasm --- compiler-rs/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler-rs/README.md b/compiler-rs/README.md index 4d75601..0740c46 100644 --- a/compiler-rs/README.md +++ b/compiler-rs/README.md @@ -7,6 +7,11 @@ wasm-pack build You can find the output in `pkg/`. +To check the resulting wasm size +```bash +gzip -9 < pkg/optimized.wasm | wc -c +``` + ## TODO - [ ] Add AST node for arguments list so that we can show it as the error node when arg count is wrong. From 4cd01c57333eb88cb38f1aab74e4596796d9b56f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 65/85] Stub out logical and --- compiler-rs/src/ast.rs | 1 + compiler-rs/src/function_emitter.rs | 8 +++++++- compiler-rs/src/lexer.rs | 8 +++++++- compiler-rs/src/parser.rs | 1 + compiler-rs/src/tokens.rs | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 590d2ac..f23b609 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -49,6 +49,7 @@ pub enum BinaryOperator { Eq, BitwiseAnd, BitwiseOr, + LogicalAnd, Pow, } diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 899483d..2453563 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -1,4 +1,3 @@ -use crate::utils::f64_const; use crate::{ ast::{ Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, ExpressionBlock, @@ -12,6 +11,7 @@ use crate::{ EelFunctionType, }; use crate::{constants::EPSILON, error::EmitterResult}; +use crate::{span::Span, utils::f64_const}; use parity_wasm::elements::{BlockType, FuncBody, Instruction, Instructions, Local, ValueType}; pub fn emit_function( @@ -158,6 +158,12 @@ impl<'a> FunctionEmitter<'a> { self.emit_is_zeroish(); self.push(Instruction::F64ConvertSI32) } + BinaryOperator::LogicalAnd => { + return Err(CompilerError::new( + "&& has not yet been implemented".to_string(), + Span::new(0, 0), + )) + } BinaryOperator::BitwiseAnd => { let func_index = self.resolve_builtin_function(BuiltinFunction::BitwiseAnd); self.push(Instruction::Call(func_index)) diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 4c5ceb7..1622a67 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -87,7 +87,13 @@ impl<'a> Lexer<'a> { ';' => self.read_char_as_kind(TokenKind::Semi), '!' => self.read_char_as_kind(TokenKind::Bang), '%' => self.read_char_as_kind(TokenKind::Percent), - '&' => self.read_char_as_kind(TokenKind::And), + '&' => { + self.chars.next(); + match self.chars.next { + '&' => self.read_char_as_kind(TokenKind::AndAnd), + _ => TokenKind::And, + } + } '|' => self.read_char_as_kind(TokenKind::Pipe), '^' => self.read_char_as_kind(TokenKind::Caret), c if is_whitepsace(c) => { diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index da0e797..f47cc62 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -178,6 +178,7 @@ impl<'a> Parser<'a> { (left_associative(ASSIGNMENT_PRECEDENCE), BinaryOperator::Eq) } TokenKind::And => (0, BinaryOperator::BitwiseAnd), + TokenKind::AndAnd => (0, BinaryOperator::LogicalAnd), TokenKind::Pipe => (0, BinaryOperator::BitwiseOr), TokenKind::Caret if precedence < EXPONENTIATION_PRECEDENCE => ( left_associative(EXPONENTIATION_PRECEDENCE), diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index ed7d3d9..1456c82 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -17,6 +17,7 @@ pub enum TokenKind { DoubleEqual, Percent, And, + AndAnd, Pipe, Caret, PlusEqual, From 17fba383b98b4f15714a91ed4e5f73872c359027 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 66/85] Unify function index --- compiler-rs/src/emitter_context.rs | 57 ++++++++++++++ compiler-rs/src/function_emitter.rs | 116 +++++++++------------------- compiler-rs/src/lib.rs | 1 + compiler-rs/src/module_emitter.rs | 104 ++++++++++++------------- 4 files changed, 144 insertions(+), 134 deletions(-) create mode 100644 compiler-rs/src/emitter_context.rs diff --git a/compiler-rs/src/emitter_context.rs b/compiler-rs/src/emitter_context.rs new file mode 100644 index 0000000..24fc96c --- /dev/null +++ b/compiler-rs/src/emitter_context.rs @@ -0,0 +1,57 @@ +use crate::{ + builtin_functions::BuiltinFunction, index_store::IndexStore, shim::Shim, EelFunctionType, +}; + +#[derive(PartialEq, Eq, Hash)] +pub enum ModuleFunction { + Shim(Shim), + Builtin(BuiltinFunction), + Eel(usize), +} + +pub struct EmitterContext { + pub current_pool: String, + pub globals: IndexStore<(Option, String)>, + pub functions: IndexStore, + pub function_types: IndexStore, +} + +impl EmitterContext { + pub fn new() -> Self { + Self { + current_pool: "".to_string(), // TODO: Is this okay to be empty? + globals: Default::default(), + function_types: Default::default(), + functions: IndexStore::new(), + } + } + pub fn resolve_variable(&mut self, name: String) -> u32 { + let pool = if variable_is_register(&name) { + None + } else { + Some(self.current_pool.clone()) + }; + + self.globals.get((pool, name)) + } + + pub fn resolve_shim_function(&mut self, shim: Shim) -> u32 { + self.function_types.ensure(shim.get_type()); + self.functions.get(ModuleFunction::Shim(shim)) + } + + pub fn resolve_builtin_function(&mut self, builtin: BuiltinFunction) -> u32 { + self.function_types.ensure(builtin.get_type()); + self.functions.get(ModuleFunction::Builtin(builtin)) + } + + pub fn resolve_eel_function(&mut self, idx: usize) -> u32 { + self.functions.get(ModuleFunction::Eel(idx)) + } +} + +fn variable_is_register(name: &str) -> bool { + let chars: Vec<_> = name.chars().collect(); + // We avoided pulling in the regex crate! (But at what cost?) + matches!(chars.as_slice(), ['r', 'e', 'g', '0'..='9', '0'..='9']) +} diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 2453563..d80ccdb 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -1,3 +1,4 @@ +use crate::emitter_context::EmitterContext; use crate::{ ast::{ Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, ExpressionBlock, @@ -6,9 +7,7 @@ use crate::{ builtin_functions::BuiltinFunction, constants::BUFFER_SIZE, error::CompilerError, - index_store::IndexStore, shim::Shim, - EelFunctionType, }; use crate::{constants::EPSILON, error::EmitterResult}; use crate::{span::Span, utils::f64_const}; @@ -16,21 +15,9 @@ use parity_wasm::elements::{BlockType, FuncBody, Instruction, Instructions, Loca pub fn emit_function( eel_function: EelFunction, - current_pool: String, - globals: &mut IndexStore<(Option, String)>, - shims: &mut IndexStore, - builtin_functions: &mut IndexStore, - function_types: &mut IndexStore, - builtin_offset: &Option, + context: &mut EmitterContext, ) -> EmitterResult { - let mut function_emitter = FunctionEmitter::new( - current_pool, - globals, - shims, - builtin_functions, - function_types, - builtin_offset, - ); + let mut function_emitter = FunctionEmitter::new(context); function_emitter.emit_expression_block(eel_function.expressions)?; @@ -48,32 +35,15 @@ pub fn emit_function( } struct FunctionEmitter<'a> { - current_pool: String, - globals: &'a mut IndexStore<(Option, String)>, - shims: &'a mut IndexStore, - builtin_functions: &'a mut IndexStore, - function_types: &'a mut IndexStore, - builtin_offset: &'a Option, - locals: Vec, + context: &'a mut EmitterContext, instructions: Vec, + locals: Vec, } impl<'a> FunctionEmitter<'a> { - fn new( - current_pool: String, - globals: &'a mut IndexStore<(Option, String)>, - shims: &'a mut IndexStore, - builtin_functions: &'a mut IndexStore, - function_types: &'a mut IndexStore, - builtin_offset: &'a Option, - ) -> Self { + fn new(context: &'a mut EmitterContext) -> Self { Self { - current_pool, - globals, - shims, - function_types, - builtin_functions, - builtin_offset, + context, locals: Vec::new(), instructions: Vec::new(), } @@ -110,7 +80,7 @@ impl<'a> FunctionEmitter<'a> { self.emit_expression_block(expression_block) } Expression::Identifier(identifier) => { - let index = self.resolve_variable(identifier.name); + let index = self.context.resolve_variable(identifier.name); self.push(Instruction::GetGlobal(index)); Ok(()) } @@ -146,11 +116,11 @@ impl<'a> FunctionEmitter<'a> { BinaryOperator::Subtract => self.push(Instruction::F64Sub), BinaryOperator::Multiply => self.push(Instruction::F64Mul), BinaryOperator::Divide => { - let func_index = self.resolve_builtin_function(BuiltinFunction::Div); + let func_index = self.context.resolve_builtin_function(BuiltinFunction::Div); self.push(Instruction::Call(func_index)) } BinaryOperator::Mod => { - let func_index = self.resolve_builtin_function(BuiltinFunction::Mod); + let func_index = self.context.resolve_builtin_function(BuiltinFunction::Mod); self.push(Instruction::Call(func_index)) } BinaryOperator::Eq => { @@ -165,15 +135,19 @@ impl<'a> FunctionEmitter<'a> { )) } BinaryOperator::BitwiseAnd => { - let func_index = self.resolve_builtin_function(BuiltinFunction::BitwiseAnd); + let func_index = self + .context + .resolve_builtin_function(BuiltinFunction::BitwiseAnd); self.push(Instruction::Call(func_index)) } BinaryOperator::BitwiseOr => { - let func_index = self.resolve_builtin_function(BuiltinFunction::BitwiseOr); + let func_index = self + .context + .resolve_builtin_function(BuiltinFunction::BitwiseOr); self.push(Instruction::Call(func_index)) } BinaryOperator::Pow => { - let shim_index = self.shims.get(Shim::Pow); + let shim_index = self.context.resolve_shim_function(Shim::Pow); self.push(Instruction::Call(shim_index)) } }; @@ -181,7 +155,9 @@ impl<'a> FunctionEmitter<'a> { } fn emit_assignment(&mut self, assignment_expression: Assignment) -> EmitterResult<()> { - let resolved_name = self.resolve_variable(assignment_expression.left.name); + let resolved_name = self + .context + .resolve_variable(assignment_expression.left.name); self.emit_expression(*assignment_expression.right)?; self.push(Instruction::SetGlobal(resolved_name)); @@ -281,21 +257,25 @@ impl<'a> FunctionEmitter<'a> { "sqr" => { assert_arity(&function_call, 1)?; self.emit_function_args(function_call)?; - let func_index = self.resolve_builtin_function(BuiltinFunction::Sqr); + let func_index = self.context.resolve_builtin_function(BuiltinFunction::Sqr); self.push(Instruction::Call(func_index)) } "bor" => { assert_arity(&function_call, 2)?; self.emit_function_args(function_call)?; - let func_index = self.resolve_builtin_function(BuiltinFunction::LogicalOr); + let func_index = self + .context + .resolve_builtin_function(BuiltinFunction::LogicalOr); self.push(Instruction::Call(func_index)) } "band" => { assert_arity(&function_call, 2)?; self.emit_function_args(function_call)?; - let func_index = self.resolve_builtin_function(BuiltinFunction::LogicalAnd); + let func_index = self + .context + .resolve_builtin_function(BuiltinFunction::LogicalAnd); self.push(Instruction::Call(func_index)) } @@ -303,14 +283,14 @@ impl<'a> FunctionEmitter<'a> { "mod" => { assert_arity(&function_call, 2)?; self.emit_function_args(function_call)?; - let func_index = self.resolve_builtin_function(BuiltinFunction::Mod); + let func_index = self.context.resolve_builtin_function(BuiltinFunction::Mod); self.push(Instruction::Call(func_index)) } "sign" => { assert_arity(&function_call, 1)?; self.emit_function_args(function_call)?; - let func_index = self.resolve_builtin_function(BuiltinFunction::Sign); + let func_index = self.context.resolve_builtin_function(BuiltinFunction::Sign); self.push(Instruction::Call(func_index)) } @@ -321,7 +301,7 @@ impl<'a> FunctionEmitter<'a> { assert_arity(&function_call, shim.arity())?; self.emit_function_args(function_call)?; - let shim_index = self.shims.get(shim); + let shim_index = self.context.resolve_shim_function(shim); self.push(Instruction::Call(shim_index)); } _ => { @@ -349,7 +329,9 @@ impl<'a> FunctionEmitter<'a> { let index = self.resolve_local(ValueType::I32); self.emit_expression(function_call.arguments.pop().unwrap())?; - let call_index = self.resolve_builtin_function(BuiltinFunction::GetBufferIndex); + let call_index = self + .context + .resolve_builtin_function(BuiltinFunction::GetBufferIndex); self.push(Instruction::Call(call_index)); // self.push(Instruction::TeeLocal(index)); @@ -366,29 +348,6 @@ impl<'a> FunctionEmitter<'a> { Ok(()) } - fn resolve_variable(&mut self, name: String) -> u32 { - let pool = if variable_is_register(&name) { - None - } else { - Some(self.current_pool.clone()) - }; - - 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 - .builtin_offset - .expect("Tried to compute builtin index before setting offset."); - self.builtin_functions.get(builtin) + offset - } - fn push(&mut self, instruction: Instruction) { self.instructions.push(instruction) } @@ -404,12 +363,11 @@ impl<'a> FunctionEmitter<'a> { self.push(Instruction::F64Const(f64_const(EPSILON))); self.push(Instruction::F64Lt); } -} -fn variable_is_register(name: &str) -> bool { - let chars: Vec<_> = name.chars().collect(); - // We avoided pulling in the regex crate! (But at what cost?) - matches!(chars.as_slice(), ['r', 'e', 'g', '0'..='9', '0'..='9']) + fn resolve_local(&mut self, type_: ValueType) -> u32 { + self.locals.push(type_); + return self.locals.len() as u32 - 1; + } } fn assert_arity(function_call: &FunctionCall, arity: usize) -> EmitterResult<()> { diff --git a/compiler-rs/src/lib.rs b/compiler-rs/src/lib.rs index f6cb3ae..e69f822 100644 --- a/compiler-rs/src/lib.rs +++ b/compiler-rs/src/lib.rs @@ -1,6 +1,7 @@ mod ast; mod builtin_functions; mod constants; +mod emitter_context; mod error; mod file_chars; mod function_emitter; diff --git a/compiler-rs/src/module_emitter.rs b/compiler-rs/src/module_emitter.rs index c395894..051f04a 100644 --- a/compiler-rs/src/module_emitter.rs +++ b/compiler-rs/src/module_emitter.rs @@ -1,16 +1,14 @@ +use crate::emitter_context::{EmitterContext, ModuleFunction}; use std::collections::{HashMap, HashSet}; use crate::{ ast::EelFunction, - builtin_functions::BuiltinFunction, constants::WASM_MEMORY_SIZE, error::{CompilerError, EmitterResult}, function_emitter::emit_function, - index_store::IndexStore, shim::Shim, span::Span, utils::f64_const, - EelFunctionType, }; use parity_wasm::elements::{ CodeSection, ExportEntry, ExportSection, External, Func, FuncBody, FunctionSection, @@ -28,23 +26,13 @@ pub fn emit_module( } struct Emitter { - current_pool: String, - globals: IndexStore<(Option, String)>, - shims: IndexStore, - builtin_functions: IndexStore, - function_types: IndexStore, - builtin_offset: Option, + context: EmitterContext, } impl Emitter { fn new() -> Self { Emitter { - current_pool: "".to_string(), // TODO: Is this okay to be empty? - globals: Default::default(), - shims: IndexStore::new(), - function_types: Default::default(), - builtin_functions: IndexStore::new(), - builtin_offset: None, + context: EmitterContext::new(), } } fn emit( @@ -55,11 +43,14 @@ impl Emitter { let mut imports = Vec::new(); for (pool_name, globals) in globals_map { - self.current_pool = pool_name; + self.context.current_pool = pool_name; for global in globals { // TODO: Lots of clones. - self.resolve_variable(global.clone()); - imports.push(make_import_entry(self.current_pool.clone(), global.clone())); + self.context.resolve_variable(global.clone()); + imports.push(make_import_entry( + self.context.current_pool.clone(), + global.clone(), + )); } } @@ -81,15 +72,18 @@ impl Emitter { for shim in shims { let field_str = shim.as_str().to_string(); let type_ = shim.get_type(); - self.shims.ensure(shim); + self.context.resolve_shim_function(shim); imports.push(ImportEntry::new( "shims".to_string(), field_str, - External::Function(self.function_types.get(type_)), + External::Function(self.context.function_types.get(type_)), )); } - self.builtin_offset = Some(eel_functions.len() as u32 + shim_offset); + // Reserve an index for each eel function + for (idx, _) in eel_functions.iter().enumerate() { + self.context.resolve_eel_function(idx); + } let (function_exports, function_bodies, funcs) = self.emit_eel_functions(eel_functions, shim_offset)?; @@ -126,6 +120,7 @@ impl Emitter { fn emit_type_section(&self) -> TypeSection { let function_types = self + .context .function_types .keys() .into_iter() @@ -149,16 +144,26 @@ impl Emitter { } fn emit_function_section(&mut self, funcs: Vec) -> FunctionSection { - let mut entries = funcs.clone(); - for builtin in self.builtin_functions.keys() { - let type_idx = self.function_types.get(builtin.get_type()); - entries.push(Func::new(type_idx)); + let mut entries = Vec::new(); + for module_function in self.context.functions.keys() { + let func = match module_function { + ModuleFunction::Shim(_) => None, + ModuleFunction::Builtin(builtin) => { + let type_idx = self.context.function_types.get(builtin.get_type()); + Some(Func::new(type_idx)) + } + ModuleFunction::Eel(func_idx) => Some(funcs.get(*func_idx).unwrap().clone()), + }; + if let Some(func) = func { + entries.push(func); + } } FunctionSection::with_entries(entries) } fn emit_global_section(&self) -> Option { let globals: Vec = self + .context .globals .keys() .iter() @@ -177,10 +182,19 @@ impl Emitter { } fn emit_code_section(&self, function_bodies: Vec) -> CodeSection { - // TODO: Avoid this clone - let mut bodies = function_bodies.clone(); - for builtin in self.builtin_functions.keys() { - bodies.push(builtin.func_body()); + let mut bodies = Vec::new(); + for module_function in self.context.functions.keys() { + let body = match module_function { + ModuleFunction::Shim(_) => None, + ModuleFunction::Builtin(builtin) => Some(builtin.func_body()), + ModuleFunction::Eel(func_idx) => { + // TODO: Avoid this clone + Some(function_bodies.get(*func_idx).unwrap().clone()) + } + }; + if let Some(body) = body { + bodies.push(body); + } } CodeSection::with_bodies(bodies) } @@ -194,15 +208,8 @@ 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() { - let function_body = emit_function( - program, - pool_name, - &mut self.globals, - &mut self.shims, - &mut self.builtin_functions, - &mut self.function_types, - &self.builtin_offset, - )?; + self.context.current_pool = pool_name; + let function_body = emit_function(program, &mut self.context)?; function_bodies.push(function_body); @@ -211,28 +218,15 @@ impl Emitter { Internal::Function(i as u32 + offset), )); - let function_type = self.function_types.get(FunctionType::new(vec![], vec![])); + let function_type = self + .context + .function_types + .get(FunctionType::new(vec![], vec![])); function_definitions.push(Func::new(function_type)) } Ok((exports, function_bodies, function_definitions)) } - - fn resolve_variable(&mut self, name: String) -> u32 { - let pool = if variable_is_register(&name) { - None - } else { - Some(self.current_pool.clone()) - }; - - self.globals.get((pool, name)) - } -} - -fn variable_is_register(name: &str) -> bool { - let chars: Vec<_> = name.chars().collect(); - // We avoided pulling in the regex crate! (But at what cost?) - matches!(chars.as_slice(), ['r', 'e', 'g', '0'..='9', '0'..='9']) } fn make_empty_global() -> GlobalEntry { From a376e45dc524076b90662e431bf5f6d38c4d6815 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 67/85] Remove explicit offset --- compiler-rs/src/emitter_context.rs | 1 - compiler-rs/src/module_emitter.rs | 17 ++++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/compiler-rs/src/emitter_context.rs b/compiler-rs/src/emitter_context.rs index 24fc96c..6ba0d93 100644 --- a/compiler-rs/src/emitter_context.rs +++ b/compiler-rs/src/emitter_context.rs @@ -36,7 +36,6 @@ impl EmitterContext { } pub fn resolve_shim_function(&mut self, shim: Shim) -> u32 { - self.function_types.ensure(shim.get_type()); self.functions.get(ModuleFunction::Shim(shim)) } diff --git a/compiler-rs/src/module_emitter.rs b/compiler-rs/src/module_emitter.rs index 051f04a..8dc5c26 100644 --- a/compiler-rs/src/module_emitter.rs +++ b/compiler-rs/src/module_emitter.rs @@ -45,6 +45,7 @@ impl Emitter { for (pool_name, globals) in globals_map { self.context.current_pool = pool_name; for global in globals { + // TODO: Ensure none of these are `ref\d\d` // TODO: Lots of clones. self.context.resolve_variable(global.clone()); imports.push(make_import_entry( @@ -68,7 +69,6 @@ impl Emitter { Shim::Sigmoid, Shim::Exp, ]; - let shim_offset = shims.len() as u32; for shim in shims { let field_str = shim.as_str().to_string(); let type_ = shim.get_type(); @@ -80,13 +80,7 @@ impl Emitter { )); } - // Reserve an index for each eel function - for (idx, _) in eel_functions.iter().enumerate() { - self.context.resolve_eel_function(idx); - } - - let (function_exports, function_bodies, funcs) = - self.emit_eel_functions(eel_functions, shim_offset)?; + let (function_exports, function_bodies, funcs) = self.emit_eel_functions(eel_functions)?; let mut sections = vec![]; sections.push(Section::Type(self.emit_type_section())); @@ -202,21 +196,18 @@ impl Emitter { fn emit_eel_functions( &mut self, eel_functions: Vec<(String, EelFunction, String)>, - offset: u32, ) -> EmitterResult<(Vec, Vec, Vec)> { let mut exports = Vec::new(); let mut function_bodies = Vec::new(); let mut function_definitions = Vec::new(); for (i, (name, program, pool_name)) in eel_functions.into_iter().enumerate() { + let function_idx = self.context.resolve_eel_function(i); self.context.current_pool = pool_name; let function_body = emit_function(program, &mut self.context)?; function_bodies.push(function_body); - exports.push(ExportEntry::new( - name, - Internal::Function(i as u32 + offset), - )); + exports.push(ExportEntry::new(name, Internal::Function(function_idx))); let function_type = self .context From beb437c0392a145d94d8876c685ce1248cb0d49a Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:39:40 -0700 Subject: [PATCH 68/85] Setup the ability to call shims and builtins from bultins --- compiler-rs/src/builtin_functions.rs | 7 +++++-- compiler-rs/src/module_emitter.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler-rs/src/builtin_functions.rs b/compiler-rs/src/builtin_functions.rs index 0099bc9..689bb0a 100644 --- a/compiler-rs/src/builtin_functions.rs +++ b/compiler-rs/src/builtin_functions.rs @@ -2,9 +2,12 @@ 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; +use crate::{ + constants::{BUFFER_SIZE, EPSILON}, + emitter_context::EmitterContext, +}; #[derive(PartialEq, Eq, Hash)] pub enum BuiltinFunction { @@ -46,7 +49,7 @@ impl BuiltinFunction { } } - pub fn func_body(&self) -> FuncBody { + pub fn func_body(&self, _context: &EmitterContext) -> FuncBody { match self { Self::Sign => FuncBody::new( vec![], diff --git a/compiler-rs/src/module_emitter.rs b/compiler-rs/src/module_emitter.rs index 8dc5c26..435b6e5 100644 --- a/compiler-rs/src/module_emitter.rs +++ b/compiler-rs/src/module_emitter.rs @@ -180,7 +180,7 @@ impl Emitter { for module_function in self.context.functions.keys() { let body = match module_function { ModuleFunction::Shim(_) => None, - ModuleFunction::Builtin(builtin) => Some(builtin.func_body()), + ModuleFunction::Builtin(builtin) => Some(builtin.func_body(&self.context)), ModuleFunction::Eel(func_idx) => { // TODO: Avoid this clone Some(function_bodies.get(*func_idx).unwrap().clone()) From cabfcc3b78781cf5422ac0397f55594fa1f17fd3 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:51:49 -0700 Subject: [PATCH 69/85] Clean up builtin type gen --- compiler-rs/src/builtin_functions.rs | 41 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/compiler-rs/src/builtin_functions.rs b/compiler-rs/src/builtin_functions.rs index 689bb0a..c5919f1 100644 --- a/compiler-rs/src/builtin_functions.rs +++ b/compiler-rs/src/builtin_functions.rs @@ -24,28 +24,27 @@ pub enum BuiltinFunction { impl BuiltinFunction { pub fn get_type(&self) -> EelFunctionType { + FunctionType::new(vec![ValueType::F64; self.arity()], vec![self.return_type()]) + } + + pub fn return_type(&self) -> ValueType { + match self { + Self::GetBufferIndex => ValueType::I32, + _ => ValueType::F64, + } + } + + fn arity(&self) -> usize { match self { - Self::Div => { - FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) - } - Self::GetBufferIndex => FunctionType::new(vec![ValueType::F64], vec![ValueType::I32]), - Self::Mod => { - FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) - } - Self::BitwiseAnd => { - FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) - } - Self::BitwiseOr => { - FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) - } - Self::LogicalAnd => { - FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) - } - Self::LogicalOr => { - FunctionType::new(vec![ValueType::F64, ValueType::F64], vec![ValueType::F64]) - } - Self::Sqr => FunctionType::new(vec![ValueType::F64], vec![ValueType::F64]), - Self::Sign => FunctionType::new(vec![ValueType::F64], vec![ValueType::F64]), + Self::Div => 2, + Self::GetBufferIndex => 1, + Self::Mod => 2, + Self::BitwiseAnd => 2, + Self::BitwiseOr => 2, + Self::LogicalAnd => 2, + Self::LogicalOr => 2, + Self::Sqr => 1, + Self::Sign => 1, } } From aa2d2de2a1de424db376d7124090a99b72680c4c Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 22:56:12 -0700 Subject: [PATCH 70/85] Move list of shims to shims enum --- compiler-rs/src/module_emitter.rs | 16 +--------------- compiler-rs/src/shim.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/compiler-rs/src/module_emitter.rs b/compiler-rs/src/module_emitter.rs index 435b6e5..447feee 100644 --- a/compiler-rs/src/module_emitter.rs +++ b/compiler-rs/src/module_emitter.rs @@ -55,21 +55,7 @@ impl Emitter { } } - let shims: Vec = vec![ - Shim::Sin, - Shim::Pow, - Shim::Cos, - Shim::Tan, - Shim::Asin, - Shim::Acos, - Shim::Atan, - Shim::Atan2, - Shim::Log, - Shim::Log10, - Shim::Sigmoid, - Shim::Exp, - ]; - for shim in shims { + for shim in Shim::all() { let field_str = shim.as_str().to_string(); let type_ = shim.get_type(); self.context.resolve_shim_function(shim); diff --git a/compiler-rs/src/shim.rs b/compiler-rs/src/shim.rs index da097a7..d919898 100644 --- a/compiler-rs/src/shim.rs +++ b/compiler-rs/src/shim.rs @@ -19,6 +19,23 @@ pub enum Shim { } impl Shim { + pub fn all() -> Vec { + vec![ + Shim::Sin, + Shim::Pow, + Shim::Cos, + Shim::Tan, + Shim::Asin, + Shim::Acos, + Shim::Atan, + Shim::Atan2, + Shim::Log, + Shim::Log10, + Shim::Sigmoid, + Shim::Exp, + ] + } + pub fn get_type(&self) -> EelFunctionType { FunctionType::new(self.get_args(), self.get_return()) } @@ -31,6 +48,7 @@ impl Shim { pub fn get_return(&self) -> Vec { vec![ValueType::F64] } + pub fn arity(&self) -> usize { match self { Shim::Sin => 1, From cb94738e00c5d2ffd9f636d8e215592848e05042 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 8 Apr 2021 23:01:06 -0700 Subject: [PATCH 71/85] Less than, greater than --- compiler-rs/src/ast.rs | 2 ++ compiler-rs/src/function_emitter.rs | 8 ++++++++ compiler-rs/src/lexer.rs | 2 ++ compiler-rs/src/parser.rs | 3 +++ compiler-rs/src/tokens.rs | 3 +++ compiler-rs/tests/compatibility_test.rs | 4 ---- 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index f23b609..57fa6eb 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -51,6 +51,8 @@ pub enum BinaryOperator { BitwiseOr, LogicalAnd, Pow, + LessThan, + GreaterThan, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index d80ccdb..6c097c8 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -128,6 +128,14 @@ impl<'a> FunctionEmitter<'a> { self.emit_is_zeroish(); self.push(Instruction::F64ConvertSI32) } + BinaryOperator::LessThan => { + self.push(Instruction::F64Lt); + self.push(Instruction::F64ConvertSI32) + } + BinaryOperator::GreaterThan => { + self.push(Instruction::F64Gt); + self.push(Instruction::F64ConvertSI32) + } BinaryOperator::LogicalAnd => { return Err(CompilerError::new( "&& has not yet been implemented".to_string(), diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 1622a67..a72e449 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -96,6 +96,8 @@ impl<'a> Lexer<'a> { } '|' => self.read_char_as_kind(TokenKind::Pipe), '^' => self.read_char_as_kind(TokenKind::Caret), + '<' => self.read_char_as_kind(TokenKind::OpenAngel), + '>' => self.read_char_as_kind(TokenKind::CloseAngel), c if is_whitepsace(c) => { self.chars.eat_while(is_whitepsace); return self.next_token(); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index f47cc62..fcebe2f 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -174,9 +174,12 @@ impl<'a> Parser<'a> { TokenKind::Percent if precedence < MOD_PRECEDENCE => { (left_associative(MOD_PRECEDENCE), BinaryOperator::Mod) } + // TODO: prededence? TokenKind::DoubleEqual if precedence < ASSIGNMENT_PRECEDENCE => { (left_associative(ASSIGNMENT_PRECEDENCE), BinaryOperator::Eq) } + TokenKind::OpenAngel => (0, BinaryOperator::LessThan), + TokenKind::CloseAngel => (0, BinaryOperator::GreaterThan), TokenKind::And => (0, BinaryOperator::BitwiseAnd), TokenKind::AndAnd => (0, BinaryOperator::LogicalAnd), TokenKind::Pipe => (0, BinaryOperator::BitwiseOr), diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 1456c82..41fe1e6 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -21,6 +21,9 @@ pub enum TokenKind { Pipe, Caret, PlusEqual, + OpenAngel, + CloseAngel, + Than, EOF, SOF, // Allows TokenKind to be non-optional in the parser } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index e7212c6..e2c7798 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -368,10 +368,6 @@ fn compatibility_tests() { "!Equality (true)", "!Equality (false)", "!Equality epsilon", - "Less than (true)", - "Less than (false)", - "Greater than (true)", - "Greater than (false)", "Less than or equal (true)", "Less than or equal (false)", "Greater than or equal (true)", From 4254060864c350b79c35b41d20de4964385cdc1f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 9 Apr 2021 00:07:19 -0700 Subject: [PATCH 72/85] <= >= --- compiler-rs/src/ast.rs | 2 ++ compiler-rs/src/function_emitter.rs | 8 ++++++++ compiler-rs/src/lexer.rs | 16 ++++++++++++++-- compiler-rs/src/parser.rs | 2 ++ compiler-rs/src/tokens.rs | 2 ++ compiler-rs/tests/compatibility_test.rs | 4 ---- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 57fa6eb..ddf434d 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -53,6 +53,8 @@ pub enum BinaryOperator { Pow, LessThan, GreaterThan, + LessThanEqual, + GreaterThanEqual, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 6c097c8..3228cc3 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -136,6 +136,14 @@ impl<'a> FunctionEmitter<'a> { self.push(Instruction::F64Gt); self.push(Instruction::F64ConvertSI32) } + BinaryOperator::LessThanEqual => { + self.push(Instruction::F64Le); + self.push(Instruction::F64ConvertSI32) + } + BinaryOperator::GreaterThanEqual => { + self.push(Instruction::F64Ge); + self.push(Instruction::F64ConvertSI32) + } BinaryOperator::LogicalAnd => { return Err(CompilerError::new( "&& has not yet been implemented".to_string(), diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index a72e449..3f3bf8f 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -96,8 +96,20 @@ impl<'a> Lexer<'a> { } '|' => self.read_char_as_kind(TokenKind::Pipe), '^' => self.read_char_as_kind(TokenKind::Caret), - '<' => self.read_char_as_kind(TokenKind::OpenAngel), - '>' => self.read_char_as_kind(TokenKind::CloseAngel), + '<' => { + self.chars.next(); + match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::LTEqual), + _ => TokenKind::OpenAngel, + } + } + '>' => { + self.chars.next(); + match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::GTEqual), + _ => TokenKind::CloseAngel, + } + } c if is_whitepsace(c) => { self.chars.eat_while(is_whitepsace); return self.next_token(); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index fcebe2f..9713d49 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -180,6 +180,8 @@ impl<'a> Parser<'a> { } TokenKind::OpenAngel => (0, BinaryOperator::LessThan), TokenKind::CloseAngel => (0, BinaryOperator::GreaterThan), + TokenKind::LTEqual => (0, BinaryOperator::LessThanEqual), + TokenKind::GTEqual => (0, BinaryOperator::GreaterThanEqual), TokenKind::And => (0, BinaryOperator::BitwiseAnd), TokenKind::AndAnd => (0, BinaryOperator::LogicalAnd), TokenKind::Pipe => (0, BinaryOperator::BitwiseOr), diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 41fe1e6..4cca040 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -23,6 +23,8 @@ pub enum TokenKind { PlusEqual, OpenAngel, CloseAngel, + LTEqual, + GTEqual, Than, EOF, SOF, // Allows TokenKind to be non-optional in the parser diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index e2c7798..5bb52f2 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -368,10 +368,6 @@ fn compatibility_tests() { "!Equality (true)", "!Equality (false)", "!Equality epsilon", - "Less than or equal (true)", - "Less than or equal (false)", - "Greater than or equal (true)", - "Greater than or equal (false)", "Max index megabuf", "Max index + 1 megabuf", "Max index gmegabuf", From 08b085bf460a6b6291afe018bf28bc6208cc4168 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 9 Apr 2021 00:11:51 -0700 Subject: [PATCH 73/85] != --- compiler-rs/src/ast.rs | 1 + compiler-rs/src/function_emitter.rs | 5 +++++ compiler-rs/src/lexer.rs | 8 +++++++- compiler-rs/src/parser.rs | 1 + compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/compatibility_test.rs | 3 --- 6 files changed, 15 insertions(+), 4 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index ddf434d..ddaaf58 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -55,6 +55,7 @@ pub enum BinaryOperator { GreaterThan, LessThanEqual, GreaterThanEqual, + NotEqual, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 3228cc3..c7e1e45 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -128,6 +128,11 @@ impl<'a> FunctionEmitter<'a> { self.emit_is_zeroish(); self.push(Instruction::F64ConvertSI32) } + BinaryOperator::NotEqual => { + self.push(Instruction::F64Sub); + self.emit_is_not_zeroish(); + self.push(Instruction::F64ConvertSI32) + } BinaryOperator::LessThan => { self.push(Instruction::F64Lt); self.push(Instruction::F64ConvertSI32) diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 3f3bf8f..6c1dc0f 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -85,7 +85,13 @@ impl<'a> Lexer<'a> { ')' => self.read_char_as_kind(TokenKind::CloseParen), ',' => self.read_char_as_kind(TokenKind::Comma), ';' => self.read_char_as_kind(TokenKind::Semi), - '!' => self.read_char_as_kind(TokenKind::Bang), + '!' => { + self.chars.next(); + match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::NotEqual), + _ => TokenKind::Bang, + } + } '%' => self.read_char_as_kind(TokenKind::Percent), '&' => { self.chars.next(); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 9713d49..c4ccb55 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -182,6 +182,7 @@ impl<'a> Parser<'a> { TokenKind::CloseAngel => (0, BinaryOperator::GreaterThan), TokenKind::LTEqual => (0, BinaryOperator::LessThanEqual), TokenKind::GTEqual => (0, BinaryOperator::GreaterThanEqual), + TokenKind::NotEqual => (0, BinaryOperator::NotEqual), TokenKind::And => (0, BinaryOperator::BitwiseAnd), TokenKind::AndAnd => (0, BinaryOperator::LogicalAnd), TokenKind::Pipe => (0, BinaryOperator::BitwiseOr), diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 4cca040..6957d6e 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -25,6 +25,7 @@ pub enum TokenKind { CloseAngel, LTEqual, GTEqual, + NotEqual, Than, EOF, SOF, // Allows TokenKind to be non-optional in the parser diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 5bb52f2..572253d 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -365,9 +365,6 @@ fn compatibility_tests() { "Loop zero times", "Loop negative times", "Loop negative fractional times", - "!Equality (true)", - "!Equality (false)", - "!Equality epsilon", "Max index megabuf", "Max index + 1 megabuf", "Max index gmegabuf", From f7a06533c66d372dc0fcc7321702be9a5d771e28 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 9 Apr 2021 00:20:51 -0700 Subject: [PATCH 74/85] exec2/3 --- compiler-rs/src/function_emitter.rs | 16 ++++++++++++++-- compiler-rs/tests/compatibility_test.rs | 6 ++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index c7e1e45..4c86bc0 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -50,8 +50,12 @@ impl<'a> FunctionEmitter<'a> { } fn emit_expression_block(&mut self, block: ExpressionBlock) -> EmitterResult<()> { - let last_index = block.expressions.len() - 1; - for (i, expression) in block.expressions.into_iter().enumerate() { + self.emit_expression_list(block.expressions) + } + + fn emit_expression_list(&mut self, expressions: Vec) -> EmitterResult<()> { + let last_index = expressions.len() - 1; + for (i, expression) in expressions.into_iter().enumerate() { self.emit_expression(expression)?; if i != last_index { self.push(Instruction::Drop) @@ -315,6 +319,14 @@ impl<'a> FunctionEmitter<'a> { self.push(Instruction::Call(func_index)) } + "exec2" => { + assert_arity(&function_call, 2)?; + self.emit_expression_list(function_call.arguments)? + } + "exec3" => { + assert_arity(&function_call, 3)?; + self.emit_expression_list(function_call.arguments)? + } "megabuf" => self.emit_memory_access(&mut function_call, 0)?, "gmegabuf" => self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8)?, shim_name if Shim::from_str(shim_name).is_some() => { diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 572253d..e51a281 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -357,8 +357,6 @@ fn compatibility_tests() { "Logical or (first value false)", "Logical and shortcircuts", "Logical or shortcircuts", - "Exec2", - "Exec3", "While", "Loop", "Loop fractional times", @@ -396,6 +394,10 @@ fn compatibility_tests() { ]; println!("Failing: {}/{}", expected_failing.len(), test_cases.len()); + println!( + "Passing: {}%", + (test_cases.len() - expected_failing.len()) as f64 / test_cases.len() as f64 + ); for (name, code, expected) in test_cases { let mut globals = HashMap::default(); From 1867147b296d1ad6b4e5ddcce6b8db0f940a5383 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 9 Apr 2021 00:35:53 -0700 Subject: [PATCH 75/85] While --- compiler-rs/src/function_emitter.rs | 34 +++++++++++++++++++++++++ compiler-rs/tests/compatibility_test.rs | 2 -- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 4c86bc0..7e7ea43 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -13,6 +13,9 @@ use crate::{constants::EPSILON, error::EmitterResult}; use crate::{span::Span, utils::f64_const}; use parity_wasm::elements::{BlockType, FuncBody, Instruction, Instructions, Local, ValueType}; +// https://github.com/WACUP/vis_milk2/blob/de9625a89e724afe23ed273b96b8e48496095b6c/ns-eel2/ns-eel.h#L136 +static MAX_LOOP_COUNT: i32 = 1048576; + pub fn emit_function( eel_function: EelFunction, context: &mut EmitterContext, @@ -327,6 +330,11 @@ impl<'a> FunctionEmitter<'a> { assert_arity(&function_call, 3)?; self.emit_expression_list(function_call.arguments)? } + "while" => { + assert_arity(&function_call, 1)?; + let body = function_call.arguments.pop().unwrap(); + self.emit_while(body)?; + } "megabuf" => self.emit_memory_access(&mut function_call, 0)?, "gmegabuf" => self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8)?, shim_name if Shim::from_str(shim_name).is_some() => { @@ -381,6 +389,32 @@ impl<'a> FunctionEmitter<'a> { Ok(()) } + fn emit_while(&mut self, body: Expression) -> EmitterResult<()> { + let iteration_idx = self.resolve_local(ValueType::I32); + self.push(Instruction::I32Const(0)); + self.push(Instruction::SetLocal(iteration_idx)); + + self.push(Instruction::Loop(BlockType::NoResult)); + + // Increment and check loop count + self.push(Instruction::GetLocal(iteration_idx)); + self.push(Instruction::I32Const(1)); + self.push(Instruction::I32Add); + self.push(Instruction::TeeLocal(iteration_idx)); + // STACK: [iteration count] + self.push(Instruction::I32Const(MAX_LOOP_COUNT)); + self.push(Instruction::I32LtU); + // STACK: [loop in range] + self.emit_expression(body)?; + self.emit_is_not_zeroish(); + // STACK: [loop in range, body is truthy] + self.push(Instruction::I32And); + self.push(Instruction::BrIf(0)); + self.push(Instruction::End); + self.push(Instruction::F64Const(f64_const(0.0))); + Ok(()) + } + fn push(&mut self, instruction: Instruction) { self.instructions.push(instruction) } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index e51a281..05cdd31 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -357,7 +357,6 @@ fn compatibility_tests() { "Logical or (first value false)", "Logical and shortcircuts", "Logical or shortcircuts", - "While", "Loop", "Loop fractional times", "Loop zero times", @@ -390,7 +389,6 @@ fn compatibility_tests() { "gmegabuf does not write megabuf", "megabuf does not write gmegabuf", "Adjacent buf indicies don\'t collide", - "Loop limit", ]; println!("Failing: {}/{}", expected_failing.len(), test_cases.len()); From 659f446b73c894fe9f7e5896855c8a582c4e1cb4 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 9 Apr 2021 15:53:28 -0700 Subject: [PATCH 76/85] Loop --- compiler-rs/src/function_emitter.rs | 34 +++++++++++++++++++++++++ compiler-rs/tests/compatibility_test.rs | 5 ---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 7e7ea43..95becba 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -335,6 +335,12 @@ impl<'a> FunctionEmitter<'a> { let body = function_call.arguments.pop().unwrap(); self.emit_while(body)?; } + "loop" => { + assert_arity(&function_call, 2)?; + let body = function_call.arguments.pop().unwrap(); + let count = function_call.arguments.pop().unwrap(); + self.emit_loop(count, body)?; + } "megabuf" => self.emit_memory_access(&mut function_call, 0)?, "gmegabuf" => self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8)?, shim_name if Shim::from_str(shim_name).is_some() => { @@ -415,6 +421,34 @@ impl<'a> FunctionEmitter<'a> { Ok(()) } + fn emit_loop(&mut self, count: Expression, body: Expression) -> EmitterResult<()> { + let iteration_idx = self.resolve_local(ValueType::I32); + self.push(Instruction::Block(BlockType::NoResult)); + // Assign the count to a variable + self.emit_expression(count)?; + self.push(Instruction::I32TruncSF64); + self.push(Instruction::TeeLocal(iteration_idx)); + self.push(Instruction::I32Const(0)); + self.push(Instruction::I32LeS); + self.push(Instruction::BrIf(1)); + self.push(Instruction::Loop(BlockType::NoResult)); + // Run the body + self.emit_expression(body)?; + self.push(Instruction::Drop); + // Decrement the count + self.push(Instruction::GetLocal(iteration_idx)); + self.push(Instruction::I32Const(1)); + self.push(Instruction::I32Sub); + self.push(Instruction::TeeLocal(iteration_idx)); + self.push(Instruction::I32Const(0)); + self.push(Instruction::I32Ne); + self.push(Instruction::BrIf(0)); // Return to the top of the loop + self.push(Instruction::End); // End loop + self.push(Instruction::End); // End block + self.push(Instruction::F64Const(f64_const(0.0))); // Implicitly return zero + Ok(()) + } + fn push(&mut self, instruction: Instruction) { self.instructions.push(instruction) } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 05cdd31..bf6384d 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -357,11 +357,6 @@ fn compatibility_tests() { "Logical or (first value false)", "Logical and shortcircuts", "Logical or shortcircuts", - "Loop", - "Loop fractional times", - "Loop zero times", - "Loop negative times", - "Loop negative fractional times", "Max index megabuf", "Max index + 1 megabuf", "Max index gmegabuf", From e34468e4634d75efd18dc251112932ce3870c809 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 9 Apr 2021 16:28:33 -0700 Subject: [PATCH 77/85] || --- compiler-rs/src/ast.rs | 1 + compiler-rs/src/function_emitter.rs | 53 +++++++++++++++++++++---- compiler-rs/src/lexer.rs | 8 +++- compiler-rs/src/parser.rs | 1 + compiler-rs/src/tokens.rs | 1 + compiler-rs/tests/compatibility_test.rs | 8 ---- 6 files changed, 56 insertions(+), 16 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index ddaaf58..5a8d799 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -50,6 +50,7 @@ pub enum BinaryOperator { BitwiseAnd, BitwiseOr, LogicalAnd, + LogicalOr, Pow, LessThan, GreaterThan, diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 95becba..e699baf 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -1,4 +1,5 @@ use crate::emitter_context::EmitterContext; +use crate::utils::f64_const; use crate::{ ast::{ Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, ExpressionBlock, @@ -10,7 +11,6 @@ use crate::{ shim::Shim, }; use crate::{constants::EPSILON, error::EmitterResult}; -use crate::{span::Span, utils::f64_const}; use parity_wasm::elements::{BlockType, FuncBody, Instruction, Instructions, Local, ValueType}; // https://github.com/WACUP/vis_milk2/blob/de9625a89e724afe23ed273b96b8e48496095b6c/ns-eel2/ns-eel.h#L136 @@ -114,11 +114,56 @@ impl<'a> FunctionEmitter<'a> { } } } + fn emit_logical_expression( + &mut self, + left: Expression, + right: Expression, + and: bool, + ) -> EmitterResult<()> { + self.emit_expression(left)?; + if and { + self.emit_is_zeroish(); + } else { + self.emit_is_not_zeroish(); + } + self.push(Instruction::If(BlockType::Value(ValueType::F64))); + self.push(Instruction::F64Const(f64_const(if and { + 0.0 + } else { + 1.0 + }))); + self.push(Instruction::Else); + self.emit_expression(right)?; + self.emit_is_not_zeroish(); + self.push(Instruction::F64ConvertSI32); + self.push(Instruction::End); + Ok(()) + } fn emit_binary_expression(&mut self, binary_expression: BinaryExpression) -> EmitterResult<()> { + match binary_expression.op { + BinaryOperator::LogicalAnd => { + return self.emit_logical_expression( + *binary_expression.left, + *binary_expression.right, + true, + ); + } + BinaryOperator::LogicalOr => { + return self.emit_logical_expression( + *binary_expression.left, + *binary_expression.right, + false, + ); + } + _ => {} + } self.emit_expression(*binary_expression.left)?; self.emit_expression(*binary_expression.right)?; match binary_expression.op { + BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr => { + // Handled above. Is there was a cleaner way to do this? + } BinaryOperator::Add => self.push(Instruction::F64Add), BinaryOperator::Subtract => self.push(Instruction::F64Sub), BinaryOperator::Multiply => self.push(Instruction::F64Mul), @@ -156,12 +201,6 @@ impl<'a> FunctionEmitter<'a> { self.push(Instruction::F64Ge); self.push(Instruction::F64ConvertSI32) } - BinaryOperator::LogicalAnd => { - return Err(CompilerError::new( - "&& has not yet been implemented".to_string(), - Span::new(0, 0), - )) - } BinaryOperator::BitwiseAnd => { let func_index = self .context diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index 6c1dc0f..a756044 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -100,7 +100,13 @@ impl<'a> Lexer<'a> { _ => TokenKind::And, } } - '|' => self.read_char_as_kind(TokenKind::Pipe), + '|' => { + self.chars.next(); + match self.chars.next { + '|' => self.read_char_as_kind(TokenKind::PipePipe), + _ => TokenKind::Pipe, + } + } '^' => self.read_char_as_kind(TokenKind::Caret), '<' => { self.chars.next(); diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index c4ccb55..c0b6d3e 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -185,6 +185,7 @@ impl<'a> Parser<'a> { TokenKind::NotEqual => (0, BinaryOperator::NotEqual), TokenKind::And => (0, BinaryOperator::BitwiseAnd), TokenKind::AndAnd => (0, BinaryOperator::LogicalAnd), + TokenKind::PipePipe => (0, BinaryOperator::LogicalOr), TokenKind::Pipe => (0, BinaryOperator::BitwiseOr), TokenKind::Caret if precedence < EXPONENTIATION_PRECEDENCE => ( left_associative(EXPONENTIATION_PRECEDENCE), diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 6957d6e..9c78797 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -18,6 +18,7 @@ pub enum TokenKind { Percent, And, AndAnd, + PipePipe, Pipe, Caret, PlusEqual, diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index bf6384d..9f0abe3 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -349,14 +349,6 @@ fn compatibility_tests() { "Mod equals (local var)", "Mod equals (megabuf)", "Statement block as argument", - "Logical and (both true)", - "Logical and does not run the left twice", - "Logical and (first value false)", - "Logical and (second value false)", - "Logical or (both true)", - "Logical or (first value false)", - "Logical and shortcircuts", - "Logical or shortcircuts", "Max index megabuf", "Max index + 1 megabuf", "Max index gmegabuf", From ab876d63f6f1b81ed805c98dcc6984718fbf6246 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 9 Apr 2021 16:52:16 -0700 Subject: [PATCH 78/85] Update assignment --- compiler-rs/src/ast.rs | 5 +++ compiler-rs/src/function_emitter.rs | 42 +++++++++++++++++++++++-- compiler-rs/src/lexer.rs | 25 +++++++++++++-- compiler-rs/src/parser.rs | 33 ++++++++++++++----- compiler-rs/src/tokens.rs | 4 +++ compiler-rs/tests/compatibility_test.rs | 10 ------ 6 files changed, 95 insertions(+), 24 deletions(-) diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index 5a8d799..e1c097a 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -75,6 +75,11 @@ pub struct Identifier { #[derive(Debug, PartialEq)] pub enum AssignmentOperator { Equal, + PlusEqual, + MinusEqual, + TimesEqual, + DivEqual, + ModEqual, } #[derive(Debug, PartialEq)] diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index e699baf..a5d2398 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -1,5 +1,5 @@ -use crate::emitter_context::EmitterContext; use crate::utils::f64_const; +use crate::{ast::AssignmentOperator, emitter_context::EmitterContext}; use crate::{ ast::{ Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, ExpressionBlock, @@ -221,17 +221,53 @@ impl<'a> FunctionEmitter<'a> { Ok(()) } - fn emit_assignment(&mut self, assignment_expression: Assignment) -> EmitterResult<()> { + fn emit_update_assignment( + &mut self, + assignment_expression: Assignment, + update: Instruction, + ) -> EmitterResult<()> { let resolved_name = self .context .resolve_variable(assignment_expression.left.name); + self.push(Instruction::GetGlobal(resolved_name)); self.emit_expression(*assignment_expression.right)?; - + self.push(update); self.push(Instruction::SetGlobal(resolved_name)); self.push(Instruction::GetGlobal(resolved_name)); Ok(()) } + fn emit_assignment(&mut self, assignment_expression: Assignment) -> EmitterResult<()> { + match assignment_expression.operator { + AssignmentOperator::Equal => { + let resolved_name = self + .context + .resolve_variable(assignment_expression.left.name); + self.emit_expression(*assignment_expression.right)?; + + self.push(Instruction::SetGlobal(resolved_name)); + self.push(Instruction::GetGlobal(resolved_name)); + } + AssignmentOperator::PlusEqual => { + self.emit_update_assignment(assignment_expression, Instruction::F64Add)?; + } + AssignmentOperator::MinusEqual => { + self.emit_update_assignment(assignment_expression, Instruction::F64Sub)?; + } + AssignmentOperator::TimesEqual => { + self.emit_update_assignment(assignment_expression, Instruction::F64Mul)?; + } + AssignmentOperator::DivEqual => { + self.emit_update_assignment(assignment_expression, Instruction::F64Div)?; + } + AssignmentOperator::ModEqual => { + let index = self.context.resolve_builtin_function(BuiltinFunction::Mod); + self.emit_update_assignment(assignment_expression, Instruction::Call(index))?; + } + } + Ok(()) + } + fn emit_function_call(&mut self, mut function_call: FunctionCall) -> EmitterResult<()> { match &function_call.name.name[..] { "int" => { diff --git a/compiler-rs/src/lexer.rs b/compiler-rs/src/lexer.rs index a756044..bfaa865 100644 --- a/compiler-rs/src/lexer.rs +++ b/compiler-rs/src/lexer.rs @@ -39,11 +39,24 @@ impl<'a> Lexer<'a> { _ => TokenKind::Plus, } } - '-' => self.read_char_as_kind(TokenKind::Minus), - '*' => self.read_char_as_kind(TokenKind::Asterisk), + '-' => { + self.chars.next(); + match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::MinusEqual), + _ => TokenKind::Minus, + } + } + '*' => { + self.chars.next(); + match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::TimesEqual), + _ => TokenKind::Asterisk, + } + } '/' => { self.chars.next(); match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::DivEqual), '/' => { self.chars.next(); self.eat_inline_comment_tail(); @@ -92,7 +105,13 @@ impl<'a> Lexer<'a> { _ => TokenKind::Bang, } } - '%' => self.read_char_as_kind(TokenKind::Percent), + '%' => { + self.chars.next(); + match self.chars.next { + '=' => self.read_char_as_kind(TokenKind::ModEqual), + _ => TokenKind::Percent, + } + } '&' => { self.chars.next(); match self.chars.next { diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index c0b6d3e..f1d0ce2 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -235,19 +235,36 @@ impl<'a> Parser<'a> { }) } + fn parse_assignment( + &mut self, + identifier: Identifier, + operator: AssignmentOperator, + ) -> ParseResult { + self.advance()?; + let right = self.parse_expression(0)?; + Ok(Expression::Assignment(Assignment { + left: identifier, + operator, + right: Box::new(right), + })) + } + fn parse_identifier_expression(&mut self) -> ParseResult { let identifier = self.parse_identifier()?; match &self.token.kind { - TokenKind::Equal => { - self.advance()?; - let right = self.parse_expression(0)?; - Ok(Expression::Assignment(Assignment { - left: identifier, - operator: AssignmentOperator::Equal, - right: Box::new(right), - })) + TokenKind::Equal => self.parse_assignment(identifier, AssignmentOperator::Equal), + TokenKind::PlusEqual => { + self.parse_assignment(identifier, AssignmentOperator::PlusEqual) + } + TokenKind::MinusEqual => { + self.parse_assignment(identifier, AssignmentOperator::MinusEqual) + } + TokenKind::TimesEqual => { + self.parse_assignment(identifier, AssignmentOperator::TimesEqual) } + TokenKind::DivEqual => self.parse_assignment(identifier, AssignmentOperator::DivEqual), + TokenKind::ModEqual => self.parse_assignment(identifier, AssignmentOperator::ModEqual), TokenKind::OpenParen => { self.advance()?; let mut arguments = vec![]; diff --git a/compiler-rs/src/tokens.rs b/compiler-rs/src/tokens.rs index 9c78797..fbcaf51 100644 --- a/compiler-rs/src/tokens.rs +++ b/compiler-rs/src/tokens.rs @@ -22,11 +22,15 @@ pub enum TokenKind { Pipe, Caret, PlusEqual, + MinusEqual, OpenAngel, CloseAngel, LTEqual, GTEqual, NotEqual, + TimesEqual, + DivEqual, + ModEqual, Than, EOF, SOF, // Allows TokenKind to be non-optional in the parser diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 9f0abe3..52d36db 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -333,20 +333,10 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Plus equals", - "Plus equals (local var)", "Plus equals (megabuf)", - "Minus equals", - "Minus equals (local var)", "Minus equals (megabuf)", - "Times equals", - "Times equals (local var)", "Times equals (megabuf)", - "Divide equals", - "Divide equals (local var)", "Divide equals (megabuf)", - "Mod equals", - "Mod equals (local var)", "Mod equals (megabuf)", "Statement block as argument", "Max index megabuf", From 7b58b8a135fc732dc495d24b1540e680e82706bf Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 9 Apr 2021 17:00:48 -0700 Subject: [PATCH 79/85] Assign --- compiler-rs/src/function_emitter.rs | 18 ++++++++++++++++++ compiler-rs/tests/compatibility_test.rs | 2 -- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index a5d2398..896f8bf 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -416,6 +416,24 @@ impl<'a> FunctionEmitter<'a> { let count = function_call.arguments.pop().unwrap(); self.emit_loop(count, body)?; } + "assign" => { + assert_arity(&function_call, 2)?; + let value = function_call.arguments.pop().unwrap(); + let variable = function_call.arguments.pop().unwrap(); + if let Expression::Identifier(identifier) = variable { + let resolved_name = self.context.resolve_variable(identifier.name); + self.emit_expression(value)?; + self.push(Instruction::SetGlobal(resolved_name)); + self.push(Instruction::GetGlobal(resolved_name)); + } else { + Err(CompilerError::new( + "Expected the first argument of `assign()` to be an identifier." + .to_string(), + // TODO: Point this to the first arg + function_call.name.span, + ))? + } + } "megabuf" => self.emit_memory_access(&mut function_call, 0)?, "gmegabuf" => self.emit_memory_access(&mut function_call, BUFFER_SIZE * 8)?, shim_name if Shim::from_str(shim_name).is_some() => { diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 52d36db..64ca2e6 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -349,8 +349,6 @@ fn compatibility_tests() { "Gmegabuf", "Megabuf != Gmegabuf", "Gmegabuf != Megabuf", - "Assign", - "Assign return value", "EPSILON buffer indexes", "+EPSILON & rounding -#s toward 0", "Negative buffer index", From 27fd103bd49f13ad633c6f5e6b875d712aae2fde Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sat, 10 Apr 2021 14:50:32 -0700 Subject: [PATCH 80/85] Write to buffers --- compiler-rs/src/ast.rs | 8 +- compiler-rs/src/function_emitter.rs | 169 +++++++++++++----- compiler-rs/src/parser.rs | 112 ++++++++---- compiler-rs/tests/compatibility_test.rs | 17 -- .../tests/fixtures/ast/local_variables.eel | 1 + .../fixtures/ast/local_variables.snapshot | 64 +++++++ .../ast/order_of_operations_unary.eel | 1 + .../ast/order_of_operations_unary.snapshot | 42 +++++ 8 files changed, 315 insertions(+), 99 deletions(-) create mode 100644 compiler-rs/tests/fixtures/ast/local_variables.eel create mode 100644 compiler-rs/tests/fixtures/ast/local_variables.snapshot create mode 100644 compiler-rs/tests/fixtures/ast/order_of_operations_unary.eel create mode 100644 compiler-rs/tests/fixtures/ast/order_of_operations_unary.snapshot diff --git a/compiler-rs/src/ast.rs b/compiler-rs/src/ast.rs index e1c097a..32c7079 100644 --- a/compiler-rs/src/ast.rs +++ b/compiler-rs/src/ast.rs @@ -84,11 +84,17 @@ pub enum AssignmentOperator { #[derive(Debug, PartialEq)] pub struct Assignment { - pub left: Identifier, + pub left: AssignmentTarget, pub operator: AssignmentOperator, pub right: Box, } +#[derive(Debug, PartialEq)] +pub enum AssignmentTarget { + Identifier(Identifier), + FunctionCall(FunctionCall), +} + #[derive(Debug, PartialEq)] pub struct FunctionCall { pub name: Identifier, diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 896f8bf..557439e 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -1,9 +1,9 @@ use crate::utils::f64_const; -use crate::{ast::AssignmentOperator, emitter_context::EmitterContext}; +use crate::{ast::AssignmentOperator, emitter_context::EmitterContext, span::Span}; use crate::{ ast::{ - Assignment, BinaryExpression, BinaryOperator, EelFunction, Expression, ExpressionBlock, - FunctionCall, UnaryExpression, UnaryOperator, + Assignment, AssignmentTarget, BinaryExpression, BinaryOperator, EelFunction, Expression, + ExpressionBlock, FunctionCall, UnaryExpression, UnaryOperator, }, builtin_functions::BuiltinFunction, constants::BUFFER_SIZE, @@ -221,51 +221,134 @@ impl<'a> FunctionEmitter<'a> { Ok(()) } - fn emit_update_assignment( - &mut self, - assignment_expression: Assignment, - update: Instruction, - ) -> EmitterResult<()> { - let resolved_name = self - .context - .resolve_variable(assignment_expression.left.name); - self.push(Instruction::GetGlobal(resolved_name)); - self.emit_expression(*assignment_expression.right)?; - self.push(update); - self.push(Instruction::SetGlobal(resolved_name)); - self.push(Instruction::GetGlobal(resolved_name)); - Ok(()) - } - fn emit_assignment(&mut self, assignment_expression: Assignment) -> EmitterResult<()> { - match assignment_expression.operator { - AssignmentOperator::Equal => { - let resolved_name = self - .context - .resolve_variable(assignment_expression.left.name); - self.emit_expression(*assignment_expression.right)?; - - self.push(Instruction::SetGlobal(resolved_name)); - self.push(Instruction::GetGlobal(resolved_name)); - } - AssignmentOperator::PlusEqual => { - self.emit_update_assignment(assignment_expression, Instruction::F64Add)?; - } - AssignmentOperator::MinusEqual => { - self.emit_update_assignment(assignment_expression, Instruction::F64Sub)?; - } - AssignmentOperator::TimesEqual => { - self.emit_update_assignment(assignment_expression, Instruction::F64Mul)?; - } - AssignmentOperator::DivEqual => { - self.emit_update_assignment(assignment_expression, Instruction::F64Div)?; - } + let updater: Option = match assignment_expression.operator { + AssignmentOperator::Equal => None, + AssignmentOperator::PlusEqual => Some(Instruction::F64Add), + AssignmentOperator::MinusEqual => Some(Instruction::F64Sub), + AssignmentOperator::TimesEqual => Some(Instruction::F64Mul), + AssignmentOperator::DivEqual => Some(Instruction::F64Div), AssignmentOperator::ModEqual => { let index = self.context.resolve_builtin_function(BuiltinFunction::Mod); - self.emit_update_assignment(assignment_expression, Instruction::Call(index))?; + Some(Instruction::Call(index)) + } + }; + + match assignment_expression.left { + AssignmentTarget::Identifier(identifier) => { + let resolved_name = self.context.resolve_variable(identifier.name); + match updater { + None => { + self.emit_expression(*assignment_expression.right)?; + + self.push(Instruction::SetGlobal(resolved_name)); + self.push(Instruction::GetGlobal(resolved_name)); + } + Some(update) => { + self.push(Instruction::GetGlobal(resolved_name)); + self.emit_expression(*assignment_expression.right)?; + self.push(update); + self.push(Instruction::SetGlobal(resolved_name)); + self.push(Instruction::GetGlobal(resolved_name)); + } + } + Ok(()) + } + AssignmentTarget::FunctionCall(mut function_call) => { + let memory_offset = match &function_call.name.name[..] { + "megabuf" => 0, + "gmegabuf" => BUFFER_SIZE * 8, + _ => Err(CompilerError::new( + "Only `megabuf()` and `gmegabuf()` can be an assignemnt targets." + .to_string(), + function_call.name.span, + ))?, + }; + // assert arity, assert name + match updater { + None => { + // TODO: Move this to builtin_functions + let unnormalized_index = self.resolve_local(ValueType::I32); + let right_value = self.resolve_local(ValueType::F64); + + let index = function_call.arguments.pop().unwrap(); + // Emit the right hand side unconditionally to ensure it always runs. + self.emit_expression(*assignment_expression.right)?; + self.push(Instruction::SetLocal(right_value)); + self.emit_expression(index)?; + let get_buffer_index = self + .context + .resolve_builtin_function(BuiltinFunction::GetBufferIndex); + self.push(Instruction::Call(get_buffer_index)); + self.push(Instruction::TeeLocal(unnormalized_index)); + self.push(Instruction::I32Const(0)); + self.push(Instruction::I32LtS); + // STACK: [is the index out of range?] + self.push(Instruction::If(BlockType::Value(ValueType::F64))); + self.push(Instruction::F64Const(f64_const(0.0))); + self.push(Instruction::Else); + self.push(Instruction::GetLocal(unnormalized_index)); + self.push(Instruction::TeeLocal(unnormalized_index)); + // STACK: [buffer index] + self.push(Instruction::GetLocal(right_value)); + // STACK: [buffer index, right] + self.push(Instruction::F64Store(3, memory_offset)); + // STACK: [] + self.push(Instruction::GetLocal(right_value)); + // STACK: [Right/Buffer value] + self.push(Instruction::End); + Ok(()) + } + Some(_update) => { + /* + // TODO: Move this to wasmFunctions once we know how to call functions + // from within functions (need to get the offset). + const index = context.resolveLocal(VAL_TYPE.i32); + const inBounds = context.resolveLocal(VAL_TYPE.i32); + const rightValue = context.resolveLocal(VAL_TYPE.f64); + const result = context.resolveLocal(VAL_TYPE.f64); + return [ + ...rightCode, + ...op.local_set(rightValue), + ...emit(left.arguments[0], context), + ...(context.resolveFunc("_getBufferIndex") ?? []), + ...op.local_tee(index), + // STACK: [index] + ...op.i32_const(-1), + op.i32_ne, + ...op.local_tee(inBounds), + ...op.if(BLOCK.f64), + ...op.local_get(index), + ...op.f64_load(3, addOffset), + op.else, + ...op.f64_const(0), + op.end, + // STACK: [current value from memory || 0] + + // Apply the mutation + ...op.local_get(rightValue), + ...mutationCode, + + ...op.local_tee(result), + // STACK: [new value] + + ...op.local_get(inBounds), + ...op.if(BLOCK.void), + ...op.local_get(index), + ...op.local_get(result), + ...op.f64_store(3, addOffset), + op.end, + ]; + */ + Err(CompilerError::new( + "Assinging to function calls with an update is not yet supported" + .to_string(), + Span::empty(), + )) + } + } } } - Ok(()) } fn emit_function_call(&mut self, mut function_call: FunctionCall) -> EmitterResult<()> { diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index f1d0ce2..6986c38 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -1,8 +1,8 @@ use std::num::ParseFloatError; use crate::ast::{ - Assignment, AssignmentOperator, BinaryExpression, BinaryOperator, ExpressionBlock, - FunctionCall, Identifier, UnaryExpression, UnaryOperator, + Assignment, AssignmentOperator, AssignmentTarget, BinaryExpression, BinaryOperator, + ExpressionBlock, FunctionCall, Identifier, UnaryExpression, UnaryOperator, }; use super::ast::{EelFunction, Expression, NumberLiteral}; @@ -235,63 +235,99 @@ impl<'a> Parser<'a> { }) } - fn parse_assignment( + fn parse_identifier_assignment( &mut self, - identifier: Identifier, + target: AssignmentTarget, operator: AssignmentOperator, ) -> ParseResult { self.advance()?; let right = self.parse_expression(0)?; Ok(Expression::Assignment(Assignment { - left: identifier, + left: target, operator, right: Box::new(right), })) } - fn parse_identifier_expression(&mut self) -> ParseResult { - let identifier = self.parse_identifier()?; - - match &self.token.kind { - TokenKind::Equal => self.parse_assignment(identifier, AssignmentOperator::Equal), + fn parse_assignment_tail(&mut self, left: AssignmentTarget) -> ParseResult { + match self.token.kind { + TokenKind::Equal => self.parse_identifier_assignment(left, AssignmentOperator::Equal), TokenKind::PlusEqual => { - self.parse_assignment(identifier, AssignmentOperator::PlusEqual) + self.parse_identifier_assignment(left, AssignmentOperator::PlusEqual) } TokenKind::MinusEqual => { - self.parse_assignment(identifier, AssignmentOperator::MinusEqual) + self.parse_identifier_assignment(left, AssignmentOperator::MinusEqual) } TokenKind::TimesEqual => { - self.parse_assignment(identifier, AssignmentOperator::TimesEqual) + self.parse_identifier_assignment(left, AssignmentOperator::TimesEqual) } - TokenKind::DivEqual => self.parse_assignment(identifier, AssignmentOperator::DivEqual), - TokenKind::ModEqual => self.parse_assignment(identifier, AssignmentOperator::ModEqual), - TokenKind::OpenParen => { - self.advance()?; - let mut arguments = vec![]; - while self.peek_expression() { - arguments.push(self.parse_expression(0)?); - match self.peek().kind { - TokenKind::Comma => self.advance()?, - TokenKind::CloseParen => { - self.advance()?; - break; - } - _ => { - return Err(CompilerError::new( - "Expected , or )".to_string(), - self.token.span, - )) - } - } + TokenKind::DivEqual => { + self.parse_identifier_assignment(left, AssignmentOperator::DivEqual) + } + TokenKind::ModEqual => { + self.parse_identifier_assignment(left, AssignmentOperator::ModEqual) + } + _ => { + // If you hit this, peek_assignment is wrong. + Err(CompilerError::new( + "Unexpected assignment token".to_string(), + Span::empty(), + )) + } + } + } + + fn peek_assignment(&self) -> bool { + match self.token.kind { + TokenKind::Equal + | TokenKind::PlusEqual + | TokenKind::MinusEqual + | TokenKind::TimesEqual + | TokenKind::DivEqual + | TokenKind::ModEqual => true, + _ => false, + } + } + + fn parse_function_call(&mut self, name: Identifier) -> ParseResult { + self.advance()?; + let mut arguments = vec![]; + while self.peek_expression() { + arguments.push(self.parse_expression(0)?); + match self.peek().kind { + TokenKind::Comma => self.advance()?, + TokenKind::CloseParen => { + self.advance()?; + break; + } + _ => { + return Err(CompilerError::new( + "Expected , or )".to_string(), + self.token.span, + )) } - Ok(Expression::FunctionCall(FunctionCall { - name: identifier, - arguments, - })) } + } + let function_call = FunctionCall { name, arguments }; + + if self.peek_assignment() { + self.parse_assignment_tail(AssignmentTarget::FunctionCall(function_call)) + } else { + Ok(Expression::FunctionCall(function_call)) + } + } + + fn parse_identifier_expression(&mut self) -> ParseResult { + let identifier = self.parse_identifier()?; + + if self.peek_assignment() { + return self.parse_assignment_tail(AssignmentTarget::Identifier(identifier)); + } + + match &self.token.kind { + TokenKind::OpenParen => self.parse_function_call(identifier), _ => Ok(Expression::Identifier(identifier)), } - // TODO: Support other operator types } fn peek(&self) -> &Token { diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 64ca2e6..4e6bf8c 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -339,31 +339,14 @@ fn compatibility_tests() { "Divide equals (megabuf)", "Mod equals (megabuf)", "Statement block as argument", - "Max index megabuf", - "Max index + 1 megabuf", - "Max index gmegabuf", - "Max index+1 gmegabuf", - "Megabuf assignment", - "Megabuf assignment (idx 100.0)", - "Megabuf (float)", - "Gmegabuf", - "Megabuf != Gmegabuf", - "Gmegabuf != Megabuf", - "EPSILON buffer indexes", - "+EPSILON & rounding -#s toward 0", - "Negative buffer index", - "Negative buffer index gmegabuf", - "Negative buf index execs right hand side", "Negative buf index +=", "Negative buf index -=", "Negative buf index *=", "Negative buf index /=", "Negative buf index %=", "Buff += mutates", - "Buffers don\'t collide", "gmegabuf does not write megabuf", "megabuf does not write gmegabuf", - "Adjacent buf indicies don\'t collide", ]; println!("Failing: {}/{}", expected_failing.len(), test_cases.len()); diff --git a/compiler-rs/tests/fixtures/ast/local_variables.eel b/compiler-rs/tests/fixtures/ast/local_variables.eel new file mode 100644 index 0000000..b137939 --- /dev/null +++ b/compiler-rs/tests/fixtures/ast/local_variables.eel @@ -0,0 +1 @@ +a = 10; g = a * a; \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/ast/local_variables.snapshot b/compiler-rs/tests/fixtures/ast/local_variables.snapshot new file mode 100644 index 0000000..b6dfc4f --- /dev/null +++ b/compiler-rs/tests/fixtures/ast/local_variables.snapshot @@ -0,0 +1,64 @@ +a = 10; g = a * a; +======================================================================== +EelFunction { + expressions: ExpressionBlock { + expressions: [ + Assignment( + Assignment { + left: Identifier( + Identifier { + name: "a", + span: Span { + start: 0, + end: 1, + }, + }, + ), + operator: Equal, + right: NumberLiteral( + NumberLiteral { + value: 10.0, + }, + ), + }, + ), + Assignment( + Assignment { + left: Identifier( + Identifier { + name: "g", + span: Span { + start: 8, + end: 9, + }, + }, + ), + operator: Equal, + right: BinaryExpression( + BinaryExpression { + left: Identifier( + Identifier { + name: "a", + span: Span { + start: 12, + end: 13, + }, + }, + ), + right: Identifier( + Identifier { + name: "a", + span: Span { + start: 16, + end: 17, + }, + }, + ), + op: Multiply, + }, + ), + }, + ), + ], + }, +} diff --git a/compiler-rs/tests/fixtures/ast/order_of_operations_unary.eel b/compiler-rs/tests/fixtures/ast/order_of_operations_unary.eel new file mode 100644 index 0000000..a824a89 --- /dev/null +++ b/compiler-rs/tests/fixtures/ast/order_of_operations_unary.eel @@ -0,0 +1 @@ +g = -1 + 1; \ No newline at end of file diff --git a/compiler-rs/tests/fixtures/ast/order_of_operations_unary.snapshot b/compiler-rs/tests/fixtures/ast/order_of_operations_unary.snapshot new file mode 100644 index 0000000..7280046 --- /dev/null +++ b/compiler-rs/tests/fixtures/ast/order_of_operations_unary.snapshot @@ -0,0 +1,42 @@ +g = -1 + 1; +======================================================================== +EelFunction { + expressions: ExpressionBlock { + expressions: [ + Assignment( + Assignment { + left: Identifier( + Identifier { + name: "g", + span: Span { + start: 0, + end: 1, + }, + }, + ), + operator: Equal, + right: BinaryExpression( + BinaryExpression { + left: UnaryExpression( + UnaryExpression { + right: NumberLiteral( + NumberLiteral { + value: 1.0, + }, + ), + op: Minus, + }, + ), + right: NumberLiteral( + NumberLiteral { + value: 1.0, + }, + ), + op: Add, + }, + ), + }, + ), + ], + }, +} From e35acab5d8e1ea91f2b3bbc6a91e0dcb10161af2 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sat, 10 Apr 2021 19:10:17 -0700 Subject: [PATCH 81/85] += etc for buffer --- compiler-rs/src/function_emitter.rs | 83 +++++++++++-------------- compiler-rs/tests/compatibility_test.rs | 11 ---- 2 files changed, 38 insertions(+), 56 deletions(-) diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 557439e..9d76a99 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -299,52 +299,45 @@ impl<'a> FunctionEmitter<'a> { self.push(Instruction::End); Ok(()) } - Some(_update) => { - /* - // TODO: Move this to wasmFunctions once we know how to call functions + Some(update) => { + // TODO: Move this to wasmFunctions once we know how to call functions // from within functions (need to get the offset). - const index = context.resolveLocal(VAL_TYPE.i32); - const inBounds = context.resolveLocal(VAL_TYPE.i32); - const rightValue = context.resolveLocal(VAL_TYPE.f64); - const result = context.resolveLocal(VAL_TYPE.f64); - return [ - ...rightCode, - ...op.local_set(rightValue), - ...emit(left.arguments[0], context), - ...(context.resolveFunc("_getBufferIndex") ?? []), - ...op.local_tee(index), - // STACK: [index] - ...op.i32_const(-1), - op.i32_ne, - ...op.local_tee(inBounds), - ...op.if(BLOCK.f64), - ...op.local_get(index), - ...op.f64_load(3, addOffset), - op.else, - ...op.f64_const(0), - op.end, - // STACK: [current value from memory || 0] - - // Apply the mutation - ...op.local_get(rightValue), - ...mutationCode, - - ...op.local_tee(result), - // STACK: [new value] - - ...op.local_get(inBounds), - ...op.if(BLOCK.void), - ...op.local_get(index), - ...op.local_get(result), - ...op.f64_store(3, addOffset), - op.end, - ]; - */ - Err(CompilerError::new( - "Assinging to function calls with an update is not yet supported" - .to_string(), - Span::empty(), - )) + let index = self.resolve_local(ValueType::I32); + let in_bounds = self.resolve_local(ValueType::I32); + let right_value = self.resolve_local(ValueType::F64); + let result = self.resolve_local(ValueType::F64); + let left = function_call.arguments.pop().unwrap(); + self.emit_expression(*assignment_expression.right)?; + self.push(Instruction::SetLocal(right_value)); + self.emit_expression(left)?; + let get_buffer_index = self + .context + .resolve_builtin_function(BuiltinFunction::GetBufferIndex); + self.push(Instruction::Call(get_buffer_index)); + self.push(Instruction::TeeLocal(index)); + // STACK: [index] + self.push(Instruction::I32Const(-1)); + self.push(Instruction::I32Ne); + self.push(Instruction::TeeLocal(in_bounds)); + self.push(Instruction::If(BlockType::Value(ValueType::F64))); + self.push(Instruction::GetLocal(index)); + self.push(Instruction::F64Load(3, memory_offset)); + self.push(Instruction::Else); + self.push(Instruction::F64Const(f64_const(0.0))); + self.push(Instruction::End); + // STACK: [current value from memory || 0] + // Apply the mutation + self.push(Instruction::GetLocal(right_value)); + self.push(update); + self.push(Instruction::TeeLocal(result)); + self.push(Instruction::GetLocal(in_bounds)); + // STACK: [new value] + self.push(Instruction::If(BlockType::NoResult)); + self.push(Instruction::GetLocal(index)); + self.push(Instruction::GetLocal(result)); + self.push(Instruction::F64Store(3, memory_offset)); + self.push(Instruction::End); + Ok(()) } } } diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 4e6bf8c..116bd2f 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -333,18 +333,7 @@ fn compatibility_tests() { ]; let expected_failing: Vec<&str> = vec![ - "Plus equals (megabuf)", - "Minus equals (megabuf)", - "Times equals (megabuf)", - "Divide equals (megabuf)", - "Mod equals (megabuf)", "Statement block as argument", - "Negative buf index +=", - "Negative buf index -=", - "Negative buf index *=", - "Negative buf index /=", - "Negative buf index %=", - "Buff += mutates", "gmegabuf does not write megabuf", "megabuf does not write gmegabuf", ]; From f0e9b0f608626f64ef61b9426dfc98532211f4ed Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sat, 10 Apr 2021 19:11:24 -0700 Subject: [PATCH 82/85] Enable passing test --- compiler-rs/tests/compatibility_test.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index 116bd2f..c847ea0 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -228,8 +228,7 @@ fn compatibility_tests() { ("Case insensitive funcs", "g = InT(10);", 10.0), ("Consecutive semis", "g = 10;;; ;g = 20;;", 20.0), ("Equality (< epsilon)", "g = 0.000009 == 0;", 1.0), - // TODO: Fix this - // ("Equality (< -epsilon)", "g = -0.000009 == 0;", 1.0), + ("Equality (< -epsilon)", "g = -0.000009 == 0;", 1.0), ("Variables don't collide", "g = 1; not_g = 2;", 1.0), ("Simple block comment", "g = 1; /* g = 10 */", 1.0), ("Block comment", "g = 1; /* g = 10 */ g = g * 2;", 2.0), From ccb5ad9e7ab23e08c47d7b0fd27282b996c63aba Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sat, 10 Apr 2021 20:32:38 -0700 Subject: [PATCH 83/85] Clean up CLI --- compiler-rs/src/bin/compiler.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler-rs/src/bin/compiler.rs b/compiler-rs/src/bin/compiler.rs index 4fd84e0..bd26734 100644 --- a/compiler-rs/src/bin/compiler.rs +++ b/compiler-rs/src/bin/compiler.rs @@ -1,4 +1,5 @@ use eel_wasm::compile; +use std::io::{self, Write}; use std::process; use std::{collections::HashMap, fs}; @@ -14,7 +15,7 @@ struct Opt { /// Output file, stdout if not present #[structopt(parse(from_os_str))] - output: PathBuf, + output: Option, } fn main() { @@ -34,10 +35,18 @@ fn main() { process::exit(1); }); - fs::write(opt.output, result).unwrap_or_else(|err| { - eprintln!("Error writing output: {}", err); - process::exit(1); - }); - - println!("Done."); + match opt.output { + Some(output) => { + fs::write(output, result).unwrap_or_else(|err| { + eprintln!("Error writing output: {}", err); + process::exit(1); + }); + } + None => { + io::stdout().write_all(&result).unwrap_or_else(|err| { + eprintln!("Error writing to stdout: {}", err); + process::exit(1); + }); + } + } } From afd7ae7facdcf3e32d88b7fef72d11c2b01a8187 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sun, 11 Apr 2021 23:54:07 -0700 Subject: [PATCH 84/85] Allow function args to be expression blocks --- compiler-rs/src/function_emitter.rs | 2 +- compiler-rs/src/parser.rs | 10 +++++++++- compiler-rs/tests/compatibility_test.rs | 25 ++++--------------------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 9d76a99..0ce663c 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -1,5 +1,5 @@ use crate::utils::f64_const; -use crate::{ast::AssignmentOperator, emitter_context::EmitterContext, span::Span}; +use crate::{ast::AssignmentOperator, emitter_context::EmitterContext}; use crate::{ ast::{ Assignment, AssignmentTarget, BinaryExpression, BinaryOperator, EelFunction, Expression, diff --git a/compiler-rs/src/parser.rs b/compiler-rs/src/parser.rs index 6986c38..4d1fe29 100644 --- a/compiler-rs/src/parser.rs +++ b/compiler-rs/src/parser.rs @@ -293,7 +293,15 @@ impl<'a> Parser<'a> { self.advance()?; let mut arguments = vec![]; while self.peek_expression() { - arguments.push(self.parse_expression(0)?); + let mut block = self.parse_expression_block()?; + // Janky here. Some functions are special and expect the arguments to be specific kinds of + // expressions. The possiblity of an ExpressionBlock wrapper complicates those checks, so we + // avoid the wrapper in the common case of a single expression. + if block.expressions.len() == 1 { + arguments.push(block.expressions.pop().unwrap()); + } else { + arguments.push(Expression::ExpressionBlock(block)); + } match self.peek().kind { TokenKind::Comma => self.advance()?, TokenKind::CloseParen => { diff --git a/compiler-rs/tests/compatibility_test.rs b/compiler-rs/tests/compatibility_test.rs index c847ea0..5365758 100644 --- a/compiler-rs/tests/compatibility_test.rs +++ b/compiler-rs/tests/compatibility_test.rs @@ -331,18 +331,6 @@ fn compatibility_tests() { ), ]; - let expected_failing: Vec<&str> = vec![ - "Statement block as argument", - "gmegabuf does not write megabuf", - "megabuf does not write gmegabuf", - ]; - - println!("Failing: {}/{}", expected_failing.len(), test_cases.len()); - println!( - "Passing: {}%", - (test_cases.len() - expected_failing.len()) as f64 / test_cases.len() as f64 - ); - for (name, code, expected) in test_cases { let mut globals = HashMap::default(); let mut pool_globals = HashSet::new(); @@ -355,9 +343,6 @@ fn compatibility_tests() { "test", ) { Ok(actual) => { - if expected_failing.contains(name) { - panic!(format!("Expected {} to fail, but it passed!", name)); - } if &actual != expected { panic!(format!( "Bad result for {}. Expected {}, but got {}.", @@ -366,12 +351,10 @@ fn compatibility_tests() { } } Err(err) => { - if !expected_failing.contains(name) { - panic!(format!( - "Didn't expect \"{}\" to fail. Failed with {:?}", - name, err - )); - } + panic!(format!( + "Didn't expect \"{}\" to fail. Failed with {:?}", + name, err + )); } } } From baf216c72ac5fd98f3725bdad89b3f1780c945ce Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sun, 11 Apr 2021 23:59:06 -0700 Subject: [PATCH 85/85] Cleanup --- compiler-rs/src/function_emitter.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler-rs/src/function_emitter.rs b/compiler-rs/src/function_emitter.rs index 0ce663c..232cb03 100644 --- a/compiler-rs/src/function_emitter.rs +++ b/compiler-rs/src/function_emitter.rs @@ -141,6 +141,7 @@ impl<'a> FunctionEmitter<'a> { } fn emit_binary_expression(&mut self, binary_expression: BinaryExpression) -> EmitterResult<()> { + // First we handle cases where we don't just emit arguments in order. match binary_expression.op { BinaryOperator::LogicalAnd => { return self.emit_logical_expression( @@ -158,6 +159,8 @@ impl<'a> FunctionEmitter<'a> { } _ => {} } + + // Now handle the common cases where we omit arguments in order. self.emit_expression(*binary_expression.left)?; self.emit_expression(*binary_expression.right)?; match binary_expression.op { @@ -229,8 +232,8 @@ impl<'a> FunctionEmitter<'a> { AssignmentOperator::TimesEqual => Some(Instruction::F64Mul), AssignmentOperator::DivEqual => Some(Instruction::F64Div), AssignmentOperator::ModEqual => { - let index = self.context.resolve_builtin_function(BuiltinFunction::Mod); - Some(Instruction::Call(index)) + let mod_index = self.context.resolve_builtin_function(BuiltinFunction::Mod); + Some(Instruction::Call(mod_index)) } }; @@ -264,7 +267,7 @@ impl<'a> FunctionEmitter<'a> { function_call.name.span, ))?, }; - // assert arity, assert name + assert_arity(&function_call, 1)?; match updater { None => { // TODO: Move this to builtin_functions