@@ -10026,6 +10026,95 @@ export class Compiler extends DiagnosticEmitter {
1002610026 // === Specialized code generation ==============================================================
1002710027
1002810028 /** Makes a constant zero of the specified type. */
10029+ /** Checks if an expression evaluates to a zero value for the given type. */
10030+ shouldSkipZeroInit ( expr : ExpressionRef , type : Type ) : bool {
10031+ let module = this . module ;
10032+ // Try to evaluate the expression at compile time
10033+ let evaled = module . runExpression ( expr , ExpressionRunnerFlags . Default ) ;
10034+ if ( ! evaled ) return false ; // Can't evaluate at compile time
10035+
10036+ const evaledType = getExpressionType ( evaled ) ;
10037+ switch ( type . kind ) {
10038+ case TypeKind . Bool :
10039+ case TypeKind . I8 :
10040+ case TypeKind . I16 :
10041+ case TypeKind . I32 :
10042+ case TypeKind . U8 :
10043+ case TypeKind . U16 :
10044+ case TypeKind . U32 :
10045+ // All small integer types are represented as i32 in WebAssembly
10046+ // But we still check for safety and consistency
10047+ if ( evaledType == TypeRef . I32 ) {
10048+ return getConstValueI32 ( evaled ) === 0 ;
10049+ }
10050+ return false ;
10051+ case TypeKind . I64 :
10052+ case TypeKind . U64 :
10053+ case TypeKind . Isize :
10054+ case TypeKind . Usize :
10055+ // Only call getConstValueI64* if the expression is actually i64
10056+ if ( evaledType == TypeRef . I64 ) {
10057+ return getConstValueI64Low ( evaled ) === 0 && getConstValueI64High ( evaled ) === 0 ;
10058+ }
10059+ // For size types on 32-bit platforms, they might be i32
10060+ if ( evaledType == TypeRef . I32 && ( type . kind == TypeKind . Isize || type . kind == TypeKind . Usize ) ) {
10061+ return getConstValueI32 ( evaled ) === 0 ;
10062+ }
10063+ return false ;
10064+ case TypeKind . F32 :
10065+ if ( evaledType == TypeRef . F32 ) {
10066+ return getConstValueF32 ( evaled ) === 0.0 ;
10067+ }
10068+ return false ;
10069+ case TypeKind . F64 :
10070+ if ( evaledType == TypeRef . F64 ) {
10071+ return getConstValueF64 ( evaled ) === 0.0 ;
10072+ }
10073+ return false ;
10074+ default :
10075+ // For reference types, zero means null
10076+ if ( type . isReference && type . is ( TypeFlags . Nullable ) ) {
10077+ return getExpressionId ( evaled ) === ExpressionId . RefNull ;
10078+ }
10079+ return false ;
10080+ }
10081+ }
10082+
10083+ /** Checks if a field type can use the default zero-initialized memory value. */
10084+ canUseZeroDefault ( type : Type ) : bool {
10085+ switch ( type . kind ) {
10086+ default : assert ( false ) ;
10087+ case TypeKind . Bool :
10088+ case TypeKind . I8 :
10089+ case TypeKind . I16 :
10090+ case TypeKind . I32 :
10091+ case TypeKind . U8 :
10092+ case TypeKind . U16 :
10093+ case TypeKind . U32 :
10094+ case TypeKind . I64 :
10095+ case TypeKind . U64 :
10096+ case TypeKind . Isize :
10097+ case TypeKind . Usize :
10098+ case TypeKind . F32 :
10099+ case TypeKind . F64 :
10100+ case TypeKind . V128 :
10101+ return true ; // Value types default to zero in zero-initialized memory
10102+ case TypeKind . Func :
10103+ case TypeKind . Extern :
10104+ case TypeKind . Any :
10105+ case TypeKind . Eq :
10106+ case TypeKind . Struct :
10107+ case TypeKind . Array :
10108+ case TypeKind . String :
10109+ case TypeKind . StringviewWTF8 :
10110+ case TypeKind . StringviewWTF16 :
10111+ case TypeKind . StringviewIter :
10112+ case TypeKind . I31 :
10113+ // Reference types: only nullable refs can use zero (null) default
10114+ return type . is ( TypeFlags . Nullable ) ;
10115+ }
10116+ }
10117+
1002910118 makeZero ( type : Type ) : ExpressionRef {
1003010119 let module = this . module ;
1003110120 switch ( type . kind ) {
@@ -10372,6 +10461,7 @@ export class Compiler extends DiagnosticEmitter {
1037210461 let parameterIndex = fieldPrototype . parameterIndex ;
1037310462
1037410463 // Defer non-parameter fields until parameter fields are initialized
10464+ // Since non-parameter may depend on parameter fields
1037510465 if ( parameterIndex < 0 ) {
1037610466 if ( ! nonParameterFields ) nonParameterFields = new Array ( ) ;
1037710467 nonParameterFields . push ( property ) ;
@@ -10407,16 +10497,25 @@ export class Compiler extends DiagnosticEmitter {
1040710497 let initializerNode = fieldPrototype . initializerNode ;
1040810498 assert ( fieldPrototype . parameterIndex < 0 ) ;
1040910499 let setterInstance = assert ( field . setterInstance ) ;
10410- let expr = this . makeCallDirect ( setterInstance , [
10411- module . local_get ( thisLocalIndex , sizeTypeRef ) ,
10412- initializerNode // use initializer if present, otherwise initialize with zero
10413- ? this . compileExpression ( initializerNode , fieldType , Constraints . ConvImplicit )
10414- : this . makeZero ( fieldType )
10415- ] , field . identifierNode , true ) ;
10416- if ( this . currentType != Type . void ) { // in case
10417- expr = module . drop ( expr ) ;
10500+
10501+ if ( initializerNode ) {
10502+ // Explicit initializer
10503+ // Check if we need to initialize this field
10504+ const valueExpr : ExpressionRef = this . compileExpression ( initializerNode , fieldType , Constraints . ConvImplicit ) ;
10505+ if ( ! this . shouldSkipZeroInit ( valueExpr , fieldType ) ) {
10506+ let expr = this . makeCallDirect ( setterInstance , [
10507+ module . local_get ( thisLocalIndex , sizeTypeRef ) ,
10508+ valueExpr
10509+ ] , field . identifierNode , true ) ;
10510+ if ( this . currentType != Type . void ) { // in case
10511+ expr = module . drop ( expr ) ;
10512+ }
10513+ stmts . push ( expr ) ;
10514+ }
10515+ } else {
10516+ // No explicit initializer
10517+ assert ( this . canUseZeroDefault ( fieldType ) ) ;
1041810518 }
10419- stmts . push ( expr ) ;
1042010519 }
1042110520 }
1042210521
0 commit comments