The VM only knows i64.add. Extend the dispatch with i64.sub, i64.mul, and i64.div_s. Each is its own else if branch with the same pop-pop-compute-push shape.
To test, set write_wat_manually = true and try this WAT:
i64.const 8
i64.const 2
i64.div_s
Then try i64.sub (with 5 then 3) and i64.mul.
if (stringStartsWith(line, "i64.const ")) {
stack[stack_pointer] = @as(i64, line[line.len - 1]) - '0';
stack_pointer += 1;
} else if (stringsEqual(line, "i64.add")) {
const y: i64 = stack[stack_pointer - 1];
const x: i64 = stack[stack_pointer - 2];
stack_pointer -= 2;
stack[stack_pointer] = x + y;
stack_pointer += 1;
}
// YOU: else if "i64.sub" -> pop, pop, push x - y
// YOU: else if "i64.mul" -> pop, pop, push x * y
// YOU: else if "i64.div_s" -> pop, pop, push @divTrunc(x, y)
else {
printString("error: unknown instruction\n");
return;
}
} else if (stringsEqual(line, "i64.sub")) {
const y: i64 = stack[stack_pointer - 1];
const x: i64 = stack[stack_pointer - 2];
stack_pointer -= 2;
stack[stack_pointer] = x - y;
stack_pointer += 1;
} else if (stringsEqual(line, "i64.mul")) {
const y: i64 = stack[stack_pointer - 1];
const x: i64 = stack[stack_pointer - 2];
stack_pointer -= 2;
stack[stack_pointer] = x * y;
stack_pointer += 1;
} else if (stringsEqual(line, "i64.div_s")) {
const y: i64 = stack[stack_pointer - 1];
const x: i64 = stack[stack_pointer - 2];
stack_pointer -= 2;
stack[stack_pointer] = @divTrunc(x, y);
stack_pointer += 1;
}
Order matters now. Walk through 5 - 3:
1. Source order is 5, then -, then 3.
2. Push 5. Stack: [5].
3. Push 3. Stack: [5, 3] -- 3 is on top.
4. Pop into y: gets 3 (top). Pop into x: gets 5.
5. Compute x - y = 5 - 3 = 2. Push. Stack: [2].
The order in the source flows straight to the order on the stack. That's why we name y first -- it's the one on top, the one pushed last, the right-hand operand. Same shape for i64.div_s: x / y where y is the divisor. Get this wrong once and 8 / 2 becomes 2 / 8 = 0.
The repetition is real -- four branches that almost do the same thing. Hold off the urge to factor it. Once we're solid on stack semantics, a refactor is cheap.