authorgravatar for jhc@dismail.deJimmi Holst Christensen <jhc@dismail.de> 2022-01-01 17:36:53+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-12 11:53:07-07:00
log1676729c66784a0e14e80f59454625c7d77f95d9
tree4fc5dbd751a76f93f7ad39cca0e72a3d134a8ee1
parenteee395287f701b32e222c396ba4ae2615b23c4bb

fmt: Refactor parsing of placeholders into its own function

This saves on comptime format string parsing, as the compiler caches comptime calls. The catch here, is that parsePlaceHolder cannot take the placeholder string as a slice. It must take it as an array by value for the caching to occure. There is also some logic in here that ensures that the specifier_arg is always them same slice when the items they contain are the same. This makes the compiler stamp out less copies of formatType.

2 files changed, 271 insertions(+), 264 deletions(-)

lib/std/fmt.zig+271-222
......@@ -75,111 +75,20 @@ pub fn format(
7575 comptime fmt: []const u8,
7676 args: anytype,
7777) !void {
78 const ArgSetType = u32;
79
8078 const ArgsType = @TypeOf(args);
79 const args_type_info = @typeInfo(ArgsType);
8180 // XXX: meta.trait.is(.Struct)(ArgsType) doesn't seem to work...
82 if (@typeInfo(ArgsType) != .Struct) {
81 if (args_type_info != .Struct) {
8382 @compileError("Expected tuple or struct argument, found " ++ @typeName(ArgsType));
8483 }
8584
86 const fields_info = meta.fields(ArgsType);
87 if (fields_info.len > @typeInfo(ArgSetType).Int.bits) {
85 const fields_info = args_type_info.Struct.fields;
86 if (fields_info.len > max_format_args) {
8887 @compileError("32 arguments max are supported per format call");
8988 }
9089
91 comptime var arg_state: struct {
92 next_arg: usize = 0,
93 used_args: usize = 0,
94 args_len: usize = fields_info.len,
95
96 fn hasUnusedArgs(comptime self: *@This()) bool {
97 return @popCount(ArgSetType, self.used_args) != self.args_len;
98 }
99
100 fn nextArg(comptime self: *@This(), comptime arg_index: ?usize) comptime_int {
101 const next_index = arg_index orelse init: {
102 const arg = self.next_arg;
103 self.next_arg += 1;
104 break :init arg;
105 };
106
107 if (next_index >= self.args_len) {
108 @compileError("Too few arguments");
109 }
110
111 // Mark this argument as used
112 self.used_args |= 1 << next_index;
113
114 return next_index;
115 }
116 } = .{};
117
118 comptime var parser: struct {
119 buf: []const u8 = undefined,
120 pos: comptime_int = 0,
121
122 // Returns a decimal number or null if the current character is not a
123 // digit
124 fn number(comptime self: *@This()) ?usize {
125 var r: ?usize = null;
126
127 while (self.pos < self.buf.len) : (self.pos += 1) {
128 switch (self.buf[self.pos]) {
129 '0'...'9' => {
130 if (r == null) r = 0;
131 r.? *= 10;
132 r.? += self.buf[self.pos] - '0';
133 },
134 else => break,
135 }
136 }
137
138 return r;
139 }
140
141 // Returns a substring of the input starting from the current position
142 // and ending where `ch` is found or until the end if not found
143 fn until(comptime self: *@This(), comptime ch: u8) []const u8 {
144 const start = self.pos;
145
146 if (start >= self.buf.len)
147 return &[_]u8{};
148
149 while (self.pos < self.buf.len) : (self.pos += 1) {
150 if (self.buf[self.pos] == ch) break;
151 }
152 return self.buf[start..self.pos];
153 }
154
155 // Returns one character, if available
156 fn char(comptime self: *@This()) ?u8 {
157 if (self.pos < self.buf.len) {
158 const ch = self.buf[self.pos];
159 self.pos += 1;
160 return ch;
161 }
162 return null;
163 }
164
165 fn maybe(comptime self: *@This(), comptime val: u8) bool {
166 if (self.pos < self.buf.len and self.buf[self.pos] == val) {
167 self.pos += 1;
168 return true;
169 }
170 return false;
171 }
172
173 // Returns the n-th next character or null if that's past the end
174 fn peek(comptime self: *@This(), comptime n: usize) ?u8 {
175 return if (self.pos + n < self.buf.len) self.buf[self.pos + n] else null;
176 }
177 } = .{};
178
179 var options: FormatOptions = .{};
180
18190 @setEvalBranchQuota(2000000);
182
91 comptime var arg_state: ArgState = .{ .args_len = fields_info.len };
18392 comptime var i = 0;
18493 inline while (i < fmt.len) {
18594 const start_index = i;
......@@ -234,134 +143,258 @@ pub fn format(
234143 comptime assert(fmt[i] == '}');
235144 i += 1;
236145
237 options = .{};
238
239 // Parse the format fragment between braces
240 parser.buf = fmt[fmt_begin..fmt_end];
241 parser.pos = 0;
242
243 // Parse the positional argument number
244 const opt_pos_arg = comptime init: {
245 if (parser.maybe('[')) {
246 const arg_name = parser.until(']');
146 const placeholder = comptime parsePlaceholder(fmt[fmt_begin..fmt_end].*);
147 const arg_pos = comptime switch (placeholder.arg) {
148 .none => null,
149 .number => |pos| pos,
150 .named => |arg_name| meta.fieldIndex(ArgsType, arg_name) orelse
151 @compileError("No argument with name '" ++ arg_name ++ "'"),
152 };
247153
248 if (!parser.maybe(']')) {
249 @compileError("Expected closing ]");
250 }
154 const width = switch (placeholder.width) {
155 .none => null,
156 .number => |v| v,
157 .named => |arg_name| blk: {
158 const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse
159 @compileError("No argument with name '" ++ arg_name ++ "'");
160 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("Too few arguments");
161 break :blk @field(args, arg_name);
162 },
163 };
251164
252 break :init meta.fieldIndex(ArgsType, arg_name) orelse
165 const precision = switch (placeholder.precision) {
166 .none => null,
167 .number => |v| v,
168 .named => |arg_name| blk: {
169 const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse
253170 @compileError("No argument with name '" ++ arg_name ++ "'");
254 } else {
255 break :init parser.number();
256 }
171 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("Too few arguments");
172 break :blk @field(args, arg_name);
173 },
257174 };
258175
259 // Parse the format specifier
260 const specifier_arg = comptime parser.until(':');
176 const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
177 @compileError("Too few arguments");
261178
262 // Skip the colon, if present
263 if (comptime parser.char()) |ch| {
264 if (ch != ':') {
265 @compileError("Expected : or }, found '" ++ [1]u8{ch} ++ "'");
266 }
179 try formatType(
180 @field(args, fields_info[arg_to_print].name),
181 placeholder.specifier_arg,
182 FormatOptions{
183 .fill = placeholder.fill,
184 .alignment = placeholder.alignment,
185 .width = width,
186 .precision = precision,
187 },
188 writer,
189 default_max_depth,
190 );
191 }
192
193 if (comptime arg_state.hasUnusedArgs()) {
194 const missing_count = arg_state.args_len - @popCount(ArgSetType, arg_state.used_args);
195 switch (missing_count) {
196 0 => unreachable,
197 1 => @compileError("Unused argument in '" ++ fmt ++ "'"),
198 else => @compileError((comptime comptimePrint("{d}", .{missing_count})) ++ " unused arguments in '" ++ fmt ++ "'"),
267199 }
200 }
201}
268202
269 // Parse the fill character
270 // The fill parameter requires the alignment parameter to be specified
271 // too
272 if (comptime parser.peek(1)) |ch| {
273 if (comptime mem.indexOfScalar(u8, "<^>", ch) != null) {
274 options.fill = comptime parser.char().?;
275 }
203fn parsePlaceholder(comptime str: anytype) Placeholder {
204 comptime var parser = Parser{ .buf = &str };
205
206 // Parse the positional argument number
207 const arg = comptime parser.specifier() catch |err|
208 @compileError(@errorName(err));
209
210 // Parse the format specifier
211 const specifier_arg = comptime parser.until(':');
212
213 // Skip the colon, if present
214 if (comptime parser.char()) |ch| {
215 if (ch != ':') {
216 @compileError("Expected : or }, found '" ++ [1]u8{ch} ++ "'");
276217 }
218 }
277219
278 // Parse the alignment parameter
279 if (comptime parser.peek(0)) |ch| {
280 switch (ch) {
281 '<' => {
282 options.alignment = .Left;
283 _ = comptime parser.char();
284 },
285 '^' => {
286 options.alignment = .Center;
287 _ = comptime parser.char();
288 },
289 '>' => {
290 options.alignment = .Right;
291 _ = comptime parser.char();
292 },
293 else => {},
294 }
220 // Parse the fill character
221 // The fill parameter requires the alignment parameter to be specified
222 // too
223 const fill = comptime if (parser.peek(1)) |ch|
224 switch (ch) {
225 '<', '^', '>' => parser.char().?,
226 else => ' ',
227 }
228 else
229 ' ';
230
231 // Parse the alignment parameter
232 const alignment: Alignment = comptime if (parser.peek(0)) |ch| init: {
233 switch (ch) {
234 '<', '^', '>' => _ = parser.char(),
235 else => {},
295236 }
237 break :init switch (ch) {
238 '<' => .Left,
239 '^' => .Center,
240 else => .Right,
241 };
242 } else .Right;
296243
297 // Parse the width parameter
298 options.width = comptime init: {
299 if (parser.maybe('[')) {
300 const arg_name = parser.until(']');
244 // Parse the width parameter
245 const width = comptime parser.specifier() catch |err|
246 @compileError(@errorName(err));
301247
302 if (!parser.maybe(']')) {
303 @compileError("Expected closing ]");
304 }
248 // Skip the dot, if present
249 if (comptime parser.char()) |ch| {
250 if (ch != '.') {
251 @compileError("Expected . or }, found '" ++ [1]u8{ch} ++ "'");
252 }
253 }
305254
306 const index = meta.fieldIndex(ArgsType, arg_name) orelse
307 @compileError("No argument with name '" ++ arg_name ++ "'");
308 const arg_index = arg_state.nextArg(index);
255 // Parse the precision parameter
256 const precision = comptime parser.specifier() catch |err|
257 @compileError(@errorName(err));
309258
310 break :init @field(args, fields_info[arg_index].name);
311 } else {
312 break :init parser.number();
313 }
314 };
259 if (comptime parser.char()) |ch| {
260 @compileError("Extraneous trailing character '" ++ [1]u8{ch} ++ "'");
261 }
262
263 return Placeholder{
264 .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*),
265 .fill = fill,
266 .alignment = alignment,
267 .arg = arg,
268 .width = width,
269 .precision = precision,
270 };
271}
272
273fn cacheString(str: anytype) []const u8 {
274 return &str;
275}
276
277const Placeholder = struct {
278 specifier_arg: []const u8,
279 fill: u8,
280 alignment: Alignment,
281 arg: Specifier,
282 width: Specifier,
283 precision: Specifier,
284};
285
286const Specifier = union(enum) {
287 none,
288 number: usize,
289 named: []const u8,
290};
315291
316 // Skip the dot, if present
317 if (comptime parser.char()) |ch| {
318 if (ch != '.') {
319 @compileError("Expected . or }, found '" ++ [1]u8{ch} ++ "'");
292const Parser = struct {
293 buf: []const u8,
294 pos: usize = 0,
295
296 // Returns a decimal number or null if the current character is not a
297 // digit
298 fn number(self: *@This()) ?usize {
299 var r: ?usize = null;
300
301 while (self.pos < self.buf.len) : (self.pos += 1) {
302 switch (self.buf[self.pos]) {
303 '0'...'9' => {
304 if (r == null) r = 0;
305 r.? *= 10;
306 r.? += self.buf[self.pos] - '0';
307 },
308 else => break,
320309 }
321310 }
322311
323 // Parse the precision parameter
324 options.precision = comptime init: {
325 if (parser.maybe('[')) {
326 const arg_name = parser.until(']');
312 return r;
313 }
327314
328 if (!parser.maybe(']')) {
329 @compileError("Expected closing ]");
330 }
315 // Returns a substring of the input starting from the current position
316 // and ending where `ch` is found or until the end if not found
317 fn until(self: *@This(), ch: u8) []const u8 {
318 const start = self.pos;
331319
332 const arg_i = meta.fieldIndex(ArgsType, arg_name) orelse
333 @compileError("No argument with name '" ++ arg_name ++ "'");
334 const arg_to_use = arg_state.nextArg(arg_i);
320 if (start >= self.buf.len)
321 return &[_]u8{};
335322
336 break :init @field(args, fields_info[arg_to_use].name);
337 } else {
338 break :init parser.number();
339 }
340 };
323 while (self.pos < self.buf.len) : (self.pos += 1) {
324 if (self.buf[self.pos] == ch) break;
325 }
326 return self.buf[start..self.pos];
327 }
341328
342 if (comptime parser.char()) |ch| {
343 @compileError("Extraneous trailing character '" ++ [1]u8{ch} ++ "'");
329 // Returns one character, if available
330 fn char(self: *@This()) ?u8 {
331 if (self.pos < self.buf.len) {
332 const ch = self.buf[self.pos];
333 self.pos += 1;
334 return ch;
344335 }
336 return null;
337 }
345338
346 const arg_to_print = comptime arg_state.nextArg(opt_pos_arg);
347 try formatType(
348 @field(args, fields_info[arg_to_print].name),
349 specifier_arg,
350 options,
351 writer,
352 default_max_depth,
353 );
339 fn maybe(self: *@This(), val: u8) bool {
340 if (self.pos < self.buf.len and self.buf[self.pos] == val) {
341 self.pos += 1;
342 return true;
343 }
344 return false;
354345 }
355346
356 if (comptime arg_state.hasUnusedArgs()) {
357 const missing_count = arg_state.args_len - @popCount(ArgSetType, arg_state.used_args);
358 switch (missing_count) {
359 0 => unreachable,
360 1 => @compileError("Unused argument in '" ++ fmt ++ "'"),
361 else => @compileError((comptime comptimePrint("{d}", .{missing_count})) ++ " unused arguments in '" ++ fmt ++ "'"),
347 // Returns a decimal number or null if the current character is not a
348 // digit
349 fn specifier(self: *@This()) !Specifier {
350 if (self.maybe('[')) {
351 const arg_name = self.until(']');
352
353 if (!self.maybe(']'))
354 return @field(anyerror, "Expected closing ]");
355
356 return Specifier{ .named = arg_name };
362357 }
358 if (self.number()) |i|
359 return Specifier{ .number = i };
360
361 return Specifier{ .none = {} };
362 }
363
364 // Returns the n-th next character or null if that's past the end
365 fn peek(self: *@This(), n: usize) ?u8 {
366 return if (self.pos + n < self.buf.len) self.buf[self.pos + n] else null;
367 }
368};
369
370const ArgSetType = u32;
371const max_format_args = @typeInfo(ArgSetType).Int.bits;
372
373const ArgState = struct {
374 next_arg: usize = 0,
375 used_args: ArgSetType = 0,
376 args_len: usize,
377
378 fn hasUnusedArgs(self: *@This()) bool {
379 return @popCount(ArgSetType, self.used_args) != self.args_len;
363380 }
364}
381
382 fn nextArg(self: *@This(), arg_index: ?usize) ?usize {
383 const next_index = arg_index orelse init: {
384 const arg = self.next_arg;
385 self.next_arg += 1;
386 break :init arg;
387 };
388
389 if (next_index >= self.args_len) {
390 return null;
391 }
392
393 // Mark this argument as used
394 self.used_args |= @as(ArgSetType, 1) << @intCast(u5, next_index);
395 return next_index;
396 }
397};
365398
366399pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @TypeOf(writer).Error!void {
367400 _ = options;
......@@ -535,14 +568,19 @@ pub fn formatType(
535568 if (actual_fmt.len == 0)
536569 @compileError("cannot format array ref without a specifier (i.e. {s} or {*})");
537570 if (info.child == u8) {
538 if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) {
539 return formatText(value, actual_fmt, options, writer);
571 switch (actual_fmt[0]) {
572 's', 'x', 'X', 'e', 'E' => {
573 comptime checkTextFmt(actual_fmt);
574 return formatBuf(value, options, writer);
575 },
576 else => {},
540577 }
541578 }
542579 if (comptime std.meta.trait.isZigString(info.child)) {
543580 for (value) |item, i| {
544 if (i != 0) try formatText(", ", actual_fmt, options, writer);
545 try formatText(item, actual_fmt, options, writer);
581 comptime checkTextFmt(actual_fmt);
582 if (i != 0) try formatBuf(", ", options, writer);
583 try formatBuf(item, options, writer);
546584 }
547585 return;
548586 }
......@@ -560,8 +598,12 @@ pub fn formatType(
560598 return formatType(mem.span(value), actual_fmt, options, writer, max_depth);
561599 }
562600 if (ptr_info.child == u8) {
563 if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) {
564 return formatText(mem.span(value), actual_fmt, options, writer);
601 switch (actual_fmt[0]) {
602 's', 'x', 'X', 'e', 'E' => {
603 comptime checkTextFmt(actual_fmt);
604 return formatBuf(mem.span(value), options, writer);
605 },
606 else => {},
565607 }
566608 }
567609 @compileError("Unknown format string: '" ++ actual_fmt ++ "' for type '" ++ @typeName(T) ++ "'");
......@@ -573,8 +615,12 @@ pub fn formatType(
573615 return writer.writeAll("{ ... }");
574616 }
575617 if (ptr_info.child == u8) {
576 if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) {
577 return formatText(value, actual_fmt, options, writer);
618 switch (actual_fmt[0]) {
619 's', 'x', 'X', 'e', 'E' => {
620 comptime checkTextFmt(actual_fmt);
621 return formatBuf(value, options, writer);
622 },
623 else => {},
578624 }
579625 }
580626 try writer.writeAll("{ ");
......@@ -594,8 +640,12 @@ pub fn formatType(
594640 return writer.writeAll("{ ... }");
595641 }
596642 if (info.child == u8) {
597 if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) {
598 return formatText(&value, actual_fmt, options, writer);
643 switch (actual_fmt[0]) {
644 's', 'x', 'X', 'e', 'E' => {
645 comptime checkTextFmt(actual_fmt);
646 return formatBuf(&value, options, writer);
647 },
648 else => {},
599649 }
600650 }
601651 try writer.writeAll("{ ");
......@@ -881,29 +931,28 @@ pub fn fmtIntSizeBin(value: u64) std.fmt.Formatter(formatSizeBin) {
881931 return .{ .data = value };
882932}
883933
934fn checkTextFmt(comptime fmt: []const u8) void {
935 if (fmt.len != 1)
936 @compileError("Unsupported format string '" ++ fmt ++ "' when formatting text");
937 switch (fmt[0]) {
938 'x' => @compileError("specifier 'x' has been deprecated, wrap your argument in std.fmt.fmtSliceHexLower instead"),
939 'X' => @compileError("specifier 'X' has been deprecated, wrap your argument in std.fmt.fmtSliceHexUpper instead"),
940 'e' => @compileError("specifier 'e' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeLower instead"),
941 'E' => @compileError("specifier 'E' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeUpper instead"),
942 'z' => @compileError("specifier 'z' has been deprecated, wrap your argument in std.zig.fmtId instead"),
943 'Z' => @compileError("specifier 'Z' has been deprecated, wrap your argument in std.zig.fmtEscapes instead"),
944 else => {},
945 }
946}
947
884948pub fn formatText(
885949 bytes: []const u8,
886950 comptime fmt: []const u8,
887951 options: FormatOptions,
888952 writer: anytype,
889953) !void {
890 if (comptime std.mem.eql(u8, fmt, "s")) {
891 return formatBuf(bytes, options, writer);
892 } else if (comptime (std.mem.eql(u8, fmt, "x"))) {
893 @compileError("specifier 'x' has been deprecated, wrap your argument in std.fmt.fmtSliceHexLower instead");
894 } else if (comptime (std.mem.eql(u8, fmt, "X"))) {
895 @compileError("specifier 'X' has been deprecated, wrap your argument in std.fmt.fmtSliceHexUpper instead");
896 } else if (comptime (std.mem.eql(u8, fmt, "e"))) {
897 @compileError("specifier 'e' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeLower instead");
898 } else if (comptime (std.mem.eql(u8, fmt, "E"))) {
899 @compileError("specifier 'E' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeUpper instead");
900 } else if (comptime std.mem.eql(u8, fmt, "z")) {
901 @compileError("specifier 'z' has been deprecated, wrap your argument in std.zig.fmtId instead");
902 } else if (comptime std.mem.eql(u8, fmt, "Z")) {
903 @compileError("specifier 'Z' has been deprecated, wrap your argument in std.zig.fmtEscapes instead");
904 } else {
905 @compileError("Unsupported format string '" ++ fmt ++ "' when formatting text");
906 }
954 comptime checkTextFmt(fmt);
955 return formatBuf(bytes, options, writer);
907956}
908957
909958pub fn formatAsciiChar(
src/stage1/analyze.cpp-42
......@@ -5876,49 +5876,7 @@ static bool can_mutate_comptime_var_state(ZigValue *value) {
58765876 zig_unreachable();
58775877}
58785878
5879static bool return_type_is_cacheable(ZigType *return_type) {
5880 switch (return_type->id) {
5881 case ZigTypeIdInvalid:
5882 zig_unreachable();
5883 case ZigTypeIdMetaType:
5884 case ZigTypeIdVoid:
5885 case ZigTypeIdBool:
5886 case ZigTypeIdUnreachable:
5887 case ZigTypeIdInt:
5888 case ZigTypeIdFloat:
5889 case ZigTypeIdComptimeFloat:
5890 case ZigTypeIdComptimeInt:
5891 case ZigTypeIdEnumLiteral:
5892 case ZigTypeIdUndefined:
5893 case ZigTypeIdNull:
5894 case ZigTypeIdBoundFn:
5895 case ZigTypeIdFn:
5896 case ZigTypeIdOpaque:
5897 case ZigTypeIdErrorSet:
5898 case ZigTypeIdEnum:
5899 case ZigTypeIdPointer:
5900 case ZigTypeIdVector:
5901 case ZigTypeIdFnFrame:
5902 case ZigTypeIdAnyFrame:
5903 return true;
5904
5905 case ZigTypeIdArray:
5906 case ZigTypeIdStruct:
5907 case ZigTypeIdUnion:
5908 return false;
5909
5910 case ZigTypeIdOptional:
5911 return return_type_is_cacheable(return_type->data.maybe.child_type);
5912
5913 case ZigTypeIdErrorUnion:
5914 return return_type_is_cacheable(return_type->data.error_union.payload_type);
5915 }
5916 zig_unreachable();
5917}
5918
59195879bool fn_eval_cacheable(Scope *scope, ZigType *return_type) {
5920 if (!return_type_is_cacheable(return_type))
5921 return false;
59225880 while (scope) {
59235881 if (scope->id == ScopeIdVarDecl) {
59245882 ScopeVarDecl *var_scope = (ScopeVarDecl *)scope;