Source file src/simd/archsimd/converts_emulated_wasm.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.simd && wasm
     6  
     7  package archsimd
     8  
     9  // This file emulates the 2:1 narrowing integer conversions. Wasm has
    10  // saturating narrowing from signed integers (narrow_s and narrow_u) but not
    11  // from unsigned integers, and has no truncating narrowing.
    12  //
    13  // TODO: ExtendTo, TruncTo and SaturateTo are only provided for a 2:1 element
    14  // size ratio, which is what wasm does in one instruction (or emulates here),
    15  // but the spec defines every ratio. Add the 4:1 and 8:1 forms as sequences of
    16  // 2:1 conversions, and perhaps peephole chains of 2:1 conversions into those
    17  // sequences.
    18  
    19  // Byte permutations for truncation. An out-of-range index (0xff) produces a
    20  // zero.
    21  var (
    22  	trunc16To8Idx  = [16]uint8{0, 2, 4, 6, 8, 10, 12, 14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
    23  	trunc32To16Idx = [16]uint8{0, 1, 4, 5, 8, 9, 12, 13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
    24  	trunc64To32Idx = [16]uint8{0, 1, 2, 3, 8, 9, 10, 11, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
    25  )
    26  
    27  // truncBytes returns the bytes of x selected by idx.
    28  func truncBytes(x Uint8x16, idx *[16]uint8) Uint8x16 {
    29  	return x.BitsToInt8().LookupOrZero(LoadUint8x16Array(idx)).ToBits()
    30  }
    31  
    32  // TruncToInt8 converts each element of x to int8 by discarding its high
    33  // bits.
    34  //
    35  //	z[i] = int8(x[i])
    36  //
    37  // The results are stored in the low 8 elements of z.
    38  // The remaining elements of z are zero.
    39  //
    40  // Emulated
    41  func (x Int16x8) TruncToInt8() (z Int8x16) {
    42  	return x.ToBits().TruncToUint8().BitsToInt8()
    43  }
    44  
    45  // TruncToUint8 converts each element of x to uint8 by discarding its high
    46  // bits.
    47  //
    48  //	z[i] = uint8(x[i])
    49  //
    50  // The results are stored in the low 8 elements of z.
    51  // The remaining elements of z are zero.
    52  //
    53  // Emulated
    54  func (x Uint16x8) TruncToUint8() (z Uint8x16) {
    55  	return truncBytes(x.ReshapeToUint8s(), &trunc16To8Idx)
    56  }
    57  
    58  // TruncToInt16 converts each element of x to int16 by discarding its high
    59  // bits.
    60  //
    61  //	z[i] = int16(x[i])
    62  //
    63  // The results are stored in the low 4 elements of z.
    64  // The remaining elements of z are zero.
    65  //
    66  // Emulated
    67  func (x Int32x4) TruncToInt16() (z Int16x8) {
    68  	return x.ToBits().TruncToUint16().BitsToInt16()
    69  }
    70  
    71  // TruncToUint16 converts each element of x to uint16 by discarding its high
    72  // bits.
    73  //
    74  //	z[i] = uint16(x[i])
    75  //
    76  // The results are stored in the low 4 elements of z.
    77  // The remaining elements of z are zero.
    78  //
    79  // Emulated
    80  func (x Uint32x4) TruncToUint16() (z Uint16x8) {
    81  	return truncBytes(x.ReshapeToUint8s(), &trunc32To16Idx).ReshapeToUint16s()
    82  }
    83  
    84  // TruncToInt32 converts each element of x to int32 by discarding its high
    85  // bits.
    86  //
    87  //	z[i] = int32(x[i])
    88  //
    89  // The results are stored in the low 2 elements of z.
    90  // The remaining elements of z are zero.
    91  //
    92  // Emulated
    93  func (x Int64x2) TruncToInt32() (z Int32x4) {
    94  	return x.ToBits().TruncToUint32().BitsToInt32()
    95  }
    96  
    97  // TruncToUint32 converts each element of x to uint32 by discarding its high
    98  // bits.
    99  //
   100  //	z[i] = uint32(x[i])
   101  //
   102  // The results are stored in the low 2 elements of z.
   103  // The remaining elements of z are zero.
   104  //
   105  // Emulated
   106  func (x Uint64x2) TruncToUint32() (z Uint32x4) {
   107  	return truncBytes(x.ReshapeToUint8s(), &trunc64To32Idx).ReshapeToUint32s()
   108  }
   109  
   110  // SaturateToInt8 converts each element of x to int8, saturating values
   111  // that are outside the range of int8.
   112  //
   113  //	z[i] = sat(x[i])
   114  //
   115  // The results are stored in the low 8 elements of z.
   116  // The remaining elements of z are zero.
   117  //
   118  // Emulated
   119  func (x Int16x8) SaturateToInt8() (z Int8x16) {
   120  	var zero Int16x8
   121  	return x.concatSaturateToInt8(zero)
   122  }
   123  
   124  // SaturateToUint8 converts each element of x to uint8, saturating values
   125  // that are outside the range of uint8. Negative values become 0.
   126  //
   127  //	z[i] = sat(x[i])
   128  //
   129  // The results are stored in the low 8 elements of z.
   130  // The remaining elements of z are zero.
   131  //
   132  // Emulated
   133  func (x Int16x8) SaturateToUint8() (z Uint8x16) {
   134  	var zero Int16x8
   135  	return x.concatSaturateToUint8(zero)
   136  }
   137  
   138  // SaturateToUint8 converts each element of x to uint8, saturating values
   139  // that are outside the range of uint8.
   140  //
   141  //	z[i] = sat(x[i])
   142  //
   143  // The results are stored in the low 8 elements of z.
   144  // The remaining elements of z are zero.
   145  //
   146  // Emulated
   147  func (x Uint16x8) SaturateToUint8() (z Uint8x16) {
   148  	// After the Min, x is non-negative as an int16, so the signed-to-unsigned
   149  	// saturation doesn't change it.
   150  	var zero Int16x8
   151  	x = x.Min(BroadcastUint16x8(maxUint8))
   152  	return x.BitsToInt16().concatSaturateToUint8(zero)
   153  }
   154  
   155  // SaturateToInt16 converts each element of x to int16, saturating values
   156  // that are outside the range of int16.
   157  //
   158  //	z[i] = sat(x[i])
   159  //
   160  // The results are stored in the low 4 elements of z.
   161  // The remaining elements of z are zero.
   162  //
   163  // Emulated
   164  func (x Int32x4) SaturateToInt16() (z Int16x8) {
   165  	var zero Int32x4
   166  	return x.concatSaturateToInt16(zero)
   167  }
   168  
   169  // SaturateToUint16 converts each element of x to uint16, saturating values
   170  // that are outside the range of uint16. Negative values become 0.
   171  //
   172  //	z[i] = sat(x[i])
   173  //
   174  // The results are stored in the low 4 elements of z.
   175  // The remaining elements of z are zero.
   176  //
   177  // Emulated
   178  func (x Int32x4) SaturateToUint16() (z Uint16x8) {
   179  	var zero Int32x4
   180  	return x.concatSaturateToUint16(zero)
   181  }
   182  
   183  // SaturateToUint16 converts each element of x to uint16, saturating values
   184  // that are outside the range of uint16.
   185  //
   186  //	z[i] = sat(x[i])
   187  //
   188  // The results are stored in the low 4 elements of z.
   189  // The remaining elements of z are zero.
   190  //
   191  // Emulated
   192  func (x Uint32x4) SaturateToUint16() (z Uint16x8) {
   193  	// After the Min, x is non-negative as an int32, so the signed-to-unsigned
   194  	// saturation doesn't change it.
   195  	var zero Int32x4
   196  	x = x.Min(BroadcastUint32x4(maxUint16))
   197  	return x.BitsToInt32().concatSaturateToUint16(zero)
   198  }
   199  
   200  // SaturateToInt32 converts each element of x to int32, saturating values
   201  // that are outside the range of int32.
   202  //
   203  //	z[i] = sat(x[i])
   204  //
   205  // The results are stored in the low 2 elements of z.
   206  // The remaining elements of z are zero.
   207  //
   208  // Emulated
   209  func (x Int64x2) SaturateToInt32() (z Int32x4) {
   210  	hi, lo := BroadcastInt64x2(maxInt32), BroadcastInt64x2(minInt32)
   211  	x = hi.IfElse(x.Greater(hi), x)
   212  	x = lo.IfElse(x.Less(lo), x)
   213  	return x.TruncToInt32()
   214  }
   215  
   216  // SaturateToUint32 converts each element of x to uint32, saturating values
   217  // that are outside the range of uint32. Negative values become 0.
   218  //
   219  //	z[i] = sat(x[i])
   220  //
   221  // The results are stored in the low 2 elements of z.
   222  // The remaining elements of z are zero.
   223  //
   224  // Emulated
   225  func (x Int64x2) SaturateToUint32() (z Uint32x4) {
   226  	var zero Int64x2
   227  	hi := BroadcastInt64x2(maxUint32)
   228  	x = x.Masked(x.Greater(zero))
   229  	x = hi.IfElse(x.Greater(hi), x)
   230  	return x.ToBits().TruncToUint32()
   231  }
   232  
   233  // SaturateToUint32 converts each element of x to uint32, saturating values
   234  // that are outside the range of uint32.
   235  //
   236  //	z[i] = sat(x[i])
   237  //
   238  // The results are stored in the low 2 elements of z.
   239  // The remaining elements of z are zero.
   240  //
   241  // Emulated
   242  func (x Uint64x2) SaturateToUint32() (z Uint32x4) {
   243  	// x fits in a uint32 if its high 32 bits are zero.
   244  	var zero Uint64x2
   245  	hi := BroadcastUint64x2(maxUint32)
   246  	return x.IfElse(x.ShiftAllRight(32).Equal(zero), hi).TruncToUint32()
   247  }
   248  

View as plain text