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) {