From 89cf262e395b7f7242c4206fd0eae95c1e036c14 Mon Sep 17 00:00:00 2001 From: Pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:03:16 +0000 Subject: [PATCH] more refactor --- README.md | 1 + playground/lib/project-runner.js | 13 ++-- src/ir.rs | 53 ++++++++-------- src/targets/wasm.rs | 105 ++++++++++++++++++++----------- 4 files changed, 106 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 1009d78..d7b8ff3 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ If you experience runtime stack overflow errors in debug mode, try using the `-O | 0x00 | float64 | `f64` | a float | | 0x01 | bool64 | `i64` | an integer - only the least significant bit is used | | 0x02 | externref string (64 bit) | `i64` | wrapped to a 32 bit pointer to an `externref` value in the `anyref` table | +| 0x03 | int64 | `i64` | an integer. may be too large to convert to a float | ### Memory layout guarantees diff --git a/playground/lib/project-runner.js b/playground/lib/project-runner.js index 966ff1b..202ad51 100644 --- a/playground/lib/project-runner.js +++ b/playground/lib/project-runner.js @@ -18,6 +18,7 @@ export default async ( framerate: 30, } ) => { + window.open(URL.createObjectURL(new Blob([wasm_bytes], { type: 'application/wasm' }))); const framerate_wait = Math.round(1000 / framerate); let assert; let exit; @@ -144,10 +145,10 @@ export default async ( }, runtime: { looks_say: (ty, val, targetIndex) => { - targetOutput(targetIndex, "say", wasm_val_to_js(ty, val)); + targetOutput(targetIndex, "say", wasm_val_to_js(ty, val).toString()); }, looks_think: (ty, val, targetIndex) => { - targetOutput(targetIndex, "think", wasm_val_to_js(ty, val)); + targetOutput(targetIndex, "think", wasm_val_to_js(ty, val).toString()); }, operator_equals: (ty1, val1, ty2, val2) => { if (ty1 === ty2 && val1 === val2) return true; @@ -159,12 +160,12 @@ export default async ( }, operator_random: (lower, upper) => Math.random() * (upper - lower) + lower, - operator_join: (ty1, val1, ty2, val2) => - wasm_val_to_js(ty1, val1).toString() + - wasm_val_to_js(ty2, val2).toString(), + operator_join: (str1, str2) => + str1.toString() + + str2.toString(), operator_letterof: (idx, ty, val) => wasm_val_to_js(ty, val).toString()[idx - 1] ?? "", - operator_length: (ty, val) => wasm_val_to_js(ty, val).toString().length, + operator_length: (str) => str.length, operator_contains: (ty1, val1, ty2, val2) => wasm_val_to_js(ty1, val1) .toString() diff --git a/src/ir.rs b/src/ir.rs index 871b220..5d6e467 100644 --- a/src/ir.rs +++ b/src/ir.rs @@ -423,7 +423,6 @@ impl IrBlock { let expected = expected_inputs.get(i).ok_or(make_hq_bug!(""))?; let actual = &type_stack.get(i).borrow().clone().unwrap().1; if !expected.includes(actual) { - dbg!("cast: {:?} -> {:?}, input {}", &actual, &expected, i); add_cast(i, expected); } } @@ -446,7 +445,6 @@ impl IrBlock { type_stack.len() ); } - dbg!(&type_stack); for i in 0..expected_inputs.len() { let expected = expected_inputs.get(i).ok_or(make_hq_bug!(""))?; let actual = &type_stack @@ -519,18 +517,27 @@ pub enum InputType { } impl InputType { - fn base_type(&self) -> InputType { + pub fn base_type(&self) -> InputType { use InputType::*; + // unions of types should be represented with the least restrictive type first, so that casting chooses the less restrictive type to cast to match self { Any => Union(Box::new(String), Box::new(Number)).base_type(), Number => Union(Box::new(Float), Box::new(Integer)).base_type(), - Integer => Union(Box::new(Boolean), Box::new(ConcreteInteger)).base_type(), + Integer => Union(Box::new(ConcreteInteger), Box::new(Boolean)).base_type(), Union(a, b) => Union(Box::new(a.base_type()), Box::new(b.base_type())), _ => self.clone(), } } - fn includes(&self, other: &Self) -> bool { + pub fn least_restrictive_concrete_type(&self) -> InputType { + use InputType::*; + match self.base_type() { + Union(a, _) => a.least_restrictive_concrete_type(), + a @ _ => a.clone(), + } + } + + pub fn includes(&self, other: &Self) -> bool { if self.base_type() == other.base_type() { true } else if let InputType::Union(a, b) = self.base_type() { @@ -556,7 +563,8 @@ impl IrOpcode { operator_add | operator_subtract | operator_multiply | operator_divide | operator_mod | operator_random => vec![Number, Number], operator_round | operator_mathop { .. } => vec![Number], - looks_say | looks_think | data_setvariableto { .. } => vec![Any], + looks_say | looks_think => vec![Unknown], + data_setvariableto { .. } | data_teevariable { .. } => vec![Unknown], math_integer { .. } | math_angle { .. } | math_whole_number { .. } @@ -567,7 +575,7 @@ impl IrOpcode { | data_variable { .. } | text { .. } => vec![], operator_lt | operator_gt => vec![Number, Number], - operator_equals => vec![Any, Any], + operator_equals => vec![Unknown, Unknown], operator_and | operator_or => vec![Boolean, Boolean], operator_not => vec![Boolean], operator_join | operator_contains => vec![String, String], @@ -582,7 +590,6 @@ impl IrOpcode { hq_goto_if { .. } => vec![Boolean], hq_drop(n) => vec![Any; *n], hq_cast(from, _to) => vec![from.clone()], - data_teevariable { .. } => vec![Any], pen_setPenColorToColor | pen_changePenSizeBy | pen_setPenSizeTo @@ -969,6 +976,7 @@ impl IrBlockVec for Vec { steps: &mut IndexMap<(String, String), Step, BuildHasherDefault>, target_id: String, ) -> Result<(), HQError> { + use InputType::*; let block = blocks.get(&block_id).ok_or(make_hq_bug!(""))?; match block { Block::Normal { block_info, .. } => { @@ -1325,9 +1333,11 @@ impl IrBlockVec for Vec { IrOpcode::hq_cast(InputType::Unknown, InputType::Float), // todo: integer IrOpcode::math_whole_number { NUM: 1.0 }, IrOpcode::operator_subtract, + IrOpcode::hq_cast(Float, Unknown), IrOpcode::data_teevariable { VARIABLE: looper_id.clone(), }, + IrOpcode::hq_cast(Unknown, Float), IrOpcode::math_whole_number { NUM: 1.0 }, IrOpcode::operator_lt, ] @@ -1340,15 +1350,10 @@ impl IrBlockVec for Vec { ), )?); } - for op in [ - IrOpcode::math_whole_number { NUM: 1.0 }, - IrOpcode::operator_lt, - ] - .into_iter() - .chain(condition_opcodes.clone().into_iter()) + for op in condition_opcodes.iter() { looper_opcodes.push(IrBlock::new_with_stack_no_cast( - op, + op.clone(), Rc::clone( &looper_opcodes.last().ok_or(make_hq_bug!(""))?.type_stack, ), @@ -1384,7 +1389,12 @@ impl IrBlockVec for Vec { steps, target_id.clone(), )?; - for op in condition_opcodes.into_iter() { + for op in [ + IrOpcode::math_whole_number { NUM: 1.0 }, + IrOpcode::operator_lt, + ] + .into_iter() + .chain(condition_opcodes.clone().into_iter()) { opcodes.push(IrBlock::new_with_stack_no_cast( op, Rc::clone(&opcodes.last().ok_or(make_hq_bug!(""))?.type_stack), @@ -1397,9 +1407,11 @@ impl IrBlockVec for Vec { ); vec![ IrOpcode::operator_round, + IrOpcode::hq_cast(Integer, Unknown), IrOpcode::data_teevariable { VARIABLE: looper_id, }, + IrOpcode::hq_cast(Integer, Unknown), IrOpcode::math_number { NUM: 1.0 }, IrOpcode::operator_lt, IrOpcode::hq_goto_if { @@ -1610,15 +1622,6 @@ impl IrBlockVec for Vec { .rposition(|b| b.type_stack.len() == type_stack.len() - j) .ok_or(make_hq_bug!(""))?; let cast_stack = self.get_type_stack(Some(cast_pos)); - dbg!( - &j, - &type_stack.len(), - &self.len(), - &cast_pos, - &cast_type, - &cast_stack, - &self - ); let cast_block = IrBlock::new_with_stack_no_cast( IrOpcode::hq_cast( { diff --git a/src/targets/wasm.rs b/src/targets/wasm.rs index 0c4c822..f587876 100644 --- a/src/targets/wasm.rs +++ b/src/targets/wasm.rs @@ -1,4 +1,6 @@ -use crate::ir::{InputType, IrBlock, IrOpcode, IrProject, Step, ThreadContext, ThreadStart}; +use crate::ir::{ + InputType, IrBlock, IrOpcode, IrProject, Step, ThreadContext, ThreadStart, TypeStackImpl, +}; use crate::sb3::VarVal; use crate::HQError; use alloc::collections::BTreeMap; @@ -72,12 +74,28 @@ fn instructions( ] } } - operator_add => vec![F64Add], - operator_subtract => vec![F64Sub], + operator_add => { + if InputType::Integer.includes(&op.type_stack.get(0).borrow().clone().unwrap().1) + && InputType::Integer.includes(&op.type_stack.get(1).borrow().clone().unwrap().1) + { + vec![I32Add] + } else { + vec![F64Add] + } + } + operator_subtract => { + if InputType::Integer.includes(&op.type_stack.get(0).borrow().clone().unwrap().1) + && InputType::Integer.includes(&op.type_stack.get(1).borrow().clone().unwrap().1) + { + vec![I32Sub] + } else { + vec![F64Sub] + } + } operator_divide => vec![F64Div], - operator_multiply => vec![F64Mul], - operator_mod => vec![Call(func_indices::FMOD)], - operator_round => vec![F64Nearest], + operator_multiply => vec![F64Mul], // todo: int opt + operator_mod => vec![Call(func_indices::FMOD)], // todo: int opt + operator_round => vec![F64Nearest, I32TruncF64S], math_number { NUM } | math_integer { NUM } | math_angle { NUM } @@ -967,7 +985,8 @@ fn instructions( End, ] } - hq_cast(from, to) => match (from.clone(), to.clone()) { + hq_cast(from, to) => match (from.clone(), to.least_restrictive_concrete_type()) { + // cast from type should always be a concrete type (String, Float) => vec![Call(func_indices::CAST_PRIMITIVE_STRING_FLOAT)], (String, Boolean) => vec![Call(func_indices::CAST_PRIMITIVE_STRING_BOOL)], (String, Unknown) => vec![ @@ -992,10 +1011,16 @@ fn instructions( LocalGet(step_func_locals::F64), I64ReinterpretF64, ], + (ConcreteInteger, Unknown) => vec![ + LocalSet(step_func_locals::I64), + I32Const(hq_value_types::INT64), + LocalGet(step_func_locals::I64), + ], (Unknown, String) => vec![Call(func_indices::CAST_ANY_STRING)], (Unknown, Float) => vec![Call(func_indices::CAST_ANY_FLOAT)], (Unknown, Boolean) => vec![Call(func_indices::CAST_ANY_BOOL)], - _ => hq_todo!("unimplemented cast: {:?} -> {:?}", to, from), + (Unknown, ConcreteInteger) => vec![Call(func_indices::CAST_ANY_INT)], + _ => hq_todo!("unimplemented cast: {:?} -> {:?} at {:?}", to, from, op), }, other => hq_todo!("missing WASM impl for {:?}", other), }; @@ -1150,10 +1175,11 @@ pub mod func_indices { pub const CAST_ANY_STRING: u32 = 46; pub const CAST_ANY_FLOAT: u32 = 47; pub const CAST_ANY_BOOL: u32 = 48; - pub const TABLE_ADD_STRING: u32 = 49; - pub const SPRITE_UPDATE_PEN_COLOR: u32 = 50; + pub const CAST_ANY_INT: u32 = 49; + pub const TABLE_ADD_STRING: u32 = 50; + pub const SPRITE_UPDATE_PEN_COLOR: u32 = 51; } -pub const BUILTIN_FUNCS: u32 = 51; +pub const BUILTIN_FUNCS: u32 = 52; pub const IMPORTED_FUNCS: u32 = 42; pub mod types { @@ -1197,6 +1223,7 @@ pub mod types { pub const EXTERNREFF64I32_NORESULT: u32 = 36; pub const F64x3F32x4_NORESULT: u32 = 37; pub const F64x5F32x4_NORESULT: u32 = 38; + pub const EXTERNREFx2_EXTERNREF: u32 = 39; } pub mod table_indices { @@ -1208,6 +1235,7 @@ pub mod hq_value_types { pub const FLOAT64: i32 = 0; pub const BOOL64: i32 = 1; pub const EXTERN_STRING_REF64: i32 = 2; + pub const INT64: i32 = 3; } // the number of bytes that one step takes up in linear memory @@ -1349,6 +1377,7 @@ impl TryFrom for WasmProject { ], [], ); + types.function([ValType::Ref(RefType::EXTERNREF), ValType::Ref(RefType::EXTERNREF)], [ValType::Ref(RefType::EXTERNREF)]); imports.import("dbg", "log", EntityType::Function(types::I32I64_NORESULT)); imports.import( @@ -1395,7 +1424,7 @@ impl TryFrom for WasmProject { imports.import( "runtime", "operator_join", - EntityType::Function(types::I32I64I32I64_EXTERNREF), + EntityType::Function(types::EXTERNREFx2_EXTERNREF), ); imports.import( "runtime", @@ -1405,7 +1434,7 @@ impl TryFrom for WasmProject { imports.import( "runtime", "operator_length", - EntityType::Function(types::I32I64_F64), + EntityType::Function(types::EXTERNREF_F64), ); imports.import( "runtime", @@ -1716,6 +1745,12 @@ impl TryFrom for WasmProject { any2bool_func.instruction(&Instruction::End); code.function(&any2bool_func); + functions.function(types::I32I64_I64); + let mut any2int_func = Function::new(vec![]); + any2int_func.instruction(&Instruction::Unreachable); + any2int_func.instruction(&Instruction::End); + code.function(&any2int_func); + functions.function(types::EXTERNREF_I32); let mut tbl_add_string_func = Function::new(vec![(1, ValType::I32)]); tbl_add_string_func.instruction(&Instruction::LocalGet(0)); @@ -2199,28 +2234,6 @@ impl TryFrom for WasmProject { ), }); - tables.table(TableType { - element_type: RefType::EXTERNREF, - minimum: string_consts - .len() - .try_into() - .map_err(|_| make_hq_bug!("string_consts len out of bounds"))?, - maximum: None, - }); - - let step_indices = (BUILTIN_FUNCS - ..(u32::try_from(step_funcs.len()) - .map_err(|_| make_hq_bug!("step_funcs length out of bounds"))? - + BUILTIN_FUNCS)) - .collect::>(); - let step_func_indices = Elements::Functions(&step_indices[..]); - elements.active( - Some(table_indices::STEP_FUNCS), - &ConstExpr::i32_const(0), - RefType::FUNCREF, - step_func_indices, - ); - globals.global( GlobalType { val_type: ValType::I32, @@ -2309,6 +2322,28 @@ impl TryFrom for WasmProject { data.active(0, &ConstExpr::i32_const(0), default_data); + tables.table(TableType { + element_type: RefType::EXTERNREF, + minimum: string_consts + .len() + .try_into() + .map_err(|_| make_hq_bug!("string_consts len out of bounds"))?, + maximum: None, + }); + + let step_indices = (BUILTIN_FUNCS + ..(u32::try_from(step_funcs.len()) + .map_err(|_| make_hq_bug!("step_funcs length out of bounds"))? + + BUILTIN_FUNCS)) + .collect::>(); + let step_func_indices = Elements::Functions(&step_indices[..]); + elements.active( + Some(table_indices::STEP_FUNCS), + &ConstExpr::i32_const(0), + RefType::FUNCREF, + step_func_indices, + ); + module .section(&types) .section(&imports)