From 59e30ec97e3a412ec91dd94fd191ce5eebac26c8 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Thu, 1 Jan 2026 21:48:15 +0000 Subject: [PATCH 01/14] support parsing broadcast event blocks, plus store a list of all broadcasts in IR --- src/instructions/hq/yield.rs | 4 ++-- src/instructions/tests.rs | 4 ++-- src/ir/blocks.rs | 1 + src/ir/event.rs | 3 ++- src/ir/project.rs | 27 +++++++++++++++++++++++++-- src/ir/thread.rs | 31 +++++++++++++++++++++++++------ src/wasm/project.rs | 9 +++++++-- 7 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/instructions/hq/yield.rs b/src/instructions/hq/yield.rs index f0386227..4e9bc8c4 100644 --- a/src/instructions/hq/yield.rs +++ b/src/instructions/hq/yield.rs @@ -225,14 +225,14 @@ crate::instructions_test! { @ super::Fields { mode: super::YieldMode::Schedule( crate::rc::Rc::downgrade(&crate::ir::Step::new_empty( - &crate::rc::Rc::downgrade(&Rc::new(crate::ir::IrProject::new(BTreeMap::default(), BTreeMap::default()))), + &crate::rc::Rc::downgrade(&Rc::new(crate::ir::IrProject::new(BTreeMap::default(), BTreeMap::default(), Box::from([])))), true, Rc::new( crate::ir::Target::new( false, BTreeMap::default(), BTreeMap::default(), - crate::rc::Rc::downgrade(&Rc::new(crate::ir::IrProject::new(BTreeMap::default(), BTreeMap::default()))), + crate::rc::Rc::downgrade(&Rc::new(crate::ir::IrProject::new(BTreeMap::default(), BTreeMap::default(), Box::from([])))), RefCell::default(), 0, Box::from([]) diff --git a/src/instructions/tests.rs b/src/instructions/tests.rs index 66e2da33..666e23a5 100644 --- a/src/instructions/tests.rs +++ b/src/instructions/tests.rs @@ -90,7 +90,7 @@ macro_rules! instructions_test { println!("skipping failed output_type"); continue; }; - let ir = Rc::new(IrProject::new(BTreeMap::default(), BTreeMap::default())); + let ir = Rc::new(IrProject::new(BTreeMap::default(), BTreeMap::default(), Box::from([]))); let proj = WasmProject::new(flags(), ExternalEnvironment::WebBrowser); let registries = proj.registries(); let types: &[IrType] = &[$($type_arg,)*]; @@ -144,7 +144,7 @@ macro_rules! instructions_test { continue; }; println!("{output_type:?}"); - let ir = Rc::new(IrProject::new(BTreeMap::default(), BTreeMap::default())); + let ir = Rc::new(IrProject::new(BTreeMap::default(), BTreeMap::default(), Box::from([]))); let proj = WasmProject::new(flags(), ExternalEnvironment::WebBrowser); let registries = proj.registries(); let types: &[IrType] = &[$($type_arg,)*]; diff --git a/src/ir/blocks.rs b/src/ir/blocks.rs index 3a405f07..93a8f87f 100644 --- a/src/ir/blocks.rs +++ b/src/ir/blocks.rs @@ -592,6 +592,7 @@ fn generate_if_else( let dummy_project = Rc::new(IrProject::new( this_project.global_variables().clone(), this_project.global_lists().clone(), + Box::from(this_project.broadcasts()), )); let dummy_target = Rc::new(Target::new( false, diff --git a/src/ir/event.rs b/src/ir/event.rs index ba34b02a..b6c59d59 100644 --- a/src/ir/event.rs +++ b/src/ir/event.rs @@ -1,5 +1,6 @@ // Ord is required to be used in a BTreeMap; Ord requires PartialOrd, Eq and PartialEq -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Event { FlagCLicked, + Broadcast(Box), } diff --git a/src/ir/project.rs b/src/ir/project.rs index 4a8ad3ff..22e4475e 100644 --- a/src/ir/project.rs +++ b/src/ir/project.rs @@ -20,6 +20,7 @@ pub struct IrProject { inlined_steps: RefCell, global_variables: TargetVars, global_lists: TargetLists, + broadcasts: Box<[Box]>, targets: RefCell, Rc>>, } @@ -48,14 +49,23 @@ impl IrProject { &self.global_lists } + pub const fn broadcasts(&self) -> &[Box] { + &self.broadcasts + } + #[must_use] - pub fn new(global_variables: TargetVars, global_lists: TargetLists) -> Self { + pub fn new( + global_variables: TargetVars, + global_lists: TargetLists, + broadcasts: Box<[Box]>, + ) -> Self { Self { threads: RefCell::new(Box::new([])), steps: RefCell::new(IndexSet::default()), inlined_steps: RefCell::new(IndexSet::default()), global_variables, global_lists, + broadcasts, targets: RefCell::new(IndexMap::default()), } } @@ -76,7 +86,14 @@ impl IrProject { flags, )?; - let project = Rc::new(Self::new(global_variables, global_lists)); + let broadcasts = sb3 + .targets + .iter() + .flat_map(|target| target.broadcasts.values()) + .cloned() + .collect(); + + let project = Rc::new(Self::new(global_variables, global_lists, broadcasts)); let (threads_vec, targets): (Vec<_>, Vec<_>) = sb3 .targets @@ -338,6 +355,11 @@ impl fmt::Display for IrProject { .iter() .map(|thread| format!("{thread}")) .join(", "); + let broadcasts = self + .broadcasts() + .iter() + .map(|name| format!(r#""{name}""#)) + .join(", "); let steps = self .steps() .borrow() @@ -356,6 +378,7 @@ impl fmt::Display for IrProject { "targets": {{{targets}}}, "global_variables": {{{variables}}}, "global_lists": {{{lists}}}, + "broadcasts": [{broadcasts}], "threads": [{threads}], "steps": [{steps}], "inlined_steps": [{inlined_steps}] diff --git a/src/ir/thread.rs b/src/ir/thread.rs index ec5bef7e..0588f351 100644 --- a/src/ir/thread.rs +++ b/src/ir/thread.rs @@ -1,8 +1,8 @@ use super::blocks::NextBlocks; use super::{Event, IrProject, Step, StepContext, Target}; -use crate::prelude::*; -use crate::sb3::{Block, BlockMap, BlockOpcode}; +use crate::sb3::{Block, BlockMap, BlockOpcode, VarVal}; use crate::wasm::WasmFlags; +use crate::{prelude::*, sb3}; #[derive(Clone, Debug)] pub struct Thread { @@ -11,8 +11,8 @@ pub struct Thread { } impl Thread { - pub const fn event(&self) -> Event { - self.event + pub const fn event(&self) -> &Event { + &self.event } pub const fn first_step(&self) -> &Rc { @@ -35,8 +35,27 @@ impl Thread { #[expect(clippy::wildcard_enum_match_arm, reason = "too many variants to match")] let event = match block_info.opcode { BlockOpcode::event_whenflagclicked => Event::FlagCLicked, + BlockOpcode::event_whenbroadcastreceived => { + let sb3::Field::ValueId(val, _id) = + block_info.fields.get("BROADCAST_OPTION").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field BROADCAST_OPTION") + })? + else { + hq_bad_proj!( + "invalid project.json - missing broadcast name for BROADCAST_OPTION field" + ); + }; + let VarVal::String(name) = val.clone().ok_or_else(|| { + make_hq_bad_proj!( + "invalid project.json - null broadcast name for BROADCAST_OPTION field" + ) + })? + else { + hq_bad_proj!("non-string broadcast name") + }; + Event::Broadcast(name) + } BlockOpcode::event_whenbackdropswitchesto - | BlockOpcode::event_whenbroadcastreceived | BlockOpcode::event_whengreaterthan | BlockOpcode::event_whenkeypressed | BlockOpcode::event_whenstageclicked @@ -78,7 +97,7 @@ impl fmt::Display for Thread { write!( f, r#"{{ - "event": "{event:?}", + "event": '{event:?}', "first_step": "{first_step}", }}"# ) diff --git a/src/wasm/project.rs b/src/wasm/project.rs index 6be3bd50..35f86bd4 100644 --- a/src/wasm/project.rs +++ b/src/wasm/project.rs @@ -396,6 +396,7 @@ impl WasmProject { self.finish_event( match event { Event::FlagCLicked => "flag_clicked", + Event::Broadcast(_) => continue, // broadcasts handled in the sender blocks }, indices, funcs, @@ -549,7 +550,7 @@ impl WasmProject { } // add thread event handlers for them for thread in ir_project.threads().try_borrow()?.iter() { - events.entry(thread.event()).or_default().push( + events.entry(thread.event().clone()).or_default().push( u32::try_from( steps .try_borrow()? @@ -593,7 +594,11 @@ mod tests { #[test] fn empty_project_is_valid_wasm() { let registries = Rc::new(Registries::default()); - let project = Rc::new(IrProject::new(BTreeMap::default(), BTreeMap::default())); + let project = Rc::new(IrProject::new( + BTreeMap::default(), + BTreeMap::default(), + Box::from([]), + )); let steps = Rc::new(RefCell::new(IndexMap::default())); StepFunc::compile_step( Step::new_empty( From 94f7822d63192b7bb31a3398284eca6b24dc1180 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Fri, 2 Jan 2026 15:56:27 +0000 Subject: [PATCH 02/14] implement event_broadcast --- rustfmt.toml | 3 +- src/instructions.rs | 4 +- src/instructions/data/listcontents.rs | 103 ++- src/instructions/event.rs | 1 + src/instructions/event/broadcast.rs | 45 + src/instructions/hq/cast.rs | 3 +- src/instructions/input_switcher.rs | 3 +- src/instructions/procedures/argument.rs | 3 +- src/ir/blocks.rs | 108 ++- src/lib.rs | 3 +- src/optimisation/ssa.rs | 30 +- src/sb3.rs | 1 + src/wasm/func.rs | 85 +- src/wasm/project.rs | 100 ++- src/wasm/registries/functions.rs | 1039 ++++++++++++----------- 15 files changed, 931 insertions(+), 600 deletions(-) create mode 100644 src/instructions/event.rs create mode 100644 src/instructions/event/broadcast.rs diff --git a/rustfmt.toml b/rustfmt.toml index a5e1c6cd..54376893 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ -unstable_features = true \ No newline at end of file +unstable_features = true +format_strings = true \ No newline at end of file diff --git a/src/instructions.rs b/src/instructions.rs index fbd1bc0f..a7ab5ea6 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -3,7 +3,8 @@ #![allow( clippy::unnecessary_wraps, - reason = "many functions here needlessly return `Result`s in order to keep type signatures consistent" + reason = "many functions here needlessly return `Result`s in order to keep type signatures \ + consistent" )] #![allow( clippy::needless_pass_by_value, @@ -15,6 +16,7 @@ use crate::prelude::*; mod control; mod data; +mod event; mod hq; mod looks; mod motion; diff --git a/src/instructions/data/listcontents.rs b/src/instructions/data/listcontents.rs index 1221783f..39ab590a 100644 --- a/src/instructions/data/listcontents.rs +++ b/src/instructions/data/listcontents.rs @@ -66,10 +66,7 @@ pub fn wasm( vec![] }) .chain( - if elem_type.intersects( - IrType::StringNan - .or(IrType::StringNumber), - ) { + if elem_type.intersects(IrType::StringNan.or(IrType::StringNumber)) { let string_length = func.registries().external_functions().register( ("wasm:js-string", "length".into()), (vec![ValType::EXTERNREF], vec![ValType::I32]), @@ -82,40 +79,39 @@ pub fn wasm( #LazyGlobalGet(list_global), LocalGet(i_local), ArrayGet(array_type), - ].into_iter() - .chain( - match list.possible_types().base_type() { - Some(IrType::String) => { - wasm![ - Call(string_length), - I32Const(1), - I32Eq, - ] - }, - None => { - let i64_local = func.local(ValType::I64)?; - let strings_table = func.registries().tables().register::()?; - wasm![ - LocalTee(i64_local), - I64Const(BOXED_STRING_PATTERN), - I64And, - I64Const(BOXED_STRING_PATTERN), - I64Eq, - If(WasmBlockType::Result(ValType::I32)), - LocalGet(i64_local), - I32WrapI64, - TableGet(strings_table), - Call(string_length), - I32Const(1), - I32Eq, - Else, - I32Const(0), - End, - ] - }, - _ => hq_bug!("shouldn't be checking for single chars in list contents for list with possible types {}", *list.possible_types()) + ] + .into_iter() + .chain(match list.possible_types().base_type() { + Some(IrType::String) => { + wasm![Call(string_length), I32Const(1), I32Eq,] + } + None => { + let i64_local = func.local(ValType::I64)?; + let strings_table = func.registries().tables().register::()?; + wasm![ + LocalTee(i64_local), + I64Const(BOXED_STRING_PATTERN), + I64And, + I64Const(BOXED_STRING_PATTERN), + I64Eq, + If(WasmBlockType::Result(ValType::I32)), + LocalGet(i64_local), + I32WrapI64, + TableGet(strings_table), + Call(string_length), + I32Const(1), + I32Eq, + Else, + I32Const(0), + End, + ] } - ) + _ => hq_bug!( + "shouldn't be checking for single chars in list contents for list with \ + possible types {}", + *list.possible_types() + ), + }) .chain(wasm![ LocalTee(is_single_chars_local), I32Eqz, @@ -132,12 +128,7 @@ pub fn wasm( |_| make_hq_bug!("list initial value length out of bounds") )?)] }) - .chain(wasm![ - I32LtS, - BrIf(1), - End, - End, - ]) + .chain(wasm![I32LtS, BrIf(1), End, End,]) .collect() } else { wasm![I32Const(0), LocalSet(is_single_chars_local)] @@ -182,8 +173,14 @@ pub fn wasm( wasm![Call(int_to_string)] } Some(IrType::Boolean) => { - let true_string = func.registries().strings().register_default("true".into())?; - let false_string = func.registries().strings().register_default("false".into())?; + let true_string = func + .registries() + .strings() + .register_default("true".into())?; + let false_string = func + .registries() + .strings() + .register_default("false".into())?; let bool_local = func.local(ValType::I32)?; wasm![ LocalSet(bool_local), @@ -232,7 +229,10 @@ pub fn wasm( End, ] } - _ => hq_bug!("unexpected list type for list contents, {}", *list.possible_types()) + _ => hq_bug!( + "unexpected list type for list contents, {}", + *list.possible_types() + ), }) .chain(wasm![ Call(string_concat), @@ -241,18 +241,15 @@ pub fn wasm( I32Const(1), I32Add, LocalTee(i_local), - ]).chain(if let Some(length_global) = maybe_length_global { + ]) + .chain(if let Some(length_global) = maybe_length_global { wasm![#LazyGlobalGet(length_global)] } else { wasm![I32Const(list.initial_value().len().try_into().map_err( |_| make_hq_bug!("list initial value length out of bounds") )?)] - }).chain(wasm![ - I32LtS, - BrIf(0), - End, - LocalGet(output_local), - ]) + }) + .chain(wasm![I32LtS, BrIf(0), End, LocalGet(output_local),]) .chain(wasm![End]) .collect()) } diff --git a/src/instructions/event.rs b/src/instructions/event.rs new file mode 100644 index 00000000..2ed30622 --- /dev/null +++ b/src/instructions/event.rs @@ -0,0 +1 @@ +pub mod broadcast; diff --git a/src/instructions/event/broadcast.rs b/src/instructions/event/broadcast.rs new file mode 100644 index 00000000..fba38342 --- /dev/null +++ b/src/instructions/event/broadcast.rs @@ -0,0 +1,45 @@ +use super::super::prelude::*; +use crate::wasm::StepFunc; + +#[derive(Clone, Debug)] +pub struct Fields(pub Box); + +impl fmt::Display for Fields { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + r#"{{ + "broadcast": "{:}", + }}"#, + self.0 + ) + } +} + +pub fn wasm( + _func: &StepFunc, + _inputs: Rc<[IrType]>, + Fields(broadcast): &Fields, +) -> HQResult> { + Ok(wasm![ + #LazyBroadcastSpawn(broadcast.clone()) + ]) +} + +pub fn acceptable_inputs(_fields: &Fields) -> HQResult> { + Ok(Rc::from([])) +} + +pub fn output_type(_inputs: Rc<[IrType]>, _fields: &Fields) -> HQResult { + Ok(ReturnType::None) +} + +pub const REQUESTS_SCREEN_REFRESH: bool = false; + +pub const fn const_fold( + _inputs: &[ConstFoldItem], + _state: &mut ConstFoldState, + _fields: &Fields, +) -> HQResult { + Ok(NotFoldable) +} diff --git a/src/instructions/hq/cast.rs b/src/instructions/hq/cast.rs index a44b6bc5..42075e95 100644 --- a/src/instructions/hq/cast.rs +++ b/src/instructions/hq/cast.rs @@ -51,7 +51,8 @@ fn best_cast_candidate(from: IrType, to: IrType) -> HQResult { IrType::Boolean, ] as &[IrType], IrType::ColorRGB | IrType::ColorARGB => hq_bad_proj!( - "should not be casting from colour. this is probably a project using 'hacked' blocks, or a bug" + "should not be casting from colour. this is probably a project using 'hacked' \ + blocks, or a bug" ), _ => unreachable!(), } { diff --git a/src/instructions/input_switcher.rs b/src/instructions/input_switcher.rs index ce94515f..02ea4a58 100644 --- a/src/instructions/input_switcher.rs +++ b/src/instructions/input_switcher.rs @@ -113,7 +113,8 @@ fn generate_branches( wasm.append(&mut wasm![@boxed(this_base_type)]); } else if let ReturnType::MultiValue(_) = this_output { crate::warn( - "found multi-valued output type for this block in `generate_branches`... suspicious.", + "found multi-valued output type for this block in `generate_branches`... \ + suspicious.", ); } } diff --git a/src/instructions/procedures/argument.rs b/src/instructions/procedures/argument.rs index 5bde78f9..3a913a56 100644 --- a/src/instructions/procedures/argument.rs +++ b/src/instructions/procedures/argument.rs @@ -64,7 +64,8 @@ pub fn wasm( } }) ), - "struct parameter did not match the expected type shape; expected (structref), got {:?}", + "struct parameter did not match the expected type shape; expected (structref), got \ + {:?}", func.params().get(1) ); let struct_type_index = func diff --git a/src/ir/blocks.rs b/src/ir/blocks.rs index 93a8f87f..56c5c7e8 100644 --- a/src/ir/blocks.rs +++ b/src/ir/blocks.rs @@ -4,7 +4,7 @@ use super::{IrProject, RcVar, Step, Type as IrType}; use crate::instructions::{ DataAddtolistFields, DataDeletealloflistFields, DataDeleteoflistFields, DataInsertatlistFields, DataItemoflistFields, DataLengthoflistFields, DataListcontentsFields, - DataReplaceitemoflistFields, ProceduresCallNonwarpFields, + DataReplaceitemoflistFields, EventBroadcastFields, ProceduresCallNonwarpFields, }; use crate::instructions::{ IrOpcode, YieldMode, @@ -77,8 +77,9 @@ pub fn insert_casts(blocks: &mut Vec, ignore_variables: bool) -> HQRes | IrOpcode::data_replaceitemoflist(_) ) { hq_bug!( - "attempted to insert a cast before a variable/list operation - variables should \ - encompass all possible types, rather than causing values to be coerced. + "attempted to insert a cast before a variable/list operation - variables \ + should encompass all possible types, rather than causing values to be \ + coerced. Tried to cast from {} to {}, at position {}. Occurred on these opcodes: [ {} @@ -248,7 +249,9 @@ pub fn input_names(block_info: &BlockInfo, context: &StepContext) -> HQResult vec![], + | BlockOpcode::control_stop + | BlockOpcode::event_broadcast_menu => vec![], + BlockOpcode::event_broadcast => vec!["BROADCAST_INPUT"], BlockOpcode::data_setvariableto | BlockOpcode::data_changevariableby => vec!["VALUE"], BlockOpcode::operator_random => vec!["FROM", "TO"], BlockOpcode::pen_setPenColorParamTo => vec!["COLOR_PARAM", "VALUE"], @@ -1130,6 +1133,91 @@ fn from_normal_block( } } BlockOpcode::operator_random => vec![IrOpcode::operator_random], + BlockOpcode::event_broadcast_menu => { + let sb3::Field::ValueId(val, _id) = + block_info.fields.get("BROADCAST_OPTION").ok_or_else(|| { + make_hq_bad_proj!( + "invalid project.json - missing field BROADCAST_OPTION" + ) + })? + else { + hq_bad_proj!( + "invalid project.json - missing broadcast name for \ + BROADCAST_OPTION field" + ); + }; + let VarVal::String(name) = val.clone().ok_or_else(|| { + make_hq_bad_proj!( + "invalid project.json - null broadcast name for \ + BROADCAST_OPTION field" + ) + })? + else { + hq_bad_proj!("non-string broadcast name") + }; + vec![IrOpcode::hq_text(HqTextFields(name))] + } + BlockOpcode::event_broadcast => { + let var = RcVar::new(IrType::String, VarVal::String("".into()))?; + vec![ + IrOpcode::hq_cast(HqCastFields(IrType::String)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(var.clone()), + local_write: RefCell::new(true), + }), + ] + .into_iter() + .chain( + context + .project()? + .broadcasts() + .iter() + .try_fold( + Step::new_empty( + project, + false, + Rc::clone(context.target()), + )?, + |branch_else, broadcast_name| { + let branch_if = Step::new_rc( + None, + context.clone(), + vec![IrOpcode::event_broadcast( + EventBroadcastFields(broadcast_name.clone()), + )], + project, + false, + )?; + Step::new_rc( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_text(HqTextFields( + broadcast_name.clone(), + )), + IrOpcode::operator_equals, // todo: this should be a case-sensitive comparison + IrOpcode::control_if_else( + ControlIfElseFields { + branch_if, + branch_else, + }, + ), + ], + project, + false, + ) + }, + )? + .opcodes() + .borrow() + .clone(), + ) + .collect() + } BlockOpcode::data_setvariableto => { let sb3::Field::ValueId(_val, maybe_id) = block_info.fields.get("VARIABLE").ok_or_else(|| { @@ -1904,12 +1992,14 @@ fn from_normal_block( let sb3::VarVal::String(number_name) = val.clone().ok_or_else(|| { make_hq_bad_proj!( - "invalid project.json - null costume name for NUMBER_NAME field" - ) + "invalid project.json - null costume name for NUMBER_NAME \ + field" + ) })? else { hq_bad_proj!( - "invalid project.json - NUMBER_NAME field is not of type String" + "invalid project.json - NUMBER_NAME field is not of type \ + String" ); }; match &*number_name { @@ -2182,9 +2272,9 @@ fn from_special_block( } _ => hq_bad_proj!("bad project json (block array of type ({}, string))", ty), }, - BlockArray::Broadcast(ty, _name, id) | BlockArray::VariableOrList(ty, _name, id, _, _) => { + BlockArray::Broadcast(ty, name, id) | BlockArray::VariableOrList(ty, name, id, _, _) => { match ty { - 11 => hq_todo!("broadcast input"), + 11 => IrOpcode::hq_text(HqTextFields(name.clone())), 12 => { let target = context.target(); let variable = if let Some(var) = target.variables().get(id) { diff --git a/src/lib.rs b/src/lib.rs index aed74bde..d1384c79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,8 @@ )] #![allow( clippy::missing_errors_doc, - reason = "Too many Results everywhere to document every possible error case. Errors should be self-descriptive and user readable anyway." + reason = "Too many Results everywhere to document every possible error case. Errors should be \ + self-descriptive and user readable anyway." )] #![allow(clippy::too_many_arguments, reason = "unavoidable at this stage")] #![allow( diff --git a/src/optimisation/ssa.rs b/src/optimisation/ssa.rs index 538d6cc7..7130af20 100644 --- a/src/optimisation/ssa.rs +++ b/src/optimisation/ssa.rs @@ -401,7 +401,16 @@ impl VarGraph { index: var_index, arg_var: arg_var.clone(), in_warped: true, - arg_vars: Rc::clone(&maybe_proc_context.ok_or_else(|| make_hq_bug!("tried to access proc argument when proc context was None"))?.arg_vars) + arg_vars: Rc::clone( + &maybe_proc_context + .ok_or_else(|| { + make_hq_bug!( + "tried to access proc argument when proc \ + context was None" + ) + })? + .arg_vars, + ), }), )); Ok(arg_var.clone()) @@ -522,7 +531,17 @@ impl VarGraph { index: arg_index, arg_var: arg_var.clone(), in_warped: true, - arg_vars: Rc::clone(&maybe_proc_context.ok_or_else(|| make_hq_bug!("tried to access proc argument when proc context was None"))?.arg_vars) + arg_vars: Rc::clone( + &maybe_proc_context + .ok_or_else(|| { + make_hq_bug!( + "tried to access proc \ + argument when proc \ + context was None" + ) + })? + .arg_vars, + ), }, ), arg_var.clone(), @@ -729,8 +748,8 @@ impl VarGraph { next_steps.push(rcstep); } else { crate::warn( - "couldn't upgrade Weak in Schedule in variables optimisation pass;\ - what's going on here?", + "couldn't upgrade Weak in Schedule in variables \ + optimisation pass;what's going on here?", ); } should_propagate_ssa = true; @@ -773,7 +792,8 @@ impl VarGraph { && let Some(proc_context) = maybe_proc_context { crate::log!( - "found spare items on type stack at end of step visit, in warped proc: {type_stack:?}" + "found spare items on type stack at end of step visit, in warped proc: \ + {type_stack:?}" ); // crate::log!( // "return vars: {}", diff --git a/src/sb3.rs b/src/sb3.rs index 5e45e261..8b274264 100644 --- a/src/sb3.rs +++ b/src/sb3.rs @@ -77,6 +77,7 @@ pub enum BlockOpcode { data_hidelist, data_showlist, event_broadcast, + event_broadcast_menu, event_broadcastandwait, event_whenflagclicked, event_whenkeypressed, diff --git a/src/wasm/func.rs b/src/wasm/func.rs index 535ea73b..aa5c935c 100644 --- a/src/wasm/func.rs +++ b/src/wasm/func.rs @@ -1,13 +1,13 @@ use super::{Registries, WasmFlags, WasmProject}; use crate::instructions::IrOpcode; -use crate::ir::{PartialStep, RcVar, ReturnType, Step}; +use crate::ir::{Event, PartialStep, RcVar, ReturnType, Step}; use crate::prelude::*; use crate::wasm::registries::TypeRegistry; use crate::{instructions::wrap_instruction, ir::Proc}; use alloc::collections::btree_map; use wasm_encoder::{ - self, CodeSection, Function, FunctionSection, HeapType, Instruction as WInstruction, RefType, - ValType, + self, AbstractHeapType, CodeSection, Function, FunctionSection, HeapType, + Instruction as WInstruction, RefType, ValType, }; use wasm_gen::wasm; @@ -20,6 +20,7 @@ pub enum Instruction { LazyNonWarpedProcRef(Rc), LazyGlobalGet(u32), LazyGlobalSet(u32), + LazyBroadcastSpawn(Box), StaticFunctionCall(u32), } @@ -27,16 +28,19 @@ impl Instruction { pub fn eval( &self, steps: &Rc, StepFunc>>>, + events: &BTreeMap>, imported_func_count: u32, static_func_count: u32, imported_global_count: u32, - ) -> HQResult> { + threads_count_global: u32, + spawn_new_thread_func: u32, + ) -> HQResult]>> { Ok(match self { - Self::Immediate(instr) => instr.clone(), + Self::Immediate(instr) => Box::from([instr.clone()]), #[cfg(test)] - Self::LazyStepRef(_step) => { - WInstruction::RefFunc(imported_func_count + static_func_count) - } + Self::LazyStepRef(_step) => Box::from([WInstruction::RefFunc( + imported_func_count + static_func_count, + )]), #[cfg(not(test))] Self::LazyStepRef(step) => { let step_index: u32 = steps @@ -49,7 +53,40 @@ impl Instruction { .ok_or_else(|| make_hq_bug!("couldn't find step in step map"))? .try_into() .map_err(|_| make_hq_bug!("step index out of bounds"))?; - WInstruction::RefFunc(imported_func_count + static_func_count + step_index) + Box::from([WInstruction::RefFunc( + imported_func_count + static_func_count + step_index, + )]) + } + Self::LazyBroadcastSpawn(broadcast) => { + let broadcast_indices = events + .get(&Event::Broadcast(broadcast.clone())) + .cloned() + .unwrap_or_default(); + + // todo: these should begin execution in the same step, I think, possibly immediately? + + broadcast_indices + .iter() + .flat_map(|&i| { + [ + WInstruction::RefFunc(i + imported_func_count + static_func_count), + WInstruction::RefNull(HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Struct, + }), + WInstruction::Call(spawn_new_thread_func + imported_func_count), + ] + }) + .chain([ + WInstruction::GlobalGet(threads_count_global + imported_global_count), + WInstruction::I32Const( + i32::try_from(broadcast_indices.len()) + .map_err(|_| make_hq_bug!("indices len out of bounds"))?, + ), + WInstruction::I32Add, + WInstruction::GlobalSet(threads_count_global + imported_global_count), + ]) + .collect() } Self::LazyStepIndex(step) => { let step_index: i32 = steps @@ -62,7 +99,7 @@ impl Instruction { .ok_or_else(|| make_hq_bug!("couldn't find step in step map"))? .try_into() .map_err(|_| make_hq_bug!("step index out of bounds"))?; - WInstruction::I32Const(step_index) + Box::from([WInstruction::I32Const(step_index)]) } Self::LazyWarpedProcCall(proc) => { let Some(ref warped_specific_proc) = *proc.warped_specific_proc() else { @@ -77,7 +114,9 @@ impl Instruction { .ok_or_else(|| make_hq_bug!("couldn't find step in step map"))? .try_into() .map_err(|_| make_hq_bug!("step index out of bounds"))?; - WInstruction::Call(imported_func_count + static_func_count + step_index) + Box::from([WInstruction::Call( + imported_func_count + static_func_count + step_index, + )]) } Self::LazyNonWarpedProcRef(proc) => { let Some(ref nonwarped_specific_proc) = *proc.nonwarped_specific_proc() else { @@ -92,17 +131,21 @@ impl Instruction { .ok_or_else(|| make_hq_bug!("couldn't find step in step map"))? .try_into() .map_err(|_| make_hq_bug!("step index out of bounds"))?; - WInstruction::RefFunc(imported_func_count + static_func_count + step_index) + Box::from([WInstruction::RefFunc( + imported_func_count + static_func_count + step_index, + )]) } Self::LazyGlobalGet(idx) => { // crate::log!("global get {idx}. imported globals: {imported_global_count}"); - WInstruction::GlobalGet(idx + imported_global_count) + Box::from([WInstruction::GlobalGet(idx + imported_global_count)]) } Self::LazyGlobalSet(idx) => { // crate::log!("global get {idx}. imported globals: {imported_global_count}"); - WInstruction::GlobalSet(idx + imported_global_count) + Box::from([WInstruction::GlobalSet(idx + imported_global_count)]) + } + Self::StaticFunctionCall(idx) => { + Box::from([WInstruction::Call(imported_func_count + idx)]) } - Self::StaticFunctionCall(idx) => WInstruction::Call(imported_func_count + idx), }) } } @@ -252,18 +295,26 @@ impl StepFunc { funcs: &mut FunctionSection, code: &mut CodeSection, steps: &Rc, Self>>>, + events: &BTreeMap>, imported_func_count: u32, static_func_count: u32, imported_global_count: u32, + threads_count_global: u32, + spawn_new_thread_func: u32, ) -> HQResult<()> { let mut func = Function::new_with_locals_types(self.locals.take()); for instruction in self.instructions().take() { - func.instruction(&instruction.eval( + for real_instruction in instruction.eval( steps, + events, imported_func_count, static_func_count, imported_global_count, - )?); + threads_count_global, + spawn_new_thread_func, + )? { + func.instruction(&real_instruction); + } } func.instruction(&wasm_encoder::Instruction::End); let type_index = self diff --git a/src/wasm/project.rs b/src/wasm/project.rs index 35f86bd4..17ec6b16 100644 --- a/src/wasm/project.rs +++ b/src/wasm/project.rs @@ -1,6 +1,7 @@ use super::{ExternalEnvironment, GlobalExportable, GlobalMutable, Registries}; use crate::ir::{Event, IrProject, Step, Target as IrTarget, Type as IrType}; use crate::prelude::*; +use crate::wasm::registries::functions::static_functions::SpawnNewThread; use crate::wasm::{StepFunc, StringsTable, ThreadsTable, WasmFlags}; use itertools::Itertools; use wasm_bindgen::prelude::*; @@ -130,6 +131,16 @@ impl WasmProject { .clone() .finish(&mut imports, self.registries().types())?; + self.registries() + .static_functions() + .register_override::(( + self.registries().types().step_func_type()?, + self.registries().types().stack_struct_type()?, + self.registries().types().stack_array_type()?, + self.registries().types().thread_struct_type()?, + self.threads_table_index()?, + ))?; + self.registries().static_functions().clone().finish( &mut functions, &mut codes, @@ -141,9 +152,12 @@ impl WasmProject { &mut functions, &mut codes, self.steps(), + &self.events, self.imported_func_count()?, self.static_func_count()?, self.imported_global_count()?, + self.threads_count_global()?, + self.spawn_new_thread_func()?, )?; } @@ -293,6 +307,32 @@ impl WasmProject { self.registries().tables().register::() } + fn spawn_new_thread_func(&self) -> HQResult + where + N: TryFrom, + >::Error: fmt::Debug, + { + self.registries() + .static_functions() + .register::() + } + + fn threads_count_global(&self) -> HQResult + where + N: TryFrom, + >::Error: fmt::Debug, + { + self.registries().globals().register( + "threads_count".into(), + ( + ValType::I32, + ConstExpr::i32_const(0), + GlobalMutable(true), + GlobalExportable(true), + ), + ) + } + fn finish_event( &self, export_name: &str, @@ -303,59 +343,37 @@ impl WasmProject { ) -> HQResult<()> { let mut func = Function::new(vec![]); - let threads_count = self.registries().globals().register( - "threads_count".into(), - ( - ValType::I32, - ConstExpr::i32_const(0), - GlobalMutable(true), - GlobalExportable(true), - ), - )?; + let threads_count = self.threads_count_global()?; - let stack_struct_ty = self.registries().types().stack_struct_type()?; - let stack_array_ty = self.registries().types().stack_array_type()?; - let thread_struct_ty = self.registries().types().thread_struct_type()?; + let spawn_new_thread = self.spawn_new_thread_func()?; let instrs = indices .iter() .map(|&i| { Ok(wasm![ - I32Const(1), RefFunc(i + self.imported_func_count()? + self.static_func_count()?), RefNull(HeapType::Abstract { shared: false, ty: AbstractHeapType::Struct }), - StructNew(stack_struct_ty), - // todo: play around with initial size of stack array - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - ArrayNewFixed { - array_size: 8, - array_type_index: stack_array_ty, - }, - StructNew(thread_struct_ty), - I32Const(1), - TableGrow(self.threads_table_index()?), - Drop, + #StaticFunctionCall(spawn_new_thread), ]) }) .flatten_ok() .collect::>>()?; for instruction in instrs { - func.instruction(&instruction.eval( + for real_instruction in instruction.eval( self.steps(), + &self.events, self.imported_func_count()?, self.static_func_count()?, self.imported_global_count()?, - )?); + self.threads_count_global()?, + self.spawn_new_thread_func()?, + )? { + func.instruction(&real_instruction); + } } for instruction in wasm![ #LazyGlobalGet(threads_count), @@ -366,12 +384,17 @@ impl WasmProject { I32Add, #LazyGlobalSet(threads_count) ] { - func.instruction(&instruction.eval( + for real_instruction in instruction.eval( self.steps(), + &self.events, self.imported_func_count()?, self.static_func_count()?, self.imported_global_count()?, - )?); + self.threads_count_global()?, + self.spawn_new_thread_func()?, + )? { + func.instruction(&real_instruction); + } } func.instruction(&Instruction::End); @@ -499,12 +522,17 @@ impl WasmProject { End, ]; for instr in instructions { - tick_func.instruction(&instr.eval( + for real_instruction in instr.eval( self.steps(), + &self.events, self.imported_func_count()?, self.static_func_count()?, self.imported_global_count()?, - )?); + self.threads_count_global()?, + self.spawn_new_thread_func()?, + )? { + tick_func.instruction(&real_instruction); + } } tick_func.instruction(&Instruction::End); funcs.function(self.registries().types().function(vec![], vec![])?); diff --git a/src/wasm/registries/functions.rs b/src/wasm/registries/functions.rs index 48e6df30..08ba749c 100644 --- a/src/wasm/registries/functions.rs +++ b/src/wasm/registries/functions.rs @@ -23,15 +23,21 @@ impl ExternalFunctionRegistry { #[derive(Clone)] pub struct StaticFunction { - pub instructions: &'static [WInstruction<'static>], - pub params: &'static [ValType], - pub returns: &'static [ValType], - pub locals: &'static [ValType], + pub instructions: Box<[WInstruction<'static>]>, + pub params: Box<[ValType]>, + pub returns: Box<[ValType]>, + pub locals: Box<[ValType]>, +} + +#[derive(Clone)] +pub struct MaybeStaticFunction { + pub static_function: Option, + pub maybe_populate: fn() -> Option, } pub struct StaticFunctionRegistrar; impl RegistryType for StaticFunctionRegistrar { - type Value = StaticFunction; + type Value = MaybeStaticFunction; } pub type StaticFunctionRegistry = NamedRegistry; @@ -44,19 +50,29 @@ impl StaticFunctionRegistry { ) -> HQResult<()> { for ( _name, - StaticFunction { + MaybeStaticFunction { + static_function, + maybe_populate, + }, + ) in self.registry().take() + { + let Some(StaticFunction { instructions, params, returns, locals, - }, - ) in self.registry().take() - { + }) = static_function.map_or_else(maybe_populate, Some) + else { + hq_bug!( + "static functions must either be overriden, or have a non-None maybe_populate \ + field" + ) + }; let type_index = type_registry.function(params.into(), returns.into())?; functions.function(type_index); let mut func = Function::new_with_locals_types(locals.iter().copied()); for instruction in instructions { - func.instruction(instruction); + func.instruction(&instruction); } codes.function(&func); } @@ -65,13 +81,74 @@ impl StaticFunctionRegistry { } pub mod static_functions { - use super::StaticFunction; + use super::{MaybeStaticFunction, StaticFunction}; use crate::prelude::*; use crate::wasm::{f32_to_ieeef32, mem_layout}; use mem_layout::{sprite as sprite_layout, stage as stage_layout}; - use wasm_encoder::{BlockType as WasmBlockType, MemArg, ValType}; + use wasm_encoder::{ + AbstractHeapType, BlockType as WasmBlockType, HeapType, MemArg, RefType, ValType, + }; use wasm_gen::wasm_const; + pub struct SpawnNewThread; + impl NamedRegistryItem for SpawnNewThread { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || None, + }; + } + pub type SpawnNewThreadOverride = (u32, u32, u32, u32, u32); + impl NamedRegistryItemOverride for SpawnNewThread { + fn r#override( + (func_ty, stack_struct_ty, stack_array_ty, thread_struct_ty, threads_table_index): SpawnNewThreadOverride, + ) -> MaybeStaticFunction { + MaybeStaticFunction { + static_function: Some(StaticFunction { + params: Box::from([ + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(func_ty), + }), + ValType::Ref(RefType { + nullable: true, + heap_type: wasm_encoder::HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Struct, + }, + }), + ]), + returns: Box::from([]), + locals: Box::from([]), + instructions: (wasm_const![ + I32Const(1), + LocalGet(0), + LocalGet(1), + StructNew(stack_struct_ty), + // todo: play around with initial size of stack array + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + ArrayNewFixed { + array_size: 8, + array_type_index: stack_array_ty, + }, + StructNew(thread_struct_ty), + I32Const(1), + TableGrow(threads_table_index), + Drop, + End, + ] as &[_]) + .into(), + }), + maybe_populate: || None, + } + } + } + index_counter! { hsv2rgb_locals SPRITE_INDEX @@ -85,259 +162,266 @@ pub mod static_functions { } pub struct UpdatePenColorFromHSV; - impl NamedRegistryItem for UpdatePenColorFromHSV { - const VALUE: StaticFunction = StaticFunction { - params: &[ValType::I32], - returns: &[], - locals: &const { - const PARAMS_NUM: usize = 1; - let mut locals = [ValType::I32; hsv2rgb_locals::BLOCK_SIZE as usize - PARAMS_NUM]; - locals[hsv2rgb_locals::VAL_F as usize - PARAMS_NUM] = ValType::F32; - locals + impl NamedRegistryItem for UpdatePenColorFromHSV { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || { + Some(StaticFunction { + params: Box::from([ValType::I32]), + returns: Box::from([]), + locals: Box::from({ + const PARAMS_NUM: usize = 1; + let mut locals = + [ValType::I32; hsv2rgb_locals::BLOCK_SIZE as usize - PARAMS_NUM]; + locals[hsv2rgb_locals::VAL_F as usize - PARAMS_NUM] = ValType::F32; + locals + }), + instructions: (wasm_const![ + // hsv->rgb based off of https://stackoverflow.com/a/14733008 + LocalGet(hsv2rgb_locals::SPRITE_INDEX), + I32Const(sprite_layout::BLOCK_SIZE as i32), + I32Mul, + I32Const(stage_layout::BLOCK_SIZE as i32), + I32Add, + LocalTee(hsv2rgb_locals::MEM_POS), // position in memory of sprite info + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR.into(), + align: 2, + memory_index: 0, + }), + F32Const(f32_to_ieeef32(2.55)), + F32Mul, + I32TruncF32S, + LocalSet(hsv2rgb_locals::HUE), + LocalGet(hsv2rgb_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_SATURATION.into(), + align: 2, + memory_index: 0, + }), + F32Const(f32_to_ieeef32(2.55)), + F32Mul, + I32TruncF32S, + LocalSet(hsv2rgb_locals::SAT), // saturation ∈ [0, 256) + LocalGet(hsv2rgb_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_BRIGHTNESS.into(), + align: 2, + memory_index: 0, + }), + F32Const(f32_to_ieeef32(2.55)), + F32Mul, + I32TruncF32S, + LocalSet(hsv2rgb_locals::VAL), // value ∈ [0, 256) + LocalGet(hsv2rgb_locals::MEM_POS), + F32Const(f32_to_ieeef32(100.0)), + LocalGet(hsv2rgb_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_TRANSPARENCY.into(), + align: 2, + memory_index: 0, + }), // transparency ∈ [0, 100] + F32Sub, + F32Const(f32_to_ieeef32(100.0)), + F32Div, // alpha ∈ [0, 1] + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_A.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_A.into(), + align: 2, + memory_index: 0, + }), + F32Const(f32_to_ieeef32(0.01)), + F32Lt, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::MEM_POS), + F32Const(f32_to_ieeef32(0.0)), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_A.into(), + align: 2, + memory_index: 0, + }), + Return, // if alpha is 0, return (it is already set to 0 so it doesn't matter what r, g & b are) + End, + LocalGet(hsv2rgb_locals::SAT), + I32Eqz, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::VAL), + F32ConvertI32S, + F32Const(f32_to_ieeef32(255.0)), + F32Div, + LocalSet(hsv2rgb_locals::VAL_F), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::VAL_F), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_R.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::VAL_F), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_G.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::VAL_F), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_B.into(), + align: 2, + memory_index: 0, + }), + Return, + End, + LocalGet(hsv2rgb_locals::HUE), + I32Const(43), + I32DivU, + LocalSet(hsv2rgb_locals::REGION), // 'region' + LocalGet(hsv2rgb_locals::HUE), + I32Const(43), + I32RemU, + I32Const(6), + I32Mul, + LocalSet(hsv2rgb_locals::REMAINDER), // 'remainder' + I32Const(255), + LocalGet(hsv2rgb_locals::SAT), + I32Sub, + LocalGet(hsv2rgb_locals::VAL), + I32Mul, + I32Const(8), + I32ShrU, + LocalSet(hsv2rgb_locals::P), // 'p' + I32Const(255), + LocalGet(hsv2rgb_locals::REMAINDER), + LocalGet(hsv2rgb_locals::SAT), + I32Mul, + I32Const(8), + I32ShrU, + I32Sub, + LocalGet(hsv2rgb_locals::VAL), + I32Mul, + I32Const(8), + I32ShrU, + LocalSet(hsv2rgb_locals::Q), // 'q' + I32Const(255), + I32Const(255), + LocalGet(hsv2rgb_locals::REMAINDER), + I32Sub, + LocalGet(hsv2rgb_locals::SAT), + I32Mul, + I32Const(8), + I32ShrU, + I32Sub, + LocalGet(hsv2rgb_locals::VAL), + I32Mul, + I32Const(8), + I32ShrU, + LocalSet(hsv2rgb_locals::T), // 't' + LocalGet(hsv2rgb_locals::REGION), + I32Eqz, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::T), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(1), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::Q), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(2), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::T), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(3), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::Q), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(4), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::T), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(5), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::Q), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::R), + F32ConvertI32S, + F32Const(f32_to_ieeef32(255.0)), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_R.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::G), + F32ConvertI32S, + F32Const(f32_to_ieeef32(255.0)), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_G.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::B), + F32ConvertI32S, + F32Const(f32_to_ieeef32(255.0)), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_B.into(), + align: 2, + memory_index: 0, + }), + End, + ] as &[_]) + .into(), + }) }, - instructions: wasm_const![ - // hsv->rgb based off of https://stackoverflow.com/a/14733008 - LocalGet(hsv2rgb_locals::SPRITE_INDEX), - I32Const(sprite_layout::BLOCK_SIZE as i32), - I32Mul, - I32Const(stage_layout::BLOCK_SIZE as i32), - I32Add, - LocalTee(hsv2rgb_locals::MEM_POS), // position in memory of sprite info - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR as u64, - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(2.55)), - F32Mul, - I32TruncF32S, - LocalSet(hsv2rgb_locals::HUE), - LocalGet(hsv2rgb_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_SATURATION as u64, - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(2.55)), - F32Mul, - I32TruncF32S, - LocalSet(hsv2rgb_locals::SAT), // saturation ∈ [0, 256) - LocalGet(hsv2rgb_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_BRIGHTNESS as u64, - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(2.55)), - F32Mul, - I32TruncF32S, - LocalSet(hsv2rgb_locals::VAL), // value ∈ [0, 256) - LocalGet(hsv2rgb_locals::MEM_POS), - F32Const(f32_to_ieeef32(100.0)), - LocalGet(hsv2rgb_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_TRANSPARENCY as u64, - align: 2, - memory_index: 0, - }), // transparency ∈ [0, 100] - F32Sub, - F32Const(f32_to_ieeef32(100.0)), - F32Div, // alpha ∈ [0, 1] - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_A as u64, - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_A as u64, - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(0.01)), - F32Lt, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_A as u64, - align: 2, - memory_index: 0, - }), - Return, // if alpha is 0, return (it is already set to 0 so it doesn't matter what r, g & b are) - End, - LocalGet(hsv2rgb_locals::SAT), - I32Eqz, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::VAL), - F32ConvertI32S, - F32Const(f32_to_ieeef32(255.0)), - F32Div, - LocalSet(hsv2rgb_locals::VAL_F), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::VAL_F), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_R as u64, - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::VAL_F), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_G as u64, - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::VAL_F), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_B as u64, - align: 2, - memory_index: 0, - }), - Return, - End, - LocalGet(hsv2rgb_locals::HUE), - I32Const(43), - I32DivU, - LocalSet(hsv2rgb_locals::REGION), // 'region' - LocalGet(hsv2rgb_locals::HUE), - I32Const(43), - I32RemU, - I32Const(6), - I32Mul, - LocalSet(hsv2rgb_locals::REMAINDER), // 'remainder' - I32Const(255), - LocalGet(hsv2rgb_locals::SAT), - I32Sub, - LocalGet(hsv2rgb_locals::VAL), - I32Mul, - I32Const(8), - I32ShrU, - LocalSet(hsv2rgb_locals::P), // 'p' - I32Const(255), - LocalGet(hsv2rgb_locals::REMAINDER), - LocalGet(hsv2rgb_locals::SAT), - I32Mul, - I32Const(8), - I32ShrU, - I32Sub, - LocalGet(hsv2rgb_locals::VAL), - I32Mul, - I32Const(8), - I32ShrU, - LocalSet(hsv2rgb_locals::Q), // 'q' - I32Const(255), - I32Const(255), - LocalGet(hsv2rgb_locals::REMAINDER), - I32Sub, - LocalGet(hsv2rgb_locals::SAT), - I32Mul, - I32Const(8), - I32ShrU, - I32Sub, - LocalGet(hsv2rgb_locals::VAL), - I32Mul, - I32Const(8), - I32ShrU, - LocalSet(hsv2rgb_locals::T), // 't' - LocalGet(hsv2rgb_locals::REGION), - I32Eqz, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::T), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(1), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::Q), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(2), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::T), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(3), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::Q), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(4), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::T), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(5), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::Q), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::R), - F32ConvertI32S, - F32Const(f32_to_ieeef32(255.0)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_R as u64, - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::G), - F32ConvertI32S, - F32Const(f32_to_ieeef32(255.0)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_G as u64, - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::B), - F32ConvertI32S, - F32Const(f32_to_ieeef32(255.0)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_B as u64, - align: 2, - memory_index: 0, - }), - End, - ], }; } @@ -351,217 +435,224 @@ pub mod static_functions { } pub struct UpdatePenColorFromRGB; - impl NamedRegistryItem for UpdatePenColorFromRGB { - const VALUE: StaticFunction = StaticFunction { - params: &[ValType::I32], - returns: &[], - locals: &const { - const PARAMS_NUM: usize = 1; - let mut locals = [ValType::I32; rgb2hsv_locals::BLOCK_SIZE as usize - PARAMS_NUM]; - locals[rgb2hsv_locals::A as usize - PARAMS_NUM] = ValType::F32; - locals + impl NamedRegistryItem for UpdatePenColorFromRGB { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || { + Some(StaticFunction { + params: Box::from([ValType::I32]), + returns: Box::from([]), + locals: Box::from({ + const PARAMS_NUM: usize = 1; + let mut locals = + [ValType::I32; rgb2hsv_locals::BLOCK_SIZE as usize - PARAMS_NUM]; + locals[rgb2hsv_locals::A as usize - PARAMS_NUM] = ValType::F32; + locals + }), + instructions: (wasm_const![ + // rgb->hsv based off of https://stackoverflow.com/a/14733008 + LocalGet(rgb2hsv_locals::SPRITE_INDEX), + I32Const(sprite_layout::BLOCK_SIZE as i32), + I32Mul, + I32Const(stage_layout::BLOCK_SIZE as i32), + I32Add, + LocalTee(rgb2hsv_locals::MEM_POS), // position in memory of sprite info + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_R.into(), + align: 2, + memory_index: 0, + }), + F32Const(f32_to_ieeef32(255.0)), + F32Mul, + I32TruncF32S, + LocalSet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_G.into(), + align: 2, + memory_index: 0, + }), + F32Const(f32_to_ieeef32(255.0)), + F32Mul, + I32TruncF32S, + LocalSet(rgb2hsv_locals::G), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_B.into(), + align: 2, + memory_index: 0, + }), + F32Const(f32_to_ieeef32(255.0)), + F32Mul, + I32TruncF32S, + LocalSet(rgb2hsv_locals::B), + LocalGet(rgb2hsv_locals::MEM_POS), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_A.into(), + align: 2, + memory_index: 0, + }), // transparency ∈ [0, 100] + F32Const(f32_to_ieeef32(100.0)), + F32Mul, // alpha ∈ [0, 1] + LocalTee(rgb2hsv_locals::A), + F32Const(f32_to_ieeef32(100.0)), + F32Sub, + F32Store(MemArg { + offset: sprite_layout::PEN_TRANSPARENCY.into(), + align: 2, + memory_index: 0, + }), + // we don't need to check for alpha=0 to shortcircuit, because scratch doesn't allow + // alpha=0 for rgb colours + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + I32LtS, + Select, + LocalTee(rgb2hsv_locals::RGB_MIN), + LocalGet(rgb2hsv_locals::B), + LocalGet(rgb2hsv_locals::RGB_MIN), + LocalGet(rgb2hsv_locals::B), + I32LtS, + Select, + LocalSet(rgb2hsv_locals::RGB_MIN), + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + I32GtS, + Select, + LocalTee(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::B), + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::B), + I32GtS, + Select, + LocalSet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::MEM_POS), + LocalGet(rgb2hsv_locals::RGB_MAX), + F32ConvertI32S, + F32Const(f32_to_ieeef32(2.55)), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_BRIGHTNESS.into(), + align: 2, + memory_index: 0, + }), + LocalGet(rgb2hsv_locals::RGB_MAX), + I32Eqz, + If(WasmBlockType::Empty), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Const(f32_to_ieeef32(0.0)), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR.into(), + align: 2, + memory_index: 0, + }), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Const(f32_to_ieeef32(0.0)), + F32Store(MemArg { + offset: sprite_layout::PEN_SATURATION.into(), + align: 2, + memory_index: 0, + }), + Return, + End, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::RGB_MIN), + I32Sub, + I32Const(255), + I32Mul, + LocalGet(rgb2hsv_locals::RGB_MAX), + I32DivS, + LocalTee(rgb2hsv_locals::SAT), + I32Eqz, + If(WasmBlockType::Empty), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Const(f32_to_ieeef32(0.0)), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR.into(), + align: 2, + memory_index: 0, + }), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Const(f32_to_ieeef32(0.0)), + F32Store(MemArg { + offset: sprite_layout::PEN_SATURATION.into(), + align: 2, + memory_index: 0, + }), + Return, + End, + LocalGet(rgb2hsv_locals::MEM_POS), + LocalGet(rgb2hsv_locals::SAT), + F32ConvertI32S, + F32Const(f32_to_ieeef32(2.55)), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_SATURATION.into(), + align: 2, + memory_index: 0, + }), + LocalGet(rgb2hsv_locals::MEM_POS), + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::R), + I32Eq, + If(WasmBlockType::Result(ValType::I32)), + LocalGet(rgb2hsv_locals::G), + LocalGet(rgb2hsv_locals::B), + I32Sub, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::RGB_MIN), + I32Sub, + I32DivS, + I32Const(43), + I32Mul, + Else, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::G), + I32Eq, + If(WasmBlockType::Result(ValType::I32)), + LocalGet(rgb2hsv_locals::B), + LocalGet(rgb2hsv_locals::R), + I32Sub, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::RGB_MIN), + I32Sub, + I32DivS, + I32Const(43), + I32Mul, + I32Const(85), + I32Add, + Else, + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + I32Sub, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::RGB_MIN), + I32Sub, + I32DivS, + I32Const(43), + I32Mul, + I32Const(171), + I32Add, + End, + End, + F32ConvertI32S, + F32Const(f32_to_ieeef32(2.55)), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR.into(), + align: 2, + memory_index: 0, + }), + End, + ] as &[_]) + .into(), + }) }, - instructions: wasm_const![ - // rgb->hsv based off of https://stackoverflow.com/a/14733008 - LocalGet(rgb2hsv_locals::SPRITE_INDEX), - I32Const(sprite_layout::BLOCK_SIZE as i32), - I32Mul, - I32Const(stage_layout::BLOCK_SIZE as i32), - I32Add, - LocalTee(rgb2hsv_locals::MEM_POS), // position in memory of sprite info - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_R as u64, - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(255.0)), - F32Mul, - I32TruncF32S, - LocalSet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_G as u64, - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(255.0)), - F32Mul, - I32TruncF32S, - LocalSet(rgb2hsv_locals::G), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_B as u64, - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(255.0)), - F32Mul, - I32TruncF32S, - LocalSet(rgb2hsv_locals::B), - LocalGet(rgb2hsv_locals::MEM_POS), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_A as u64, - align: 2, - memory_index: 0, - }), // transparency ∈ [0, 100] - F32Const(f32_to_ieeef32(100.0)), - F32Mul, // alpha ∈ [0, 1] - LocalTee(rgb2hsv_locals::A), - F32Const(f32_to_ieeef32(100.0)), - F32Sub, - F32Store(MemArg { - offset: sprite_layout::PEN_TRANSPARENCY as u64, - align: 2, - memory_index: 0, - }), - // we don't need to check for alpha=0 to shortcircuit, because scratch doesn't allow - // alpha=0 for rgb colours - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - I32LtS, - Select, - LocalTee(rgb2hsv_locals::RGB_MIN), - LocalGet(rgb2hsv_locals::B), - LocalGet(rgb2hsv_locals::RGB_MIN), - LocalGet(rgb2hsv_locals::B), - I32LtS, - Select, - LocalSet(rgb2hsv_locals::RGB_MIN), - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - I32GtS, - Select, - LocalTee(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::B), - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::B), - I32GtS, - Select, - LocalSet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::MEM_POS), - LocalGet(rgb2hsv_locals::RGB_MAX), - F32ConvertI32S, - F32Const(f32_to_ieeef32(2.55)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_BRIGHTNESS as u64, - align: 2, - memory_index: 0, - }), - LocalGet(rgb2hsv_locals::RGB_MAX), - I32Eqz, - If(WasmBlockType::Empty), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR as u64, - align: 2, - memory_index: 0, - }), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_SATURATION as u64, - align: 2, - memory_index: 0, - }), - Return, - End, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::RGB_MIN), - I32Sub, - I32Const(255), - I32Mul, - LocalGet(rgb2hsv_locals::RGB_MAX), - I32DivS, - LocalTee(rgb2hsv_locals::SAT), - I32Eqz, - If(WasmBlockType::Empty), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR as u64, - align: 2, - memory_index: 0, - }), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_SATURATION as u64, - align: 2, - memory_index: 0, - }), - Return, - End, - LocalGet(rgb2hsv_locals::MEM_POS), - LocalGet(rgb2hsv_locals::SAT), - F32ConvertI32S, - F32Const(f32_to_ieeef32(2.55)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_SATURATION as u64, - align: 2, - memory_index: 0, - }), - LocalGet(rgb2hsv_locals::MEM_POS), - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::R), - I32Eq, - If(WasmBlockType::Result(ValType::I32)), - LocalGet(rgb2hsv_locals::G), - LocalGet(rgb2hsv_locals::B), - I32Sub, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::RGB_MIN), - I32Sub, - I32DivS, - I32Const(43), - I32Mul, - Else, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::G), - I32Eq, - If(WasmBlockType::Result(ValType::I32)), - LocalGet(rgb2hsv_locals::B), - LocalGet(rgb2hsv_locals::R), - I32Sub, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::RGB_MIN), - I32Sub, - I32DivS, - I32Const(43), - I32Mul, - I32Const(85), - I32Add, - Else, - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - I32Sub, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::RGB_MIN), - I32Sub, - I32DivS, - I32Const(43), - I32Mul, - I32Const(171), - I32Add, - End, - End, - F32ConvertI32S, - F32Const(f32_to_ieeef32(2.55)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR as u64, - align: 2, - memory_index: 0, - }), - End, - ], }; } } From ff023b54070a249b6035ae8b9b3e797472de3350 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:30:14 +0000 Subject: [PATCH 03/14] massive format --- build.rs | 6 +-- enum-field-getter/src/lib.rs | 56 +++++++++++++-------- rustfmt.toml | 4 +- src/error.rs | 2 +- src/instructions.rs | 13 +++-- src/instructions/control/if_else.rs | 3 +- src/instructions/control/loop.rs | 3 +- src/instructions/data/addtolist.rs | 4 +- src/instructions/data/deleteoflist.rs | 4 +- src/instructions/data/insertatlist.rs | 4 +- src/instructions/data/itemoflist.rs | 4 +- src/instructions/data/listcontents.rs | 4 +- src/instructions/data/replaceitemoflist.rs | 4 +- src/instructions/hq/dup.rs | 3 +- src/instructions/hq/swap.rs | 3 +- src/instructions/hq/yield.rs | 3 +- src/instructions/input_switcher.rs | 17 +++---- src/instructions/motion/direction.rs | 5 +- src/instructions/motion/gotoxy.rs | 5 +- src/instructions/motion/pointindirection.rs | 5 +- src/instructions/operator/modulo.rs | 3 +- src/instructions/operator/random.rs | 3 +- src/instructions/pen/setpencolorparamto.rs | 5 +- src/instructions/pen/setpencolortocolor.rs | 5 +- src/instructions/procedures/argument.rs | 7 ++- src/instructions/procedures/call_nonwarp.rs | 3 +- src/instructions/procedures/call_warp.rs | 3 +- src/ir/blocks.rs | 25 +++++---- src/ir/context.rs | 3 +- src/ir/proc.rs | 8 +-- src/ir/step.rs | 6 ++- src/ir/target.rs | 6 ++- src/ir/thread.rs | 3 +- src/ir/types.rs | 3 +- src/ir/variable.rs | 16 +++--- src/lib.rs | 16 +++--- src/optimisation.rs | 3 +- src/optimisation/ssa.rs | 23 +++++---- src/registry.rs | 3 +- src/sb3.rs | 3 +- src/wasm.rs | 5 +- src/wasm/flags.rs | 3 +- src/wasm/func.rs | 13 ++--- src/wasm/project.rs | 14 +++--- src/wasm/registries.rs | 3 +- src/wasm/registries/functions.rs | 14 +++--- src/wasm/registries/globals.rs | 6 ++- src/wasm/registries/lists.rs | 10 ++-- src/wasm/registries/strings.rs | 3 +- src/wasm/registries/tables.rs | 3 +- src/wasm/registries/types.rs | 8 +-- src/wasm/registries/variables.rs | 3 +- wasm-gen/src/lib.rs | 3 +- 53 files changed, 218 insertions(+), 169 deletions(-) diff --git a/build.rs b/build.rs index 9d35be07..1e3b695e 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,8 @@ -use convert_case::{Case, Casing}; use std::collections::HashSet; -use std::env; -use std::fs; use std::path::Path; +use std::{env, fs}; + +use convert_case::{Case, Casing}; // I hate to admit this, but a fair bit of this file was written by chatgpt to speed things up // and to allow me to continue to procrastinate about learning how to do i/o stuff in rust. diff --git a/enum-field-getter/src/lib.rs b/enum-field-getter/src/lib.rs index 637dc7e5..b5f6b9cc 100644 --- a/enum-field-getter/src/lib.rs +++ b/enum-field-getter/src/lib.rs @@ -1,12 +1,12 @@ #![doc = include_str!("../README.md")] +use std::collections::{HashMap, HashSet}; + use proc_macro::TokenStream; use proc_macro_error::{abort_call_site, emit_warning, proc_macro_error}; use quote::{format_ident, quote}; use syn::{parse_macro_input, Data, DeriveInput, Fields, Type}; -use std::collections::{HashMap, HashSet}; - /// See top-level crate documentation. #[proc_macro_error] #[proc_macro_derive(EnumFieldGetter)] @@ -25,29 +25,45 @@ pub fn enum_field_getter(stream: TokenStream) -> TokenStream { let ident = field.ident.clone().unwrap().to_string(); let field_ty = field.ty.clone(); let df = (field_ty.clone(), vec![variant.ident.to_string()]); - field_info.entry(ident.clone()).and_modify(|info| { - let (ty, used_variants) = info; - if quote!{#field_ty}.to_string() != quote!{#ty}.to_string() { - emit_warning!(field, "fields must be the same type across all variants - no getter will be emitted for this field"); - incompatible.insert(ident.clone()); - } else { - used_variants.push(variant.ident.to_string()); - } - }).or_insert(df); + field_info + .entry(ident.clone()) + .and_modify(|info| { + let (ty, used_variants) = info; + if quote! {#field_ty}.to_string() != quote! {#ty}.to_string() { + emit_warning!( + field, + "fields must be the same type across all variants - no getter \ + will be emitted for this field" + ); + incompatible.insert(ident.clone()); + } else { + used_variants.push(variant.ident.to_string()); + } + }) + .or_insert(df); } } else if let Fields::Unnamed(_) = variant.fields { for (i, field) in variant.fields.iter().enumerate() { let field_ty = field.ty.clone(); let df = (field_ty.clone(), vec![variant.ident.to_string()]); - tuple_field_info.entry(i).and_modify(|info| { - let (ty, used_variants) = info; - if quote!{#field_ty}.to_string() != quote!{#ty}.to_string() { - emit_warning!(field, "Fields must be the same type across all variants - no getter will be emitted for this field.\nExpected type {}, got {}.", quote!{#ty}.to_string(), quote!{#field_ty}.to_string()); - tuple_incompatible.insert(i); - } else { - used_variants.push(variant.ident.to_string()); - } - }).or_insert(df); + tuple_field_info + .entry(i) + .and_modify(|info| { + let (ty, used_variants) = info; + if quote! {#field_ty}.to_string() != quote! {#ty}.to_string() { + emit_warning!( + field, + "Fields must be the same type across all variants - no getter \ + will be emitted for this field.\nExpected type {}, got {}.", + quote! {#ty}.to_string(), + quote! {#field_ty}.to_string() + ); + tuple_incompatible.insert(i); + } else { + used_variants.push(variant.ident.to_string()); + } + }) + .or_insert(df); } } } diff --git a/rustfmt.toml b/rustfmt.toml index 54376893..b637feb0 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,4 @@ unstable_features = true -format_strings = true \ No newline at end of file +format_strings = true +imports_granularity = "Module" +group_imports = "StdExternalCrate" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 08b8c5c5..18210a82 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ +use alloc::boxed::Box; use core::cell::{BorrowError, BorrowMutError}; -use alloc::boxed::Box; use wasm_bindgen::JsValue; pub type HQResult = Result; diff --git a/src/instructions.rs b/src/instructions.rs index a7ab5ea6..f552126c 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -101,9 +101,8 @@ where include!(concat!(env!("OUT_DIR"), "/ir-opcodes.rs")); pub mod input_switcher; -pub use input_switcher::wrap_instruction; - pub use hq::r#yield::YieldMode; +pub use input_switcher::wrap_instruction; /// Canonical NaN + bit 33, + string pointer in bits 1-32 pub const BOXED_STRING_PATTERN: i64 = 0x7FF8_0001 << 32; @@ -116,11 +115,6 @@ pub const BOXED_COLOR_RGB_PATTERN: i64 = 0x7ff8_0008 << 32; /// Canonical NaN + bit 37, + i32 in bits 1-32 pub const BOXED_COLOR_ARGB_PATTERN: i64 = 0x7ff8_000f << 32; mod prelude { - pub use crate::ir::{ReturnType, Type as IrType}; - pub use crate::optimisation::{ConstFold, ConstFoldItem, ConstFoldState}; - pub use crate::prelude::*; - pub use crate::sb3::VarVal; - pub use crate::wasm::{InternalInstruction, StepFunc}; pub use ConstFold::NotFoldable; pub use ReturnType::{MultiValue, Singleton}; pub use wasm_encoder::{RefType, ValType}; @@ -130,4 +124,9 @@ mod prelude { BOXED_BOOL_PATTERN, BOXED_COLOR_ARGB_PATTERN, BOXED_COLOR_RGB_PATTERN, BOXED_INT_PATTERN, BOXED_STRING_PATTERN, }; + pub use crate::ir::{ReturnType, Type as IrType}; + pub use crate::optimisation::{ConstFold, ConstFoldItem, ConstFoldState}; + pub use crate::prelude::*; + pub use crate::sb3::VarVal; + pub use crate::wasm::{InternalInstruction, StepFunc}; } diff --git a/src/instructions/control/if_else.rs b/src/instructions/control/if_else.rs index abb7a7bb..7efaa224 100644 --- a/src/instructions/control/if_else.rs +++ b/src/instructions/control/if_else.rs @@ -1,6 +1,7 @@ +use wasm_encoder::BlockType; + use super::super::prelude::*; use crate::ir::Step; -use wasm_encoder::BlockType; #[derive(Debug)] pub struct Fields { diff --git a/src/instructions/control/loop.rs b/src/instructions/control/loop.rs index 0b5ecc3d..8e1cef6c 100644 --- a/src/instructions/control/loop.rs +++ b/src/instructions/control/loop.rs @@ -1,8 +1,9 @@ // for use in warped contexts only. +use wasm_encoder::BlockType; + use super::super::prelude::*; use crate::ir::Step; -use wasm_encoder::BlockType; #[derive(Debug)] pub struct Fields { diff --git a/src/instructions/data/addtolist.rs b/src/instructions/data/addtolist.rs index ac60466c..58dd098e 100644 --- a/src/instructions/data/addtolist.rs +++ b/src/instructions/data/addtolist.rs @@ -1,9 +1,9 @@ +use wasm_encoder::BlockType as WasmBlockType; + use super::super::prelude::*; use crate::ir::RcList; use crate::wasm::WasmProject; -use wasm_encoder::BlockType as WasmBlockType; - /// we need these fields to be mutable for optimisations to be feasible #[derive(Debug, Clone)] pub struct Fields { diff --git a/src/instructions/data/deleteoflist.rs b/src/instructions/data/deleteoflist.rs index 0f1566b4..7029d019 100644 --- a/src/instructions/data/deleteoflist.rs +++ b/src/instructions/data/deleteoflist.rs @@ -1,8 +1,8 @@ +use wasm_encoder::BlockType as WasmBlockType; + use super::super::prelude::*; use crate::ir::RcList; -use wasm_encoder::BlockType as WasmBlockType; - /// we need these fields to be mutable for optimisations to be feasible #[derive(Debug, Clone)] pub struct Fields { diff --git a/src/instructions/data/insertatlist.rs b/src/instructions/data/insertatlist.rs index 7ae2bee1..d18cea68 100644 --- a/src/instructions/data/insertatlist.rs +++ b/src/instructions/data/insertatlist.rs @@ -1,9 +1,9 @@ +use wasm_encoder::BlockType as WasmBlockType; + use super::super::prelude::*; use crate::ir::RcList; use crate::wasm::WasmProject; -use wasm_encoder::BlockType as WasmBlockType; - /// we need these fields to be mutable for optimisations to be feasible #[derive(Debug, Clone)] pub struct Fields { diff --git a/src/instructions/data/itemoflist.rs b/src/instructions/data/itemoflist.rs index 63ddd9f4..59a28f92 100644 --- a/src/instructions/data/itemoflist.rs +++ b/src/instructions/data/itemoflist.rs @@ -1,9 +1,9 @@ +use wasm_encoder::BlockType as WasmBlockType; + use super::super::prelude::*; use crate::ir::RcList; use crate::wasm::WasmProject; -use wasm_encoder::BlockType as WasmBlockType; - /// we need these fields to be mutable for optimisations to be feasible #[derive(Debug, Clone)] pub struct Fields { diff --git a/src/instructions/data/listcontents.rs b/src/instructions/data/listcontents.rs index 39ab590a..885b7638 100644 --- a/src/instructions/data/listcontents.rs +++ b/src/instructions/data/listcontents.rs @@ -1,9 +1,9 @@ +use wasm_encoder::{BlockType as WasmBlockType, HeapType}; + use super::super::prelude::*; use crate::ir::RcList; use crate::wasm::StringsTable; -use wasm_encoder::{BlockType as WasmBlockType, HeapType}; - /// we need these fields to be mutable for optimisations to be feasible #[derive(Debug, Clone)] pub struct Fields { diff --git a/src/instructions/data/replaceitemoflist.rs b/src/instructions/data/replaceitemoflist.rs index 61745e87..b5f8a6b5 100644 --- a/src/instructions/data/replaceitemoflist.rs +++ b/src/instructions/data/replaceitemoflist.rs @@ -1,9 +1,9 @@ +use wasm_encoder::BlockType as WasmBlockType; + use super::super::prelude::*; use crate::ir::RcList; use crate::wasm::WasmProject; -use wasm_encoder::BlockType as WasmBlockType; - /// we need these fields to be mutable for optimisations to be feasible #[derive(Debug, Clone)] pub struct Fields { diff --git a/src/instructions/hq/dup.rs b/src/instructions/hq/dup.rs index 012bb57d..87708683 100644 --- a/src/instructions/hq/dup.rs +++ b/src/instructions/hq/dup.rs @@ -1,6 +1,5 @@ -use crate::wasm::WasmProject; - use super::super::prelude::*; +use crate::wasm::WasmProject; pub fn wasm(func: &StepFunc, inputs: Rc<[IrType]>) -> HQResult> { let local = func.local(WasmProject::ir_type_to_wasm(inputs[0])?)?; diff --git a/src/instructions/hq/swap.rs b/src/instructions/hq/swap.rs index af3f3fa6..f06f30ac 100644 --- a/src/instructions/hq/swap.rs +++ b/src/instructions/hq/swap.rs @@ -1,6 +1,5 @@ -use crate::wasm::WasmProject; - use super::super::prelude::*; +use crate::wasm::WasmProject; pub fn wasm(func: &StepFunc, inputs: Rc<[IrType]>) -> HQResult> { hq_assert!(inputs.len() == 2); diff --git a/src/instructions/hq/yield.rs b/src/instructions/hq/yield.rs index 4e9bc8c4..04439ecd 100644 --- a/src/instructions/hq/yield.rs +++ b/src/instructions/hq/yield.rs @@ -1,7 +1,8 @@ +use wasm_encoder::{BlockType, ConstExpr, HeapType}; + use super::super::prelude::*; use crate::ir::Step; use crate::wasm::{GlobalExportable, GlobalMutable, StepFunc, ThreadsTable}; -use wasm_encoder::{BlockType, ConstExpr, HeapType}; #[derive(Debug)] pub enum YieldMode { diff --git a/src/instructions/input_switcher.rs b/src/instructions/input_switcher.rs index 02ea4a58..2fc59706 100644 --- a/src/instructions/input_switcher.rs +++ b/src/instructions/input_switcher.rs @@ -1,19 +1,14 @@ //! Provides the logic for having boxed input types to blocks -use super::HqCastFields; -use super::IrOpcode; -use super::prelude::*; -use crate::ir::{Type as IrType, base_types}; -use crate::wasm::GlobalExportable; -use crate::wasm::GlobalMutable; -use crate::wasm::StepFunc; -use crate::wasm::StringsTable; -use crate::wasm::WasmProject; use itertools::Itertools; -use wasm_encoder::ConstExpr; -use wasm_encoder::{BlockType, Instruction as WInstruction}; +use wasm_encoder::{BlockType, ConstExpr, Instruction as WInstruction}; use wasm_gen::wasm; +use super::prelude::*; +use super::{HqCastFields, IrOpcode}; +use crate::ir::{Type as IrType, base_types}; +use crate::wasm::{GlobalExportable, GlobalMutable, StepFunc, StringsTable, WasmProject}; + /// Takes a base type and gives the NaN-boxed pattern for that type fn boxed_pattern(ty: IrType) -> HQResult { Ok(match ty { diff --git a/src/instructions/motion/direction.rs b/src/instructions/motion/direction.rs index ca54abe6..a6102437 100644 --- a/src/instructions/motion/direction.rs +++ b/src/instructions/motion/direction.rs @@ -1,8 +1,9 @@ -use super::super::prelude::*; -use crate::wasm::{StepTarget, mem_layout}; use mem_layout::{sprite as sprite_layout, stage as stage_layout}; use wasm_encoder::MemArg; +use super::super::prelude::*; +use crate::wasm::{StepTarget, mem_layout}; + pub fn wasm(func: &StepFunc, _inputs: Rc<[IrType]>) -> HQResult> { let StepTarget::Sprite(wasm_target_index) = func.target() else { hq_bad_proj!("motion_direction called in stage") diff --git a/src/instructions/motion/gotoxy.rs b/src/instructions/motion/gotoxy.rs index f6c9cb52..54fcd320 100644 --- a/src/instructions/motion/gotoxy.rs +++ b/src/instructions/motion/gotoxy.rs @@ -1,8 +1,9 @@ -use super::super::prelude::*; -use crate::wasm::{StepTarget, mem_layout}; use mem_layout::{sprite as sprite_layout, stage as stage_layout}; use wasm_encoder::MemArg; +use super::super::prelude::*; +use crate::wasm::{StepTarget, mem_layout}; + pub fn wasm(func: &StepFunc, _inputs: Rc<[IrType]>) -> HQResult> { let ir_target_index: i32 = func .target_index() diff --git a/src/instructions/motion/pointindirection.rs b/src/instructions/motion/pointindirection.rs index 73fe62ab..2e16e46e 100644 --- a/src/instructions/motion/pointindirection.rs +++ b/src/instructions/motion/pointindirection.rs @@ -1,8 +1,9 @@ -use super::super::prelude::*; -use crate::wasm::{StepTarget, mem_layout}; use mem_layout::{sprite as sprite_layout, stage as stage_layout}; use wasm_encoder::MemArg; +use super::super::prelude::*; +use crate::wasm::{StepTarget, mem_layout}; + pub fn wasm(func: &StepFunc, inputs: Rc<[IrType]>) -> HQResult> { let StepTarget::Sprite(wasm_target_index) = func.target() else { hq_bad_proj!("motion_pointindirection called in stage") diff --git a/src/instructions/operator/modulo.rs b/src/instructions/operator/modulo.rs index 28afe008..817d54d8 100644 --- a/src/instructions/operator/modulo.rs +++ b/src/instructions/operator/modulo.rs @@ -1,8 +1,9 @@ //! this is called modulo rather than mod, just because mod.rs has a special meaning in rust -use super::super::prelude::*; use wasm_encoder::BlockType as WasmBlockType; +use super::super::prelude::*; + pub fn wasm(func: &StepFunc, inputs: Rc<[IrType]>) -> HQResult> { hq_assert_eq!(inputs.len(), 2); let t1 = inputs[0]; diff --git a/src/instructions/operator/random.rs b/src/instructions/operator/random.rs index 36e5a17c..fdc9562c 100644 --- a/src/instructions/operator/random.rs +++ b/src/instructions/operator/random.rs @@ -1,6 +1,7 @@ -use super::super::prelude::*; use wasm_encoder::BlockType as WasmBlockType; +use super::super::prelude::*; + pub fn wasm(func: &StepFunc, inputs: Rc<[IrType]>) -> HQResult> { hq_assert_eq!(inputs.len(), 2); let t1 = inputs[0]; diff --git a/src/instructions/pen/setpencolorparamto.rs b/src/instructions/pen/setpencolorparamto.rs index fd5ad663..947024cb 100644 --- a/src/instructions/pen/setpencolorparamto.rs +++ b/src/instructions/pen/setpencolorparamto.rs @@ -1,8 +1,9 @@ +use registries::functions::static_functions::UpdatePenColorFromHSV; +use wasm_encoder::{BlockType as WasmBlockType, MemArg}; + use super::super::prelude::*; use crate::instructions::{HqTextFields, IrOpcode}; use crate::wasm::{StepTarget, mem_layout, registries}; -use registries::functions::static_functions::UpdatePenColorFromHSV; -use wasm_encoder::{BlockType as WasmBlockType, MemArg}; pub fn wasm(func: &StepFunc, _inputs: Rc<[IrType]>) -> HQResult> { let StepTarget::Sprite(wasm_target_index) = func.target() else { diff --git a/src/instructions/pen/setpencolortocolor.rs b/src/instructions/pen/setpencolortocolor.rs index 6f1383e7..92928ed9 100644 --- a/src/instructions/pen/setpencolortocolor.rs +++ b/src/instructions/pen/setpencolortocolor.rs @@ -1,8 +1,9 @@ -use super::super::prelude::*; -use crate::wasm::{StepTarget, mem_layout, registries}; use registries::functions::static_functions::UpdatePenColorFromRGB; use wasm_encoder::{BlockType, MemArg}; +use super::super::prelude::*; +use crate::wasm::{StepTarget, mem_layout, registries}; + pub fn wasm(func: &StepFunc, inputs: Rc<[IrType]>) -> HQResult> { let t1 = inputs[0]; let StepTarget::Sprite(wasm_target_index) = func.target() else { diff --git a/src/instructions/procedures/argument.rs b/src/instructions/procedures/argument.rs index 3a913a56..28ef3068 100644 --- a/src/instructions/procedures/argument.rs +++ b/src/instructions/procedures/argument.rs @@ -1,10 +1,9 @@ use wasm_encoder::{AbstractHeapType, HeapType}; use super::super::prelude::*; -use crate::{ - ir::RcVar, - wasm::{StepFunc, WasmProject, registries::types::WasmType}, -}; +use crate::ir::RcVar; +use crate::wasm::registries::types::WasmType; +use crate::wasm::{StepFunc, WasmProject}; #[derive(Clone, Debug)] pub struct Fields { diff --git a/src/instructions/procedures/call_nonwarp.rs b/src/instructions/procedures/call_nonwarp.rs index b2b65c0f..513aa035 100644 --- a/src/instructions/procedures/call_nonwarp.rs +++ b/src/instructions/procedures/call_nonwarp.rs @@ -1,7 +1,8 @@ +use wasm_encoder::{FieldType, HeapType, Instruction as WInstruction, StorageType}; + use super::super::prelude::*; use crate::ir::{Proc, Step}; use crate::wasm::{StepFunc, ThreadsTable, WasmProject}; -use wasm_encoder::{FieldType, HeapType, Instruction as WInstruction, StorageType}; #[derive(Clone, Debug)] pub struct Fields { diff --git a/src/instructions/procedures/call_warp.rs b/src/instructions/procedures/call_warp.rs index a10b3ab7..e64a6a9b 100644 --- a/src/instructions/procedures/call_warp.rs +++ b/src/instructions/procedures/call_warp.rs @@ -1,7 +1,8 @@ +use wasm_encoder::Instruction as WInstruction; + use super::super::prelude::*; use crate::ir::Proc; use crate::wasm::{StepFunc, WasmProject}; -use wasm_encoder::Instruction as WInstruction; #[derive(Clone, Debug)] pub struct Fields { diff --git a/src/ir/blocks.rs b/src/ir/blocks.rs index 56c5c7e8..0396f138 100644 --- a/src/ir/blocks.rs +++ b/src/ir/blocks.rs @@ -1,28 +1,27 @@ +use lazy_regex::{Lazy, lazy_regex}; +use regex::Regex; +use sb3::{Block, BlockArray, BlockArrayOrId, BlockInfo, BlockMap, BlockOpcode, Input}; + use super::context::StepContext; use super::target::Target; use super::{IrProject, RcVar, Step, Type as IrType}; +use crate::instructions::fields::{ + ControlIfElseFields, ControlLoopFields, DataSetvariabletoFields, DataTeevariableFields, + DataVariableFields, HqBooleanFields, HqCastFields, HqColorRgbFields, HqFloatFields, + HqIntegerFields, HqTextFields, HqYieldFields, LooksSayFields, LooksThinkFields, + ProceduresArgumentFields, ProceduresCallWarpFields, +}; use crate::instructions::{ DataAddtolistFields, DataDeletealloflistFields, DataDeleteoflistFields, DataInsertatlistFields, DataItemoflistFields, DataLengthoflistFields, DataListcontentsFields, - DataReplaceitemoflistFields, EventBroadcastFields, ProceduresCallNonwarpFields, -}; -use crate::instructions::{ - IrOpcode, YieldMode, - fields::{ - ControlIfElseFields, ControlLoopFields, DataSetvariabletoFields, DataTeevariableFields, - DataVariableFields, HqBooleanFields, HqCastFields, HqColorRgbFields, HqFloatFields, - HqIntegerFields, HqTextFields, HqYieldFields, LooksSayFields, LooksThinkFields, - ProceduresArgumentFields, ProceduresCallWarpFields, - }, + DataReplaceitemoflistFields, EventBroadcastFields, IrOpcode, ProceduresCallNonwarpFields, + YieldMode, }; use crate::ir::{RcList, ReturnType}; use crate::prelude::*; use crate::sb3::{self, Field, VarVal}; use crate::wasm::WasmFlags; use crate::wasm::flags::Switch; -use lazy_regex::{Lazy, lazy_regex}; -use regex::Regex; -use sb3::{Block, BlockArray, BlockArrayOrId, BlockInfo, BlockMap, BlockOpcode, Input}; pub fn insert_casts(blocks: &mut Vec, ignore_variables: bool) -> HQResult<()> { let mut type_stack: Vec<(IrType, usize)> = vec![]; // a vector of types, and where they came from diff --git a/src/ir/context.rs b/src/ir/context.rs index 2b542924..173cc6f8 100644 --- a/src/ir/context.rs +++ b/src/ir/context.rs @@ -1,5 +1,6 @@ use super::{IrProject, Target}; -use crate::{ir::RcVar, prelude::*}; +use crate::ir::RcVar; +use crate::prelude::*; #[derive(Debug, Clone)] pub struct ProcContext { diff --git a/src/ir/proc.rs b/src/ir/proc.rs index fe309a2e..f6ef6150 100644 --- a/src/ir/proc.rs +++ b/src/ir/proc.rs @@ -2,6 +2,11 @@ //! we can (we do this to improve variable type analysis). Procedures can return as many values //! as we want, because we can use the WASM multi-value proposal. +use core::cell::{Ref, RefMut}; + +use lazy_regex::{Lazy, lazy_regex}; +use regex::Regex; + use super::blocks::NextBlocks; use super::context::StepContext; use super::{Step, Target as IrTarget}; @@ -9,9 +14,6 @@ use crate::ir::{ProcContext, RcVar}; use crate::prelude::*; use crate::sb3::{Block, BlockArrayOrId, BlockMap, BlockOpcode, Input, Target as Sb3Target}; use crate::wasm::WasmFlags; -use core::cell::{Ref, RefMut}; -use lazy_regex::{Lazy, lazy_regex}; -use regex::Regex; #[derive(Clone, Debug, PartialEq, Eq)] pub enum PartialStep { diff --git a/src/ir/step.rs b/src/ir/step.rs index 4052b5a0..cafd7c61 100644 --- a/src/ir/step.rs +++ b/src/ir/step.rs @@ -1,3 +1,7 @@ +use core::cell::RefMut; + +use uuid::Uuid; + use super::blocks::{self, NextBlocks}; use super::{IrProject, StepContext}; use crate::instructions::{ControlIfElseFields, HqYieldFields, IrOpcode, YieldMode}; @@ -5,8 +9,6 @@ use crate::ir::{RcVar, Target, used_vars}; use crate::prelude::*; use crate::sb3::{Block, BlockMap}; use crate::wasm::WasmFlags; -use core::cell::RefMut; -use uuid::Uuid; #[derive(Debug)] pub struct Step { diff --git a/src/ir/target.rs b/src/ir/target.rs index b18ed833..5dd54258 100644 --- a/src/ir/target.rs +++ b/src/ir/target.rs @@ -1,8 +1,10 @@ -use super::{IrProject, proc::Proc}; +use core::cell::{Ref, RefMut}; + +use super::IrProject; +use super::proc::Proc; use crate::ir::variable::{TargetLists, TargetVars}; use crate::prelude::*; use crate::sb3::CostumeDataFormat; -use core::cell::{Ref, RefMut}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct IrCostume { diff --git a/src/ir/thread.rs b/src/ir/thread.rs index 0588f351..159d2c02 100644 --- a/src/ir/thread.rs +++ b/src/ir/thread.rs @@ -1,8 +1,9 @@ use super::blocks::NextBlocks; use super::{Event, IrProject, Step, StepContext, Target}; +use crate::prelude::*; +use crate::sb3; use crate::sb3::{Block, BlockMap, BlockOpcode, VarVal}; use crate::wasm::WasmFlags; -use crate::{prelude::*, sb3}; #[derive(Clone, Debug)] pub struct Thread { diff --git a/src/ir/types.rs b/src/ir/types.rs index 72a89fc0..9032b574 100644 --- a/src/ir/types.rs +++ b/src/ir/types.rs @@ -1,9 +1,10 @@ +use bitmask_enum::bitmask; + use crate::instructions::{ HqBooleanFields, HqFloatFields, HqIntegerFields, HqTextFields, IrOpcode, }; use crate::prelude::*; use crate::sb3::VarVal; -use bitmask_enum::bitmask; /// a bitmask of possible IR types #[bitmask(u32)] diff --git a/src/ir/variable.rs b/src/ir/variable.rs index ac6434fc..588a4fa8 100644 --- a/src/ir/variable.rs +++ b/src/ir/variable.rs @@ -1,14 +1,14 @@ +use core::cell::Ref; +use core::hash::{Hash, Hasher}; + use uuid::Uuid; use super::Type; -use crate::{ - ir::types::var_val_type, - prelude::*, - sb3::{Target as Sb3Target, VarVal}, - wasm::{WasmFlags, flags::Switch}, -}; -use core::cell::Ref; -use core::hash::{Hash, Hasher}; +use crate::ir::types::var_val_type; +use crate::prelude::*; +use crate::sb3::{Target as Sb3Target, VarVal}; +use crate::wasm::WasmFlags; +use crate::wasm::flags::Switch; #[derive(Debug)] struct Variable { diff --git a/src/lib.rs b/src/lib.rs index d1384c79..82c27f23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,11 +73,6 @@ pub mod rc; /// commonly used _things_ which would be nice not to have to type out every time pub mod prelude { - pub use crate::registry::{ - NamedRegistrar, NamedRegistry, NamedRegistryItem, NamedRegistryItemOverride, Registry, - RegistryDefault, RegistryType, TryNamedRegistryItemOverride, - }; - pub use crate::{HQError, HQResult}; pub use alloc::borrow::Cow; pub use alloc::boxed::Box; pub use alloc::collections::{BTreeMap, BTreeSet}; @@ -86,14 +81,19 @@ pub mod prelude { pub use core::borrow::Borrow; pub use core::cell::RefCell; pub use core::fmt; + use core::hash::BuildHasherDefault; pub use core::marker::PhantomPinned; pub use core::pin::Pin; - pub use crate::rc::{Rc, Weak}; - - use core::hash::BuildHasherDefault; use hashers::fnv::FNV1aHasher64; use indexmap; + + pub use crate::rc::{Rc, Weak}; + pub use crate::registry::{ + NamedRegistrar, NamedRegistry, NamedRegistryItem, NamedRegistryItemOverride, Registry, + RegistryDefault, RegistryType, TryNamedRegistryItemOverride, + }; + pub use crate::{HQError, HQResult}; pub type IndexMap = indexmap::IndexMap>; pub type IndexSet = indexmap::IndexSet>; diff --git a/src/optimisation.rs b/src/optimisation.rs index fb5c4b92..0b4ef248 100644 --- a/src/optimisation.rs +++ b/src/optimisation.rs @@ -1,6 +1,7 @@ +use crate::ir::IrProject; use crate::prelude::*; +use crate::wasm::WasmFlags; use crate::wasm::flags::Switch; -use crate::{ir::IrProject, wasm::WasmFlags}; mod const_folding; mod loop_unrolling; diff --git a/src/optimisation/ssa.rs b/src/optimisation/ssa.rs index 7130af20..46cfc977 100644 --- a/src/optimisation/ssa.rs +++ b/src/optimisation/ssa.rs @@ -88,6 +88,18 @@ reason = "implementations of Eq and Ord for RcVar and Step are independent of actual contents" )] +use alloc::collections::btree_map::Entry; +use core::convert::identity; +use core::hash::{Hash, Hasher}; +use core::marker::PhantomData; +use core::mem; + +// use petgraph::dot::Dot; +use petgraph::graph::{EdgeIndex, NodeIndex}; +use petgraph::stable_graph::StableDiGraph; +use petgraph::visit::EdgeRef; +use petgraph::{Incoming as EdgeIn, Outgoing as EdgeOut}; + use crate::instructions::{ ControlIfElseFields, ControlLoopFields, DataAddtolistFields, DataInsertatlistFields, DataItemoflistFields, DataReplaceitemoflistFields, DataSetvariabletoFields, @@ -99,17 +111,6 @@ use crate::ir::{ }; use crate::prelude::*; -use alloc::collections::btree_map::Entry; -use core::convert::identity; -use core::hash::{Hash, Hasher}; -use core::marker::PhantomData; -use core::mem; -// use petgraph::dot::Dot; - -use petgraph::graph::{EdgeIndex, NodeIndex}; -use petgraph::visit::EdgeRef; -use petgraph::{Incoming as EdgeIn, Outgoing as EdgeOut, stable_graph::StableDiGraph}; - #[derive(Clone, Debug)] enum StackElement { Drop, diff --git a/src/registry.rs b/src/registry.rs index aadc2e96..858e1550 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -1,6 +1,7 @@ -use crate::prelude::*; use core::hash::Hash; +use crate::prelude::*; + #[derive(Clone)] pub struct MapRegistry(RefCell>) where diff --git a/src/sb3.rs b/src/sb3.rs index 8b274264..ad9a3507 100644 --- a/src/sb3.rs +++ b/src/sb3.rs @@ -5,11 +5,12 @@ //! `sb3` files must be unzipped first. See //! for a loose informal specification. -use crate::prelude::*; use enum_field_getter::EnumFieldGetter; use serde::{Deserialize, Serialize}; use serde_json::Value; +use crate::prelude::*; + /// A scratch project #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Sb3Project { diff --git a/src/wasm.rs b/src/wasm.rs index 2a9d708e..1449ae7c 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -10,8 +10,9 @@ pub use external::ExternalEnvironment; pub use flags::WasmFlags; pub use func::{Instruction as InternalInstruction, StepFunc, StepTarget}; pub use project::{FinishedWasm, WasmProject}; -pub use registries::Registries; -pub use registries::{GlobalExportable, GlobalMutable, StepsTable, StringsTable, ThreadsTable}; +pub use registries::{ + GlobalExportable, GlobalMutable, Registries, StepsTable, StringsTable, ThreadsTable, +}; /// the same as Into, but `const`. #[must_use] diff --git a/src/wasm/flags.rs b/src/wasm/flags.rs index e3e1817a..a5a265a0 100644 --- a/src/wasm/flags.rs +++ b/src/wasm/flags.rs @@ -1,9 +1,10 @@ #![allow(clippy::enum_glob_use, reason = "easier and little risk of pollution")] -use crate::prelude::*; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; +use crate::prelude::*; + #[derive(Copy, Clone, Serialize, Deserialize)] #[wasm_bindgen] pub enum WasmStringType { diff --git a/src/wasm/func.rs b/src/wasm/func.rs index aa5c935c..75add1bb 100644 --- a/src/wasm/func.rs +++ b/src/wasm/func.rs @@ -1,16 +1,17 @@ -use super::{Registries, WasmFlags, WasmProject}; -use crate::instructions::IrOpcode; -use crate::ir::{Event, PartialStep, RcVar, ReturnType, Step}; -use crate::prelude::*; -use crate::wasm::registries::TypeRegistry; -use crate::{instructions::wrap_instruction, ir::Proc}; use alloc::collections::btree_map; + use wasm_encoder::{ self, AbstractHeapType, CodeSection, Function, FunctionSection, HeapType, Instruction as WInstruction, RefType, ValType, }; use wasm_gen::wasm; +use super::{Registries, WasmFlags, WasmProject}; +use crate::instructions::{IrOpcode, wrap_instruction}; +use crate::ir::{Event, PartialStep, Proc, RcVar, ReturnType, Step}; +use crate::prelude::*; +use crate::wasm::registries::TypeRegistry; + #[derive(Clone, Debug)] pub enum Instruction { Immediate(wasm_encoder::Instruction<'static>), diff --git a/src/wasm/project.rs b/src/wasm/project.rs index 17ec6b16..e05697be 100644 --- a/src/wasm/project.rs +++ b/src/wasm/project.rs @@ -1,8 +1,3 @@ -use super::{ExternalEnvironment, GlobalExportable, GlobalMutable, Registries}; -use crate::ir::{Event, IrProject, Step, Target as IrTarget, Type as IrType}; -use crate::prelude::*; -use crate::wasm::registries::functions::static_functions::SpawnNewThread; -use crate::wasm::{StepFunc, StringsTable, ThreadsTable, WasmFlags}; use itertools::Itertools; use wasm_bindgen::prelude::*; use wasm_encoder::{ @@ -13,6 +8,12 @@ use wasm_encoder::{ }; use wasm_gen::wasm; +use super::{ExternalEnvironment, GlobalExportable, GlobalMutable, Registries}; +use crate::ir::{Event, IrProject, Step, Target as IrTarget, Type as IrType}; +use crate::prelude::*; +use crate::wasm::registries::functions::static_functions::SpawnNewThread; +use crate::wasm::{StepFunc, StringsTable, ThreadsTable, WasmFlags}; + /// A respresentation of a WASM representation of a project. Cannot be created directly; /// use `TryFrom`. pub struct WasmProject { @@ -617,7 +618,8 @@ mod tests { use super::{Registries, WasmProject}; use crate::ir::{IrProject, Step, Target as IrTarget}; use crate::prelude::*; - use crate::wasm::{ExternalEnvironment, StepFunc, WasmFlags, flags::all_wasm_features}; + use crate::wasm::flags::all_wasm_features; + use crate::wasm::{ExternalEnvironment, StepFunc, WasmFlags}; #[test] fn empty_project_is_valid_wasm() { diff --git a/src/wasm/registries.rs b/src/wasm/registries.rs index da558666..b12a1ff5 100644 --- a/src/wasm/registries.rs +++ b/src/wasm/registries.rs @@ -7,7 +7,6 @@ pub mod targets; pub mod types; pub mod variables; -use crate::prelude::*; pub use functions::{ExternalFunctionRegistry, StaticFunctionRegistry}; pub use globals::{GlobalExportable, GlobalMutable, GlobalRegistry}; pub use lists::ListRegistry; @@ -17,6 +16,8 @@ pub use targets::SpriteRegistry; pub use types::TypeRegistry; pub use variables::VariableRegistry; +use crate::prelude::*; + pub struct Registries { strings: Rc, tabled_strings: Rc, diff --git a/src/wasm/registries/functions.rs b/src/wasm/registries/functions.rs index 08ba749c..dd0410d6 100644 --- a/src/wasm/registries/functions.rs +++ b/src/wasm/registries/functions.rs @@ -1,13 +1,14 @@ #![allow(clippy::cast_possible_wrap, reason = "can't use try_into in const")] -use super::TypeRegistry; -use crate::prelude::*; -use crate::registry::{MapRegistry, Registry}; use wasm_encoder::{ CodeSection, EntityType, Function, FunctionSection, ImportSection, Instruction as WInstruction, ValType, }; +use super::TypeRegistry; +use crate::prelude::*; +use crate::registry::{MapRegistry, Registry}; + pub type ExternalFunctionRegistry = MapRegistry<(&'static str, Box), (Vec, Vec)>; @@ -81,15 +82,16 @@ impl StaticFunctionRegistry { } pub mod static_functions { - use super::{MaybeStaticFunction, StaticFunction}; - use crate::prelude::*; - use crate::wasm::{f32_to_ieeef32, mem_layout}; use mem_layout::{sprite as sprite_layout, stage as stage_layout}; use wasm_encoder::{ AbstractHeapType, BlockType as WasmBlockType, HeapType, MemArg, RefType, ValType, }; use wasm_gen::wasm_const; + use super::{MaybeStaticFunction, StaticFunction}; + use crate::prelude::*; + use crate::wasm::{f32_to_ieeef32, mem_layout}; + pub struct SpawnNewThread; impl NamedRegistryItem for SpawnNewThread { const VALUE: MaybeStaticFunction = MaybeStaticFunction { diff --git a/src/wasm/registries/globals.rs b/src/wasm/registries/globals.rs index f205aba7..9cdd522e 100644 --- a/src/wasm/registries/globals.rs +++ b/src/wasm/registries/globals.rs @@ -1,8 +1,10 @@ -use crate::prelude::*; -use crate::registry::MapRegistry; use core::ops::Deref; + use wasm_encoder::{ConstExpr, ExportKind, ExportSection, GlobalSection, GlobalType, ValType}; +use crate::prelude::*; +use crate::registry::MapRegistry; + #[derive(Copy, Clone, Debug)] pub struct GlobalMutable(pub bool); diff --git a/src/wasm/registries/lists.rs b/src/wasm/registries/lists.rs index bc2e5a2b..65d0e926 100644 --- a/src/wasm/registries/lists.rs +++ b/src/wasm/registries/lists.rs @@ -1,3 +1,8 @@ +use wasm_encoder::{ + ConstExpr, DataSection, ElementSection, Elements, Function, HeapType, Instruction, RefType, + StorageType, ValType, +}; + use super::super::WasmProject; use super::{GlobalExportable, GlobalMutable, GlobalRegistry, TypeRegistry}; use crate::instructions::{BOXED_BOOL_PATTERN, BOXED_INT_PATTERN, BOXED_STRING_PATTERN}; @@ -7,11 +12,6 @@ use crate::registry::MapRegistry; use crate::sb3::VarVal; use crate::wasm::registries::{StringRegistry, TabledStringRegistry}; -use wasm_encoder::{ - ConstExpr, DataSection, ElementSection, Elements, Function, HeapType, Instruction, RefType, - StorageType, ValType, -}; - #[derive(Clone)] pub struct ListRegistry( MapRegistry, // for keeping track of initialisers diff --git a/src/wasm/registries/strings.rs b/src/wasm/registries/strings.rs index 5f4b5be2..73738ca8 100644 --- a/src/wasm/registries/strings.rs +++ b/src/wasm/registries/strings.rs @@ -1,6 +1,7 @@ +use wasm_encoder::{EntityType, Function, GlobalType, ImportSection, Instruction, ValType}; + use crate::prelude::*; use crate::registry::SetRegistry; -use wasm_encoder::{EntityType, Function, GlobalType, ImportSection, Instruction, ValType}; #[derive(Clone, Default)] pub struct StringRegistry(SetRegistry>); diff --git a/src/wasm/registries/tables.rs b/src/wasm/registries/tables.rs index cba2b40e..35546452 100644 --- a/src/wasm/registries/tables.rs +++ b/src/wasm/registries/tables.rs @@ -1,8 +1,9 @@ -use crate::prelude::*; use wasm_encoder::{ ConstExpr, ExportKind, ExportSection, HeapType, RefType, TableSection, TableType, }; +use crate::prelude::*; + #[derive(Clone, Debug)] pub struct TableOptions { pub element_type: RefType, diff --git a/src/wasm/registries/types.rs b/src/wasm/registries/types.rs index c17ad517..af44053d 100644 --- a/src/wasm/registries/types.rs +++ b/src/wasm/registries/types.rs @@ -1,10 +1,12 @@ -use crate::registry::SetRegistry; -use crate::wasm::WasmProject; -use crate::{ir::RcVar, prelude::*}; use wasm_encoder::{ AbstractHeapType, FieldType, HeapType, RefType, StorageType, TypeSection, ValType, }; +use crate::ir::RcVar; +use crate::prelude::*; +use crate::registry::SetRegistry; +use crate::wasm::WasmProject; + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum WasmType { Function(Vec, Vec), diff --git a/src/wasm/registries/variables.rs b/src/wasm/registries/variables.rs index 5491406c..58309df3 100644 --- a/src/wasm/registries/variables.rs +++ b/src/wasm/registries/variables.rs @@ -1,10 +1,11 @@ +use wasm_encoder::ConstExpr; + use super::super::WasmProject; use super::{GlobalExportable, GlobalMutable, GlobalRegistry}; use crate::ir::{RcVar, Type as IrType}; use crate::prelude::*; use crate::sb3::VarVal; use crate::wasm::registries::{StringRegistry, TabledStringRegistry}; -use wasm_encoder::ConstExpr; pub struct VariableRegistry( Rc, diff --git a/wasm-gen/src/lib.rs b/wasm-gen/src/lib.rs index c1eb0a8f..2c6cffbb 100644 --- a/wasm-gen/src/lib.rs +++ b/wasm-gen/src/lib.rs @@ -1,7 +1,8 @@ +use std::collections::{HashMap, HashSet}; + use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote, quote_spanned}; -use std::collections::{HashMap, HashSet}; use syn::parse::{Parse, ParseStream}; use syn::{parenthesized, Error as SynError, Expr, Ident, Token}; From dba170567221981f5a77388b3cfb0aaaef36aca8 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:36:38 +0000 Subject: [PATCH 04/14] split spawning a thread in the same stack into its own function --- src/instructions/procedures/call_nonwarp.rs | 70 ++---------- src/wasm/project.rs | 12 ++- src/wasm/registries/functions.rs | 112 ++++++++++++++++++++ 3 files changed, 132 insertions(+), 62 deletions(-) diff --git a/src/instructions/procedures/call_nonwarp.rs b/src/instructions/procedures/call_nonwarp.rs index 513aa035..3d61f212 100644 --- a/src/instructions/procedures/call_nonwarp.rs +++ b/src/instructions/procedures/call_nonwarp.rs @@ -2,7 +2,8 @@ use wasm_encoder::{FieldType, HeapType, Instruction as WInstruction, StorageType use super::super::prelude::*; use crate::ir::{Proc, Step}; -use crate::wasm::{StepFunc, ThreadsTable, WasmProject}; +use crate::wasm::registries::functions::static_functions::SpawnThreadInStack; +use crate::wasm::{StepFunc, WasmProject}; #[derive(Clone, Debug)] pub struct Fields { @@ -51,18 +52,11 @@ pub fn wasm( nullable: false, heap_type: HeapType::Concrete(arg_struct_type), }))?; - let stack_struct_type = func.registries().types().stack_struct_type()?; - let stack_struct_local = func.local(ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::Concrete(stack_struct_type), - }))?; - let stack_array_type = func.registries().types().stack_array_type()?; - let thread_struct_type = func.registries().types().thread_struct_type()?; - let thread_struct_local = func.local(ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::Concrete(thread_struct_type), - }))?; - let threads_table = func.registries().tables().register::()?; + + let spawn_thread_in_stack = func + .registries() + .static_functions() + .register::()?; let locals = inputs .iter() @@ -97,57 +91,11 @@ pub fn wasm( wasm.extend(wasm![ StructNew(arg_struct_type), LocalSet(arg_struct_local), + LocalGet((func.params().len() - 2).try_into().map_err(|_| make_hq_bug!("local index out of bounds"))?), #LazyNonWarpedProcRef(Rc::clone(proc)), LocalGet(arg_struct_local), - StructNew(stack_struct_type), - LocalSet(stack_struct_local), - LocalGet((func.params().len() - 2).try_into().map_err(|_| make_hq_bug!("local index out of bounds"))?), - TableGet(threads_table), - RefAsNonNull, - LocalTee(thread_struct_local), - StructGet { - struct_type_index: thread_struct_type, - field_index: 1, - }, - LocalGet(thread_struct_local), - StructGet { - struct_type_index: thread_struct_type, - field_index: 0, - }, - LocalGet(stack_struct_local), - // todo: consider the case where we need to resize the array - ArraySet(stack_array_type), - LocalGet(thread_struct_local), - StructGet { - struct_type_index: thread_struct_type, - field_index: 1, - }, - LocalGet(thread_struct_local), - StructGet { - struct_type_index: thread_struct_type, - field_index: 0, - }, - I32Const(1), - I32Sub, - ArrayGet(stack_array_type), #LazyStepRef(Rc::downgrade(next_step)), - StructSet { - struct_type_index: stack_struct_type, - field_index: 0, - }, - - LocalGet(thread_struct_local), - LocalGet(thread_struct_local), - StructGet { - struct_type_index: thread_struct_type, - field_index: 0, - }, - I32Const(1), - I32Add, - StructSet { - struct_type_index: thread_struct_type, - field_index: 0, - }, + #StaticFunctionCall(spawn_thread_in_stack), LocalGet((func.params().len() - 2).try_into().map_err(|_| make_hq_bug!("local index out of bounds"))?), LocalGet(arg_struct_local), #LazyNonWarpedProcRef(Rc::clone(proc)), diff --git a/src/wasm/project.rs b/src/wasm/project.rs index e05697be..f9ebbe26 100644 --- a/src/wasm/project.rs +++ b/src/wasm/project.rs @@ -11,7 +11,7 @@ use wasm_gen::wasm; use super::{ExternalEnvironment, GlobalExportable, GlobalMutable, Registries}; use crate::ir::{Event, IrProject, Step, Target as IrTarget, Type as IrType}; use crate::prelude::*; -use crate::wasm::registries::functions::static_functions::SpawnNewThread; +use crate::wasm::registries::functions::static_functions::{SpawnNewThread, SpawnThreadInStack}; use crate::wasm::{StepFunc, StringsTable, ThreadsTable, WasmFlags}; /// A respresentation of a WASM representation of a project. Cannot be created directly; @@ -142,6 +142,16 @@ impl WasmProject { self.threads_table_index()?, ))?; + self.registries() + .static_functions() + .register_override::(( + self.registries().types().step_func_type()?, + self.registries().types().stack_struct_type()?, + self.registries().types().stack_array_type()?, + self.registries().types().thread_struct_type()?, + self.threads_table_index()?, + ))?; + self.registries().static_functions().clone().finish( &mut functions, &mut codes, diff --git a/src/wasm/registries/functions.rs b/src/wasm/registries/functions.rs index dd0410d6..8a61cde7 100644 --- a/src/wasm/registries/functions.rs +++ b/src/wasm/registries/functions.rs @@ -92,6 +92,118 @@ pub mod static_functions { use crate::prelude::*; use crate::wasm::{f32_to_ieeef32, mem_layout}; + /// Spawns a new thread in the same stack (i.e. a thread that yields back to the current + /// thread once it completes.) + /// + /// Takes 4 parameters: + /// - i32 - the current thread index + /// - step funcref - the step to spawn + /// - structref - the structref to pass to the step being spawned + /// - step funcref - the step to return to after + pub struct SpawnThreadInStack; + impl NamedRegistryItem for SpawnThreadInStack { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || None, + }; + } + pub type SpawnThreadInStackOverride = (u32, u32, u32, u32, u32); + impl NamedRegistryItemOverride + for SpawnThreadInStack + { + fn r#override( + (func_ty, stack_struct_type, stack_array_type, thread_struct_type, threads_table): SpawnThreadInStackOverride, + ) -> MaybeStaticFunction { + MaybeStaticFunction { + static_function: Some(StaticFunction { + instructions: Box::from(wasm_const![ + LocalGet(1), + LocalGet(2), + StructNew(stack_struct_type), + LocalSet(4), + LocalGet(0), + TableGet(threads_table), + RefAsNonNull, + LocalTee(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 1, + }, + LocalGet(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 0, + }, + LocalGet(4), + // todo: consider the case where we need to resize the array + ArraySet(stack_array_type), + LocalGet(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 1, + }, + LocalGet(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 0, + }, + I32Const(1), + I32Sub, + ArrayGet(stack_array_type), + LocalGet(3), + StructSet { + struct_type_index: stack_struct_type, + field_index: 0, + }, + LocalGet(5), + LocalGet(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 0, + }, + I32Const(1), + I32Add, + StructSet { + struct_type_index: thread_struct_type, + field_index: 0, + }, + End + ] as &[_]), + params: Box::from([ + ValType::I32, + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(func_ty), + }), + ValType::Ref(RefType { + nullable: true, + heap_type: wasm_encoder::HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Struct, + }, + }), + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(func_ty), + }), + ]), + returns: Box::from([]), + locals: Box::from([ + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(stack_struct_type), + }), + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(thread_struct_type), + }), + ]), + }), + maybe_populate: || None, + } + } + } + pub struct SpawnNewThread; impl NamedRegistryItem for SpawnNewThread { const VALUE: MaybeStaticFunction = MaybeStaticFunction { From 73bfdc928b4152814afbb357e2764e6235f4ba25 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:40:14 +0000 Subject: [PATCH 05/14] implement event_broadcastandwait --- src/instructions/event.rs | 2 + src/instructions/event/broadcast_and_wait.rs | 68 ++++++++ .../event/poll_waiting_threads.rs | 100 +++++++++++ src/ir/blocks.rs | 158 +++++++++++------- src/ir/step.rs | 19 +++ src/wasm/func.rs | 107 +++++++++++- src/wasm/project.rs | 38 ++++- 7 files changed, 414 insertions(+), 78 deletions(-) create mode 100644 src/instructions/event/broadcast_and_wait.rs create mode 100644 src/instructions/event/poll_waiting_threads.rs diff --git a/src/instructions/event.rs b/src/instructions/event.rs index 2ed30622..f623923a 100644 --- a/src/instructions/event.rs +++ b/src/instructions/event.rs @@ -1 +1,3 @@ pub mod broadcast; +pub mod broadcast_and_wait; +pub mod poll_waiting_threads; diff --git a/src/instructions/event/broadcast_and_wait.rs b/src/instructions/event/broadcast_and_wait.rs new file mode 100644 index 00000000..2ce63a31 --- /dev/null +++ b/src/instructions/event/broadcast_and_wait.rs @@ -0,0 +1,68 @@ +use wasm_encoder::{HeapType, StorageType}; + +use super::super::prelude::*; +use crate::ir::Step; +use crate::wasm::StepFunc; + +#[derive(Clone, Debug)] +pub struct Fields { + pub broadcast: Box, + pub poll_step: Rc, + pub next_step: Rc, +} + +impl fmt::Display for Fields { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + r#"{{ + "broadcast": "{}", + "poll_step": {}, + "next_step": {} + }}"#, + self.broadcast, self.poll_step, self.next_step, + ) + } +} + +pub fn wasm( + func: &StepFunc, + _inputs: Rc<[IrType]>, + Fields { + broadcast, + poll_step, + next_step, + }: &Fields, +) -> HQResult> { + let i32_array_type = func + .registries() + .types() + .array(StorageType::Val(ValType::I32), true)?; + let arr_local = func.local(ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(i32_array_type), + }))?; + + Ok(wasm![ + LocalGet((func.params().len() - 2).try_into().map_err(|_| make_hq_bug!("local index out of bounds"))?), + #LazyBroadcastSpawnAndWait((broadcast.clone(), Rc::clone(poll_step), Rc::clone(next_step), arr_local)) + ]) +} + +pub fn acceptable_inputs(_fields: &Fields) -> HQResult> { + Ok(Rc::from([])) +} + +pub fn output_type(_inputs: Rc<[IrType]>, _fields: &Fields) -> HQResult { + Ok(ReturnType::None) +} + +pub const REQUESTS_SCREEN_REFRESH: bool = false; + +pub const fn const_fold( + _inputs: &[ConstFoldItem], + _state: &mut ConstFoldState, + _fields: &Fields, +) -> HQResult { + Ok(NotFoldable) +} diff --git a/src/instructions/event/poll_waiting_threads.rs b/src/instructions/event/poll_waiting_threads.rs new file mode 100644 index 00000000..7f5ec564 --- /dev/null +++ b/src/instructions/event/poll_waiting_threads.rs @@ -0,0 +1,100 @@ +//! This is a bit of a strange instruction in that it relies on only ever being used inside a step +//! that was spawned from an `event_broadcast_and_wait` block. Any other use will cause invalid +//! WASM to be generated. +//! +//! Returns 1 if still waiting on any threads, 0 otherwise. + +use wasm_encoder::{BlockType as WasmBlockType, FieldType, HeapType, StorageType}; + +use super::super::prelude::*; +use crate::wasm::{StepFunc, ThreadsTable}; + +pub fn wasm(func: &StepFunc, _inputs: Rc<[IrType]>) -> HQResult> { + let i32_array_type = func + .registries() + .types() + .array(StorageType::Val(ValType::I32), true)?; + let poll_struct_type = func.registries().types().struct_(vec![FieldType { + mutable: false, + element_type: StorageType::Val(ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(i32_array_type), + })), + }])?; + + let arr_local = func.local(ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(i32_array_type), + }))?; + + let arr_len_local = func.local(ValType::I32)?; + let i_local = func.local(ValType::I32)?; + let wait_local = func.local(ValType::I32)?; + + let threads_table = func.registries().tables().register::()?; + + Ok(wasm![ + LocalGet(1), // this should never have additional function arguments so this is fine + RefCastNonNull(HeapType::Concrete(poll_struct_type)), + StructGet { + struct_type_index: poll_struct_type, + field_index: 0 + }, + LocalTee(arr_local), + ArrayLen, + LocalSet(arr_len_local), + I32Const(-1), + LocalSet(i_local), + I32Const(0), + LocalSet(wait_local), + Block(WasmBlockType::Empty), + Loop(WasmBlockType::Empty), + LocalGet(i_local), + I32Const(1), + I32Add, + LocalTee(i_local), + LocalGet(arr_len_local), + I32Eq, + BrIf(1), + LocalGet(i_local), + I32Const(0), + I32LtS, + BrIf(0), + Block(WasmBlockType::Empty), + LocalGet(arr_local), + LocalGet(i_local), + ArrayGet(i32_array_type), + TableGet(threads_table), + RefIsNull, + BrIf(0), + I32Const(1), + LocalSet(wait_local), + Br(1), + End, + LocalGet(arr_local), + LocalGet(i_local), + I32Const(-1), + ArraySet(i32_array_type), + Br(0), + End, + End, + LocalGet(wait_local), + ]) +} + +pub fn acceptable_inputs() -> HQResult> { + Ok(Rc::from([])) +} + +pub fn output_type(_inputs: Rc<[IrType]>) -> HQResult { + Ok(Singleton(IrType::Boolean)) +} + +pub const REQUESTS_SCREEN_REFRESH: bool = false; + +pub const fn const_fold( + _inputs: &[ConstFoldItem], + _state: &mut ConstFoldState, +) -> HQResult { + Ok(NotFoldable) +} diff --git a/src/ir/blocks.rs b/src/ir/blocks.rs index 0396f138..a2e0b5f2 100644 --- a/src/ir/blocks.rs +++ b/src/ir/blocks.rs @@ -14,8 +14,8 @@ use crate::instructions::fields::{ use crate::instructions::{ DataAddtolistFields, DataDeletealloflistFields, DataDeleteoflistFields, DataInsertatlistFields, DataItemoflistFields, DataLengthoflistFields, DataListcontentsFields, - DataReplaceitemoflistFields, EventBroadcastFields, IrOpcode, ProceduresCallNonwarpFields, - YieldMode, + DataReplaceitemoflistFields, EventBroadcastAndWaitFields, EventBroadcastFields, IrOpcode, + ProceduresCallNonwarpFields, YieldMode, }; use crate::ir::{RcList, ReturnType}; use crate::prelude::*; @@ -250,7 +250,9 @@ pub fn input_names(block_info: &BlockInfo, context: &StepContext) -> HQResult vec![], - BlockOpcode::event_broadcast => vec!["BROADCAST_INPUT"], + BlockOpcode::event_broadcast | BlockOpcode::event_broadcastandwait => { + vec!["BROADCAST_INPUT"] + } BlockOpcode::data_setvariableto | BlockOpcode::data_changevariableby => vec!["VALUE"], BlockOpcode::operator_random => vec!["FROM", "TO"], BlockOpcode::pen_setPenColorParamTo => vec!["COLOR_PARAM", "VALUE"], @@ -1028,6 +1030,66 @@ where .collect()) } +fn generate_exhaustive_string_comparison( + string_source: I, + instruction: F, + context: &StepContext, + project: &Weak, +) -> HQResult> +where + I: IntoIterator, + S: Into> + Clone, + F: Fn(Box) -> IrOpcode, +{ + let var = RcVar::new(IrType::String, VarVal::String("".into()))?; + Ok(vec![ + IrOpcode::hq_cast(HqCastFields(IrType::String)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(var.clone()), + local_write: RefCell::new(true), + }), + ] + .into_iter() + .chain( + string_source + .into_iter() + .try_fold( + Step::new_empty(project, false, Rc::clone(context.target()))?, + |branch_else, string| { + let branch_if = Step::new_rc( + None, + context.clone(), + vec![instruction(string.clone().into())], + project, + false, + )?; + Step::new_rc( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_text(HqTextFields(string.into())), + IrOpcode::operator_equals, // todo: this should be a case-sensitive comparison + IrOpcode::control_if_else(ControlIfElseFields { + branch_if, + branch_else, + }), + ], + project, + false, + ) + }, + )? + .opcodes() + .borrow() + .clone(), + ) + .collect()) +} + #[expect( clippy::too_many_lines, reason = "a big monolithic function is somewhat unavoidable here" @@ -1156,66 +1218,38 @@ fn from_normal_block( }; vec![IrOpcode::hq_text(HqTextFields(name))] } - BlockOpcode::event_broadcast => { - let var = RcVar::new(IrType::String, VarVal::String("".into()))?; - vec![ - IrOpcode::hq_cast(HqCastFields(IrType::String)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(var.clone()), - local_write: RefCell::new(true), - }), - ] - .into_iter() - .chain( - context - .project()? - .broadcasts() - .iter() - .try_fold( - Step::new_empty( - project, - false, - Rc::clone(context.target()), - )?, - |branch_else, broadcast_name| { - let branch_if = Step::new_rc( - None, - context.clone(), - vec![IrOpcode::event_broadcast( - EventBroadcastFields(broadcast_name.clone()), - )], - project, - false, - )?; - Step::new_rc( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_text(HqTextFields( - broadcast_name.clone(), - )), - IrOpcode::operator_equals, // todo: this should be a case-sensitive comparison - IrOpcode::control_if_else( - ControlIfElseFields { - branch_if, - branch_else, - }, - ), - ], - project, - false, - ) + BlockOpcode::event_broadcast => generate_exhaustive_string_comparison( + context.project()?.broadcasts().iter().cloned(), + |broadcast| IrOpcode::event_broadcast(EventBroadcastFields(broadcast)), + context, + project, + )?, + BlockOpcode::event_broadcastandwait => { + let poll_step = + Step::new_poll_waiting_threads(context.clone(), project)?; + should_break = true; + let next_step = generate_next_step( + true, + block_info, + blocks, + context, + final_next_blocks.clone(), + flags, + )?; + generate_exhaustive_string_comparison( + context.project()?.broadcasts().iter().cloned(), + |broadcast| { + IrOpcode::event_broadcast_and_wait( + EventBroadcastAndWaitFields { + broadcast, + poll_step: Rc::clone(&poll_step), + next_step: Rc::clone(&next_step), }, - )? - .opcodes() - .borrow() - .clone(), - ) - .collect() + ) + }, + context, + project, + )? } BlockOpcode::data_setvariableto => { let sb3::Field::ValueId(_val, maybe_id) = diff --git a/src/ir/step.rs b/src/ir/step.rs index cafd7c61..92ec7917 100644 --- a/src/ir/step.rs +++ b/src/ir/step.rs @@ -147,6 +147,25 @@ impl Step { ) } + pub fn new_poll_waiting_threads( + context: StepContext, + project: &Weak, + ) -> HQResult> { + Self::new_rc( + None, + context.clone(), + vec![ + IrOpcode::event_poll_waiting_threads, + IrOpcode::control_if_else(ControlIfElseFields { + branch_if: Self::new_rc(None, context.clone(), vec![], project, false)?, + branch_else: Self::new_terminating(context, project, false)?, + }), + ], + project, + true, + ) + } + pub fn opcodes_mut(&self) -> HQResult>> { self.opcodes .try_borrow_mut() diff --git a/src/wasm/func.rs b/src/wasm/func.rs index 75add1bb..08d3aba3 100644 --- a/src/wasm/func.rs +++ b/src/wasm/func.rs @@ -1,8 +1,8 @@ use alloc::collections::btree_map; use wasm_encoder::{ - self, AbstractHeapType, CodeSection, Function, FunctionSection, HeapType, - Instruction as WInstruction, RefType, ValType, + self, AbstractHeapType, CodeSection, FieldType, Function, FunctionSection, HeapType, + Instruction as WInstruction, RefType, StorageType, ValType, }; use wasm_gen::wasm; @@ -22,6 +22,7 @@ pub enum Instruction { LazyGlobalGet(u32), LazyGlobalSet(u32), LazyBroadcastSpawn(Box), + LazyBroadcastSpawnAndWait((Box, Rc, Rc, u32)), StaticFunctionCall(u32), } @@ -30,11 +31,14 @@ impl Instruction { &self, steps: &Rc, StepFunc>>>, events: &BTreeMap>, + types: &Rc, + threads_count_global: u32, + spawn_new_thread_func: u32, + spawn_thread_in_stack_func: u32, + threads_table: u32, imported_func_count: u32, static_func_count: u32, imported_global_count: u32, - threads_count_global: u32, - spawn_new_thread_func: u32, ) -> HQResult]>> { Ok(match self { Self::Immediate(instr) => Box::from([instr.clone()]), @@ -89,6 +93,87 @@ impl Instruction { ]) .collect() } + Self::LazyBroadcastSpawnAndWait((broadcast, poll_step, next_step, arr_local)) => { + let broadcast_indices = events + .get(&Event::Broadcast(broadcast.clone())) + .cloned() + .unwrap_or_default(); + + let i32_array_type = types.array(StorageType::Val(ValType::I32), true)?; + let thread_poll_struct = types.struct_(vec![FieldType { + element_type: StorageType::Val(ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(i32_array_type), + })), + mutable: false, + }])?; + + let poll_step_index: u32 = steps + .try_borrow()? + .get_index_of(poll_step) + .ok_or_else(|| make_hq_bug!("couldn't find poll_step in step map"))? + .try_into() + .map_err(|_| make_hq_bug!("poll_step index out of bounds"))?; + + let next_step_index: u32 = steps + .try_borrow()? + .get_index_of(next_step) + .ok_or_else(|| make_hq_bug!("couldn't find next_step in step map"))? + .try_into() + .map_err(|_| make_hq_bug!("next_step index out of bounds"))?; + + let broadcast_num = i32::try_from(broadcast_indices.len()) + .map_err(|_| make_hq_bug!("indices len out of bounds"))?; + + [ + WInstruction::I32Const(broadcast_num), + WInstruction::ArrayNewDefault(i32_array_type), + WInstruction::LocalSet(*arr_local), + ] + .into_iter() + .chain( + broadcast_indices + .iter() + .enumerate() + .map(|(j, &i)| { + // todo: should these should begin execution in the same step? + Ok([ + WInstruction::LocalGet(*arr_local), + WInstruction::I32Const( + j.try_into() + .map_err(|_| make_hq_bug!("index out of bounds"))?, + ), + WInstruction::TableSize(threads_table), + WInstruction::ArraySet(i32_array_type), + WInstruction::RefFunc(i + imported_func_count + static_func_count), + WInstruction::RefNull(HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Struct, + }), + WInstruction::Call(spawn_new_thread_func + imported_func_count), + ]) + }) + .collect::>>()? + .into_iter() + .flatten(), + ) + .chain([ + WInstruction::GlobalGet(threads_count_global + imported_global_count), + WInstruction::I32Const(broadcast_num), + WInstruction::I32Add, + WInstruction::GlobalSet(threads_count_global + imported_global_count), + WInstruction::RefFunc( + poll_step_index + imported_func_count + static_func_count, + ), + WInstruction::LocalGet(*arr_local), + WInstruction::StructNew(thread_poll_struct), + WInstruction::RefFunc( + next_step_index + imported_func_count + static_func_count, + ), + WInstruction::Call(spawn_thread_in_stack_func + imported_func_count), + ]) + .collect() + } Self::LazyStepIndex(step) => { let step_index: i32 = steps .try_borrow()? @@ -297,22 +382,28 @@ impl StepFunc { code: &mut CodeSection, steps: &Rc, Self>>>, events: &BTreeMap>, + types: &Rc, + threads_count_global: u32, + spawn_new_thread_func: u32, + spawn_thread_in_stack_func: u32, + threads_table: u32, imported_func_count: u32, static_func_count: u32, imported_global_count: u32, - threads_count_global: u32, - spawn_new_thread_func: u32, ) -> HQResult<()> { let mut func = Function::new_with_locals_types(self.locals.take()); for instruction in self.instructions().take() { for real_instruction in instruction.eval( steps, events, + types, + threads_count_global, + spawn_new_thread_func, + spawn_thread_in_stack_func, + threads_table, imported_func_count, static_func_count, imported_global_count, - threads_count_global, - spawn_new_thread_func, )? { func.instruction(&real_instruction); } diff --git a/src/wasm/project.rs b/src/wasm/project.rs index f9ebbe26..266546a2 100644 --- a/src/wasm/project.rs +++ b/src/wasm/project.rs @@ -164,11 +164,14 @@ impl WasmProject { &mut codes, self.steps(), &self.events, + self.registries().types(), + self.threads_count_global()?, + self.spawn_new_thread_func()?, + self.spawn_thread_in_stack_func()?, + self.threads_table_index()?, self.imported_func_count()?, self.static_func_count()?, self.imported_global_count()?, - self.threads_count_global()?, - self.spawn_new_thread_func()?, )?; } @@ -328,6 +331,16 @@ impl WasmProject { .register::() } + fn spawn_thread_in_stack_func(&self) -> HQResult + where + N: TryFrom, + >::Error: fmt::Debug, + { + self.registries() + .static_functions() + .register::() + } + fn threads_count_global(&self) -> HQResult where N: TryFrom, @@ -377,11 +390,14 @@ impl WasmProject { for real_instruction in instruction.eval( self.steps(), &self.events, + self.registries().types(), + self.threads_count_global()?, + self.spawn_new_thread_func()?, + self.spawn_thread_in_stack_func()?, + self.threads_table_index()?, self.imported_func_count()?, self.static_func_count()?, self.imported_global_count()?, - self.threads_count_global()?, - self.spawn_new_thread_func()?, )? { func.instruction(&real_instruction); } @@ -398,11 +414,14 @@ impl WasmProject { for real_instruction in instruction.eval( self.steps(), &self.events, + self.registries().types(), + self.threads_count_global()?, + self.spawn_new_thread_func()?, + self.spawn_thread_in_stack_func()?, + self.threads_table_index()?, self.imported_func_count()?, self.static_func_count()?, self.imported_global_count()?, - self.threads_count_global()?, - self.spawn_new_thread_func()?, )? { func.instruction(&real_instruction); } @@ -536,11 +555,14 @@ impl WasmProject { for real_instruction in instr.eval( self.steps(), &self.events, + self.registries().types(), + self.threads_count_global()?, + self.spawn_new_thread_func()?, + self.spawn_thread_in_stack_func()?, + self.threads_table_index()?, self.imported_func_count()?, self.static_func_count()?, self.imported_global_count()?, - self.threads_count_global()?, - self.spawn_new_thread_func()?, )? { tick_func.instruction(&real_instruction); } From 6e182c7d8ef5234e7eae95935c3d73ac90953797 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:58:48 +0000 Subject: [PATCH 06/14] provide fallback when broadcast isn't found --- src/ir/blocks.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ir/blocks.rs b/src/ir/blocks.rs index a2e0b5f2..9e0c5af9 100644 --- a/src/ir/blocks.rs +++ b/src/ir/blocks.rs @@ -1033,6 +1033,7 @@ where fn generate_exhaustive_string_comparison( string_source: I, instruction: F, + fallback: Vec, context: &StepContext, project: &Weak, ) -> HQResult> @@ -1054,7 +1055,7 @@ where string_source .into_iter() .try_fold( - Step::new_empty(project, false, Rc::clone(context.target()))?, + Step::new_rc(None, context.clone(), fallback, project, false)?, |branch_else, string| { let branch_if = Step::new_rc( None, @@ -1221,6 +1222,7 @@ fn from_normal_block( BlockOpcode::event_broadcast => generate_exhaustive_string_comparison( context.project()?.broadcasts().iter().cloned(), |broadcast| IrOpcode::event_broadcast(EventBroadcastFields(broadcast)), + vec![], context, project, )?, @@ -1247,6 +1249,9 @@ fn from_normal_block( }, ) }, + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(Rc::downgrade(&next_step)), + })], context, project, )? From d5c048c41b03e9434ca1b45c5abeda38fa50eae4 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sat, 3 Jan 2026 20:49:41 +0000 Subject: [PATCH 07/14] allow loading projects that weren't uploaded from the online editor --- playground/components/ProjectIdPlayer.vue | 24 +++++++++++++---------- playground/components/ProjectPlayer.vue | 2 ++ playground/lib/project-loader.js | 2 ++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/playground/components/ProjectIdPlayer.vue b/playground/components/ProjectIdPlayer.vue index 66ba4504..20c866c7 100644 --- a/playground/components/ProjectIdPlayer.vue +++ b/playground/components/ProjectIdPlayer.vue @@ -7,7 +7,7 @@ :title="title" :instructions="instructions" :description="description" - :zip="null" + :zip="zip" >