lib/choir/src/backends/wasm/emission/function/instruction/arithmetic/opcode.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const dialects = @import("../../../../../../dialects/root.zig");
3
4 const CmpPredicate = dialects.arith.CmpPredicate;
5 const ScalarKind = dialects.arith.ScalarKind;
6
7 pub const BinaryKind = enum {
8 add,
9 sub,
10 mul,
11 div,
12 rem,
13 band,
14 bor,
15 bxor,
16 shl,
17 shr,
18 ushr,
19 };
20
21 const CmpClass = enum {
22 eq,
23 ne,
24 lt,
25 le,
26 gt,
27 ge,
28 };
29
30 pub fn requireNoSubwordArithmetic(kind: ScalarKind) error{CodeGenFailed}!void {
31 return switch (kind) {
32 .i8, .i16, .u8, .u16, .f16, .bf16 => error.CodeGenFailed,
33 else => {},
34 };
35 }
36
37 pub fn binary(kind: BinaryKind, scalar: ScalarKind) error{CodeGenFailed}!u8 {
38 return switch (scalar) {
39 .i32, .index, .bool => switch (kind) {
40 .add => 0x6a,
41 .sub => 0x6b,
42 .mul => 0x6c,
43 .div => 0x6d,
44 .rem => 0x6f,
45 .band => 0x71,
46 .bor => 0x72,
47 .bxor => 0x73,
48 .shl => 0x74,
49 .shr => 0x75,
50 .ushr => 0x76,
51 },
52 .u32 => switch (kind) {
53 .add => 0x6a,
54 .sub => 0x6b,
55 .mul => 0x6c,
56 .div => 0x6e,
57 .rem => 0x70,
58 .band => 0x71,
59 .bor => 0x72,
60 .bxor => 0x73,
61 .shl => 0x74,
62 .shr, .ushr => 0x76,
63 },
64 .i64 => switch (kind) {
65 .add => 0x7c,
66 .sub => 0x7d,
67 .mul => 0x7e,
68 .div => 0x7f,
69 .rem => 0x81,
70 .band => 0x83,
71 .bor => 0x84,
72 .bxor => 0x85,
73 .shl => 0x86,
74 .shr => 0x87,
75 .ushr => 0x88,
76 },
77 .u64 => switch (kind) {
78 .add => 0x7c,
79 .sub => 0x7d,
80 .mul => 0x7e,
81 .div => 0x80,
82 .rem => 0x82,
83 .band => 0x83,
84 .bor => 0x84,
85 .bxor => 0x85,
86 .shl => 0x86,
87 .shr, .ushr => 0x88,
88 },
89 .f32 => switch (kind) {
90 .add => 0x92,
91 .sub => 0x93,
92 .mul => 0x94,
93 .div => 0x95,
94 else => error.CodeGenFailed,
95 },
96 .f64 => switch (kind) {
97 .add => 0xa0,
98 .sub => 0xa1,
99 .mul => 0xa2,
100 .div => 0xa3,
101 else => error.CodeGenFailed,
102 },
103 else => error.CodeGenFailed,
104 };
105 }
106
107 pub fn comparison(
108 predicate_value: CmpPredicate,
109 scalar: ScalarKind,
110 ) error{CodeGenFailed}!u8 {
111 return switch (scalar) {
112 .i32, .u32, .index, .bool => switch (integerPredicate(predicate_value)) {
113 .eq => 0x46,
114 .ne => 0x47,
115 .lt => if (integerPredicateIsUnsigned(predicate_value, scalar)) 0x49 else 0x48,
116 .le => if (integerPredicateIsUnsigned(predicate_value, scalar)) 0x4d else 0x4c,
117 .gt => if (integerPredicateIsUnsigned(predicate_value, scalar)) 0x4b else 0x4a,
118 .ge => if (integerPredicateIsUnsigned(predicate_value, scalar)) 0x4f else 0x4e,
119 },
120 .i64, .u64 => switch (integerPredicate(predicate_value)) {
121 .eq => 0x51,
122 .ne => 0x52,
123 .lt => if (integerPredicateIsUnsigned(predicate_value, scalar)) 0x54 else 0x53,
124 .le => if (integerPredicateIsUnsigned(predicate_value, scalar)) 0x58 else 0x57,
125 .gt => if (integerPredicateIsUnsigned(predicate_value, scalar)) 0x56 else 0x55,
126 .ge => if (integerPredicateIsUnsigned(predicate_value, scalar)) 0x5a else 0x59,
127 },
128 .f32 => switch (floatPredicate(predicate_value)) {
129 .eq => 0x5b,
130 .ne => 0x5c,
131 .lt => 0x5d,
132 .le => 0x5f,
133 .gt => 0x5e,
134 .ge => 0x60,
135 },
136 .f64 => switch (floatPredicate(predicate_value)) {
137 .eq => 0x61,
138 .ne => 0x62,
139 .lt => 0x63,
140 .le => 0x65,
141 .gt => 0x64,
142 .ge => 0x66,
143 },
144 else => error.CodeGenFailed,
145 };
146 }
147
148 fn integerPredicate(predicate_value: CmpPredicate) CmpClass {
149 return switch (predicate_value) {
150 .eq => .eq,
151 .ne => .ne,
152 .lt, .slt, .ult => .lt,
153 .le, .sle, .ule => .le,
154 .gt, .sgt, .ugt => .gt,
155 .ge, .sge, .uge => .ge,
156 };
157 }
158
159 fn integerPredicateIsUnsigned(predicate_value: CmpPredicate, scalar: ScalarKind) bool {
160 return switch (predicate_value) {
161 .ult, .ule, .ugt, .uge => true,
162 .slt, .sle, .sgt, .sge => false,
163 else => switch (scalar) {
164 .u32, .u64 => true,
165 else => false,
166 },
167 };
168 }
169
170 fn floatPredicate(predicate_value: CmpPredicate) CmpClass {
171 return switch (predicate_value) {
172 .eq => .eq,
173 .ne => .ne,
174 .lt, .slt, .ult => .lt,
175 .le, .sle, .ule => .le,
176 .gt, .sgt, .ugt => .gt,
177 .ge, .sge, .uge => .ge,
178 };
179 }
180
181 test "wasm comparison opcodes honor explicit integer signedness" {
182 try std.testing.expectEqual(@as(u8, 0x48), try comparison(.slt, .u32));
183 try std.testing.expectEqual(@as(u8, 0x49), try comparison(.ult, .i32));
184 try std.testing.expectEqual(@as(u8, 0x49), try comparison(.lt, .u32));
185 try std.testing.expectEqual(@as(u8, 0x48), try comparison(.lt, .i32));
186 try std.testing.expectEqual(@as(u8, 0x53), try comparison(.slt, .u64));
187 try std.testing.expectEqual(@as(u8, 0x54), try comparison(.ult, .i64));
188 }
189
190 test "wasm integer arithmetic opcodes preserve scalar signedness" {
191 try std.testing.expectEqual(@as(u8, 0x6d), try binary(.div, .i32));
192 try std.testing.expectEqual(@as(u8, 0x6e), try binary(.div, .u32));
193 try std.testing.expectEqual(@as(u8, 0x7f), try binary(.div, .i64));
194 try std.testing.expectEqual(@as(u8, 0x80), try binary(.div, .u64));
195 try std.testing.expectEqual(@as(u8, 0x75), try binary(.shr, .i32));
196 try std.testing.expectEqual(@as(u8, 0x76), try binary(.shr, .u32));
197 }