lib/choir/src/backends/wasm/emission/function/instruction/cast.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const ir = @import("../../../../../core/root.zig");
3 const dialects = @import("../../../../../dialects/root.zig");
4 const emission = @import("../../root.zig");
5 const instruction = @import("root.zig");
6
7 const ScalarKind = dialects.arith.ScalarKind;
8
9 pub fn write(
10 comptime Out: type,
11 writer: *instruction.Writer(Out),
12 operation: *ir.Operation,
13 bitcast: bool,
14 ) emission.Error!void {
15 const operand = operation.getOperand(0) orelse return error.CodeGenFailed;
16 const result = operation.getResult(0) orelse return error.CodeGenFailed;
17 const source = emission.types.scalarKindForType(operand.type) orelse
18 return error.CodeGenFailed;
19 const target = emission.types.scalarKindForType(result.type) orelse
20 return error.CodeGenFailed;
21 try writer.writeValue(operand);
22 if (!scalarCastIsWasmNoop(source, target)) {
23 const opcode = if (bitcast)
24 try bitcastOpcode(source, target)
25 else
26 try castOpcode(source, target);
27 try writer.out.writeByte(opcode);
28 }
29 try writer.writeLocalSet(try writer.localFor(result));
30 }
31
32 fn bitcastOpcode(source: ScalarKind, target: ScalarKind) error{CodeGenFailed}!u8 {
33 return switch (source) {
34 .i32, .u32, .index, .bool => switch (target) {
35 .f32 => 0xbe,
36 else => error.CodeGenFailed,
37 },
38 .i64, .u64 => switch (target) {
39 .f64 => 0xbf,
40 else => error.CodeGenFailed,
41 },
42 .f32 => switch (target) {
43 .i32, .u32, .index => 0xbc,
44 else => error.CodeGenFailed,
45 },
46 .f64 => switch (target) {
47 .i64, .u64 => 0xbd,
48 else => error.CodeGenFailed,
49 },
50 else => error.CodeGenFailed,
51 };
52 }
53
54 fn castOpcode(source: ScalarKind, target: ScalarKind) error{CodeGenFailed}!u8 {
55 return switch (source) {
56 .i32, .index, .bool => switch (target) {
57 .i64 => 0xac,
58 .u64 => 0xad,
59 .f32 => 0xb2,
60 .f64 => 0xb7,
61 else => error.CodeGenFailed,
62 },
63 .u32 => switch (target) {
64 .i64, .u64 => 0xad,
65 .f32 => 0xb3,
66 .f64 => 0xb8,
67 else => error.CodeGenFailed,
68 },
69 .i64 => switch (target) {
70 .i32, .u32, .index => 0xa7,
71 .f32 => 0xb4,
72 .f64 => 0xb9,
73 else => error.CodeGenFailed,
74 },
75 .u64 => switch (target) {
76 .i32, .u32, .index => 0xa7,
77 .f32 => 0xb5,
78 .f64 => 0xba,
79 else => error.CodeGenFailed,
80 },
81 .f32 => switch (target) {
82 .f64 => 0xbb,
83 else => error.CodeGenFailed,
84 },
85 .f64 => switch (target) {
86 .f32 => 0xb6,
87 else => error.CodeGenFailed,
88 },
89 else => error.CodeGenFailed,
90 };
91 }
92
93 fn scalarCastIsWasmNoop(source: ScalarKind, target: ScalarKind) bool {
94 if (source == target) return true;
95 return switch (source) {
96 .i32, .u32, .index, .bool => switch (target) {
97 .i32, .u32, .index => true,
98 else => false,
99 },
100 .i64, .u64 => switch (target) {
101 .i64, .u64 => true,
102 else => false,
103 },
104 else => false,
105 };
106 }
107
108 test "wasm integer representation casts preserve exact no-op rules" {
109 try std.testing.expect(scalarCastIsWasmNoop(.index, .u32));
110 try std.testing.expect(scalarCastIsWasmNoop(.u32, .i32));
111 try std.testing.expect(scalarCastIsWasmNoop(.i64, .u64));
112 try std.testing.expect(scalarCastIsWasmNoop(.bool, .i32));
113 try std.testing.expect(!scalarCastIsWasmNoop(.i32, .bool));
114 try std.testing.expect(!scalarCastIsWasmNoop(.i32, .i64));
115 try std.testing.expect(!scalarCastIsWasmNoop(.f32, .i32));
116 }