lib/chant/src/lower/expression/builtin.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const choir = @import("choir");
3 const ast = @import("../../ast/root.zig");
4 const lower_mod = @import("../root.zig");
5 const place_mod = @import("place.zig");
6 const types = @import("types.zig");
7
8 const ArithDialect = choir.dialects.ArithDialect;
9 const CmpPredicate = choir.dialects.arith.CmpPredicate;
10 const Error = lower_mod.Error;
11 const Lowerer = lower_mod.Lowerer;
12 const Place = lower_mod.memory.Place;
13 const Typed = types.Typed;
14
15 pub fn lower(lowerer: *Lowerer, call: ast.expr.Call, comptime recurse: types.LowerExpression) Error!?Typed {
16 if (try lowerCheckedInteger(lowerer, call, recurse)) |result| return result;
17 const signature = lowerer.functions.get(call.callee) orelse return null;
18 if (try lowerStdBit(lowerer, call, signature, recurse)) |result| return result;
19 return null;
20 }
21
22 const CheckedOp = enum {
23 add,
24 sub,
25 mul,
26 };
27
28 const CheckedValue = struct {
29 value: *choir.Value,
30 overflow: *choir.Value,
31 };
32
33 const IntegerSpec = struct {
34 bits: u16,
35 is_unsigned: bool,
36 };
37
38 fn lowerCheckedInteger(lowerer: *Lowerer, call: ast.expr.Call, comptime recurse: types.LowerExpression) Error!?Typed {
39 const op = checkedOp(call.callee) orelse return null;
40 if (call.arguments.len != 3) return error.UnsupportedConstruct;
41
42 const result_place = try checkedResultPlace(lowerer, call.arguments[2], recurse);
43 const spec = integerSpec(result_place.c_type) orelse return error.UnsupportedType;
44 const lhs = try recurse(lowerer, call.arguments[0]);
45 const rhs = try recurse(lowerer, call.arguments[1]);
46 if (!lower_mod.convert.sameScalar(lhs.c_type, result_place.c_type)) return error.UnsupportedType;
47 if (!lower_mod.convert.sameScalar(rhs.c_type, result_place.c_type)) return error.UnsupportedType;
48
49 const checked = if (spec.is_unsigned)
50 try lowerUnsignedChecked(lowerer, op, result_place.c_type, lhs.value, rhs.value)
51 else
52 try lowerSignedChecked(lowerer, op, result_place.c_type, spec, lhs.value, rhs.value);
53
54 try lower_mod.memory.storeElement(lowerer, result_place, checked.value);
55 return try finishBool(lowerer, checked.overflow);
56 }
57
58 fn checkedResultPlace(lowerer: *Lowerer, expr: *ast.Expr, comptime recurse: types.LowerExpression) Error!Place {
59 if (expr.* == .unary and expr.unary.op == .address_of) {
60 return place_mod.lowerPlaceOrSlot(lowerer, expr.unary.operand, recurse);
61 }
62
63 const pointer = try recurse(lowerer, expr);
64 if (!ast.types.isPointerLike(pointer.c_type)) return error.UnsupportedConstruct;
65 const element = ast.types.element(pointer.c_type) orelse return error.UnsupportedType;
66 if (!ast.types.isInteger(element)) return error.UnsupportedType;
67 const zero = try lower_mod.emit.indexConstant(lowerer, 0);
68 return .{ .memref = pointer.value, .index = zero, .c_type = element };
69 }
70
71 fn lowerUnsignedChecked(
72 lowerer: *Lowerer,
73 op: CheckedOp,
74 c_type: *const ast.Type,
75 lhs: *choir.Value,
76 rhs: *choir.Value,
77 ) Error!CheckedValue {
78 return switch (op) {
79 .add => blk: {
80 const sum = try emitBinary(lowerer, ArithDialect.AddOp, lhs, rhs);
81 const overflow = try emitCmp(lowerer, .ult, sum, lhs);
82 break :blk .{ .value = sum, .overflow = overflow };
83 },
84 .sub => blk: {
85 const diff = try emitBinary(lowerer, ArithDialect.SubOp, lhs, rhs);
86 const overflow = try emitCmp(lowerer, .ult, lhs, rhs);
87 break :blk .{ .value = diff, .overflow = overflow };
88 },
89 .mul => blk: {
90 const product = try emitBinary(lowerer, ArithDialect.MulOp, lhs, rhs);
91 const high = try emitBinary(lowerer, ArithDialect.UmulhiOp, lhs, rhs);
92 const zero = try integerConstant(lowerer, c_type, 0);
93 const overflow = try emitCmp(lowerer, .ne, high, zero);
94 break :blk .{ .value = product, .overflow = overflow };
95 },
96 };
97 }
98
99 fn lowerSignedChecked(
100 lowerer: *Lowerer,
101 op: CheckedOp,
102 c_type: *const ast.Type,
103 spec: IntegerSpec,
104 lhs: *choir.Value,
105 rhs: *choir.Value,
106 ) Error!CheckedValue {
107 return switch (op) {
108 .add => blk: {
109 const sum = try emitBinary(lowerer, ArithDialect.AddOp, lhs, rhs);
110 const sum_xor_lhs = try emitBinary(lowerer, ArithDialect.XorOp, sum, lhs);
111 const sum_xor_rhs = try emitBinary(lowerer, ArithDialect.XorOp, sum, rhs);
112 const sign = try emitBinary(lowerer, ArithDialect.AndOp, sum_xor_lhs, sum_xor_rhs);
113 const overflow = try signedNegative(lowerer, c_type, sign);
114 break :blk .{ .value = sum, .overflow = overflow };
115 },
116 .sub => blk: {
117 const diff = try emitBinary(lowerer, ArithDialect.SubOp, lhs, rhs);
118 const lhs_xor_rhs = try emitBinary(lowerer, ArithDialect.XorOp, lhs, rhs);
119 const lhs_xor_diff = try emitBinary(lowerer, ArithDialect.XorOp, lhs, diff);
120 const sign = try emitBinary(lowerer, ArithDialect.AndOp, lhs_xor_rhs, lhs_xor_diff);
121 const overflow = try signedNegative(lowerer, c_type, sign);
122 break :blk .{ .value = diff, .overflow = overflow };
123 },
124 .mul => try lowerSignedMulChecked(lowerer, c_type, spec, lhs, rhs),
125 };
126 }
127
128 fn lowerSignedMulChecked(
129 lowerer: *Lowerer,
130 c_type: *const ast.Type,
131 spec: IntegerSpec,
132 lhs: *choir.Value,
133 rhs: *choir.Value,
134 ) Error!CheckedValue {
135 if (spec.bits > 32) return error.UnsupportedConstruct;
136 const product = try emitBinary(lowerer, ArithDialect.MulOp, lhs, rhs);
137 const wide_lhs = try lower_mod.convert.convert(lowerer, lhs, c_type, &ast.types.long_type);
138 const wide_rhs = try lower_mod.convert.convert(lowerer, rhs, c_type, &ast.types.long_type);
139 const wide_product = try emitBinary(lowerer, ArithDialect.MulOp, wide_lhs, wide_rhs);
140 const min = try integerConstant(lowerer, &ast.types.long_type, signedMin(spec.bits));
141 const max = try integerConstant(lowerer, &ast.types.long_type, signedMax(spec.bits));
142 const below = try emitCmp(lowerer, .lt, wide_product, min);
143 const above = try emitCmp(lowerer, .gt, wide_product, max);
144 const overflow = try emitBoolOr(lowerer, below, above);
145 return .{ .value = product, .overflow = overflow };
146 }
147
148 fn emitBinary(
149 lowerer: *Lowerer,
150 comptime Op: type,
151 lhs: *choir.Value,
152 rhs: *choir.Value,
153 ) Error!*choir.Value {
154 const op = Op.create(lowerer.ctx, lowerer.loc, lhs, rhs) catch return error.OutOfMemory;
155 try lower_mod.emit.append(lowerer, op.op);
156 var mutable = op;
157 return mutable.getResult();
158 }
159
160 fn emitCmp(lowerer: *Lowerer, predicate: CmpPredicate, lhs: *choir.Value, rhs: *choir.Value) Error!*choir.Value {
161 const cmp = ArithDialect.CmpOp.create(lowerer.ctx, lowerer.loc, predicate, lhs, rhs) catch return error.OutOfMemory;
162 try lower_mod.emit.append(lowerer, cmp.op);
163 var mutable = cmp;
164 return mutable.getResult();
165 }
166
167 fn signedNegative(lowerer: *Lowerer, c_type: *const ast.Type, value: *choir.Value) Error!*choir.Value {
168 const zero = try integerConstant(lowerer, c_type, 0);
169 return emitCmp(lowerer, .lt, value, zero);
170 }
171
172 fn emitBoolOr(lowerer: *Lowerer, lhs: *choir.Value, rhs: *choir.Value) Error!*choir.Value {
173 const lhs_int = try boolToUint(lowerer, lhs);
174 const rhs_int = try boolToUint(lowerer, rhs);
175 const combined = try emitBinary(lowerer, ArithDialect.OrOp, lhs_int, rhs_int);
176 const zero = try integerConstant(lowerer, &ast.types.uint_type, 0);
177 return emitCmp(lowerer, .ne, combined, zero);
178 }
179
180 fn finishBool(lowerer: *Lowerer, condition: *choir.Value) Error!Typed {
181 const value = try boolToUint(lowerer, condition);
182 return .{ .value = value, .c_type = &ast.types.uint_type };
183 }
184
185 fn boolToUint(lowerer: *Lowerer, condition: *choir.Value) Error!*choir.Value {
186 const true_value = try integerConstant(lowerer, &ast.types.uint_type, 1);
187 const false_value = try integerConstant(lowerer, &ast.types.uint_type, 0);
188 const select = ArithDialect.SelectOp.create(lowerer.ctx, lowerer.loc, condition, true_value, false_value) catch return error.OutOfMemory;
189 try lower_mod.emit.append(lowerer, select.op);
190 var mutable = select;
191 return mutable.getResult();
192 }
193
194 fn integerConstant(lowerer: *Lowerer, c_type: *const ast.Type, value: i64) Error!*choir.Value {
195 const result_type = try lower_mod.convert.scalarType(lowerer, c_type);
196 const constant = ArithDialect.ConstantOp.createInt(lowerer.ctx, lowerer.loc, result_type, value) catch return error.OutOfMemory;
197 try lower_mod.emit.append(lowerer, constant.op);
198 var mutable = constant;
199 return mutable.getResult();
200 }
201
202 fn signedMin(bits: u16) i64 {
203 return -(@as(i64, 1) << @intCast(bits - 1));
204 }
205
206 fn signedMax(bits: u16) i64 {
207 return (@as(i64, 1) << @intCast(bits - 1)) - 1;
208 }
209
210 fn integerSpec(c_type: *const ast.Type) ?IntegerSpec {
211 return switch (c_type.kind) {
212 .char_type => .{ .bits = 8, .is_unsigned = c_type.is_unsigned },
213 .short_type => .{ .bits = 16, .is_unsigned = c_type.is_unsigned },
214 .int_type => .{ .bits = 32, .is_unsigned = c_type.is_unsigned },
215 .long_type => .{ .bits = 64, .is_unsigned = c_type.is_unsigned },
216 else => null,
217 };
218 }
219
220 fn checkedOp(name: []const u8) ?CheckedOp {
221 if (std.mem.eql(u8, name, "__builtin_add_overflow")) return .add;
222 if (std.mem.eql(u8, name, "__builtin_sub_overflow")) return .sub;
223 if (std.mem.eql(u8, name, "__builtin_mul_overflow")) return .mul;
224 return null;
225 }
226
227 const StdBit = struct {
228 kind: Kind,
229 argument: *const ast.Type,
230
231 const Kind = enum {
232 leading_zeros,
233 leading_ones,
234 trailing_zeros,
235 trailing_ones,
236 first_leading_zero,
237 first_leading_one,
238 first_trailing_zero,
239 first_trailing_one,
240 count_ones,
241 count_zeros,
242 has_single_bit,
243 bit_width,
244 bit_floor,
245 bit_ceil,
246 };
247 };
248
249 fn lowerStdBit(lowerer: *Lowerer, call: ast.expr.Call, signature: *const ast.Type, comptime recurse: types.LowerExpression) Error!?Typed {
250 const function = stdBitFunction(call.callee) orelse return null;
251 if (signature.params.len != 1 or call.arguments.len != 1) return error.UnsupportedConstruct;
252 const return_c_type = signature.child orelse return error.UnsupportedType;
253 if (!stdBitReturnMatches(function, return_c_type)) return error.UnsupportedType;
254 if (!lower_mod.convert.sameScalar(signature.params[0].type, function.argument)) return error.UnsupportedType;
255
256 const lowered = try recurse(lowerer, call.arguments[0]);
257 const argument = try lower_mod.convert.convert(lowerer, lowered.value, lowered.c_type, function.argument);
258 return switch (function.kind) {
259 .leading_zeros => try finishCountResult(lowerer, try emitLeadingZeros(lowerer, argument, function.argument), function.argument, return_c_type),
260 .leading_ones => try lowerStdBitLeadingOnes(lowerer, argument, function.argument, return_c_type),
261 .trailing_zeros => try finishCountResult(lowerer, try emitTrailingZeros(lowerer, argument, function.argument), function.argument, return_c_type),
262 .trailing_ones => try lowerStdBitTrailingOnes(lowerer, argument, function.argument, return_c_type),
263 .first_leading_zero => try lowerStdBitFirstLeadingZero(lowerer, argument, function.argument, return_c_type),
264 .first_leading_one => try lowerStdBitFirstLeadingOne(lowerer, argument, function.argument, return_c_type),
265 .first_trailing_zero => try lowerStdBitFirstTrailingZero(lowerer, argument, function.argument, return_c_type),
266 .first_trailing_one => try lowerStdBitFirstTrailingOne(lowerer, argument, function.argument, return_c_type),
267 .count_ones => try finishCountResult(lowerer, try emitPopcount(lowerer, argument), function.argument, return_c_type),
268 .count_zeros => try lowerStdBitCountZeros(lowerer, argument, function.argument, return_c_type),
269 .has_single_bit => try lowerStdBitHasSingleBit(lowerer, argument, function.argument),
270 .bit_width => try finishCountResult(lowerer, try emitBitWidth(lowerer, argument, function.argument), function.argument, return_c_type),
271 .bit_floor => try lowerStdBitFloor(lowerer, argument, function.argument),
272 .bit_ceil => try lowerStdBitCeil(lowerer, argument, function.argument),
273 };
274 }
275
276 fn lowerStdBitLeadingOnes(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type, return_c_type: *const ast.Type) Error!Typed {
277 const inverted = try emitNot(lowerer, argument);
278 return finishCountResult(lowerer, try emitLeadingZeros(lowerer, inverted, argument_type), argument_type, return_c_type);
279 }
280
281 fn lowerStdBitTrailingOnes(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type, return_c_type: *const ast.Type) Error!Typed {
282 const inverted = try emitNot(lowerer, argument);
283 return finishCountResult(lowerer, try emitTrailingZeros(lowerer, inverted, argument_type), argument_type, return_c_type);
284 }
285
286 fn lowerStdBitFirstLeadingZero(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type, return_c_type: *const ast.Type) Error!Typed {
287 const inverted = try emitNot(lowerer, argument);
288 const zeros = try emitLeadingZeros(lowerer, inverted, argument_type);
289 return finishCountResult(lowerer, try emitFirstBitPosition(lowerer, zeros, argument_type), argument_type, return_c_type);
290 }
291
292 fn lowerStdBitFirstLeadingOne(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type, return_c_type: *const ast.Type) Error!Typed {
293 const zeros = try emitLeadingZeros(lowerer, argument, argument_type);
294 return finishCountResult(lowerer, try emitFirstBitPosition(lowerer, zeros, argument_type), argument_type, return_c_type);
295 }
296
297 fn lowerStdBitFirstTrailingZero(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type, return_c_type: *const ast.Type) Error!Typed {
298 const inverted = try emitNot(lowerer, argument);
299 const zeros = try emitTrailingZeros(lowerer, inverted, argument_type);
300 return finishCountResult(lowerer, try emitFirstBitPosition(lowerer, zeros, argument_type), argument_type, return_c_type);
301 }
302
303 fn lowerStdBitFirstTrailingOne(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type, return_c_type: *const ast.Type) Error!Typed {
304 const zeros = try emitTrailingZeros(lowerer, argument, argument_type);
305 return finishCountResult(lowerer, try emitFirstBitPosition(lowerer, zeros, argument_type), argument_type, return_c_type);
306 }
307
308 fn lowerStdBitCountZeros(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type, return_c_type: *const ast.Type) Error!Typed {
309 const popcount = try emitPopcount(lowerer, argument);
310 const width = try bitWidthConstant(lowerer, argument_type);
311 const zeros = try emitBinary(lowerer, ArithDialect.SubOp, width, popcount);
312 return finishCountResult(lowerer, zeros, argument_type, return_c_type);
313 }
314
315 fn emitPopcount(lowerer: *Lowerer, argument: *choir.Value) Error!*choir.Value {
316 const popcount = ArithDialect.PopCountOp.create(lowerer.ctx, lowerer.loc, argument) catch return error.OutOfMemory;
317 try lower_mod.emit.append(lowerer, popcount.op);
318 var mutable = popcount;
319 return mutable.getResult();
320 }
321
322 fn emitBitWidth(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type) Error!*choir.Value {
323 return emitPopcount(lowerer, try emitFillDown(lowerer, argument, argument_type));
324 }
325
326 fn emitLeadingZeros(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type) Error!*choir.Value {
327 const width = try bitWidthConstant(lowerer, argument_type);
328 const used = try emitBitWidth(lowerer, argument, argument_type);
329 return emitBinary(lowerer, ArithDialect.SubOp, width, used);
330 }
331
332 fn emitTrailingZeros(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type) Error!*choir.Value {
333 const zero = try integerConstant(lowerer, argument_type, 0);
334 const negated = try emitBinary(lowerer, ArithDialect.SubOp, zero, argument);
335 const low_bit = try emitBinary(lowerer, ArithDialect.AndOp, argument, negated);
336 const one = try integerConstant(lowerer, argument_type, 1);
337 const mask = try emitBinary(lowerer, ArithDialect.SubOp, low_bit, one);
338 return emitPopcount(lowerer, mask);
339 }
340
341 fn emitFirstBitPosition(lowerer: *Lowerer, zero_count: *choir.Value, argument_type: *const ast.Type) Error!*choir.Value {
342 const width = try bitWidthConstant(lowerer, argument_type);
343 const found = try emitCmp(lowerer, .ne, zero_count, width);
344 const one = try integerConstant(lowerer, argument_type, 1);
345 const position = try emitBinary(lowerer, ArithDialect.AddOp, zero_count, one);
346 const zero = try integerConstant(lowerer, argument_type, 0);
347 return emitSelect(lowerer, found, position, zero);
348 }
349
350 fn emitFillDown(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type) Error!*choir.Value {
351 const bits = try bitWidth(argument_type);
352 var filled = argument;
353 var shift: u16 = 1;
354 while (shift < bits) : (shift *= 2) {
355 const amount = try integerConstant(lowerer, argument_type, shift);
356 const shifted = try emitBinary(lowerer, ArithDialect.UshrOp, filled, amount);
357 filled = try emitBinary(lowerer, ArithDialect.OrOp, filled, shifted);
358 }
359 return filled;
360 }
361
362 fn lowerStdBitFloor(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type) Error!Typed {
363 const zero = try integerConstant(lowerer, argument_type, 0);
364 const one = try integerConstant(lowerer, argument_type, 1);
365 const has_value = try emitCmp(lowerer, .ne, argument, zero);
366 const width = try emitBitWidth(lowerer, argument, argument_type);
367 const safe_width = try emitSelect(lowerer, has_value, width, one);
368 const shift = try emitBinary(lowerer, ArithDialect.SubOp, safe_width, one);
369 const candidate = try emitBinary(lowerer, ArithDialect.ShlOp, one, shift);
370 const result = try emitSelect(lowerer, has_value, candidate, zero);
371 return .{ .value = result, .c_type = argument_type };
372 }
373
374 fn lowerStdBitCeil(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type) Error!Typed {
375 const one = try integerConstant(lowerer, argument_type, 1);
376 const le_one = try emitCmp(lowerer, .ule, argument, one);
377 const minus_one = try emitBinary(lowerer, ArithDialect.SubOp, argument, one);
378 const safe_input = try emitSelect(lowerer, le_one, one, minus_one);
379 const width = try emitBitWidth(lowerer, safe_input, argument_type);
380 const candidate = try emitBinary(lowerer, ArithDialect.ShlOp, one, width);
381 const result = try emitSelect(lowerer, le_one, one, candidate);
382 return .{ .value = result, .c_type = argument_type };
383 }
384
385 fn lowerStdBitHasSingleBit(lowerer: *Lowerer, argument: *choir.Value, argument_type: *const ast.Type) Error!Typed {
386 const popcount = try emitPopcount(lowerer, argument);
387 const one = try integerConstant(lowerer, argument_type, 1);
388 const cmp = try emitCmp(lowerer, .eq, popcount, one);
389 return finishBool(lowerer, cmp);
390 }
391
392 fn emitNot(lowerer: *Lowerer, argument: *choir.Value) Error!*choir.Value {
393 const not = ArithDialect.NotOp.create(lowerer.ctx, lowerer.loc, argument) catch return error.OutOfMemory;
394 try lower_mod.emit.append(lowerer, not.op);
395 var mutable = not;
396 return mutable.getResult();
397 }
398
399 fn emitSelect(lowerer: *Lowerer, condition: *choir.Value, true_value: *choir.Value, false_value: *choir.Value) Error!*choir.Value {
400 const select = ArithDialect.SelectOp.create(lowerer.ctx, lowerer.loc, condition, true_value, false_value) catch return error.OutOfMemory;
401 try lower_mod.emit.append(lowerer, select.op);
402 var mutable = select;
403 return mutable.getResult();
404 }
405
406 fn bitWidthConstant(lowerer: *Lowerer, c_type: *const ast.Type) Error!*choir.Value {
407 return integerConstant(lowerer, c_type, try bitWidth(c_type));
408 }
409
410 fn bitWidth(c_type: *const ast.Type) Error!u16 {
411 return @intCast((ast.types.byteSize(c_type) orelse return error.UnsupportedType) * 8);
412 }
413
414 fn finishCountResult(lowerer: *Lowerer, value: *choir.Value, from: *const ast.Type, return_c_type: *const ast.Type) Error!Typed {
415 const result = try lower_mod.convert.convert(lowerer, value, from, return_c_type);
416 return .{ .value = result, .c_type = return_c_type };
417 }
418
419 fn stdBitReturnMatches(function: StdBit, c_type: *const ast.Type) bool {
420 return switch (function.kind) {
421 .leading_zeros,
422 .leading_ones,
423 .trailing_zeros,
424 .trailing_ones,
425 .first_leading_zero,
426 .first_leading_one,
427 .first_trailing_zero,
428 .first_trailing_one,
429 .count_ones,
430 .count_zeros,
431 .bit_width,
432 => lower_mod.convert.sameScalar(c_type, &ast.types.uint_type),
433 .has_single_bit => lower_mod.convert.sameScalar(c_type, &ast.types.uchar_type),
434 .bit_floor, .bit_ceil => lower_mod.convert.sameScalar(c_type, function.argument),
435 };
436 }
437
438 fn stdBitFunction(name: []const u8) ?StdBit {
439 if (stdBitArgument(name, "stdc_leading_zeros")) |argument| return .{ .kind = .leading_zeros, .argument = argument };
440 if (stdBitArgument(name, "stdc_leading_ones")) |argument| return .{ .kind = .leading_ones, .argument = argument };
441 if (stdBitArgument(name, "stdc_trailing_zeros")) |argument| return .{ .kind = .trailing_zeros, .argument = argument };
442 if (stdBitArgument(name, "stdc_trailing_ones")) |argument| return .{ .kind = .trailing_ones, .argument = argument };
443 if (stdBitArgument(name, "stdc_first_leading_zero")) |argument| return .{ .kind = .first_leading_zero, .argument = argument };
444 if (stdBitArgument(name, "stdc_first_leading_one")) |argument| return .{ .kind = .first_leading_one, .argument = argument };
445 if (stdBitArgument(name, "stdc_first_trailing_zero")) |argument| return .{ .kind = .first_trailing_zero, .argument = argument };
446 if (stdBitArgument(name, "stdc_first_trailing_one")) |argument| return .{ .kind = .first_trailing_one, .argument = argument };
447 if (stdBitArgument(name, "stdc_count_ones")) |argument| return .{ .kind = .count_ones, .argument = argument };
448 if (stdBitArgument(name, "stdc_count_zeros")) |argument| return .{ .kind = .count_zeros, .argument = argument };
449 if (stdBitArgument(name, "stdc_has_single_bit")) |argument| return .{ .kind = .has_single_bit, .argument = argument };
450 if (stdBitArgument(name, "stdc_bit_width")) |argument| return .{ .kind = .bit_width, .argument = argument };
451 if (stdBitArgument(name, "stdc_bit_floor")) |argument| return .{ .kind = .bit_floor, .argument = argument };
452 if (stdBitArgument(name, "stdc_bit_ceil")) |argument| return .{ .kind = .bit_ceil, .argument = argument };
453 return null;
454 }
455
456 fn stdBitArgument(name: []const u8, stem: []const u8) ?*const ast.Type {
457 if (!std.mem.startsWith(u8, name, stem)) return null;
458 if (name.len <= stem.len or name[stem.len] != '_') return null;
459 const suffix = name[stem.len + 1 ..];
460 if (std.mem.eql(u8, suffix, "uc")) return &ast.types.uchar_type;
461 if (std.mem.eql(u8, suffix, "us")) return &ast.types.ushort_type;
462 if (std.mem.eql(u8, suffix, "ui")) return &ast.types.uint_type;
463 if (std.mem.eql(u8, suffix, "ul")) return &ast.types.ulong_type;
464 if (std.mem.eql(u8, suffix, "ull")) return &ast.types.ulong_type;
465 return null;
466 }