From ee6d161658572b76eb31d23be33043f63be4f556 Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Sun, 27 Oct 2024 20:33:31 -0700 Subject: [PATCH] fix: rework InternPool.Key to not use slices these pointers get invalidated after appends to InternPool, so they should store `Store.Slice(T)` instead. this introduces a memory leak since there is a genuine use case for using real slices (when the query is not in InternPool), but the fix for this is more convoluted and will be done later when some other InternPool organization is done. --- CHANGELOG.md | 1 + README.md | 4 +- ci | 30 ++--- doc/language-manual.md | 4 +- src/core/Air.Dump.zig | 29 ++--- src/core/Air.zig | 27 +---- src/core/Hir.zig | 2 +- src/core/InternPool.zig | 195 +++++++++++++++----------------- src/core/Report.zig | 13 +-- src/core/Sema.zig | 103 ++++++++++------- src/core/array.zig | 196 +++++++++++++++++++++------------ src/core/js/Link.zig | 25 +++-- src/core/js/NodeGen.zig | 33 +++--- src/manual/generate_manual.zig | 14 ++- 14 files changed, 374 insertions(+), 302 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de7dd96d450826c1470e12cde31eb153e40ef39b..1a21ce7288ea641d9ff0621f12911e73f68f1700 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - fix: coerce `break` operands and implicit breaks to result type - fix: coerce results when `if` expression branches merge - fix: ensure if branch results to a runtime value when the condition is not compile-time known +- fix: coerce return value to a runtime value - fix: inferred array type uses item type for items instead of the container type. - fix(js): stop printing redundant parenthesis on ternary expressions diff --git a/README.md b/README.md index 3003546dc131b79610300a945be6e115faf78893..63de1cab7935de88a52cde3ea878cfddb335aea2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ix -Ix (`I`nferable E`x`pression) is a high-level, general purpose programming +Ix (*I*nferable E*x*pression) is a high-level, general purpose programming language. It features a mix of Rust, Zig, and JavaScript's great points. For a guide on how to use Ix, see the manual included in the binary @@ -29,6 +29,6 @@ CLI to check spelling mistakes and [`dprint`][dprint] for code formatting. It is recommended to setup editor integration with these two tools to prevent pushing broken commits. -[ci]: https://buildkite.com/paperdave/compiler +[ci]: https://buildkite.com/paperclover/compiler [typos]: https://github.com/crate-ci/typos [dprint]: https://dprint.dev diff --git a/ci b/ci index 556ae5ae133efd4692e72c47cf8a677c64034e93..f790d9fdcbb5fea0ad711e283973b5d2b1606277 100755 --- a/ci +++ b/ci @@ -12,25 +12,27 @@ commit_short=$(echo "$commit" | cut -c -9) typos dprint check -if [ -n "$BUILDKITE" ]; then +if [ -n "$BUILDKITE" ] || [ "$1" == "all" ]; then # perform the entire build ./build all --summary all - pushd zig-out/package - zip -r compiler-$commit.zip * - buildkite-agent artifact upload compiler-$commit.zip - popd + if [ -n "$BUILDKITE" ]; then + pushd zig-out/package + zip -r compiler-$commit.zip * + buildkite-agent artifact upload compiler-$commit.zip + popd - rm -fr zig-out/site - mkdir zig-out/site - cp zig-out/package/ix.html zig-out/site/index.html - branch="$BUILDKITE_BRANCH" - if [ "$branch" = "dev" ]; then - branch=main + rm -fr zig-out/site + mkdir zig-out/site + cp zig-out/package/ix.html zig-out/site/index.html + branch="$BUILDKITE_BRANCH" + if [ "$branch" = "dev" ]; then + branch=main + fi + CLOUDFLARE_API_TOKEN="$(buildkite-agent secret get CLOUDFLARE_API_TOKEN)" \ + CLOUDFLARE_ACCOUNT_ID="$(buildkite-agent secret get CLOUDFLARE_ACCOUNT_ID)" \ + wrangler pages deploy zig-out/site --branch "$branch" --commit-hash "$commit" --no-bundle --project-name language-manual fi - CLOUDFLARE_API_TOKEN="$(buildkite-agent secret get CLOUDFLARE_API_TOKEN)" \ - CLOUDFLARE_ACCOUNT_ID="$(buildkite-agent secret get CLOUDFLARE_ACCOUNT_ID)" \ - wrangler pages deploy zig-out/site --branch "$branch" --commit-hash "$commit" --no-bundle --project-name language-manual else # perform only the tests, with a shorter summary ./build test --summary new diff --git a/doc/language-manual.md b/doc/language-manual.md index 2b3563d1511bff5e23bc8943e4295e88d7677dd4..ffce967c8c2962b909c2a09ae58c93048d9e5fb8 100644 --- a/doc/language-manual.md +++ b/doc/language-manual.md @@ -179,7 +179,7 @@ it cannot be assigned to a `var` without an explicit type annotation. Since float, but a compiler warning will be emitted. ```lang -var number = 0; // `number` is a `float`, but not explicit +var number = 0; // `number` is `float`, but not explicit var number: int = 0; // `number` is `int` var number: float = 0; // `number` is `float` ``` @@ -203,7 +203,7 @@ fn main { The `return` expression can be used within functions to return early. -```lang:run +```lang let print = @extern("console").log; fn something(a: int) -> bool { diff --git a/src/core/Air.Dump.zig b/src/core/Air.Dump.zig index dd4ed41e246d1f3575913faf5b36ab02a76b79bb..552ab89576efde440898308835db59c72478b5a4 100644 --- a/src/core/Air.Dump.zig +++ b/src/core/Air.Dump.zig @@ -52,7 +52,7 @@ pub const State = struct { switch (tag) { .block => { const index_list: Air.IndexList = air.instData(.block, inst); - const insts = index_list.slice(air); + const insts = index_list.get(air); try s.dumpInstList(w, insts, '{', '}'); }, .arg => { @@ -127,7 +127,7 @@ pub const State = struct { air .typeOfIndex(field.base, s.ip) .toStructType(s.ip).? - .field_keys[field.field_index] + .field_keys.at(s.ip, field.field_index) .toSlice(s.ip), }); try tty.setColor(w, .reset); @@ -149,7 +149,7 @@ pub const State = struct { try w.writeAll("("); try s.dumpRef(w, call.base); try w.writeAll(", "); - try s.dumpRefList(w, call_data.args.slice(air), '[', ']'); + try s.dumpRefList(w, call_data.args.get(air), '[', ']'); try w.writeAll(")"); }, .@"if" => { @@ -177,7 +177,7 @@ pub const State = struct { try s.dumpValue(w, struct_init.ty.value()); try w.writeAll(", {"); s.indent += 1; - for (struct_type.field_keys, struct_init.valuesPtr(air)) |key, ref| { + for (struct_type.field_keys.get(s.ip), struct_init.valuesPtr(air)) |key, ref| { try s.newline(w); try w.print("{}: ", .{key.fmt(s.ip)}); try s.dumpRef(w, ref); @@ -192,7 +192,7 @@ pub const State = struct { try w.writeAll("("); try s.dumpValue(w, array_type.item_type.value()); - try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']'); + try s.dumpRefList(w, array_init.list.get(air).get(air), '[', ']'); try w.writeAll(")"); }, .tuple_init => { @@ -203,6 +203,7 @@ pub const State = struct { // try s.dumpValue(w, array_type.item_type.value()); // try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']'); // try w.writeAll(")"); + @panic("TODO"); }, } } @@ -244,7 +245,7 @@ pub const State = struct { try tty.setColor(w, .blue); try w.writeAll("fn("); try tty.setColor(w, .reset); - for (ty.param_types, 0..) |param, i| { + for (ty.param_types.get(s.ip), 0..) |param, i| { if (i != 0) { try tty.setColor(w, .blue); try w.writeAll(", "); @@ -322,8 +323,8 @@ pub const State = struct { try w.writeAll(" "); for ( - struct_type.field_keys, - struct_type.field_types, + struct_type.field_keys.get(s.ip), + struct_type.field_types.get(s.ip), 0.., ) |key, field_type, i| { if (i != 0) @@ -352,8 +353,8 @@ pub const State = struct { try w.writeAll(" "); for ( - struct_value.ty.toStructType(s.ip).?.field_keys, - struct_value.field_values, + struct_value.ty.toStructType(s.ip).?.field_keys.get(s.ip), + struct_value.field_values.get(s.ip), 0.., ) |key, value, i| { if (i != 0) @@ -385,7 +386,7 @@ pub const State = struct { try tty.setColor(w, .reset); try w.writeAll("["); - for (array_value.items, 0..) |value, i| { + for (array_value.items.get(s.ip), 0..) |value, i| { if (i != 0) try w.writeAll(", "); try s.dumpValue(w, value); @@ -398,7 +399,7 @@ pub const State = struct { }, .external_value => |external_value| { try w.writeAll("external '"); - for (external_value.key, 0..) |part, i| { + for (external_value.key.get(s.ip), 0..) |part, i| { if (i != 0) try w.writeAll("."); try w.writeAll(part.toSlice(s.ip)); } @@ -406,7 +407,7 @@ pub const State = struct { }, .tuple_type => |tuple_type| { try w.writeAll("("); - for (tuple_type.items, 0..) |item, i| { + for (tuple_type.items.get(s.ip), 0..) |item, i| { if (i != 0) try w.writeAll(", "); try s.dumpValue(w, item.value()); } @@ -414,7 +415,7 @@ pub const State = struct { }, .tuple_value => |tuple_value| { try w.writeAll("("); - for (tuple_value.items, 0..) |item, i| { + for (tuple_value.items.get(s.ip), 0..) |item, i| { if (i != 0) try w.writeAll(", "); try s.dumpValue(w, item); } diff --git a/src/core/Air.zig b/src/core/Air.zig index 058465edbf0d61453464213ac96e36f671168bd7..7a51cb9eda85225336fd7a5b6f60a0ac1e670bd7 100644 --- a/src/core/Air.zig +++ b/src/core/Air.zig @@ -1,5 +1,5 @@ insts: Inst.List.Slice, -extra: ExtraStore = .{}, +extra: ExtraStore, pub const ExtraStore = dod.ExtraStore(.air, .@"const"); pub const EncodedData = dod.EncodedData(Inst.Tag, u64); @@ -201,23 +201,8 @@ pub const Flags = packed struct { }; }; -pub const IndexList = struct { - start: u32, - len: u32, - - pub fn slice(self: IndexList, air: *const Air) []const Inst.Index { - return @ptrCast(air.extra.list.items[self.start..][0..self.len]); - } -}; - -pub const RefList = struct { - start: u32, - len: u32, - - pub fn slice(self: RefList, air: *const Air) []const Inst.Ref { - return @ptrCast(air.extra.list.items[self.start..][0..self.len]); - } -}; +pub const IndexList = ExtraStore.Slice(Air.Inst.Index); +pub const RefList = ExtraStore.Slice(Air.Inst.Ref); pub const Break = struct { block: Inst.Index, @@ -383,7 +368,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In // TODO: taking the last instruction is likely wrong. // The last instruction is going to be a .break and be of type .never .block => { - const items = air.instData(.block, index).slice(air); + const items = air.instData(.block, index).get(air); assert(items.len > 0); // empty blocks should always be `Value.void` return air.typeOfIndex(items[items.len - 1], ip); }, @@ -402,7 +387,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In const inst = air.instData(.field_struct, index); const base_type = air.typeOfIndex(inst.base, ip) .toStructType(ip) orelse unreachable; // field_struct demands struct type - return base_type.field_types[inst.field_index]; + return base_type.field_types.at(ip, inst.field_index); }, // Array accesses use the array they are accessing into. @@ -418,7 +403,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In const inst = air.instData(.field_tuple, index); const base_type = air.typeOfIndex(inst.base, ip) .toTupleType(ip) orelse unreachable; // field_tuple demands tuple type - return base_type.items[inst.field_index]; + return base_type.items.at(ip, inst.field_index); }, }; } diff --git a/src/core/Hir.zig b/src/core/Hir.zig index 7040ab59dd7664ea7866e42d23c98973973b2810..b4a1a0dd96a514d4d48e6c57d724fea9f7aeb442 100644 --- a/src/core/Hir.zig +++ b/src/core/Hir.zig @@ -13,7 +13,7 @@ insts: Inst.List.Slice, string_bytes: []const u8, -extra: ExtraStore = .{}, +extra: ExtraStore, report: Report, pub const ExtraStore = dod.ExtraStore(.hir, .@"const"); diff --git a/src/core/InternPool.zig b/src/core/InternPool.zig index 87b13fd222a812f94e3bce7b056f727045c0970d..9fac02a280a5485b62ae3f1fd976cd9c1164db13 100644 --- a/src/core/InternPool.zig +++ b/src/core/InternPool.zig @@ -21,9 +21,9 @@ /// /// The preferred way to access these this data through `.get()` and the methods /// on `Value` and `Type`. -map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, -items: std.MultiArrayList(Item) = .{}, -extra: ExtraStore = .{}, +map: std.AutoArrayHashMapUnmanaged(void, void) = .empty, +items: std.MultiArrayList(Item) = .empty, +extra: Extra = .empty, /// In order to store references to strings in fewer bytes, we copy all /// string bytes into here. String bytes can be null. It is up to whoever @@ -146,7 +146,7 @@ pub const Tag = enum(u8) { .int32 => i32, .opaque_value => Value, - else => ExtraIndex(tag.Payload()), + else => Extra.Index(tag.Payload()), }; } @@ -270,18 +270,7 @@ pub const Value = enum(u32) { none = std.math.maxInt(u32), _, - /// An array of `Value` existing within the `extra` array. - /// This type exists to provide a struct with lifetime that is - /// not invalidated when items are added to the `InternPool`. - // TODO: Replace with ExtraStore.Slice(Value) - pub const Slice = struct { - start: u32, - len: u32, - - pub fn get(slice: Slice, ip: *const InternPool) []const Value { - return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]); - } - }; + pub const Slice = Extra.Slice(Value); /// Used for a map of `Value` values to the index within a list of `Value` values. const Adapter = struct { @@ -316,6 +305,8 @@ pub const Value = enum(u32) { pub fn assertValid(value: Value, ip: *const InternPool) void { assert(@intFromEnum(value) < ip.items.len); + assert(value != .none); + assert(ip.items.items(.tag)[@intFromEnum(value)] != .removed); } pub fn dump(value: Value, ip: *const InternPool) std.fmt.Formatter(Value.formatValue) { @@ -459,17 +450,7 @@ pub const Type = enum(u32) { tuple_type = @intFromEnum(InternPool.Tag.tuple_type), }; - /// An array of `Index` existing within the `extra` array. - /// This type exists to provide a struct with lifetime that is - /// not invalidated when items are added to the `InternPool`. - pub const Slice = struct { - start: u32, - len: u32, - - pub fn get(slice: Slice, ip: *const InternPool) []Type { - return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]); - } - }; + pub const Slice = Extra.Slice(Type); pub fn value(ty: Type) Value { return @enumFromInt(@intFromEnum(ty)); @@ -526,7 +507,7 @@ pub const Type = enum(u32) { => true, .tuple_type => { const tuple_type = ty.toTupleType(ip).?; - for (tuple_type.items) |item| { + for (tuple_type.items.get(ip)) |item| { if (!item.hasPossibleRepresentation(ip)) { return false; } @@ -599,7 +580,7 @@ pub const Type = enum(u32) { .function_type => { const function_type = ty.toUnresolvedFunctionPrototype(ip).?; try writer.writeAll("fn("); - for (function_type.param_types, 0..) |param, i| { + for (function_type.param_types.get(ip), 0..) |param, i| { if (i != 0) try writer.writeAll(", "); try writer.print("{}", .{param.displayName(ip)}); } @@ -619,7 +600,7 @@ pub const Type = enum(u32) { }, .tuple_type => { try writer.writeAll("("); - for (ty.toTupleType(ip).?.items, 0..) |item, i| { + for (ty.toTupleType(ip).?.items.get(ip), 0..) |item, i| { if (i != 0) try writer.writeAll(", "); try writer.print("{}", .{item.displayName(ip)}); } @@ -651,9 +632,8 @@ pub const Item = struct { } }; -pub const ExtraStore = dod.ExtraStore(.intern_pool, .@"var"); +pub const Extra = dod.ExtraStore(.intern_pool, .@"var"); pub const EncodedData = dod.EncodedData(Tag, u32); -pub const ExtraIndex = ExtraStore.Index; /// How many items in the InternPool are statically known. /// This is specified with an integer literal and a corresponding comptime @@ -674,9 +654,9 @@ pub const static_keys = [_]Key{ SimpleType.never.key(), SimpleType.type.key(), SimpleType.numeric_literal.key(), - .{ .array_type = .{ .item_type = Type.numeric_literal } }, - .{ .array_type = .{ .item_type = Type.never } }, - .{ .tuple_type = .{ .items = &.{} } }, + .{ .array_type = .{ .item_type = .numeric_literal } }, + .{ .array_type = .{ .item_type = .never } }, + .{ .tuple_type = .{ .items = .empty } }, SimpleValue.void.key(), SimpleValue.true.key(), @@ -685,13 +665,13 @@ pub const static_keys = [_]Key{ .{ .float = 0.0 }, .{ .float = Float.pure_nan.decode() }, .{ .array_value = .{ - .type = Type.never_array, - .items = &.{}, + .type = .never_array, + .items = .empty, } }, .{ .string = "" }, .{ .tuple_value = .{ .type = .empty_tuple, - .items = &.{}, + .items = .empty, } }, }; @@ -814,14 +794,7 @@ pub const NullTerminatedString = enum(u32) { return .{ .data = .{ .string = self, .ip = ip } }; } - pub const Slice = struct { - start: u32, - len: u32, - - pub fn get(slice: Slice, ip: *const InternPool) []NullTerminatedString { - return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]); - } - }; + pub const Slice = Extra.Slice(NullTerminatedString); }; pub const TrackedInst = extern struct { @@ -975,66 +948,74 @@ pub const Key = union(enum) { }; pub const FunctionType = struct { - param_types: []Type, + param_types: Type.Slice, /// If a return type is missing, it is inferred and not resolved yet. /// When analysis figures out a return type, a new `FunctionType` with /// the resolved return type. return_type: ?Type, pub const Resolved = struct { - param_types: []Type, + param_types: Type.Slice, return_type: Type, }; - pub fn hash(self: FunctionType, hasher: *Hash) void { + pub fn hash(self: FunctionType, ip: *const InternPool, hasher: *Hash) void { const t = tracer.trace(@src()); defer t.end(); - for (self.param_types) |param_type| { + for (self.param_types.get(ip)) |param_type| { std.hash.autoHash(hasher, param_type); } std.hash.autoHash(hasher, self.return_type); } - pub fn eql(a: FunctionType, b: FunctionType) bool { - return std.mem.eql(Type, a.param_types, b.param_types) and + pub fn eql(a: FunctionType, b: FunctionType, ip: *const InternPool) bool { + return std.mem.eql(Type, a.param_types.get(ip), b.param_types.get(ip)) and a.return_type == b.return_type; } }; pub const StructType = struct { - field_keys: []NullTerminatedString, - field_types: []const Type, + field_keys: NullTerminatedString.Slice, + field_types: Type.Slice, - pub fn hash(self: StructType, hasher: *Hash) void { - // TODO: remove this function. structural typing is not used - // this should be hashing it's location / decl, exactly like `Func` - std.hash.autoHashStrat(hasher, self, .DeepRecursive); + /// TODO: store where the struct is declared to make this a nomial type + pub fn hash(self: StructType, ip: *const InternPool, hasher: *Hash) void { + for (self.field_keys.get(ip)) |value| { + std.hash.autoHash(hasher, value); + } + for (self.field_types.get(ip)) |value| { + std.hash.autoHash(hasher, value); + } } }; pub const StructValue = struct { ty: Type, - field_values: []const Value, + field_values: Value.Slice, - pub fn hash(self: StructValue, hasher: *Hash) void { + pub fn hash(self: StructValue, ip: *const InternPool, hasher: *Hash) void { std.hash.autoHash(hasher, self.ty); - std.hash.autoHashStrat(hasher, self.field_values, .Deep); + for (self.field_values.get(ip)) |value| { + std.hash.autoHash(hasher, value); + } } }; pub const ArrayType = struct { item_type: Type, - pub fn hash(self: ArrayType, hasher: *Hash) void { + pub fn hash(self: ArrayType, ip: *const InternPool, hasher: *Hash) void { + _ = ip; std.hash.autoHash(hasher, self.item_type); } }; pub const TupleType = struct { - items: []const Type, + items: Type.Slice, - pub fn hash(self: TupleType, hasher: *Hash) void { + pub fn hash(self: TupleType, ip: *const InternPool, hasher: *Hash) void { + _ = ip; std.hash.autoHashStrat(hasher, self.items, .Deep); } }; @@ -1042,30 +1023,31 @@ pub const Key = union(enum) { pub const ExternalValue = struct { /// A list of strings to generate the property access /// so `console.log` would be .{ "console", "log" } - key: []const NullTerminatedString, + key: NullTerminatedString.Slice, - pub fn hash(self: ExternalValue, hasher: *Hash) void { - std.hash.autoHashStrat(hasher, self.key, .Shallow); + pub fn hash(self: ExternalValue, ip: *const InternPool, hasher: *Hash) void { + std.hash.autoHashStrat(hasher, self.key.get(ip), .Shallow); } }; pub const ArrayValue = struct { /// This is the array type, not the item type type: Type, - items: []const Value, + items: Value.Slice, - pub fn hash(self: ArrayValue, hasher: *Hash) void { + pub fn hash(self: ArrayValue, ip: *const InternPool, hasher: *Hash) void { std.hash.autoHash(hasher, self.type); - std.hash.autoHashStrat(hasher, self.items, .Deep); + std.hash.autoHashStrat(hasher, self.items.get(ip), .Deep); } }; pub const TupleValue = struct { /// This is the tuple type, not the item type type: Type, - items: []const Value, + items: Value.Slice, - pub fn hash(self: TupleValue, hasher: *Hash) void { + pub fn hash(self: TupleValue, ip: *const InternPool, hasher: *Hash) void { + _ = ip; std.hash.autoHash(hasher, self.type); std.hash.autoHashStrat(hasher, self.items, .Deep); } @@ -1075,9 +1057,10 @@ pub const Key = union(enum) { ty: Type, owner_decl: DeclIndex, teg_body_inst: TrackedInst.Index, - analysis: ExtraIndex(AnalysisState), + analysis: Extra.Index(AnalysisState), - pub fn hash(self: Func, hasher: *Hash) void { + pub fn hash(self: Func, ip: *const InternPool, hasher: *Hash) void { + _ = ip; std.hash.autoHash(hasher, self.ty); std.hash.autoHash(hasher, self.owner_decl); std.hash.autoHash(hasher, self.teg_body_inst); @@ -1095,7 +1078,6 @@ pub const Key = union(enum) { } pub fn hash64(key: Key, ip: *const InternPool) u64 { - _ = ip; // autofix const t = tracer.trace(@src()); defer t.end(); @@ -1114,8 +1096,8 @@ pub const Key = union(enum) { .float, .comptime_float => |x| Hash.hash(seed, asBytes(&Float.assertPure(x))), inline .function_type, .func, - .struct_type, .struct_value, + .struct_type, .array_value, .array_type, .external_value, @@ -1123,14 +1105,13 @@ pub const Key = union(enum) { .tuple_value, => |x| { var hasher = Hash.init(seed); - x.hash(&hasher); + x.hash(ip, &hasher); return hasher.final(); }, }; } pub fn eql(a: Key, b: Key, ip: *const InternPool) bool { - _ = ip; // autofix const t = tracer.trace(@src()); defer t.end(); @@ -1148,7 +1129,7 @@ pub const Key = union(enum) { => |x| std.mem.eql(u8, x, b.string), .float => |x| Float.assertPure(x) == Float.assertPure(b.float), .comptime_float => |x| Float.assertPure(x) == Float.assertPure(b.comptime_float), - .function_type => |x| x.eql(b.function_type), + .function_type => |x| x.eql(b.function_type, ip), .func => |a_info| { const b_info = b.func; if (a_info.owner_decl != b_info.owner_decl) @@ -1165,7 +1146,7 @@ pub const Key = union(enum) { .struct_value => |a_info| { const b_info = b.struct_value; return a_info.ty == b_info.ty and - std.mem.eql(Value, a_info.field_values, b_info.field_values); + std.mem.eql(Value, a_info.field_values.get(ip), b_info.field_values.get(ip)); }, .array_type => |a_info| { return a_info.item_type == b.array_type.item_type; @@ -1173,20 +1154,21 @@ pub const Key = union(enum) { .array_value => |a_info| { const b_info = b.array_value; return a_info.type == b_info.type and - std.mem.eql(Value, a_info.items, b_info.items); + std.mem.eql(Value, a_info.items.get(ip), b_info.items.get(ip)); }, .external_value => |a_info| { const b_info = b.external_value; - return std.mem.eql(NullTerminatedString, a_info.key, b_info.key); + return std.mem.eql(NullTerminatedString, a_info.key.get(ip), b_info.key.get(ip)); }, .tuple_type => |a_info| { const b_info = b.tuple_type; - return a_info.items.len == b_info.items.len and std.mem.eql(Type, a_info.items, b_info.items); + return a_info.items.len == b_info.items.len and + std.mem.eql(Type, a_info.items.get(ip), b_info.items.get(ip)); }, .tuple_value => |a_info| { const b_info = b.tuple_value; return a_info.type == b_info.type and - std.mem.eql(Value, a_info.items, b_info.items); + std.mem.eql(Value, a_info.items.get(ip), b_info.items.get(ip)); }, }; } @@ -1438,8 +1420,8 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { data.decode(.struct_type, item.tag).getTrail(ip); return .{ .struct_type = .{ - .field_keys = trail.readNullTerminatedStringSlice(payload.fields_len).get(ip), - .field_types = trail.readTypeSlice(payload.fields_len).get(ip), + .field_keys = trail.readNullTerminatedStringSlice(payload.fields_len), + .field_types = trail.readTypeSlice(payload.fields_len), } }; }, .struct_value => { @@ -1449,8 +1431,10 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { return .{ .struct_value = .{ .ty = payload.ty, .field_values = trail.readValueSlice( - payload.ty.value().valueData(ip, .struct_type).get(ip).fields_len, - ).get(ip), + payload.ty.value() + .valueData(ip, .struct_type) + .get(ip).fields_len, + ), } }; }, .array_type => { @@ -1463,31 +1447,34 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key { const payload: Tag.ArrayValue, var trail = data.decode(.array_value, item.tag).getTrail(ip); return .{ .array_value = .{ .type = payload.type, - .items = trail.readValueSlice(payload.items_len).get(ip), + .items = trail.readValueSlice(payload.items_len), } }; }, .external_value => { const payload: Tag.ExternalValue, var trail = data.decode(.external_value, item.tag).getTrail(ip); return .{ .external_value = .{ - .key = trail.readNullTerminatedStringSlice(payload.key_count).get(ip), + .key = trail.readNullTerminatedStringSlice(payload.key_count), } }; }, .tuple_type => { const payload: Tag.TupleType, var trail = data.decode(.tuple_type, item.tag).getTrail(ip); return .{ .tuple_type = .{ - .items = trail.readTypeSlice(payload.items_len).get(ip), + .items = trail.readTypeSlice(payload.items_len), } }; }, .tuple_value => { const payload: Tag.TupleValue, var trail = data.decode(.tuple_value, item.tag).getTrail(ip); return .{ .tuple_value = .{ .type = payload.type, - .items = trail.readValueSlice(payload.items_len).get(ip), + .items = trail.readValueSlice(payload.items_len), } }; }, }; } +// TODO: this function currently has a soft leak as `Key` requires slices into +// InternPool, so querying for a something with a slice (function_type) will +// always append it to `.extra` even if it becomes unused. pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { const t = tracer.traceFmt(@src(), "{s}", .{@tagName(key)}); defer t.end(); @@ -1536,25 +1523,25 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { break :brk Item.encode(.func, extra); }, .function_type => |f| brk: { - for (f.param_types) |ty| ty.assertValid(ip); + for (f.param_types.get(ip)) |ty| ty.assertValid(ip); const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.FunctionType, .{ .params_len = @intCast(f.param_types.len), .return_type = .init(f.return_type), }, f.param_types.len); defer trail.end(); - trail.writeSlice(f.param_types); + trail.writeSlice(f.param_types.get(ip)); break :brk Item.encode(.function_type, extra); }, .struct_type => |s| brk: { - for (s.field_types) |ty| ty.assertValid(ip); + for (s.field_types.get(ip)) |ty| ty.assertValid(ip); assert(s.field_keys.len == s.field_types.len); const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.StructType, .{ .fields_len = @intCast(s.field_keys.len), }, s.field_keys.len * 2); - trail.writeSlice(s.field_keys); - trail.writeSlice(s.field_types); + trail.writeSlice(s.field_keys.get(ip)); + trail.writeSlice(s.field_types.get(ip)); break :brk Item.encode(.struct_type, extra); }, @@ -1563,7 +1550,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.StructValue, .{ .ty = s.ty, }, s.field_values.len); - trail.writeSlice(s.field_values); + trail.writeSlice(s.field_values.get(ip)); break :brk Item.encode(.struct_value, extra); }, .array_type => |s| brk: { @@ -1576,21 +1563,21 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { .type = s.type, .items_len = @intCast(s.items.len), }, s.items.len); - trail.writeSlice(s.items); + trail.writeSlice(s.items.get(ip)); break :brk Item.encode(.array_value, extra); }, .external_value => |s| brk: { const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.ExternalValue, .{ .key_count = @intCast(s.key.len), }, s.key.len); - trail.writeSlice(s.key); + trail.writeSlice(s.key.get(ip)); break :brk Item.encode(.external_value, extra); }, .tuple_type => |s| brk: { const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TupleType, .{ .items_len = @intCast(s.items.len), }, s.items.len); - trail.writeSlice(s.items); + trail.writeSlice(s.items.get(ip)); break :brk Item.encode(.tuple_type, extra); }, .tuple_value => |s| brk: { @@ -1598,7 +1585,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { .type = s.type, .items_len = @intCast(s.items.len), }, s.items.len); - trail.writeSlice(s.items); + trail.writeSlice(s.items.get(ip)); break :brk Item.encode(.tuple_value, extra); }, }; @@ -1613,13 +1600,13 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { fn extraTypeFunction( ip: *const InternPool, - extra_index: ExtraIndex(Tag.FunctionType), + extra_index: Extra.Index(Tag.FunctionType), ) Key.FunctionType { const function_type, var trail = extra_index.getTrail(ip); const param_types = trail.readTypeSlice(function_type.params_len); const result: Key.FunctionType = .{ .return_type = function_type.return_type.unwrap(), - .param_types = param_types.get(ip), + .param_types = param_types, }; return result; } diff --git a/src/core/Report.zig b/src/core/Report.zig index c2140a8f559ae83e59fd513d8f058c1654fcd10b..af29be6fe03dbe55acbe7d5e4bb337347691cbbf 100644 --- a/src/core/Report.zig +++ b/src/core/Report.zig @@ -111,8 +111,7 @@ pub const Err = union(enum) { }, no_main_func: struct { fn build(_: @This(), ctx: *MsgContext) !void { - // TODO: variant that includes file, but no location - try ctx.msgToken(0, "missing top-level function \"main\"", .{}); + try ctx.msg(.{ .codepoint_eol = 0 }, "missing top-level function \"main\"", .{}); } }, unknown_identifier: struct { @@ -455,7 +454,8 @@ pub const Err = union(enum) { data.ty.displayName(ctx.ip()), formatId( data.ty.toStructType(ctx.ip()).? - .field_keys[data.field].toSlice(ctx.ip()), + .field_keys.at(ctx.ip(), data.field) + .toSlice(ctx.ip()), ), }, ); @@ -872,7 +872,7 @@ fn formatItem( try tty.setColor(writer, .reset); try tty.setColor(writer, prefix.color()); try tty.setColor(writer, .bold); - try writer.writeBytesNTimes("━", at.slice.len); + try writer.writeBytesNTimes("━", @max(at.slice.len, 1)); try tty.setColor(writer, .reset); try writer.writeAll("\n"); } @@ -1074,11 +1074,10 @@ pub const Highlight = union(enum) { .byte = b, .slice = ast.utf8_source[b..][0..len], .loc = .{ - // TODO: .column = .start, .line = .start, - .line_start = 0, - .line_end = @intCast(ast.utf8_source.len), + .line_start = b, + .line_end = @intCast(b + len), }, }; }, diff --git a/src/core/Sema.zig b/src/core/Sema.zig index e4ae44be6e1ce2193fbf37a49f0d6552465a6ec6..e28f9493f9bf793f08e2b5977d7c6d1030bb2c4a 100644 --- a/src/core/Sema.zig +++ b/src/core/Sema.zig @@ -547,7 +547,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) return return_type.value().toAir(); }, else => return s.addRef(.arg, .{ - .type = proto.param_types[param.data], + .type = proto.param_types.at(s.ip, param.data), .arg_number = param.data, }), } @@ -664,8 +664,8 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) } return try s.makeValueAir(.{ .struct_type = .{ - .field_keys = scratch.items(.keys), - .field_types = scratch.items(.types), + .field_keys = try s.ip.extra.appendSlice(s.gpa, scratch.items(.keys)), + .field_types = try s.ip.extra.appendSlice(s.gpa, scratch.items(.types)), } }); }, .discard => { @@ -689,7 +689,10 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) @panic("TODO: support strings with null here"); }; return try s.makeValueAir(.{ .external_value = .{ - .key = &.{str}, + .key = try s.ip.extra.appendSlice( + s.gpa, + @as([]const NullTerminatedString, &.{str}), + ), } }); }, .field => { @@ -708,13 +711,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) const previous_key = value.toKey(s.ip).external_value.key; const new_key = try std.mem.concat(s.arena(), NullTerminatedString, &.{ - previous_key, + previous_key.get(s.ip), &.{field_key}, }); defer s.arena().free(new_key); return s.makeValueAir(.{ .external_value = .{ - .key = new_key, + .key = try s.ip.extra.appendSlice(s.gpa, new_key), } }); } } @@ -764,7 +767,11 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) } // validate function args - for (proto.param_types, call.args.slice(&s.hir), 0..) |param, arg, i| { + for ( + proto.param_types.get(s.ip), + call.args.slice(&s.hir), + 0.., + ) |param, arg, i| { const ref = s.getRef(arg, .marked); const coerced = try s.coerceRef( s.lazyNode(.argument(@intCast(i))), @@ -838,19 +845,23 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| { value.* = ref.decode().value; } - return try s.makeValueAir(.{ .array_value = .{ - .type = ty, - .items = result_storage, - } }); + return try s.makeValueAir(.{ + .array_value = .{ + .type = ty, + // TODO: do not clone memory here + .items = try s.ip.extra.appendSlice(s.gpa, result_storage), + }, + }); } else { // Create a runtime array instantiation try s.ensureAtRuntime(s.lazyNode(.main_node)); return try s.addRef(.array_init, .{ .ty = ty, - .list = try s.func.?.extra.append(s.gpa, Air.RefList, .{ - .start = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()), - .len = node.list.len, - }), + .list = try s.func.?.extra.append( + s.gpa, + Air.RefList, + try s.func.?.extra.appendSlice(s.gpa, scratch.slice()), + ), }); } }, @@ -1017,7 +1028,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) const brk: Hir.Break = s.frame.currentPayload(.@"break"); const operand = s.getRef(brk.operand, .marked); - assert(s.frame.block_index == brk.block); + if (s.frame.block_index != brk.block) @panic("TODO"); const mark = core.U32Stack.Mark{ .stack = &s.scratch, .index = s.frame.mark_index }; @@ -1028,7 +1039,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) const coerced = if (proto.return_type) |return_coercion| try s.coerceRef(s.lazyNode(.operand), operand, return_coercion) else - operand; + try s.ensureRefRuntime(s.lazyNode(.operand), operand); s.inst_map.add(s.frame.block_index, coerced); const indexes = mark.slice(u32); @@ -1388,7 +1399,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) // may be different than the instantiation. The fields are visited in // order of the instantiation. for (node.keys(&s.hir), node.inits(&s.hir), 0..) |init_key, init_value, i| { - const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| { + const found_index = index: for (struct_type.field_keys.get(s.ip), 0..) |decl_key, j| { if (decl_key.eqlSlice(init_key.slice(s.hir.string_bytes), s.ip)) break :index j; } else { @@ -1410,7 +1421,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) const coerced = s.getCoercedRef( s.lazyNode(.argument(@intCast(i))), init_value, - struct_type.field_types[found_index], + struct_type.field_types.at(s.ip, found_index), .marked, ) catch |e| switch (e) { error.AnalysisFail => { @@ -1452,7 +1463,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) } return try s.makeValueAir(.{ .struct_value = .{ .ty = ty, - .field_values = result_storage, + .field_values = try s.ip.extra.appendSlice(s.gpa, result_storage), } }); } else { try s.ensureAtRuntime(s.lazyNode(.main_node)); @@ -1460,7 +1471,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); return try s.addRef(.struct_init, .{ .ty = ty, - .field_values = value_index, + .field_values = value_index.start, }); } }, @@ -1481,7 +1492,9 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) dst.* = try s.getType(s.lazyNode(.argument(@intCast(i))), src); } - return s.makeValueAir(.{ .tuple_type = .{ .items = scratch.slice() } }); + return s.makeValueAir(.{ .tuple_type = .{ + .items = try s.ip.extra.appendSlice(s.gpa, scratch.slice()), + } }); }, .tuple_init => { @@ -1515,7 +1528,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) ty.* = s.typeOfRef(val); } - break :ty try s.makeType(.{ .tuple_type = .{ .items = type_scratch.slice() } }); + break :ty try s.makeType(.{ + .tuple_type = .{ + // TODO: memory leak + .items = try s.ip.extra.appendSlice(s.gpa, type_scratch.slice()), + }, + }); }; if (is_const) { @@ -1525,14 +1543,17 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) value.* = ref.decode().value; } - return try s.makeValueAir(.{ .tuple_value = .{ - .items = result_storage, - .type = ty, - } }); + return try s.makeValueAir(.{ + .tuple_value = .{ + // TODO: memory leak + .items = try s.ip.extra.appendSlice(s.gpa, result_storage), + .type = ty, + }, + }); } else { const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()); return try s.addRef(.struct_init, .{ - .field_values = value_index, + .field_values = value_index.start, .ty = ty, }); } @@ -1550,7 +1571,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) if (result.argument >= func.param_types.len) return Value.opaque_type.toAir(); - return func.param_types[result.argument].value().toAir(); + return func.param_types.at(s.ip, result.argument).value().toAir(); }, .result_type_array_value => { @@ -1575,7 +1596,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) _ = ty; - @panic("TODO: tuple result type resolution"); // tuples are not implemented + @panic("TODO: tuple result type resolution"); }, .result_type_struct_field => { @@ -1589,7 +1610,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) return Value.opaque_type.toAir(); const to_match = result.field.slice(s.hir.string_bytes); - for (struct_type.field_keys, struct_type.field_types) |k, v| { + for (struct_type.field_keys.get(s.ip), struct_type.field_types.get(s.ip)) |k, v| { if (k.eqlSlice(to_match, s.ip)) { return v.value().toAir(); } @@ -1857,9 +1878,14 @@ fn coerceRef(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref /// Same as `coerceRef` but on a compile-time known value fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value { const from = value.typeOf(s.ip); + + from.assertValid(s.ip); + to.assertValid(s.ip); + if (from == to) { return value; } + switch (to) { .@"opaque" => { return try s.makeValue(.{ .opaque_value = value }); @@ -2069,7 +2095,7 @@ fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Val return try s.makeValue(.{ .array_value = .{ .type = try s.makeType(.{ .array_type = .{ .item_type = item_type } }), - .items = scratch.slice(), + .items = try s.ip.extra.appendSlice(s.gpa, scratch.slice()), } }); } @@ -2096,8 +2122,8 @@ fn coerceTupleValue( // This function will append new items to the intern pool, invalidating // existing ones. Copy all data into a stable slice first. - @memcpy(scratch.items(.values), tuple_value.items); - @memcpy(scratch.items(.types), target_key.items); + @memcpy(scratch.items(.values), tuple_value.items.get(s.ip)); + @memcpy(scratch.items(.types), target_key.items.get(s.ip)); for (0..len) |i| { // `s.scratch` can be invalidated by having a tuple of tuples and coercing it. @@ -2107,7 +2133,7 @@ fn coerceTupleValue( return try s.makeValue(.{ .tuple_value = .{ .type = target_type, - .items = scratch.items(.values), + .items = try s.ip.extra.appendSlice(s.gpa, scratch.items(.values)), } }); } @@ -2205,7 +2231,7 @@ fn fieldAccessStruct( else => |e| return e, }; - break :found_index for (struct_type.field_keys, 0..) |key, i| { + break :found_index for (struct_type.field_keys.get(s.ip), 0..) |key, i| { if (key == lookup) break @as(u32, @intCast(i)); } else null; @@ -2341,7 +2367,7 @@ fn fieldAccessTuple( switch (lhs.decode()) { .value => |value| { - return value.toKey(s.ip).tuple_value.items[index].toAir(); + return value.toKey(s.ip).tuple_value.items.at(s.ip, index).toAir(); }, .index => |lhs_index| { try s.ensureAtRuntime(s.lazyNode(.lhs)); @@ -2359,11 +2385,10 @@ fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ? const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex()); const func_type = try s.makeType(.{ .function_type = .{ - .param_types = reserve.items(.params), + .param_types = try s.ip.extra.appendSlice(s.gpa, reserve.items(.params)), .return_type = return_type, }, }); - // TODO: make a dedicated function to insert a new function const func_value = try s.makeValue(.{ .func = .{ .ty = func_type, diff --git a/src/core/array.zig b/src/core/array.zig index b307f89555c421e4527f60e0d654f9df52474e7e..5bf96199735255abf1fa4a757d5aace718d6082c 100644 --- a/src/core/array.zig +++ b/src/core/array.zig @@ -81,24 +81,14 @@ pub const StoreKind = enum { air, js_tree, - pub fn resolveStoreConst( + pub fn resolveStore( comptime kind: StoreKind, owner: anytype, - ) *const kind.ResolvedStore(@TypeOf(owner)) { + ) kind.ResolvedStorePtr(@TypeOf(owner)) { return &owner.extra; } - pub fn resolveStoreMut( - comptime kind: StoreKind, - owner: anytype, - ) *kind.ResolvedStore(@TypeOf(owner)) { - return &owner.extra; - } - - pub fn ResolvedStore( - comptime kind: StoreKind, - comptime Owner: type, - ) type { + pub fn ResolvedStore(kind: StoreKind, Owner: type) type { const ptr = @typeInfo(Owner).pointer; assert(ptr.size == .One); const mutability = brk: for (@typeInfo(ptr.child).@"struct".fields) |field| { @@ -108,43 +98,33 @@ pub const StoreKind = enum { } else unreachable; return ExtraStore(kind, mutability); } + + pub fn ResolvedStorePtr(kind: StoreKind, Owner: type) type { + return kind.ItemPointer(Owner, .One, kind.ResolvedStore(Owner)); + } + + pub fn ItemPointer( + kind: StoreKind, + Owner: type, + size: std.builtin.Type.Pointer.Size, + child: type, + ) type { + const ptr = @typeInfo(Owner).pointer; + return @Type(.{ + .pointer = .{ + .size = size, + .is_const = ptr.is_const or ResolvedStore(kind, Owner).mutability == .@"const", + .is_volatile = false, + .is_allowzero = false, + .alignment = @alignOf(child), + .address_space = .generic, + .child = child, + .sentinel = null, + }, + }); + } }; -fn ExtraWrap(comptime kind: StoreKind) type { - return struct { - pub fn Index(comptime T: type) type { - return enum(u32) { - _, - - pub fn get(index: @This(), owner: anytype) T { - return kind.resolveStoreConst(owner).get(T, @intFromEnum(index)); - } - - pub fn set(index: @This(), owner: anytype, payload: T) void { - return kind.resolveStoreMut(owner).set(T, @intFromEnum(index), payload); - } - - /// Returns data with a trailing reader to read data stored immediately after - /// the main data structure. What is stored should be documented on `T` - pub fn getTrail(index: @This(), owner: anytype) struct { - T, - kind.ResolvedStore(@TypeOf(owner)).TrailReader, - } { - const advance = comptime extraSizeOf(T); - return .{ - index.get(owner), - .{ .index = @intFromEnum(index) + advance }, - }; - } - - pub fn jsonStringify(index: @This(), jw: anytype) !void { - try jw.write(@intFromEnum(index)); - } - }; - } - }; -} - pub fn EncodedData(comptime Tag: type, backing_type: type) type { return enum(backing_type) { _, @@ -230,23 +210,89 @@ pub inline fn extraSizeOf(comptime T: type) usize { }; } -// TODO: rename to Store -pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutability) type { +pub const ExtraStore = Store; + +/// A data structure to manage a `[]u32` with mixed data types. Provides +/// `.Index` and `.Slice` to keep strongly typed indices to items within the +/// store. +pub fn Store(set_kind: StoreKind, set_mutability: Mutability) type { + // Separate struct to avoid capturing set_mutability + const wrappers = struct { + pub const kind = set_kind; + + pub fn Index(T: type) type { + return enum(u32) { + _, + + pub fn get(index: @This(), owner: anytype) T { + return kind.resolveStore(owner).get(T, @intFromEnum(index)); + } + + pub fn set(index: @This(), owner: anytype, payload: T) void { + return kind.resolveStore(owner).set(T, @intFromEnum(index), payload); + } + + /// Returns data with a trailing reader to read data stored immediately after + /// the main data structure. What is stored should be documented on `T` + pub fn getTrail(index: @This(), owner: anytype) struct { + T, + kind.ResolvedStore(@TypeOf(owner)).TrailReader, + } { + const advance = comptime extraSizeOf(T); + return .{ + index.get(owner), + .{ .index = @intFromEnum(index) + advance }, + }; + } + + pub fn jsonStringify(index: @This(), jw: anytype) !void { + try jw.write(@intFromEnum(index)); + } + }; + } + + /// An array of `T` existing within the `extra` array. + /// This type exists to provide a struct with lifetime that is + /// not invalidated when items are added to the `InternPool`. + pub fn Slice(T: type) type { + return struct { + start: u32, + len: u32, + + pub const empty: @This() = .{ + .start = 0, + .len = 0, + }; + + pub fn get(slice: @This(), owner: anytype) kind.ItemPointer(@TypeOf(owner), .Slice, T) { + return @ptrCast(kind.resolveStore(owner).list.items[slice.start..][0..slice.len]); + } + + pub fn at(slice: @This(), owner: anytype, index: usize) T { + return slice.get(owner)[index]; + } + }; + } + }; + return struct { - list: List = .{}, + const Self = @This(); - pub const empty: @This() = .{}; + list: Inner, + + pub const empty: @This() = .{ .list = .empty }; pub const kind = set_kind; pub const mutability = set_mutability; - const Store = @This(); + pub const Index = wrappers.Index; + pub const Slice = wrappers.Slice; - pub const Index = ExtraWrap(kind).Index; - - pub const List = switch (mutability) { + pub const Inner = switch (mutability) { .@"const" => struct { - items: []const u32 = &.{}, + items: []const u32, + + pub const empty = .{ .items = &.{} }; pub fn deinit(slice: *@This(), gpa: Allocator) void { gpa.free(slice.items); @@ -255,7 +301,7 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil .@"var" => std.ArrayListUnmanaged(u32), }; - pub fn deinit(store: *Store, gpa: Allocator) void { + pub fn deinit(store: *Self, gpa: Allocator) void { store.list.deinit(gpa); store.* = undefined; } @@ -263,12 +309,12 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil pub const TrailReader = struct { index: usize, - pub fn read(trail: *TrailReader, ip: *const Store, comptime T: type) T { + pub fn read(trail: *TrailReader, ip: *const Self, comptime T: type) T { defer trail.index += comptime extraSizeOf(T); return ip.get(T, trail.index); } - pub fn at(trail: *const TrailReader, ip: *const Store, comptime T: type, index: u32) T { + pub fn at(trail: *const TrailReader, ip: *const Self, comptime T: type, index: u32) T { return ip.get(T, trail.index + index); } @@ -301,10 +347,10 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil }; pub const TrailWriter = struct { - store: *Store, + store: *Self, leftover: usize, - pub fn init(store: *Store, len: usize) TrailWriter { + pub fn init(store: *Self, len: usize) TrailWriter { return .{ .store = store, .leftover = len }; } @@ -332,13 +378,13 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil } }; - pub fn append(store: *Store, gpa: Allocator, comptime T: type, payload: T) Allocator.Error!Index(T) { + pub fn append(store: *Self, gpa: Allocator, comptime T: type, payload: T) Allocator.Error!Index(T) { comptime assert(mutability == .@"var"); try store.list.ensureUnusedCapacity(gpa, extraSizeOf(@TypeOf(payload))); return store.appendAssumeCapacity(T, payload); } - pub fn appendAssumeCapacity(ip: *Store, comptime T: type, payload: T) Index(T) { + pub fn appendAssumeCapacity(ip: *Self, comptime T: type, payload: T) Index(T) { comptime assert(mutability == .@"var"); const extra_index: u32 = @intCast(ip.list.items.len); ip.list.items.len += extraSizeOf(@TypeOf(payload)); @@ -347,7 +393,7 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil } pub fn appendTrailing( - store: *Store, + store: *Self, gpa: Allocator, comptime T: type, payload: T, @@ -365,32 +411,34 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil } // TODO: make setAsU32 allow any type - pub fn setU32(store: *Store, index: usize, payload: u32) void { + pub fn setU32(store: *Self, index: usize, payload: u32) void { comptime assert(mutability == .@"var"); store.list.items[index] = payload; } - pub fn set(store: *Store, comptime T: type, index: usize, payload: T) void { + pub fn set(store: *Self, comptime T: type, index: usize, payload: T) void { comptime assert(mutability == .@"var"); setAsU32(store.list.items, T, index, payload); } - pub fn get(store: *const Store, comptime T: type, index: usize) T { + pub fn get(store: *const Self, comptime T: type, index: usize) T { return getAsU32(store.list.items, T, index); } - pub fn toOwnedStore(store: *Store, gpa: Allocator) !ExtraStore(set_kind, .@"const") { + pub fn toOwnedStore(store: *Self, gpa: Allocator) !ExtraStore(set_kind, .@"const") { comptime assert(mutability == .@"var"); return .{ .list = .{ .items = try store.list.toOwnedSlice(gpa) } }; } - pub fn constant(store: *const Store) ExtraStore(set_kind, .@"const") { + /// Returns a `const` store pointing at the mutable's data. + /// Invalidated when the mutable store is mutated. + pub fn constant(store: *const Self) ExtraStore(set_kind, .@"const") { return .{ .list = .{ .items = store.list.items } }; } // TODO: Add some safety to the Index returned from this /// Allocate a number of u32s into this store, but they are left uninitialized. - pub fn alloc(store: *Store, gpa: Allocator, len: usize) !u32 { + pub fn alloc(store: *Self, gpa: Allocator, len: usize) !u32 { comptime assert(mutability == .@"var"); const idx = store.list.items.len; try store.list.ensureUnusedCapacity(gpa, len); @@ -398,13 +446,15 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil return @intCast(idx); } - pub fn appendSlice(store: *Store, gpa: Allocator, data: anytype) !u32 { + pub fn appendSlice(store: *Self, gpa: Allocator, data: anytype) !Slice(std.meta.Child(@TypeOf(data))) { const idx = store.list.items.len; + comptime assert(@typeInfo(@TypeOf(data)).pointer.size == .Slice); + comptime assert(@typeInfo(@TypeOf(data)).pointer.alignment == @alignOf(u32)); comptime assert(@sizeOf(@TypeOf(data[0])) == @sizeOf(u32)); comptime assert(@alignOf(@TypeOf(data[0])) == @alignOf(u32)); try store.list.appendSlice(gpa, @ptrCast(data)); store.list.items.len += data.len; - return @intCast(idx); + return .{ .start = @intCast(idx), .len = @intCast(data.len) }; } }; } diff --git a/src/core/js/Link.zig b/src/core/js/Link.zig index 7da6da8e6db045a873a40af8b03781a3686a4001..6f87b60c9a1182a95064f58cef5037ae1c9b4dc7 100644 --- a/src/core/js/Link.zig +++ b/src/core/js/Link.zig @@ -284,8 +284,9 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp try writer.writeAll("{"); for ( - struct_value.ty.toStructType(&link.comp.intern_pool).?.field_keys, - struct_value.field_values, + struct_value.ty.toStructType(&link.comp.intern_pool).? + .field_keys.get(&link.comp.intern_pool), + struct_value.field_values.get(&link.comp.intern_pool), 0.., ) |key, inner_value, i| { if (i != 0) @@ -305,7 +306,7 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp .array_value => |array_value| { try writer.writeAll("["); - for (array_value.items, 0..) |inner_value, i| { + for (array_value.items.get(&link.comp.intern_pool), 0..) |inner_value, i| { if (i != 0) try writer.writeAll(","); @@ -317,7 +318,7 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp .tuple_value => |tuple_value| { try writer.writeAll("["); - for (tuple_value.items, 0..) |inner_value, i| { + for (tuple_value.items.get(&link.comp.intern_pool), 0..) |inner_value, i| { if (i != 0) try writer.writeAll(","); @@ -327,7 +328,7 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp }, .external_value => |external_value| { - for (external_value.key, 0..) |part, i| { + for (external_value.key.get(&link.comp.intern_pool), 0..) |part, i| { if (i != 0) try writer.writeAll("."); try writer.writeAll(part.toSlice(&link.comp.intern_pool)); } @@ -477,11 +478,15 @@ fn printExprLevel( }, .comma_chain => { + if (Level.comma.needsParens(level)) + try writer.writeAll("("); const bin: Tree.IndexList = tree.nodeData(.comma_chain, node); for (bin.slice(tree), 0..) |expr, i| { if (i != 0) try writer.writeAll(", "); try link.printExprLevel(tree, expr, .assignment, writer); } + if (Level.comma.needsParens(level)) + try writer.writeAll(")"); }, inline .negate, @@ -503,9 +508,9 @@ fn printExprLevel( try writer.writeAll("("); try link.printExprLevel(tree, ternary.cond, .logical_or, writer); try writer.writeAll(" ? "); - try link.printExpr(tree, data.yes, writer); + try link.printExprLevel(tree, data.yes, .assignment, writer); try writer.writeAll(" : "); - try link.printExpr(tree, data.no, writer); + try link.printExprLevel(tree, data.no, .assignment, writer); if (wrap) try writer.writeAll(")"); }, @@ -518,7 +523,11 @@ fn printExprLevel( const k = tree.nodeData(.plain_value, call.base).value.toKey(&link.comp.intern_pool); break :brk k == .external_value and k.external_value.key.len == 1 and - std.mem.eql(u8, k.external_value.key[0].toSlice(&link.comp.intern_pool), "eval"); + std.mem.eql( + u8, + k.external_value.key.at(&link.comp.intern_pool, 0).toSlice(&link.comp.intern_pool), + "eval", + ); }) { @branchHint(.unlikely); try writer.writeAll("(0, eval)"); diff --git a/src/core/js/NodeGen.zig b/src/core/js/NodeGen.zig index 8a0086bf990ab2648c53af8cfe40beebedb6ac35..ae476c7d99037cd4b363e9289391a8715326d163 100644 --- a/src/core/js/NodeGen.zig +++ b/src/core/js/NodeGen.zig @@ -47,8 +47,8 @@ pub fn init(gpa: Allocator, arena: Allocator, ip: *InternPool, air: Air) !NodeGe .extern_symbols = .{}, .top_level_value_deps = .{}, .air_node_map = air_node_map, - .extra = .{}, - .scratch = .{}, + .extra = .empty, + .scratch = .empty, .next_id = param_count, .next_label = 0, }; @@ -100,7 +100,7 @@ pub fn build(c: *NodeGen) !void { defer t.end(); assert(c.air.instTag(.main_block) == .block); // expected Air to start with block - const statements = c.air.instData(.block, .main_block).slice(&c.air); + const statements = c.air.instData(.block, .main_block).get(&c.air); const scratch_top = c.scratch.items.len; defer c.scratch.shrinkRetainingCapacity(scratch_top); @@ -301,7 +301,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { .field = field_name: { break :field_name c.air.typeOfIndex(data.base, c.ip) .toStructType(c.ip).? - .field_keys[data.field_index]; + .field_keys.at(c.ip, data.field_index); }, }); try c.markExpression(inst, node); @@ -338,7 +338,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { const items = c.extra.list.items[start..][0..call_data.args.len]; - for (call_data.args.slice(&c.air), items) |arg, *dest| { + for (call_data.args.get(&c.air), items) |arg, *dest| { dest.* = @intFromEnum(try c.fetchExpression(arg)); } @@ -457,7 +457,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { // // This is also required for the main function, which cannot return // anything. We don't want to emit return instructions here. - const main_insts = c.air.instData(.block, .main_block).slice(&c.air); + const main_insts = c.air.instData(.block, .main_block).get(&c.air); if (main_insts[main_insts.len - 1] == inst) { return; } @@ -472,7 +472,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { const operand = try c.fetchExpression(brk.value); const block = &c.air_node_map[@intFromEnum(brk.block)].block; - const block_insts = c.air.instData(.block, brk.block).slice(&c.air); + const block_insts = c.air.instData(.block, brk.block).get(&c.air); if (block_insts[block_insts.len - 1] != inst) { block.used = true; } @@ -499,7 +499,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { const store_offset = try c.extra.alloc(c.gpa, property_count * 2); for ( - struct_type.field_keys, + struct_type.field_keys.get(c.ip), struct_init.valuesPtr(&c.air), store_offset.., store_offset + property_count.., @@ -520,7 +520,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { .array_init => { const array_init: Air.ArrayInit = c.air.instData(.array_init, inst); - const items = array_init.list.get(&c.air).slice(&c.air); + const items = array_init.list.get(&c.air).get(&c.air); const store_offset = try c.extra.alloc(c.gpa, items.len); for ( @@ -653,7 +653,12 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { .external_value => { const external = v.toKey(c.ip).external_value; - try c.extern_symbols.put(c.gpa, external.key[0].toString().withLen(external.key[0].length(c.ip)), {}); + const first_key = external.key.at(c.ip, 0); + try c.extern_symbols.put( + c.gpa, + first_key.toString().withLen(first_key.length(c.ip)), + {}, + ); return try c.add(.plain_value, .{ .value = v }); }, @@ -730,7 +735,7 @@ fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Bran }); const block: Air.IndexList = c.air.instData(.block, i); - for (block.slice(&c.air)) |item| { + for (block.get(&c.air)) |item| { try c.codeGenInst(item); } @@ -781,7 +786,7 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { }; const block: Air.IndexList = c.air.instData(.block, inst); - for (block.slice(&c.air)) |item| { + for (block.get(&c.air)) |item| { try c.codeGenInst(item); } @@ -814,7 +819,7 @@ fn codeGenBlockAsExpression(c: *NodeGen, inst: Air.Inst.Index) !Node.Index { .block = .{ .index = @intCast(scratch_top) }, }; const block: Air.IndexList = c.air.instData(.block, inst); - const instructions = block.slice(&c.air); + const instructions = block.get(&c.air); for (instructions[0 .. instructions.len - 1]) |item| { try c.codeGenInst(item); } @@ -885,7 +890,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { // are lowerable. The break at the end just becomes the result. .block => { const block: Air.IndexList = c.air.instData(.block, index); - const items = block.slice(&c.air); + const items = block.get(&c.air); for (items[0 .. items.len - 1]) |item| { if (c.air.instFlags(item).uses == .multi) return false; diff --git a/src/manual/generate_manual.zig b/src/manual/generate_manual.zig index 8bbe0bbcafe28c3f697d9207a8f14f124d83cb06..918ad8fdb18c620a8729f42c6513e964921bfb59 100644 --- a/src/manual/generate_manual.zig +++ b/src/manual/generate_manual.zig @@ -70,7 +70,7 @@ pub fn main() !void { .contents = contents.items, .navigation = header.data.items, .nav_h1 = "Ix Language", - .repository = "https://github.com/paperdave/wl", + .repository = "https://github.com/paperdave/ix", .style = minified_style, }); @@ -83,7 +83,7 @@ pub fn main() !void { std.debug.lockStdErr(); std.debug.unlockStdErr(); - std.process.exit(0); + std.process.exit(if (header.had_signaled) 1 else 0); } const BlockType = enum { @@ -190,6 +190,8 @@ const HeaderWriter = struct { has_closed_li: bool = true, + had_signaled: bool = false, + fn add(header: *HeaderWriter, alloc: Allocator, name: []const u8, id: []const u8, level: u8) !void { const w = header.data.writer(alloc); @@ -440,6 +442,10 @@ fn processMarkdown( try build.collectOutput(&out, &err, std.math.maxInt(usize)); const result = try build.wait(); + if (result == .Signal) { + std.debug.print("{s}\n", .{err.items}); + header.had_signaled = true; + } if (result != .Exited or result.Exited != 0) { try err.writer().print("[Process {s} with {d}]", .{ switch (result) { @@ -530,7 +536,9 @@ fn processMarkdown( } return e; }; - + if (run_result == .Signal) { + header.had_signaled = true; + } if (run_result != .Exited or run_result.Exited != 0) { try run_err.writer().print("[Process {s} with {d}]", .{ switch (run_result) { -- 2.54.0