P2 · Masked exponentiation¶
Compute values[i]^exponents[i] and clamp each result to 9.999999f with the course's simulated SIMD intrinsics.
1. Vectorize clamped exponentiation¶
clampedExpVector in main.cpp:
void clampedExpVector(float* values, int* exponents,
float* output, int N) {
__cs149_vec_float x;
__cs149_vec_int y;
__cs149_vec_float result;
__cs149_vec_int zero = _cs149_vset_int(0);
__cs149_vec_int one = _cs149_vset_int(1);
__cs149_vec_float ninef = _cs149_vset_float(9.999999f);
__cs149_vec_int count;
__cs149_mask maskEff, maskIsEqual, maskIsNotEqual;
for (int i=0; i<N; i+=VECTOR_WIDTH) {
maskEff = _cs149_init_ones(N-i);
maskIsEqual = _cs149_init_ones(0);
_cs149_vload_float(x, values+i, maskEff);
_cs149_vload_int(y, exponents+i, maskEff);
_cs149_veq_int(maskIsEqual, y, zero, maskEff);
_cs149_vset_float(result, 1.f, maskIsEqual);
maskIsNotEqual = _cs149_mask_not(maskIsEqual);
maskIsNotEqual = _cs149_mask_and(maskIsNotEqual, maskEff);
_cs149_vsub_int(count, y, one, maskIsNotEqual);
_cs149_vmove_float(result, x, maskIsNotEqual);
_cs149_vgt_int(maskIsNotEqual, count, zero, maskIsNotEqual);
while (_cs149_cntbits(maskIsNotEqual)) {
_cs149_vmult_float(result, result, x, maskIsNotEqual);
_cs149_vsub_int(count, count, one, maskIsNotEqual);
_cs149_vgt_int(maskIsNotEqual, count, zero, maskIsNotEqual);
}
_cs149_vgt_float(maskIsNotEqual, result, ninef, maskEff);
_cs149_vset_float(result, 9.999999f, maskIsNotEqual);
_cs149_vstore_float(output+i, result, maskEff);
}
}
1.1 Load a vector and mark valid lanes¶
Within a batch starting at i, lane j holds array element i + j. A zero mask bit preserves that lane's previous destination value.
maskEff = _cs149_init_ones(N-i) enables the first min(VECTOR_WIDTH, N-i) lanes. Loads and stores use this mask to keep the final partial vector within the array.
1.2 Translate the exponent cases into masks¶
maskIsEqual starts with all lanes disabled, then _cs149_veq_int enables valid lanes whose exponent is zero. Those lanes receive result = 1 and need no multiplication.
The other valid lanes are selected by ~maskIsEqual & maskEff. The AND is necessary because inverting a mask also turns on the unused lanes in a partial vector:
N = 6, width = 4, i = 4; lane 0 is on the left
Array index 4 5 - -
Example exponent 0 3 - -
maskEff 1 1 0 0
maskIsEqual 1 0 0 0
~maskIsEqual 0 1 1 1
~maskIsEqual & maskEff 0 1 0 0
Positive-exponent lanes begin with result = x and count = y - 1, since one factor of x is already present. The first count > 0 test excludes exponent-one lanes from the loop.
1.3 Follow the multiplication loop¶
maskIsNotEqual tracks lanes with count > 0. Only active lanes multiply and decrement; each comparison drops finished lanes. _cs149_cntbits keeps the loop running while any lane is active.
For bases [2, 2, 2, 2] and exponents [0, 1, 3, 5]:
Stage Mask used Result after this stage Remaining count
Initialize - 1 2 2 2 - 0 2 4
Multiply 1 0 0 1 1 1 2 4 4 - 0 1 3
Multiply 2 0 0 1 1 1 2 8 8 - 0 0 2
Multiply 3 0 0 0 1 1 2 8 16 - 0 0 1
Multiply 4 0 0 0 1 1 2 8 32 - 0 0 0
Exit 0 0 0 0
Finished results are preserved while the longest-running lane keeps the loop active.
1.4 Clamp and store¶
The final comparison uses maskEff to rebuild the mask for result > 9.999999f. After clamping, the example becomes [1, 2, 8, 9.999999]. The store uses maskEff to write all valid results, including exponent-zero and unclamped lanes.
1.5 Debugging and verification¶
The absVector warm-up exposed the tail-mask issue: inverting a predicate re-enabled invalid lanes. Intersecting the complement with maskEff fixed the N = 3, W = 4 case.
The first exponentiation loop used maskIsEqual for multiplication while testing maskIsNotEqual for continuation. Using the active mask consistently, testing count > 0 before entry, and adding the clamp fixed the failures.
The required function passes at widths 2, 4, 8, and 16, including partial vectors. At width 4, the original tests covered N = 1, 2, 3, 4, 5, 7, 16, 10000.
2. Compare vector utilization¶
For N = 10,000:
| Width | Vector instructions | Utilization |
|---|---|---|
| 2 | 167,727 | 77.6% |
| 4 | 97,075 | 70.5% |
| 8 | 52,877 | 66.7% |
| 16 | 27,592 | 65.0% |
A wider vector is more likely to contain a long-running lane. Finished lanes remain inactive while that lane continues, reducing utilization. More elements are processed per instruction, so total instruction count still falls.
3. Extra credit: array sum¶
Assume N is a multiple of VECTOR_WIDTH.
3.1 Accumulate and reduce¶
float arraySumVector(float* values, int N) {
__cs149_vec_float x;
__cs149_vec_float result;
// The simulator's interleave needs distinct source and destination vectors.
__cs149_vec_float temp;
__cs149_mask maskAll, maskEff, maskNotEff;
float ans = 0.f;
maskAll = _cs149_init_ones();
maskEff = _cs149_init_ones(VECTOR_WIDTH/2);
maskNotEff = _cs149_mask_not(maskEff);
_cs149_vload_float(x, values, maskAll);
_cs149_hadd_float(temp, x);
_cs149_interleave_float(x, temp);
_cs149_vmove_float(result, x, maskEff);
for (int i=VECTOR_WIDTH; i<N; i+=VECTOR_WIDTH) {
_cs149_vload_float(x, values+i, maskAll);
_cs149_hadd_float(temp, x);
_cs149_interleave_float(x, temp);
_cs149_vmove_float(result, x, maskNotEff);
_cs149_hadd_float(temp, result);
_cs149_interleave_float(result, temp);
}
for (int i=0; i<VECTOR_WIDTH/2; i++) {
ans += result.value[i];
}
return ans;
}
The lower half of result holds partial sums; the upper half receives the next chunk's pair sums. hadd combines adjacent lanes, and interleave packs the unique sums back into the lower half. The final scalar loop adds those lanes. For width W, the work is O(N/W + W).
3.2 Simulator limitation¶
The original code called interleave(x, x). The interface comments do not mention a restriction on using the same vector for input and output, but the simulator writes destination lanes before reading all source lanes. At width 4, [a, a, b, b] becomes [a, b, b, b] instead of [a, b, a, b]. A single chunk passes because only the intact lower half is used; later chunks read the corrupted upper half.
Writing each hadd result into temp gives interleave distinct input and output vectors. This preserves the original reduction algorithm and adds no vector instructions.
The corrected version passes at widths 2, 4, 8, and 16, covering one chunk, multiple chunks, and N = 10,000.