diff --git a/inputs/failing/compareFnsWithDifferentRefs.ts b/inputs/failing/compareFnsWithDifferentRefs.ts new file mode 100644 index 0000000..31ff904 --- /dev/null +++ b/inputs/failing/compareFnsWithDifferentRefs.ts @@ -0,0 +1,28 @@ +//! test_output(true) +// Should be: false. + +export default function () { + // The test functions returned have exactly the same source code, but they should not be equal + // because they reference `content` functions that are not equal. + return foo() === bar(); +} + +function foo() { + function content() { + return "foo"; + } + + return function test() { + return content(); + }; +} + +function bar() { + function content() { + return "bar"; + } + + return function test() { + return content(); + }; +} diff --git a/inputs/failing/compareClassInstancesWithEqualSources.ts b/inputs/passing/compareClassInstancesWithEqualSources.ts similarity index 62% rename from inputs/failing/compareClassInstancesWithEqualSources.ts rename to inputs/passing/compareClassInstancesWithEqualSources.ts index d6d6919..08ffbb8 100644 --- a/inputs/failing/compareClassInstancesWithEqualSources.ts +++ b/inputs/passing/compareClassInstancesWithEqualSources.ts @@ -1,10 +1,7 @@ -//! test_output(false) -// Should be: true +//! test_output(true) // When functions (and therefore classes) have the same source (and references), they should compare -// equal due to the content hash system. This isn't implemented yet - the content hash is just faked -// using its location in the bytecode, which means you only get equal comparison when the functions -// are referentially equal (inconsistent with value semantics). +// equal due to the content hash system. export default () => { const a = new classes[0](1, 2); diff --git a/inputs/passing/compareFnsWithDifferentBinds.ts b/inputs/passing/compareFnsWithDifferentBinds.ts new file mode 100644 index 0000000..bf51f4e --- /dev/null +++ b/inputs/passing/compareFnsWithDifferentBinds.ts @@ -0,0 +1,11 @@ +//! test_output(false) + +export default function () { + return make("foo") === make("bar"); +} + +function make(bind: unknown) { + return function test() { + return bind; + }; +} diff --git a/valuescript_compiler/src/asm.rs b/valuescript_compiler/src/asm.rs index 9193dd4..6746743 100644 --- a/valuescript_compiler/src/asm.rs +++ b/valuescript_compiler/src/asm.rs @@ -122,15 +122,21 @@ impl std::fmt::Display for Pointer { #[derive(Default, Debug, Clone)] pub struct Function { pub is_generator: bool, + pub metadata: Value, pub parameters: Vec, pub body: Vec, } impl std::fmt::Display for Function { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let metadata_str = match &self.metadata { + Value::Void => "".to_string(), + metadata => format!(" {}", metadata), + }; + match self.is_generator { - false => write!(f, "function(")?, - true => write!(f, "function*(")?, + false => write!(f, "function{}(", metadata_str)?, + true => write!(f, "function*{}(", metadata_str)?, } for (i, parameter) in self.parameters.iter().enumerate() { diff --git a/valuescript_compiler/src/assembler.rs b/valuescript_compiler/src/assembler.rs index e8abdc9..05f9833 100644 --- a/valuescript_compiler/src/assembler.rs +++ b/valuescript_compiler/src/assembler.rs @@ -77,6 +77,8 @@ impl Assembler { true => ValueType::GeneratorFunction, } as u8); + self.value(&function.metadata); + self.fn_data = Default::default(); self.fn_data.register_count_pos = self.output.len(); diff --git a/valuescript_compiler/src/assembly_parser.rs b/valuescript_compiler/src/assembly_parser.rs index db3d594..e1602ed 100644 --- a/valuescript_compiler/src/assembly_parser.rs +++ b/valuescript_compiler/src/assembly_parser.rs @@ -422,7 +422,16 @@ impl<'a> AssemblyParser<'a> { function.is_generator = true; } - self.parse_exact("("); + self.parse_whitespace(); + + if self.test_chars("(") { + // Leave metadata as void + self.parse_exact("("); + } else { + function.metadata = self.assemble_value(); + self.parse_optional_whitespace(); + self.parse_exact("("); + } loop { self.parse_optional_whitespace(); diff --git a/valuescript_compiler/src/function_compiler.rs b/valuescript_compiler/src/function_compiler.rs index 180b96f..86ead54 100644 --- a/valuescript_compiler/src/function_compiler.rs +++ b/valuescript_compiler/src/function_compiler.rs @@ -5,8 +5,8 @@ use std::mem::take; use swc_common::Spanned; use crate::asm::{ - Builtin, Definition, DefinitionContent, FnLine, Function, Instruction, Label, Pointer, Register, - Value, + Builtin, Definition, DefinitionContent, FnLine, Function, Instruction, Label, Object, Pointer, + Register, Value, }; use crate::diagnostic::{Diagnostic, DiagnosticContainer, DiagnosticReporter}; use crate::expression_compiler::CompiledExpression; @@ -16,6 +16,7 @@ use crate::module_compiler::ModuleCompiler; use crate::name_allocator::{NameAllocator, RegAllocator}; use crate::scope::{NameId, OwnerId}; use crate::scope_analysis::{fn_to_owner_id, Name}; +use crate::source_hash::source_hash_asm; #[derive(Clone, Debug)] pub enum Functionish { @@ -32,6 +33,40 @@ impl Functionish { Functionish::Constructor(_, owner_id, _) => owner_id.clone(), } } + + pub fn metadata(&self, source: &str) -> Value { + match self { + Functionish::Fn(ident, fn_) => Value::Object(Box::new(Object { + properties: vec![ + ( + Value::String("name".to_string()), + Value::String( + ident + .as_ref() + .map_or_else(|| "".to_string(), |ident| ident.sym.to_string()), + ), + ), + ( + Value::String("hash".to_string()), + source_hash_asm(source, fn_.span), + ), + ], + })), + Functionish::Arrow(arrow) => Value::Object(Box::new(Object { + properties: vec![ + ( + Value::String("name".to_string()), + Value::String("".to_string()), + ), + ( + Value::String("hash".to_string()), + source_hash_asm(source, arrow.span), + ), + ], + })), + Functionish::Constructor(_, _, _) => Value::Void, + } + } } #[derive(Clone, Debug)] @@ -203,6 +238,10 @@ impl<'a> FunctionCompiler<'a> { Functionish::Constructor(..) => false, }; + let metadata_pointer = self.mc.allocate_defn_numbered("meta"); + let metadata = functionish.metadata(&self.mc.source); + self.fn_.metadata = Value::Pointer(metadata_pointer.clone()); + self.set_owner_id(functionish.owner_id()); let capture_params = self @@ -289,6 +328,11 @@ impl<'a> FunctionCompiler<'a> { pointer: definition_pointer, content: DefinitionContent::Function(take(&mut self.fn_)), }); + + self.mc.module.definitions.push(Definition { + pointer: metadata_pointer, + content: DefinitionContent::Value(metadata), + }); } fn handle_block_body(&mut self, block: &swc_ecma_ast::BlockStmt) { diff --git a/valuescript_compiler/src/source_hash.rs b/valuescript_compiler/src/source_hash.rs index e0042d0..7862e06 100644 --- a/valuescript_compiler/src/source_hash.rs +++ b/valuescript_compiler/src/source_hash.rs @@ -1,7 +1,8 @@ use swc_common::BytePos; use tiny_keccak::{Hasher, Keccak}; -#[allow(dead_code)] +use crate::asm::Value; + pub fn source_hash(source: &str, span: swc_common::Span) -> [u8; 32] { let BytePos(start) = span.lo; let BytePos(end) = span.hi; @@ -19,3 +20,14 @@ pub fn source_hash(source: &str, span: swc_common::Span) -> [u8; 32] { output } + +pub fn source_hash_asm(source: &str, span: swc_common::Span) -> Value { + let mut result = String::with_capacity(66); + result.push_str("0x"); + + for byte in &source_hash(source, span) { + result.push_str(&format!("{:02x}", byte)); + } + + Value::String(result) +} diff --git a/valuescript_compiler/src/visit_pointers.rs b/valuescript_compiler/src/visit_pointers.rs index 7296daa..a46b762 100644 --- a/valuescript_compiler/src/visit_pointers.rs +++ b/valuescript_compiler/src/visit_pointers.rs @@ -47,6 +47,7 @@ where match &mut definition.content { DefinitionContent::Function(function) => { + self.value(Some(&definition.pointer), &mut function.metadata); self.body(&definition.pointer, &mut function.body); } DefinitionContent::Value(value) => { diff --git a/valuescript_vm/src/bytecode_decoder.rs b/valuescript_vm/src/bytecode_decoder.rs index e563e7f..7eb3744 100644 --- a/valuescript_vm/src/bytecode_decoder.rs +++ b/valuescript_vm/src/bytecode_decoder.rs @@ -10,7 +10,6 @@ use crate::builtins::BUILTIN_VALS; use crate::bytecode::Bytecode; use crate::vs_class::VsClass; use crate::vs_function::VsFunction; -use crate::vs_function_metadata::VsFunctionMetadata; use crate::vs_object::VsObject; use crate::vs_symbol::VsSymbol; use crate::vs_value::ToVal; @@ -134,7 +133,7 @@ impl BytecodeDecoder { } .to_val() } - BytecodeType::Function => self.decode_function(false), + BytecodeType::Function => self.decode_function(false, registers), BytecodeType::Pointer => self.decode_pointer(registers), BytecodeType::Register => match registers[self.decode_register_index().unwrap()].clone() { Val::Void => Val::Undefined, @@ -152,7 +151,7 @@ impl BytecodeDecoder { } .to_val(), BytecodeType::BigInt => self.decode_bigint().to_val(), - BytecodeType::GeneratorFunction => self.decode_function(true), + BytecodeType::GeneratorFunction => self.decode_function(true, registers), BytecodeType::Unrecognized => panic!("Unrecognized bytecode type at {}", self.pos - 1), } } @@ -281,11 +280,8 @@ impl BytecodeDecoder { } } - pub fn decode_function(&mut self, is_generator: bool) -> Val { - // TODO: Use actual content hash - let mut hash = [0u8; 32]; - hash[0] = (self.pos & 0xff) as u8; - hash[1] = ((self.pos >> 8) & 0xff) as u8; + pub fn decode_function(&mut self, is_generator: bool, registers: &mut Vec) -> Val { + let metadata = self.decode_val(registers); // TODO: Support >256 let register_count = self.decode_byte() as usize; @@ -293,10 +289,7 @@ impl BytecodeDecoder { VsFunction { bytecode: self.bytecode.clone(), - metadata: VsFunctionMetadata { - name: Rc::from(""), - hash, - }, + metadata, is_generator, register_count, parameter_count, diff --git a/valuescript_vm/src/lib.rs b/valuescript_vm/src/lib.rs index 211bd56..2682080 100644 --- a/valuescript_vm/src/lib.rs +++ b/valuescript_vm/src/lib.rs @@ -23,7 +23,6 @@ mod virtual_machine; pub mod vs_array; pub mod vs_class; mod vs_function; -mod vs_function_metadata; pub mod vs_object; mod vs_symbol; pub mod vs_value; diff --git a/valuescript_vm/src/operations.rs b/valuescript_vm/src/operations.rs index b7e5b9a..eced562 100644 --- a/valuescript_vm/src/operations.rs +++ b/valuescript_vm/src/operations.rs @@ -194,7 +194,10 @@ pub fn op_eq_impl(left: &Val, right: &Val) -> Result { } } - left.metadata.hash == right.metadata.hash + op_triple_eq_impl( + &left.metadata.sub(&"hash".to_val())?, + &right.metadata.sub(&"hash".to_val())?, + )? } _ => { if left.is_truthy() != right.is_truthy() { @@ -316,7 +319,10 @@ pub fn op_triple_eq_impl(left: &Val, right: &Val) -> Result { } } - left.metadata.hash == right.metadata.hash + op_triple_eq_impl( + &left.metadata.sub(&"hash".to_val())?, + &right.metadata.sub(&"hash".to_val())?, + )? } #[allow(clippy::vtable_address_comparisons)] // TODO: Is this ok? (Val::Static(left), Val::Static(right)) => std::ptr::eq(&**left, &**right), diff --git a/valuescript_vm/src/vs_function.rs b/valuescript_vm/src/vs_function.rs index 898154d..3de72f7 100644 --- a/valuescript_vm/src/vs_function.rs +++ b/valuescript_vm/src/vs_function.rs @@ -2,7 +2,6 @@ use std::rc::Rc; use crate::bytecode::Bytecode; use crate::make_generator_frame::MakeGeneratorFrame; -use crate::vs_function_metadata::VsFunctionMetadata; use crate::vs_value::ToVal; use super::bytecode_decoder::BytecodeDecoder; @@ -13,7 +12,7 @@ use super::vs_value::Val; #[derive(Debug, Clone)] pub struct VsFunction { pub bytecode: Rc, - pub metadata: VsFunctionMetadata, + pub metadata: Val, pub is_generator: bool, pub register_count: usize, pub parameter_count: usize, diff --git a/valuescript_vm/src/vs_function_metadata.rs b/valuescript_vm/src/vs_function_metadata.rs deleted file mode 100644 index 60e74e9..0000000 --- a/valuescript_vm/src/vs_function_metadata.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::rc::Rc; - -#[derive(Clone, Debug)] -pub struct VsFunctionMetadata { - pub name: Rc, - pub hash: [u8; 32], -}