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 }