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 }