authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-24 00:10:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-24 01:01:24-07:00
log818fbd9c567b907031c44961e4299ce1f9059be6
treedb185db14c188c6226824b2f865023d9ef7b25e8
parent8171972cbb477672ee1a99d953df4aaecb744a0c

stage2: string literal interning

This is a temporary addition to stage2 in order to match stage1 behavior, however the end-game once the lang spec is settled will be to use a global InternPool for comptime memoized objects, making this behavior consistent across all types, not only string literals. Or, we might decide to not guarantee string literals to have equal comptime pointers, in which case this commit can be reverted.

7 files changed, 242 insertions(+), 24 deletions(-)

src/Module.zig+61-6
...@@ -70,6 +70,17 @@ import_table: std.StringArrayHashMapUnmanaged(*File) = .{},...@@ -70,6 +70,17 @@ import_table: std.StringArrayHashMapUnmanaged(*File) = .{},
70/// Keys are fully resolved file paths. This table owns the keys and values.70/// Keys are fully resolved file paths. This table owns the keys and values.
71embed_table: std.StringHashMapUnmanaged(*EmbedFile) = .{},71embed_table: std.StringHashMapUnmanaged(*EmbedFile) = .{},
7272
73/// This is a temporary addition to stage2 in order to match stage1 behavior,
74/// however the end-game once the lang spec is settled will be to use a global
75/// InternPool for comptime memoized objects, making this behavior consistent across all types,
76/// not only string literals. Or, we might decide to not guarantee string literals
77/// to have equal comptime pointers, in which case this field can be deleted (perhaps
78/// the commit that introduced it can simply be reverted).
79/// This table uses an optional index so that when a Decl is destroyed, the string literal
80/// is still reclaimable by a future Decl.
81string_literal_table: std.HashMapUnmanaged(StringLiteralContext.Key, Decl.OptionalIndex, StringLiteralContext, std.hash_map.default_max_load_percentage) = .{},
82string_literal_bytes: std.ArrayListUnmanaged(u8) = .{},
83
73/// The set of all the generic function instantiations. This is used so that when a generic84/// The set of all the generic function instantiations. This is used so that when a generic
74/// function is called twice with the same comptime parameter arguments, both calls dispatch85/// function is called twice with the same comptime parameter arguments, both calls dispatch
75/// to the same function.86/// to the same function.
...@@ -157,6 +168,39 @@ decls_free_list: std.ArrayListUnmanaged(Decl.Index) = .{},...@@ -157,6 +168,39 @@ decls_free_list: std.ArrayListUnmanaged(Decl.Index) = .{},
157168
158global_assembly: std.AutoHashMapUnmanaged(Decl.Index, []u8) = .{},169global_assembly: std.AutoHashMapUnmanaged(Decl.Index, []u8) = .{},
159170
171pub const StringLiteralContext = struct {
172 bytes: *std.ArrayListUnmanaged(u8),
173
174 pub const Key = struct {
175 index: u32,
176 len: u32,
177 };
178
179 pub fn eql(self: @This(), a: Key, b: Key) bool {
180 _ = self;
181 return a.index == b.index and a.len == b.len;
182 }
183
184 pub fn hash(self: @This(), x: Key) u64 {
185 const x_slice = self.bytes.items[x.index..][0..x.len];
186 return std.hash_map.hashString(x_slice);
187 }
188};
189
190pub const StringLiteralAdapter = struct {
191 bytes: *std.ArrayListUnmanaged(u8),
192
193 pub fn eql(self: @This(), a_slice: []const u8, b: StringLiteralContext.Key) bool {
194 const b_slice = self.bytes.items[b.index..][0..b.len];
195 return mem.eql(u8, a_slice, b_slice);
196 }
197
198 pub fn hash(self: @This(), adapted_key: []const u8) u64 {
199 _ = self;
200 return std.hash_map.hashString(adapted_key);
201 }
202};
203
160const MonomorphedFuncsSet = std.HashMapUnmanaged(204const MonomorphedFuncsSet = std.HashMapUnmanaged(
161 *Fn,205 *Fn,
162 void,206 void,
...@@ -507,7 +551,8 @@ pub const Decl = struct {...@@ -507,7 +551,8 @@ pub const Decl = struct {
507 decl.name = undefined;551 decl.name = undefined;
508 }552 }
509553
510 pub fn clearValues(decl: *Decl, gpa: Allocator) void {554 pub fn clearValues(decl: *Decl, mod: *Module) void {
555 const gpa = mod.gpa;
511 if (decl.getExternFn()) |extern_fn| {556 if (decl.getExternFn()) |extern_fn| {
512 extern_fn.deinit(gpa);557 extern_fn.deinit(gpa);
513 gpa.destroy(extern_fn);558 gpa.destroy(extern_fn);
...@@ -521,6 +566,13 @@ pub const Decl = struct {...@@ -521,6 +566,13 @@ pub const Decl = struct {
521 gpa.destroy(variable);566 gpa.destroy(variable);
522 }567 }
523 if (decl.value_arena) |arena_state| {568 if (decl.value_arena) |arena_state| {
569 if (decl.owns_tv) {
570 if (decl.val.castTag(.str_lit)) |str_lit| {
571 mod.string_literal_table.getPtrContext(str_lit.data, .{
572 .bytes = &mod.string_literal_bytes,
573 }).?.* = .none;
574 }
575 }
524 arena_state.promote(gpa).deinit();576 arena_state.promote(gpa).deinit();
525 decl.value_arena = null;577 decl.value_arena = null;
526 decl.has_tv = false;578 decl.has_tv = false;
...@@ -2839,6 +2891,9 @@ pub fn deinit(mod: *Module) void {...@@ -2839,6 +2891,9 @@ pub fn deinit(mod: *Module) void {
2839 mod.decls_free_list.deinit(gpa);2891 mod.decls_free_list.deinit(gpa);
2840 mod.allocated_decls.deinit(gpa);2892 mod.allocated_decls.deinit(gpa);
2841 mod.global_assembly.deinit(gpa);2893 mod.global_assembly.deinit(gpa);
2894
2895 mod.string_literal_table.deinit(gpa);
2896 mod.string_literal_bytes.deinit(gpa);
2842}2897}
28432898
2844pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {2899pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {
...@@ -2857,7 +2912,7 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {...@@ -2857,7 +2912,7 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {
2857 if (decl.getInnerNamespace()) |namespace| {2912 if (decl.getInnerNamespace()) |namespace| {
2858 namespace.destroyDecls(mod);2913 namespace.destroyDecls(mod);
2859 }2914 }
2860 decl.clearValues(gpa);2915 decl.clearValues(mod);
2861 }2916 }
2862 decl.dependants.deinit(gpa);2917 decl.dependants.deinit(gpa);
2863 decl.dependencies.deinit(gpa);2918 decl.dependencies.deinit(gpa);
...@@ -4034,7 +4089,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -4034,7 +4089,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
4034 if (decl.getFunction()) |prev_func| {4089 if (decl.getFunction()) |prev_func| {
4035 prev_is_inline = prev_func.state == .inline_only;4090 prev_is_inline = prev_func.state == .inline_only;
4036 }4091 }
4037 decl.clearValues(gpa);4092 decl.clearValues(mod);
4038 }4093 }
40394094
4040 decl.ty = try decl_tv.ty.copy(decl_arena_allocator);4095 decl.ty = try decl_tv.ty.copy(decl_arena_allocator);
...@@ -4080,7 +4135,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -4080,7 +4135,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
4080 var type_changed = true;4135 var type_changed = true;
4081 if (decl.has_tv) {4136 if (decl.has_tv) {
4082 type_changed = !decl.ty.eql(decl_tv.ty, mod);4137 type_changed = !decl.ty.eql(decl_tv.ty, mod);
4083 decl.clearValues(gpa);4138 decl.clearValues(mod);
4084 }4139 }
40854140
4086 decl.owns_tv = false;4141 decl.owns_tv = false;
...@@ -4694,7 +4749,7 @@ pub fn clearDecl(...@@ -4694,7 +4749,7 @@ pub fn clearDecl(
4694 if (decl.getInnerNamespace()) |namespace| {4749 if (decl.getInnerNamespace()) |namespace| {
4695 try namespace.deleteAllDecls(mod, outdated_decls);4750 try namespace.deleteAllDecls(mod, outdated_decls);
4696 }4751 }
4697 decl.clearValues(gpa);4752 decl.clearValues(mod);
4698 }4753 }
46994754
4700 if (decl.deletion_flag) {4755 if (decl.deletion_flag) {
...@@ -5623,7 +5678,7 @@ pub fn populateTestFunctions(mod: *Module) !void {...@@ -5623,7 +5678,7 @@ pub fn populateTestFunctions(mod: *Module) !void {
56235678
5624 // Since we are replacing the Decl's value we must perform cleanup on the5679 // Since we are replacing the Decl's value we must perform cleanup on the
5625 // previous value.5680 // previous value.
5626 decl.clearValues(gpa);5681 decl.clearValues(mod);
5627 decl.ty = new_ty;5682 decl.ty = new_ty;
5628 decl.val = new_val;5683 decl.val = new_val;
5629 decl.has_tv = true;5684 decl.has_tv = true;
src/Sema.zig+79-13
...@@ -3842,20 +3842,44 @@ fn zirStr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins...@@ -3842,20 +3842,44 @@ fn zirStr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
3842fn addStrLit(sema: *Sema, block: *Block, zir_bytes: []const u8) CompileError!Air.Inst.Ref {3842fn addStrLit(sema: *Sema, block: *Block, zir_bytes: []const u8) CompileError!Air.Inst.Ref {
3843 // `zir_bytes` references memory inside the ZIR module, which can get deallocated3843 // `zir_bytes` references memory inside the ZIR module, which can get deallocated
3844 // after semantic analysis is complete, for example in the case of the initialization3844 // after semantic analysis is complete, for example in the case of the initialization
3845 // expression of a variable declaration. We need the memory to be in the new3845 // expression of a variable declaration.
3846 // anonymous Decl's arena.3846 const mod = sema.mod;
3847 var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded);3847 const gpa = sema.gpa;
3848 defer anon_decl.deinit();3848 const string_bytes = &mod.string_literal_bytes;
3849 const StringLiteralAdapter = Module.StringLiteralAdapter;
3850 const StringLiteralContext = Module.StringLiteralContext;
3851 try string_bytes.ensureUnusedCapacity(gpa, zir_bytes.len);
3852 const gop = try mod.string_literal_table.getOrPutContextAdapted(gpa, zir_bytes, StringLiteralAdapter{
3853 .bytes = string_bytes,
3854 }, StringLiteralContext{
3855 .bytes = string_bytes,
3856 });
3857 if (!gop.found_existing) {
3858 gop.key_ptr.* = .{
3859 .index = @intCast(u32, string_bytes.items.len),
3860 .len = @intCast(u32, zir_bytes.len),
3861 };
3862 string_bytes.appendSliceAssumeCapacity(zir_bytes);
3863 gop.value_ptr.* = .none;
3864 }
3865 const decl_index = gop.value_ptr.unwrap() orelse di: {
3866 var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded);
3867 defer anon_decl.deinit();
38493868
3850 const bytes = try anon_decl.arena().dupeZ(u8, zir_bytes);3869 const decl_index = try anon_decl.finish(
3870 try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), gop.key_ptr.len),
3871 try Value.Tag.str_lit.create(anon_decl.arena(), gop.key_ptr.*),
3872 0, // default alignment
3873 );
38513874
3852 const new_decl = try anon_decl.finish(3875 // Needed so that `Decl.clearValues` will additionally set the corresponding
3853 try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len),3876 // string literal table value back to `Decl.OptionalIndex.none`.
3854 try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),3877 mod.declPtr(decl_index).owns_tv = true;
3855 0, // default alignment
3856 );
38573878
3858 return sema.analyzeDeclRef(new_decl);3879 gop.value_ptr.* = decl_index.toOptional();
3880 break :di decl_index;
3881 };
3882 return sema.analyzeDeclRef(decl_index);
3859}3883}
38603884
3861fn zirInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {3885fn zirInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
...@@ -19762,6 +19786,35 @@ fn beginComptimePtrMutation(...@@ -19762,6 +19786,35 @@ fn beginComptimePtrMutation(
19762 .ty = elem_ty,19786 .ty = elem_ty,
19763 };19787 };
19764 },19788 },
19789 .str_lit => {
19790 // An array is memory-optimized to store a slice of bytes, but we are about
19791 // to modify an individual field and the representation has to change.
19792 // If we wanted to avoid this, there would need to be special detection
19793 // elsewhere to identify when writing a value to an array element that is stored
19794 // using the `str_lit` tag, and handle it without making a call to this function.
19795 const arena = parent.beginArena(sema.mod);
19796 defer parent.finishArena(sema.mod);
19797
19798 const str_lit = parent.val.castTag(.str_lit).?.data;
19799 const dest_len = parent.ty.arrayLenIncludingSentinel();
19800 const bytes = sema.mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
19801 const elems = try arena.alloc(Value, @intCast(usize, dest_len));
19802 for (bytes) |byte, i| {
19803 elems[i] = try Value.Tag.int_u64.create(arena, byte);
19804 }
19805 if (parent.ty.sentinel()) |sent_val| {
19806 assert(elems.len == bytes.len + 1);
19807 elems[bytes.len] = sent_val;
19808 }
19809
19810 parent.val.* = try Value.Tag.aggregate.create(arena, elems);
19811
19812 return ComptimePtrMutationKit{
19813 .decl_ref_mut = parent.decl_ref_mut,
19814 .val = &elems[elem_ptr.index],
19815 .ty = elem_ty,
19816 };
19817 },
19765 .repeated => {19818 .repeated => {
19766 // An array is memory-optimized to store only a single element value, and19819 // An array is memory-optimized to store only a single element value, and
19767 // that value is understood to be the same for the entire length of the array.19820 // that value is understood to be the same for the entire length of the array.
...@@ -20097,10 +20150,23 @@ fn beginComptimePtrLoad(...@@ -20097,10 +20150,23 @@ fn beginComptimePtrLoad(
20097 }20150 }
20098 }20151 }
2009920152
20100 deref.pointee = if (elem_ptr.index < check_len) TypedValue{20153 if (elem_ptr.index >= check_len) {
20154 deref.pointee = null;
20155 break :blk deref;
20156 }
20157 if (elem_ptr.index == check_len - 1) {
20158 if (array_tv.ty.sentinel()) |sent| {
20159 deref.pointee = TypedValue{
20160 .ty = elem_ty,
20161 .val = sent,
20162 };
20163 break :blk deref;
20164 }
20165 }
20166 deref.pointee = TypedValue{
20101 .ty = elem_ty,20167 .ty = elem_ty,
20102 .val = try array_tv.val.elemValue(sema.mod, sema.arena, elem_ptr.index),20168 .val = try array_tv.val.elemValue(sema.mod, sema.arena, elem_ptr.index),
20103 } else null;20169 };
20104 break :blk deref;20170 break :blk deref;
20105 },20171 },
2010620172
src/TypedValue.zig+5
...@@ -295,6 +295,11 @@ pub fn print(...@@ -295,6 +295,11 @@ pub fn print(
295 return writer.print(".{s}", .{ty.enumFieldName(val.castTag(.enum_field_index).?.data)});295 return writer.print(".{s}", .{ty.enumFieldName(val.castTag(.enum_field_index).?.data)});
296 },296 },
297 .bytes => return writer.print("\"{}\"", .{std.zig.fmtEscapes(val.castTag(.bytes).?.data)}),297 .bytes => return writer.print("\"{}\"", .{std.zig.fmtEscapes(val.castTag(.bytes).?.data)}),
298 .str_lit => {
299 const str_lit = val.castTag(.str_lit).?.data;
300 const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
301 return writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)});
302 },
298 .repeated => {303 .repeated => {
299 if (level == 0) {304 if (level == 0) {
300 return writer.writeAll(".{ ... }");305 return writer.writeAll(".{ ... }");
src/codegen.zig+14-2
...@@ -203,11 +203,23 @@ pub fn generateSymbol(...@@ -203,11 +203,23 @@ pub fn generateSymbol(
203 },203 },
204 .Array => switch (typed_value.val.tag()) {204 .Array => switch (typed_value.val.tag()) {
205 .bytes => {205 .bytes => {
206 const payload = typed_value.val.castTag(.bytes).?;206 const bytes = typed_value.val.castTag(.bytes).?.data;
207 const len = @intCast(usize, typed_value.ty.arrayLenIncludingSentinel());207 const len = @intCast(usize, typed_value.ty.arrayLenIncludingSentinel());
208 // The bytes payload already includes the sentinel, if any208 // The bytes payload already includes the sentinel, if any
209 try code.ensureUnusedCapacity(len);209 try code.ensureUnusedCapacity(len);
210 code.appendSliceAssumeCapacity(payload.data[0..len]);210 code.appendSliceAssumeCapacity(bytes[0..len]);
211 return Result{ .appended = {} };
212 },
213 .str_lit => {
214 const str_lit = typed_value.val.castTag(.str_lit).?.data;
215 const mod = bin_file.options.module.?;
216 const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
217 try code.ensureUnusedCapacity(bytes.len + 1);
218 code.appendSliceAssumeCapacity(bytes);
219 if (typed_value.ty.sentinel()) |sent_val| {
220 const byte = @intCast(u8, sent_val.toUnsignedInt(target));
221 code.appendAssumeCapacity(byte);
222 }
211 return Result{ .appended = {} };223 return Result{ .appended = {} };
212 },224 },
213 .aggregate => {225 .aggregate => {
src/codegen/llvm.zig+31-1
...@@ -2936,9 +2936,39 @@ pub const DeclGen = struct {...@@ -2936,9 +2936,39 @@ pub const DeclGen = struct {
2936 return dg.context.constString(2936 return dg.context.constString(
2937 bytes.ptr,2937 bytes.ptr,
2938 @intCast(c_uint, tv.ty.arrayLenIncludingSentinel()),2938 @intCast(c_uint, tv.ty.arrayLenIncludingSentinel()),
2939 .True, // don't null terminate. bytes has the sentinel, if any.2939 .True, // Don't null terminate. Bytes has the sentinel, if any.
2940 );2940 );
2941 },2941 },
2942 .str_lit => {
2943 const str_lit = tv.val.castTag(.str_lit).?.data;
2944 const bytes = dg.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
2945 if (tv.ty.sentinel()) |sent_val| {
2946 const byte = @intCast(u8, sent_val.toUnsignedInt(target));
2947 if (byte == 0 and bytes.len > 0) {
2948 return dg.context.constString(
2949 bytes.ptr,
2950 @intCast(c_uint, bytes.len),
2951 .False, // Yes, null terminate.
2952 );
2953 }
2954 var array = std.ArrayList(u8).init(dg.gpa);
2955 defer array.deinit();
2956 try array.ensureUnusedCapacity(bytes.len + 1);
2957 array.appendSliceAssumeCapacity(bytes);
2958 array.appendAssumeCapacity(byte);
2959 return dg.context.constString(
2960 array.items.ptr,
2961 @intCast(c_uint, array.items.len),
2962 .True, // Don't null terminate.
2963 );
2964 } else {
2965 return dg.context.constString(
2966 bytes.ptr,
2967 @intCast(c_uint, bytes.len),
2968 .True, // Don't null terminate. `bytes` has the sentinel, if any.
2969 );
2970 }
2971 },
2942 .aggregate => {2972 .aggregate => {
2943 const elem_vals = tv.val.castTag(.aggregate).?.data;2973 const elem_vals = tv.val.castTag(.aggregate).?.data;
2944 const elem_ty = tv.ty.elemType();2974 const elem_ty = tv.ty.elemType();
src/value.zig+41
...@@ -126,6 +126,8 @@ pub const Value = extern union {...@@ -126,6 +126,8 @@ pub const Value = extern union {
126 field_ptr,126 field_ptr,
127 /// A slice of u8 whose memory is managed externally.127 /// A slice of u8 whose memory is managed externally.
128 bytes,128 bytes,
129 /// Similar to bytes however it stores an index relative to `Module.string_literal_bytes`.
130 str_lit,
129 /// This value is repeated some number of times. The amount of times to repeat131 /// This value is repeated some number of times. The amount of times to repeat
130 /// is stored externally.132 /// is stored externally.
131 repeated,133 repeated,
...@@ -285,6 +287,7 @@ pub const Value = extern union {...@@ -285,6 +287,7 @@ pub const Value = extern union {
285 .enum_literal,287 .enum_literal,
286 => Payload.Bytes,288 => Payload.Bytes,
287289
290 .str_lit => Payload.StrLit,
288 .slice => Payload.Slice,291 .slice => Payload.Slice,
289292
290 .enum_field_index => Payload.U32,293 .enum_field_index => Payload.U32,
...@@ -538,6 +541,7 @@ pub const Value = extern union {...@@ -538,6 +541,7 @@ pub const Value = extern union {
538 };541 };
539 return Value{ .ptr_otherwise = &new_payload.base };542 return Value{ .ptr_otherwise = &new_payload.base };
540 },543 },
544 .str_lit => return self.copyPayloadShallow(arena, Payload.StrLit),
541 .repeated,545 .repeated,
542 .eu_payload,546 .eu_payload,
543 .opt_payload,547 .opt_payload,
...@@ -764,6 +768,12 @@ pub const Value = extern union {...@@ -764,6 +768,12 @@ pub const Value = extern union {
764 .enum_literal => return out_stream.print(".{}", .{std.zig.fmtId(val.castTag(.enum_literal).?.data)}),768 .enum_literal => return out_stream.print(".{}", .{std.zig.fmtId(val.castTag(.enum_literal).?.data)}),
765 .enum_field_index => return out_stream.print("(enum field {d})", .{val.castTag(.enum_field_index).?.data}),769 .enum_field_index => return out_stream.print("(enum field {d})", .{val.castTag(.enum_field_index).?.data}),
766 .bytes => return out_stream.print("\"{}\"", .{std.zig.fmtEscapes(val.castTag(.bytes).?.data)}),770 .bytes => return out_stream.print("\"{}\"", .{std.zig.fmtEscapes(val.castTag(.bytes).?.data)}),
771 .str_lit => {
772 const str_lit = val.castTag(.str_lit).?.data;
773 return out_stream.print("(.str_lit index={d} len={d})", .{
774 str_lit.index, str_lit.len,
775 });
776 },
767 .repeated => {777 .repeated => {
768 try out_stream.writeAll("(repeated) ");778 try out_stream.writeAll("(repeated) ");
769 val = val.castTag(.repeated).?.data;779 val = val.castTag(.repeated).?.data;
...@@ -824,6 +834,11 @@ pub const Value = extern union {...@@ -824,6 +834,11 @@ pub const Value = extern union {
824 const adjusted_bytes = bytes[0..adjusted_len];834 const adjusted_bytes = bytes[0..adjusted_len];
825 return allocator.dupe(u8, adjusted_bytes);835 return allocator.dupe(u8, adjusted_bytes);
826 },836 },
837 .str_lit => {
838 const str_lit = val.castTag(.str_lit).?.data;
839 const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
840 return allocator.dupe(u8, bytes);
841 },
827 .enum_literal => return allocator.dupe(u8, val.castTag(.enum_literal).?.data),842 .enum_literal => return allocator.dupe(u8, val.castTag(.enum_literal).?.data),
828 .repeated => {843 .repeated => {
829 const byte = @intCast(u8, val.castTag(.repeated).?.data.toUnsignedInt(target));844 const byte = @intCast(u8, val.castTag(.repeated).?.data.toUnsignedInt(target));
...@@ -2537,6 +2552,20 @@ pub const Value = extern union {...@@ -2537,6 +2552,20 @@ pub const Value = extern union {
2537 return initPayload(&buffer.base);2552 return initPayload(&buffer.base);
2538 }2553 }
2539 },2554 },
2555 .str_lit => {
2556 const str_lit = val.castTag(.str_lit).?.data;
2557 const bytes = mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
2558 const byte = bytes[index];
2559 if (arena) |a| {
2560 return Tag.int_u64.create(a, byte);
2561 } else {
2562 buffer.* = .{
2563 .base = .{ .tag = .int_u64 },
2564 .data = byte,
2565 };
2566 return initPayload(&buffer.base);
2567 }
2568 },
25402569
2541 // No matter the index; all the elements are the same!2570 // No matter the index; all the elements are the same!
2542 .repeated => return val.castTag(.repeated).?.data,2571 .repeated => return val.castTag(.repeated).?.data,
...@@ -2570,6 +2599,13 @@ pub const Value = extern union {...@@ -2570,6 +2599,13 @@ pub const Value = extern union {
2570 return switch (val.tag()) {2599 return switch (val.tag()) {
2571 .empty_array_sentinel => if (start == 0 and end == 1) val else Value.initTag(.empty_array),2600 .empty_array_sentinel => if (start == 0 and end == 1) val else Value.initTag(.empty_array),
2572 .bytes => Tag.bytes.create(arena, val.castTag(.bytes).?.data[start..end]),2601 .bytes => Tag.bytes.create(arena, val.castTag(.bytes).?.data[start..end]),
2602 .str_lit => {
2603 const str_lit = val.castTag(.str_lit).?.data;
2604 return Tag.str_lit.create(arena, .{
2605 .index = @intCast(u32, str_lit.index + start),
2606 .len = @intCast(u32, end - start),
2607 });
2608 },
2573 .aggregate => Tag.aggregate.create(arena, val.castTag(.aggregate).?.data[start..end]),2609 .aggregate => Tag.aggregate.create(arena, val.castTag(.aggregate).?.data[start..end]),
2574 .slice => sliceArray(val.castTag(.slice).?.data.ptr, mod, arena, start, end),2610 .slice => sliceArray(val.castTag(.slice).?.data.ptr, mod, arena, start, end),
25752611
...@@ -4721,6 +4757,11 @@ pub const Value = extern union {...@@ -4721,6 +4757,11 @@ pub const Value = extern union {
4721 data: []const u8,4757 data: []const u8,
4722 };4758 };
47234759
4760 pub const StrLit = struct {
4761 base: Payload,
4762 data: Module.StringLiteralContext.Key,
4763 };
4764
4724 pub const Aggregate = struct {4765 pub const Aggregate = struct {
4725 base: Payload,4766 base: Payload,
4726 /// Field values. The types are according to the struct or array type.4767 /// Field values. The types are according to the struct or array type.
test/behavior/eval.zig+11-2
...@@ -643,9 +643,18 @@ fn assertEqualPtrs(ptr1: *const u8, ptr2: *const u8) !void {...@@ -643,9 +643,18 @@ fn assertEqualPtrs(ptr1: *const u8, ptr2: *const u8) !void {
643 try expect(ptr1 == ptr2);643 try expect(ptr1 == ptr2);
644}644}
645645
646// This one is still up for debate in the language specification.
647// Application code should not rely on this behavior until it is solidified.
648// Currently, stage1 has special case code to make this pass for string literals
649// but it does not work if the values are constructed with comptime code, or if
650// arrays of non-u8 elements are used instead.
651// The official language specification might not make this guarantee. However, if
652// it does make this guarantee, it will make it consistently for all types, not
653// only string literals. This is why stage2 currently has a string table for
654// string literals, to match stage1 and pass this test, however the end-game once
655// the lang spec issue is settled would be to use a global InternPool for comptime
656// memoized objects, making this behavior consistent across all types.
646test "string literal used as comptime slice is memoized" {657test "string literal used as comptime slice is memoized" {
647 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
648
649 const a = "link";658 const a = "link";
650 const b = "link";659 const b = "link";
651 comptime try expect(TypeWithCompTimeSlice(a).Node == TypeWithCompTimeSlice(b).Node);660 comptime try expect(TypeWithCompTimeSlice(a).Node == TypeWithCompTimeSlice(b).Node);