@@ -5,6 +5,182 @@ use crate::{GLOBAL_THIS_BINDING, RuntimeError, Value, operations};
55use super :: { ir:: Op , vm:: Vm , vm_bindings:: is_compiler_temporary} ;
66
77impl Vm < ' _ > {
8+ /// Drops only engine-internal mirrors before a compound string assignment.
9+ /// The following `Dup` + store bytecodes restore the binding immediately;
10+ /// any real JavaScript alias keeps the Rc shared and therefore immutable.
11+ pub ( super ) fn prepare_compound_string_reuse ( & mut self , expected : & std:: rc:: Rc < String > ) -> bool {
12+ if !matches ! ( self . bytecode. code. get( self . ip) , Some ( Op :: Dup ) ) {
13+ return false ;
14+ }
15+ match self . bytecode . code . get ( self . ip + 1 ) . cloned ( ) {
16+ Some ( Op :: AssignLocal ( slot) ) => self . detach_matching_local_string ( slot, expected) ,
17+ Some ( Op :: StoreGlobalStrict ( name) ) | Some ( Op :: StoreGlobalSloppy ( name) ) => {
18+ self . detach_matching_realm_string ( & name, expected)
19+ }
20+ Some ( Op :: StoreLocalOrGlobalSloppy { slot, name } ) => {
21+ self . detach_matching_local_string ( slot, expected)
22+ || self . detach_matching_realm_string ( & name, expected)
23+ }
24+ _ => false ,
25+ }
26+ }
27+
28+ fn detach_matching_local_string (
29+ & mut self ,
30+ slot : usize ,
31+ expected : & std:: rc:: Rc < String > ,
32+ ) -> bool {
33+ if self . direct_eval_with_stack && self . bytecode . local_is_from_env ( slot) {
34+ return false ;
35+ }
36+ let Some ( local_meta) = self . bytecode . locals . get ( slot) else {
37+ return false ;
38+ } ;
39+ let name = local_meta. name . clone ( ) ;
40+ if !local_meta. mutable
41+ || self . env . has_module_import ( & name)
42+ || self . env . is_immutable_lexical_binding ( & name)
43+ || self . env . is_immutable_function_name ( & name)
44+ || self . local_slot_targets_non_writable_global ( slot, & name)
45+ {
46+ return false ;
47+ }
48+ if self . slot_is_authoritative ( slot)
49+ && let Some ( Some ( local) ) = self . locals . get_mut ( slot)
50+ && matches ! ( local, Value :: String ( current) if std:: rc:: Rc :: ptr_eq( current, expected) )
51+ {
52+ * local = Value :: Undefined ;
53+ return true ;
54+ }
55+ self . detach_matching_shared_string ( slot, expected)
56+ }
57+
58+ /// Temporarily clears the engine's internal mirrors of one shared binding
59+ /// value so `Rc::unwrap_or_clone` can reclaim its allocation. Any actual
60+ /// JavaScript alias keeps an Rc alive and therefore still forces a copy,
61+ /// preserving string immutability. The completed assignment immediately
62+ /// restores the slot/cell/realm mirrors through the normal store path.
63+ fn detach_matching_shared_string (
64+ & mut self ,
65+ slot : usize ,
66+ expected : & std:: rc:: Rc < String > ,
67+ ) -> bool {
68+ if self . direct_eval_with_stack {
69+ return false ;
70+ }
71+ let Some ( cell) = self
72+ . local_upvalues
73+ . get ( slot)
74+ . and_then ( Option :: as_ref)
75+ . cloned ( )
76+ else {
77+ return false ;
78+ } ;
79+ let matches = cell. with_value ( |value| {
80+ matches ! ( value, Value :: String ( current) if std:: rc:: Rc :: ptr_eq( current, expected) )
81+ } ) ;
82+ if !matches {
83+ return false ;
84+ }
85+
86+ let name = self . bytecode . locals [ slot] . name . clone ( ) ;
87+ let realm_cell = self . env . is_realm_binding_cell ( & name, & cell) ;
88+ let global_this = if realm_cell {
89+ match self . realm . borrow ( ) . get ( GLOBAL_THIS_BINDING ) . cloned ( ) {
90+ Some ( Value :: Object ( global_this) ) => Some ( global_this) ,
91+ _ => None ,
92+ }
93+ } else {
94+ None
95+ } ;
96+ if let Some ( property) = global_this
97+ . as_ref ( )
98+ . and_then ( |global_this| global_this. own_property ( & name) )
99+ && ( property. is_accessor ( ) || !property. writable )
100+ {
101+ return false ;
102+ }
103+
104+ cell. set ( Value :: Undefined ) ;
105+ if let Some ( Some ( local) ) = self . locals . get_mut ( slot)
106+ && matches ! ( local, Value :: String ( current) if std:: rc:: Rc :: ptr_eq( current, expected) )
107+ {
108+ * local = Value :: Undefined ;
109+ }
110+ if realm_cell {
111+ if let Some ( binding) = self . realm . borrow_mut ( ) . get_mut ( & name)
112+ && matches ! ( binding, Value :: String ( current) if std:: rc:: Rc :: ptr_eq( current, expected) )
113+ {
114+ * binding = Value :: Undefined ;
115+ }
116+ if let Some ( global_this) = global_this
117+ && global_this
118+ . own_property ( & name)
119+ . is_some_and ( |property| {
120+ matches ! ( property. value, Value :: String ( current) if std:: rc:: Rc :: ptr_eq( & current, expected) )
121+ } )
122+ {
123+ global_this. set ( name, Value :: Undefined ) ;
124+ }
125+ }
126+ true
127+ }
128+
129+ fn detach_matching_realm_string ( & mut self , name : & str , expected : & std:: rc:: Rc < String > ) -> bool {
130+ if self . env . has_module_import ( name)
131+ || self . env . is_immutable_lexical_binding ( name)
132+ || self . env . is_immutable_function_name ( name)
133+ || self . env . has_local_binding ( name)
134+ || self
135+ . bytecode
136+ . local_slot ( name)
137+ . is_some_and ( |slot| self . locals . get ( slot) . is_some_and ( Option :: is_some) )
138+ {
139+ return false ;
140+ }
141+ let realm_matches = self . realm . borrow ( ) . get ( name) . is_some_and ( |value| {
142+ matches ! ( value, Value :: String ( current) if std:: rc:: Rc :: ptr_eq( current, expected) )
143+ } ) ;
144+ if !realm_matches {
145+ return false ;
146+ }
147+ let global_this = match self . realm . borrow ( ) . get ( GLOBAL_THIS_BINDING ) . cloned ( ) {
148+ Some ( Value :: Object ( global_this) ) => Some ( global_this) ,
149+ _ => None ,
150+ } ;
151+ if let Some ( property) = global_this
152+ . as_ref ( )
153+ . and_then ( |global_this| global_this. own_property ( name) )
154+ && ( property. is_accessor ( ) || !property. writable )
155+ {
156+ return false ;
157+ }
158+ let cell = self . env . realm_binding_cell ( name) ;
159+ if let Some ( cell) = & cell
160+ && !cell. with_value ( |value| {
161+ matches ! ( value, Value :: String ( current) if std:: rc:: Rc :: ptr_eq( current, expected) )
162+ } )
163+ {
164+ return false ;
165+ }
166+ if let Some ( cell) = cell {
167+ cell. set ( Value :: Undefined ) ;
168+ }
169+ if let Some ( binding) = self . realm . borrow_mut ( ) . get_mut ( name) {
170+ * binding = Value :: Undefined ;
171+ }
172+ if let Some ( global_this) = global_this
173+ && global_this
174+ . own_property ( name)
175+ . is_some_and ( |property| {
176+ matches ! ( property. value, Value :: String ( current) if std:: rc:: Rc :: ptr_eq( & current, expected) )
177+ } )
178+ {
179+ global_this. set ( name. to_owned ( ) , Value :: Undefined ) ;
180+ }
181+ true
182+ }
183+
8184 pub ( super ) fn run_string_append_op ( & mut self , op : Op ) -> Result < ( ) , RuntimeError > {
9185 let result = match op {
10186 Op :: AppendStringLiteralLocal { slot, value } => {
@@ -172,3 +348,16 @@ impl Vm<'_> {
172348 Ok ( result)
173349 }
174350}
351+
352+ pub ( super ) fn primitive_append_suffix ( value : Value ) -> Result < String , Value > {
353+ Ok ( match value {
354+ Value :: Number ( number) => crate :: number:: number_to_js_string ( number) ,
355+ Value :: BigInt ( value) => value. to_string ( ) ,
356+ Value :: String ( value) => std:: rc:: Rc :: unwrap_or_clone ( value) ,
357+ Value :: Boolean ( true ) => "true" . to_owned ( ) ,
358+ Value :: Boolean ( false ) => "false" . to_owned ( ) ,
359+ Value :: Null => "null" . to_owned ( ) ,
360+ Value :: Undefined => "undefined" . to_owned ( ) ,
361+ value => return Err ( value) ,
362+ } )
363+ }
0 commit comments