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