From 974964a59da977061f958f51c142256ece5f30ea Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Sat, 25 May 2024 02:35:49 -0700 Subject: [PATCH] split InternPool.Index into Type + Value this ends up regressing a lot of the compiler's ability to work, but i have found many many many cases where a comptime value should have only ever been a type, but we previously allowed any value. also a revelation that `validateType` is not going to cut it- we need to have a Teg ref for any type validation, and it should be a *coersion*. the difference is these new coersion functions return !Value or !Air.Inst.Ref instead of just a !void (error being a coersion error) --- README.md | 12 +- demo.wl | 1 + notes | 19 ++ src/compiler/Air.Dump.zig | 19 +- src/compiler/Air.zig | 43 ++--- src/compiler/Comp.zig | 3 +- src/compiler/InternPool.zig | 199 ++++++++++++++++----- src/compiler/JsLink.zig | 2 + src/compiler/JsNodeGen.zig | 52 +++--- src/compiler/JsTree.zig | 4 +- src/compiler/Parse.zig | 5 +- src/compiler/Report.zig | 164 ++++++++++++------ src/compiler/Sema.zig | 335 +++++++++++++++++++++++------------- src/compiler/Teg.Dump.zig | 8 + src/compiler/Teg.zig | 56 ++++-- src/compiler/Tokenizer.zig | 2 +- src/compiler/Transform.zig | 36 ++-- src/compiler/Value.zig | 96 ----------- src/compiler/dod.zig | 10 +- src/compiler/util.zig | 23 ++- 20 files changed, 678 insertions(+), 411 deletions(-) create mode 100644 demo.wl create mode 100644 notes delete mode 100644 src/compiler/Value.zig diff --git a/README.md b/README.md index 753fb38ee29536347d8cc9a7a7bea9b702706c0e..39d6ece5b9759de894be7dd1ecc16e5947beb533 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ let hello = hello * world; @js("console").log(hello); ``` -`var` bindings create mutable memory locations at runtime. This means they can be re-assigned to in arbitrary ways. +`var` bindings create mutable memory locations at runtime. This means they can be re-assigned to: ``` var message = "first message"; @@ -58,7 +58,7 @@ if @js("Math").random() > 0.5 { @js("console").log(message); ``` -`let` is preferred whenever possible, as it will lead to more readable code, and the compiler is able to optimize it better. The above can be rewritten as: +Using `let` is preferred whenever possible, as it will lead to more readable code, and the compiler is able to optimize it better. The above can be rewritten as: ``` let message = if @js("Math").random() > 0.5 { @@ -70,10 +70,10 @@ let message = if @js("Math").random() > 0.5 { @js("console").log(message); ``` -The initializer for a `let` and `var` are always required, but the type annotations can be omitted when the data type can be inferred. For `let`, this is always the case. `var`, being runtime-mutable memory, only supports runtime representable data types. This means for numerical literals, an explicit type annotation is needed to determine weather to store an integer or a floating point number. +It should be noted that numeric literals are a comptime-only data type, meaning it cannot be assigned to a `var` without an explicit type annotation. Since `int` is a strict subset of valid `float`s, the coersion will implicitly be a float, but a compiler warning will be emitted. ``` -var a = 5; // error -var b: int = 5; // ok -var c: float = 5; // ok +var number = 0; // `number` is a `float`, but not explicit +var number: int = 0; // `number` is `int` +var number: float = 0; // `number` is `float` ``` diff --git a/demo.wl b/demo.wl new file mode 100644 index 0000000000000000000000000000000000000000..64d858c99d2f2b67f7a3e88fe8d826952afd76bd --- /dev/null +++ b/demo.wl @@ -0,0 +1 @@ +let x: int = 1.5; diff --git a/notes b/notes new file mode 100644 index 0000000000000000000000000000000000000000..1f212929e6c4a49a2518e629a4ccb8af102c76f3 --- /dev/null +++ b/notes @@ -0,0 +1,19 @@ +0.1.2 +- teg: useless discard +- teg: dangling identifier +- teg: remove `let _ = ` error +- sema: .as instruction +- sema: binary op type resolution (float + num_literal -> float) +0.1.3 +- parse/teg/sema: implement the comptime keyword +- implement all the binary operators +- implement all the unary operators +- implement string escapes +- implement proper float printing +- js/link: add parenthesis when needed +0.1.4 +- parse: impl module, struct define, struct init, defer, and optionals +- teg: impl the above, except anonymous struct initializers +- sema: implement some built-in functions on floats + - float.intTruncate() to perform lossy int conversion + diff --git a/src/compiler/Air.Dump.zig b/src/compiler/Air.Dump.zig index 4ee482de7cecaab7fb3a4f2c5216d5acd5398a01..eadc3420da48cf62ce6fa1d7ec52e32a15d512a9 100644 --- a/src/compiler/Air.Dump.zig +++ b/src/compiler/Air.Dump.zig @@ -2,7 +2,7 @@ air: Air, ip: *const InternPool, -const State = struct { +pub const State = struct { arena: std.mem.Allocator, air: *const Air, ip: *const InternPool, @@ -67,7 +67,7 @@ const State = struct { try w.print("{d}", .{arg.arg_number}); try tty.setColor(w, .reset); try w.writeAll(": "); - try s.dumpValue(w, arg.type); + try s.dumpValue(w, arg.type.value()); }, .@"break" => { const brk: Air.Break = air.instData(.@"break", inst); @@ -113,7 +113,7 @@ const State = struct { try w.writeAll("("); try s.dumpRef(w, memory.init); try w.writeAll(", type: "); - try s.dumpValue(w, memory.ty); + try s.dumpValue(w, memory.ty.value()); try w.writeAll(")"); }, .field => { @@ -175,7 +175,7 @@ const State = struct { try s.tty.setColor(w, .reset); } - fn dumpValue(s: *State, w: anytype, val: Value) !void { + pub fn dumpValue(s: *State, w: anytype, val: Value) !void { const tty = s.tty; switch (val.toKey(s.ip)) { .simple_type => |t| { @@ -198,14 +198,14 @@ const State = struct { try w.writeAll(","); try tty.setColor(w, .reset); } - try s.dumpValue(w, Value.fromInterned(param)); + try s.dumpValue(w, param.value()); } try tty.setColor(w, .blue); try w.writeAll(") -> "); try tty.setColor(w, .reset); - try s.dumpValue(w, Value.fromInterned(ty.return_type)); + try s.dumpValue(w, ty.return_type.value()); }, - inline .float, .int32 => |data, tag| { + inline .float, .int32, .comptime_float => |data, tag| { try tty.setColor(w, .bright_black); try w.writeAll("<"); try tty.setColor(w, .blue); @@ -235,7 +235,7 @@ const State = struct { .func => |func| { try tty.setColor(w, .bright_black); try w.writeAll("<"); - try s.dumpValue(w, Value.fromInterned(func.ty)); + try s.dumpValue(w, func.ty); try tty.setColor(w, .bright_black); try w.writeAll(", "); const decl_name = s.ip.declPtrConst(func.owner_decl).name.toSlice(s.ip); @@ -310,8 +310,7 @@ const assert = std.debug.assert; const Air = @import("Air.zig"); const Inst = Air.Inst; -const Value = @import("Value.zig"); - const InternPool = @import("InternPool.zig"); +const Value = InternPool.Value; const detectTTYFromWriter = @import("util.zig").detectTTYFromWriter; diff --git a/src/compiler/Air.zig b/src/compiler/Air.zig index 2505f7a8a6dc08039f8ba38f9ded1452bfc83dc8..f9eaa173a0648da3da0c7f8ace01c0ac44fbe14f 100644 --- a/src/compiler/Air.zig +++ b/src/compiler/Air.zig @@ -77,7 +77,7 @@ pub const Inst = struct { pub fn decode(ref: Ref) Decoded { return switch (ref.kind) { - .value => .{ .value = Value.fromInterned(@enumFromInt(ref.data)) }, + .value => .{ .value = @enumFromInt(ref.data) }, .inst => .{ .inst = @enumFromInt(ref.data) }, }; } @@ -97,7 +97,7 @@ pub const Inst = struct { pub fn isVoid(ref: Ref) bool { return ref.kind == .value and - ref.decode().value.toIntern() == .void_value; + ref.decode().value == .void; } pub fn eql(ref: Ref, other: Ref) bool { @@ -203,7 +203,7 @@ pub const Call = struct { }; pub const Arg = struct { - type: Value, + type: Type, arg_number: u32, }; @@ -214,13 +214,13 @@ pub const If = struct { pub const Data = struct { yes: Inst.Ref, no: Inst.Ref, - union_ty: Value, + union_ty: Type, }; }; pub const Memory = struct { init: Inst.Ref, - ty: Value, + ty: Type, }; pub fn deinit(air: *Air, gpa: std.mem.Allocator) void { @@ -249,16 +249,16 @@ pub fn instPayload(air: *const Air, comptime tag: Inst.Tag, index: Inst.Index) t return air.instData(tag, index).extra_index.get(air); } -pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *InternPool) InternPool.Index { +pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *InternPool) InternPool.Type { return switch (ref.decode()) { - .value => |v| v.type(ip).toIntern(), + .value => |v| v.typeOf(ip), .inst => |i| air.typeOfIndex(i, ip), }; } -pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *InternPool) InternPool.Index { +pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *InternPool) InternPool.Type { return switch (air.instTag(index)) { - .js_global_ref => .opaque_type, + .js_global_ref => .@"opaque", .block => { const items = air.instData(.block, index).slice(air); @@ -267,28 +267,28 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *InternPool) InternPo inline .add, .sub, .mul, .div => |t| air.typeOf(air.instData(t, index).lhs, ip), - .lt, .gt, .eq, .bool_not => .bool_type, + .lt, .gt, .eq, .bool_not => .bool, - .arg => air.instData(.arg, index).type.toIntern(), + .arg => air.instData(.arg, index).type, .negate => air.typeOf(air.instData(.negate, index).operand, ip), - .@"break" => .noreturn_type, + .@"break" => .noreturn, // todo: - .field => .opaque_type, + .field => .@"opaque", .call => { const base_type = air.typeOf(air.instData(.call, index).base, ip); - if (base_type == .opaque_type) return .opaque_type; - return base_type.toKey(ip).type_function.return_type; + if (base_type == .@"opaque") return .@"opaque"; + return base_type.value().toKey(ip).type_function.return_type; }, - .memory => air.instData(.memory, index).ty.toIntern(), + .memory => air.instData(.memory, index).ty, - .store => air.instData(.memory, air.instData(.store, index).memory).ty.toIntern(), + .store => air.instData(.memory, air.instData(.store, index).memory).ty, - .@"if" => air.instData(.@"if", index).data.get(air).union_ty.toIntern(), + .@"if" => air.instData(.@"if", index).data.get(air).union_ty, }; } @@ -310,10 +310,11 @@ const std = @import("std"); const mem = std.mem; const assert = std.debug.assert; -const Teg = @import("Teg.zig"); const InternPool = @import("InternPool.zig"); +const Value = InternPool.Value; +const Type = InternPool.Type; + +const Teg = @import("Teg.zig"); const dod = @import("dod.zig"); const Unused = enum(u32) { zero = 0 }; - -const Value = @import("Value.zig"); diff --git a/src/compiler/Comp.zig b/src/compiler/Comp.zig index 69546f10eb12e928d89aea708d4df15de2e21765..3f8566f54f4cc6278851fc637f25af3b3260b578 100644 --- a/src/compiler/Comp.zig +++ b/src/compiler/Comp.zig @@ -214,7 +214,7 @@ pub const File = struct { pub const Decl = struct { name: InternPool.NullTerminatedString, - val: Value, + val: InternPool.Value, /// The direct parent namespace of the Decl. In the case of the Decl /// corresponding to a file, this is the namespace of the struct, since /// there is no parent. @@ -432,7 +432,6 @@ const Ast = @import("Ast.zig"); const Air = @import("Air.zig"); const Teg = @import("Teg.zig"); const Cache = @import("Cache.zig"); -const Value = @import("Value.zig"); const Sema = @import("Sema.zig"); const Transform = @import("Transform.zig"); const JsNodeGen = @import("JsNodeGen.zig"); diff --git a/src/compiler/InternPool.zig b/src/compiler/InternPool.zig index 05e602024deb1dfac6c8a5d3fbd6dac5a42dbd74..58bb70294f3bc73cb0a188fb79d7f7654970a846 100644 --- a/src/compiler/InternPool.zig +++ b/src/compiler/InternPool.zig @@ -78,21 +78,25 @@ pub const Tag = enum(u8) { /// A function type /// data is an extra index into a `FuncType` type_function, - - // --- before this are types --- - // --- past this are values --- - /// A type that can be represented with only an enum tag. /// data is SimpleType enum value. simple_type, + + // --- before this are types --- + // --- past this are values --- + /// A value that can be represented with only an enum tag. /// data is SimpleValue enum value. simple_value, + /// A numeric literal that can be represented as a float. + /// Integers greater than 2^53 known at compile time are currently not supported. + /// data is an extra index into a `Float` + comptime_float, /// An int32 value /// data is i32 int32, /// A float value - /// data is an extra index into an `Float` + /// data is an extra index into a `Float` float, /// A function /// data is an extra index into a `FuncDecl` @@ -122,7 +126,7 @@ pub const Tag = enum(u8) { .simple_value, .int32, => unreachable, // does not have a payload - .float => Float, + .float, .comptime_float => Float, .func => FuncDecl, .type_function => TypeFunction, .string => String.WithLen, @@ -133,7 +137,7 @@ pub const Tag = enum(u8) { pub const FuncDecl = struct { analysis: FuncAnalysis, owner_decl: DeclIndex, - ty: Index, + ty: Value, teg_body_inst: TrackedInst.Index, }; @@ -141,26 +145,26 @@ pub const Tag = enum(u8) { /// 0. param_type: Index for each params_len pub const TypeFunction = struct { params_len: u32, - return_type: Index, + return_type: Type, }; }; -pub const Index = enum(u32) { - pub const first_type: Index = .opaque_type; - pub const last_type: Index = .noreturn_type; - pub const first_value: Index = .void_value; - pub const last_value: Index = .empty_string; +pub const Value = enum(u32) { + pub const first_type: Value = .opaque_type; + pub const last_type: Value = .noreturn_type; + pub const first_value: Value = .void; + pub const last_value: Value = .empty_string; opaque_type, bool_type, int32_type, float_type, string_type, - void_type, noreturn_type, type_type, + numeric_literal_type, - void_value, + void, bool_true, bool_false, int32_zero, @@ -178,36 +182,140 @@ pub const Index = enum(u32) { start: u32, len: u32, - pub fn get(slice: Slice, ip: *const InternPool) []Index { + pub fn get(slice: Slice, ip: *const InternPool) []Value { return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]); } }; /// Used for a map of `Index` values to the index within a list of `Index` values. const Adapter = struct { - indexes: []const Index, + indexes: []const Value, - pub fn eql(ctx: @This(), a: Index, b_void: void, b_map_index: usize) bool { + pub fn eql(ctx: @This(), a: Value, b_void: void, b_map_index: usize) bool { _ = b_void; return a == ctx.indexes[b_map_index]; } - pub fn hash(ctx: @This(), a: Index) u32 { + pub fn hash(ctx: @This(), a: Value) u32 { _ = ctx; return std.hash.uint32(@intFromEnum(a)); } }; - pub fn toAir(index: Index) Air.Inst.Ref { - return .{ .kind = .value, .data = @intCast(@intFromEnum(index)) }; + pub fn valueTag(value: Value, ip: *const InternPool) Tag { + return ip.items.items(.tag)[@intFromEnum(value)]; } - pub fn toValue(index: Index) @import("Value.zig") { - return .{ .ip_index = index }; + pub fn valueData(value: Value, ip: *const InternPool, comptime tag: Tag) tag.Data() { + return ip.items.items(.data)[@intFromEnum(value)].decode(tag, value.valueTag(ip)); } - pub fn toKey(index: Index, ip: *const InternPool) Key { - return ip.indexToKey(index); + pub fn toAir(value: Value) Air.Inst.Ref { + return .{ .kind = .value, .data = @intCast(@intFromEnum(value)) }; + } + + pub fn toKey(value: Value, ip: *const InternPool) Key { + return ip.indexToKey(value); + } + + pub fn dump(value: Value, ip: *const InternPool) std.fmt.Formatter(Value.formatValue) { + return .{ .data = .{ .val = value, .ip = ip } }; + } + + fn formatValue( + ctx: struct { val: Value, ip: *const InternPool }, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + // TODO: move the formatter out of Air.Dump + var state: Air.Dump.State = undefined; + state.ip = ctx.ip; + state.tty = @import("util.zig").detectTTYFromWriter(writer); + try Air.Dump.State.dumpValue(&state, writer, ctx.val); + } + + pub fn typeOf(value: Value, ip: *const InternPool) Type { + return switch (value.valueTag(ip)) { + .removed => unreachable, // UAF + + .type_function => .type, + .simple_type => .type, + + .simple_value => switch (value.valueData(ip, .simple_value)) { + .true => .bool, + .false => .bool, + .void => .void, + }, + + .comptime_float => .numeric_literal, + .int32 => .int32, + .float => .float, + .string => .string, + + .func => @panic("TODO"), + }; + } + + /// Try to re-interpret this `Value` as a `Type`. + /// Returns `null` if the value is not a type. + pub fn toType(value: Value, ip: *const InternPool) ?Type { + return switch (value.valueTag(ip)) { + .removed => unreachable, // UAF + + .type_function, + .simple_type, + => @enumFromInt(@intFromEnum(value)), + + // exception: void is a value AND a type + .simple_value => if (value == .void) .void else null, + + else => null, + }; + } + + pub fn asString(val: Value, ip: *const InternPool) InternPool.String.WithLen { + return val.valueData(ip, .string).get(ip); + } + + pub fn toFloat(val: Value, ip: *const InternPool) ?f64 { + return switch (val.valueTag(ip)) { + inline .comptime_float, .float => |tag| val.valueData(ip, tag).get(ip).decode(), + else => null, + }; + } +}; + +pub const Type = enum(u32) { + @"opaque" = @intFromEnum(Value.opaque_type), + bool = @intFromEnum(Value.bool_type), + int32 = @intFromEnum(Value.int32_type), + float = @intFromEnum(Value.float_type), + string = @intFromEnum(Value.string_type), + noreturn = @intFromEnum(Value.noreturn_type), + type = @intFromEnum(Value.type_type), + numeric_literal = @intFromEnum(Value.numeric_literal_type), + void = @intFromEnum(Value.void), + _, + + /// 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 fn value(ty: Type) Value { + return @enumFromInt(@intFromEnum(ty)); + } + + pub fn dump(ty: Type, ip: *const InternPool) std.fmt.Formatter(Value.formatValue) { + return ty.value().dump(ip); } }; @@ -217,9 +325,9 @@ pub const static_keys = [_]Key{ SimpleType.int32.key(), SimpleType.float.key(), SimpleType.string.key(), - SimpleType.void.key(), SimpleType.noreturn.key(), SimpleType.type.key(), + SimpleType.numeric_literal.key(), SimpleValue.void.key(), SimpleValue.true.key(), @@ -461,11 +569,11 @@ pub const NamespaceIndex = enum(u32) { /// * comptime-known value (where we store the value) /// * decl val (so that we can analyze the value lazily) /// * decl ref (so that we can analyze the reference lazily) -pub const CaptureValue = packed struct(u32) { +pub const Capture = packed struct(u32) { tag: enum(u2) { @"comptime", runtime, decl_val, decl_ref }, idx: u30, - pub fn wrap(val: Unwrapped) CaptureValue { + pub fn wrap(val: Unwrapped) Capture { return switch (val) { .@"comptime" => |i| .{ .tag = .@"comptime", .idx = @intCast(@intFromEnum(i)) }, .runtime => |i| .{ .tag = .runtime, .idx = @intCast(@intFromEnum(i)) }, @@ -473,7 +581,7 @@ pub const CaptureValue = packed struct(u32) { .decl_ref => |i| .{ .tag = .decl_ref, .idx = @intCast(@intFromEnum(i)) }, }; } - pub fn unwrap(val: CaptureValue) Unwrapped { + pub fn unwrap(val: Capture) Unwrapped { return switch (val.tag) { .@"comptime" => .{ .@"comptime" = @enumFromInt(val.idx) }, .runtime => .{ .runtime = @enumFromInt(val.idx) }, @@ -484,9 +592,9 @@ pub const CaptureValue = packed struct(u32) { pub const Unwrapped = union(enum) { /// Index refers to the value. - @"comptime": Index, + @"comptime": Value, /// Index refers to the type. - runtime: Index, + runtime: Value, decl_val: DeclIndex, decl_ref: DeclIndex, }; @@ -494,7 +602,7 @@ pub const CaptureValue = packed struct(u32) { pub const Slice = struct { start: u32, len: u32, - pub fn get(slice: Slice, ip: *const InternPool) []CaptureValue { + pub fn get(slice: Slice, ip: *const InternPool) []Capture { return @ptrCast(ip.extra.items[slice.start..][0..slice.len]); } }; @@ -516,9 +624,9 @@ const SimpleType = enum { float, string, bool, - void, noreturn, type, + numeric_literal, pub fn key(self: SimpleType) Key { return .{ .simple_type = self }; @@ -526,6 +634,7 @@ const SimpleType = enum { }; pub const Key = union(enum) { + comptime_float: f64, int32: i32, float: f64, string: []const u8, @@ -549,8 +658,8 @@ pub const Key = union(enum) { }; pub const TypeFunction = struct { - param_types: []Index, - return_type: Index, + param_types: []Type, + return_type: Type, pub fn hash(self: TypeFunction, hasher: *Hash) void { const t = tracer.trace(@src()); @@ -563,13 +672,13 @@ pub const Key = union(enum) { } pub fn eql(a: TypeFunction, b: TypeFunction) bool { - return std.mem.eql(Index, a.param_types, b.param_types) and + return std.mem.eql(Type, a.param_types, b.param_types) and a.return_type == b.return_type; } }; pub const Func = struct { - ty: Index, + ty: Value, owner_decl: DeclIndex, teg_body_inst: TrackedInst.Index, @@ -600,7 +709,7 @@ pub const Key = union(enum) { => |x| Hash.hash(seed, asBytes(&x)), .string, => |x| Hash.hash(seed, x), - .float => |x| Hash.hash(seed, asBytes(&Float.assertPure(x))), + .float, .comptime_float => |x| Hash.hash(seed, asBytes(&Float.assertPure(x))), .type_function => |x| { var hasher = Hash.init(seed); x.hash(&hasher); @@ -631,6 +740,7 @@ pub const Key = union(enum) { .string, => |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), .type_function => |x| x.eql(b.type_function), .func => |a_info| { const b_info = b.func; @@ -747,7 +857,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.* = undefined; } -pub fn indexToKey(ip: *const InternPool, index: Index) Key { +pub fn indexToKey(ip: *const InternPool, index: Value) Key { assert(index != .none); const item: Item = ip.items.get(@intFromEnum(index)); const data = item.data; @@ -762,6 +872,7 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .simple_value => .{ .simple_value = data.decode(.simple_value, item.tag) }, .int32 => .{ .int32 = data.decode(.int32, item.tag) }, .float => .{ .float = data.decode(.float, item.tag).get(ip).decode() }, + .comptime_float => .{ .comptime_float = data.decode(.comptime_float, item.tag).get(ip).decode() }, .string => .{ .string = data.decode(.string, item.tag).get(ip).toSlice(ip) }, .func => { const func = data.decode(.func, item.tag).get(ip); @@ -774,7 +885,7 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { }; } -pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Index { +pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value { const t = tracer.traceFmt(@src(), "{s}", .{@tagName(key)}); defer t.end(); @@ -806,6 +917,10 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Index { .float, try ip.extra.append(gpa, Float, Float.encode(f)), ), + .comptime_float => |f| Item.encode( + .comptime_float, + try ip.extra.append(gpa, Float, Float.encode(f)), + ), .func => |f| brk: { const extra = try ip.extra.append(gpa, Tag.FuncDecl, .{ .analysis = .{ @@ -829,7 +944,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Index { }, }; ip.items.appendAssumeCapacity(item); - const index: Index = @enumFromInt(ip.items.len - 1); + const index: Value = @enumFromInt(ip.items.len - 1); if (std.debug.runtime_safety) { assert(ip.indexToKey(index).eql(key, ip)); } @@ -841,7 +956,7 @@ fn extraTypeFunction( extra_index: ExtraIndex(Tag.TypeFunction), ) Key.TypeFunction { const type_function, var trail = extra_index.getTrail(ip); - const param_types = trail.readInternPoolIndexSlice(type_function.params_len); + const param_types = trail.readTypeSlice(type_function.params_len); const result: Key.TypeFunction = .{ .return_type = type_function.return_type, .param_types = param_types.get(ip), diff --git a/src/compiler/JsLink.zig b/src/compiler/JsLink.zig index 4ba12b29c4c85af7185b78f3eba8f7313c70c97e..99a557bcab8a283b579d45d9208b7cc340ad24c0 100644 --- a/src/compiler/JsLink.zig +++ b/src/compiler/JsLink.zig @@ -221,6 +221,8 @@ fn printExpr(link: *JsLink, tree: *const JsTree, node: Node.Index, writer: anyty // TODO: escapes .string => |val| try writer.print("\"{s}\"", .{val}), + .comptime_float => unreachable, // was not lowered to float + else => unreachable, // this value is not a plain_value } }, diff --git a/src/compiler/JsNodeGen.zig b/src/compiler/JsNodeGen.zig index 4ddaf23628c137251b9c2be82e418e024326fe26..1722043457ba866309ebb576477236029767a9b4 100644 --- a/src/compiler/JsNodeGen.zig +++ b/src/compiler/JsNodeGen.zig @@ -198,23 +198,23 @@ fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void { if ((tag == .add or tag == .sub) and inner_data.rhs.kind == .value) { - if (inner_data.rhs.decode().value.toFloat(c.ip)) |f| { - if (f == 1) { - // convert this into ++ or -- - const node = try c.add( - if (tag == .add) - .pre_inc - else - .pre_dec, - .{ - .operand = target, - }, - ); - try c.markExpression(inst, node); + // if (inner_data.rhs.decode().value.toFloat(c.ip)) |f| { + // if (f == 1) { + // // convert this into ++ or -- + // const node = try c.add( + // if (tag == .add) + // .pre_inc + // else + // .pre_dec, + // .{ + // .operand = target, + // }, + // ); + // try c.markExpression(inst, node); - return; - } - } + // return; + // } + // } } // convert into a compound assignment @@ -442,15 +442,20 @@ fn valueToExpression(c: *JsNodeGen, v: Value) !Node.Index { const t = tracer.trace(@src()); defer t.end(); - const intern_tag = c.ip.items.items(.tag)[@intFromEnum(v.toIntern())]; + const intern_tag = v.valueTag(c.ip); switch (intern_tag) { .removed => unreachable, // UAF - .type_function, .simple_type => unreachable, // Type values are not allowed here + .type_function, + .simple_type, + => unreachable, // Type values are not allowed here - .int32, .float, .string, .simple_value => { - return try c.add(.plain_value, .{ .value = v }); - }, + .int32, + .float, + .comptime_float, + .string, + .simple_value, + => return try c.add(.plain_value, .{ .value = v }), // funcs always point to decls .func => { @@ -493,7 +498,7 @@ fn codeGenBranch(c: *JsNodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Br var requires_label = false; var items = switch (inst.decode()) { .value => |v| items: { - if (v.ip_index == .void_value) + if (v == .void) return null; if (temporary) |temp| { const node = try c.valueToExpression(v); @@ -678,6 +683,7 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const InternPool = @import("InternPool.zig"); +const Value = InternPool.Value; const NullTerminatedString = InternPool.NullTerminatedString; const Comp = @import("Comp.zig"); @@ -690,7 +696,5 @@ const LocalIndex = JsTree.LocalIndex; const Air = @import("Air.zig"); -const Value = @import("Value.zig"); - const dod = @import("dod.zig"); const tracer = @import("tracer"); diff --git a/src/compiler/JsTree.zig b/src/compiler/JsTree.zig index a7cd8da9750c0194208a14e526da42a4d876ddd7..81186431c72fbcea50d645d3526f830e2586d07c 100644 --- a/src/compiler/JsTree.zig +++ b/src/compiler/JsTree.zig @@ -244,6 +244,8 @@ const std = @import("std"); const assert = std.debug.assert; const InternPool = @import("InternPool.zig"); +const Value = InternPool.Value; + const NullTerminatedString = InternPool.NullTerminatedString; const String = InternPool.String; @@ -252,6 +254,4 @@ const Decl = Comp.Decl; const dod = @import("dod.zig"); -const Value = @import("Value.zig"); - const Unused = @import("Ast.zig").Unused; diff --git a/src/compiler/Parse.zig b/src/compiler/Parse.zig index ba02a1b6584f7d4b9028af3d3d07f12b9e656646..5a6c32d31a06ed3e609698ac40eb87d5081e4757 100644 --- a/src/compiler/Parse.zig +++ b/src/compiler/Parse.zig @@ -265,7 +265,10 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { const params = try p.parseParamList(); const return_type = if (p.eatToken(.minus_lt)) |_| - try p.parseExpression(false) + if (!p.isExpressionStart()) + return p.failUnexpectedToken(.expression) + else + try p.parseExpression(false) else null; diff --git a/src/compiler/Report.zig b/src/compiler/Report.zig index 7b595efe01e95cd9f076ab258e1a476ada81f4b3..5ae1d4c26bc54014154584ed880c9e52daa16fa2 100644 --- a/src/compiler/Report.zig +++ b/src/compiler/Report.zig @@ -24,62 +24,6 @@ pub const Item = struct { pub const Type = enum { @"error", warn }; }; -/// A variant of std.builtin.StackTrace that stores it's data within itself -/// instead of being a pointer. This allows capturing the stack trace and -/// saving it for later printing. -pub const StoredTrace = struct { - data: [32]usize, - index: usize, - - pub const empty = .{ - .data = .{0} ** 32, - .index = 0, - }; - - fn trace(stored: *StoredTrace) std.builtin.StackTrace { - return .{ - .index = stored.index, - .instruction_addresses = &stored.data, - }; - } - - pub fn capture(begin: ?usize) StoredTrace { - var stored: StoredTrace = undefined; - var frame = stored.trace(); - std.debug.captureStackTrace(begin orelse @returnAddress(), &frame); - stored.index = frame.index; - return stored; - } - - pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace { - if (stack_trace) |stack| { - var data: [32]usize = undefined; - @memset(&data, 0); - const items = @min(stack.instruction_addresses.len, 32); - @memcpy(data[0..items], stack.instruction_addresses[0..items]); - return .{ - .data = data, - .index = @min(items, stack.index), - }; - } else { - return empty; - } - } -}; - -pub const DebugStoredTrace = if (builtin.mode == .Debug) - StoredTrace -else - struct { - pub fn capture() @This() { - return .{}; - } - - pub fn from(_: ?*std.builtin.StackTrace) @This() { - return .{}; - } - }; - pub const Err = union(enum) { pub const FieldEnum = std.meta.FieldEnum(Err); @@ -189,6 +133,15 @@ pub const Err = union(enum) { ); } }, + comptime_float_to_int: struct { + val: f64, + src: LazyNode, + + fn build(data: @This(), ctx: *MsgContext) !void { + const node = try data.src.get(ctx); + try ctx.msgNode(node, "numeric literal \"{d}\" cannot be coerced to \"int\"", .{data.val}); + } + }, }; pub const Warn = union(enum) { @@ -221,8 +174,74 @@ pub const Warn = union(enum) { try ctx.msgAlone("if this was intentional, discard it with \"_ = {s};\"", .{id}); } }, + numeric_int_var_ambiguity: struct { + node: LazyNode, + + fn build(data: @This(), ctx: *MsgContext) !void { + const var_decl_node = try data.node.get(ctx); + const init_node = ctx.ast.nodeData(var_decl_node).var_decl.init; + try ctx.msgNode(init_node, "constant integer in a \"var\" has an unknown type, assuming \"float\"", .{}); + try ctx.msgAlone("add \": float\" or \": int\" after the variable name to set the type", .{}); + } + }, }; +/// A variant of std.builtin.StackTrace that stores it's data within itself +/// instead of being a pointer. This allows capturing the stack trace and +/// saving it for later printing. +pub const StoredTrace = struct { + data: [32]usize, + index: usize, + + pub const empty = .{ + .data = .{0} ** 32, + .index = 0, + }; + + fn trace(stored: *StoredTrace) std.builtin.StackTrace { + return .{ + .index = stored.index, + .instruction_addresses = &stored.data, + }; + } + + pub fn capture(begin: ?usize) StoredTrace { + var stored: StoredTrace = undefined; + var frame = stored.trace(); + std.debug.captureStackTrace(begin orelse @returnAddress(), &frame); + stored.index = frame.index; + return stored; + } + + pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace { + if (stack_trace) |stack| { + var data: [32]usize = undefined; + @memset(&data, 0); + const items = @min(stack.instruction_addresses.len, 32); + @memcpy(data[0..items], stack.instruction_addresses[0..items]); + return .{ + .data = data, + .index = @min(items, stack.index), + }; + } else { + return empty; + } + } +}; + +pub const DebugStoredTrace = if (builtin.mode == .Debug) + StoredTrace +else + struct { + pub fn capture() @This() { + return .{}; + } + + pub fn from(_: ?*std.builtin.StackTrace) @This() { + return .{}; + } + }; + pub const UnusedKind = enum { param, constant, @@ -509,6 +528,7 @@ pub const Builder = struct { pub fn deinit(b: *Builder) void { b.errors.deinit(b.gpa); b.warnings.deinit(b.gpa); + b.string_bytes.deinit(b.gpa); } pub fn finalize(b: *Builder) !Report { @@ -592,6 +612,38 @@ pub const MsgContext = struct { } }; +pub const LazyNode = struct { + decl: Comp.Decl.Index, + node_offset: i32, + + pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex { + const decl = ctx.comp.intern_pool.declPtrConst(lazy.decl); + const ns = ctx.comp.intern_pool.namespacePtr(decl.src_namespace); + + // TODO: This might change if we have errors like + // - function arguments not matching at + // - function declared + // And they are both in different files. + // To resolve this, `Component` needs to store which file it is in. + assert(ns.file_scope == ctx.file); + + if (ns.parent.unwrap() != null) { + @panic("TODO: LazyNode.get when the decl is in a nested namespace"); + } + + const teg = &(ctx.file.teg orelse + // this will be required once incremental compilation hits + @panic("TODO: teg was unloaded")); + + const decl_inst = decl.teg_decl_index.unwrap() orelse + unreachable; // file was not analyzed, yet teg loaded? + + const teg_decl = teg.instData(.declaration, decl_inst.resolve(&ctx.comp.intern_pool)); + + return @intCast(teg_decl.src_node - lazy.node_offset); + } +}; + const Report = @This(); const std = @import("std"); const ArrayListUnmanaged = std.ArrayListUnmanaged; diff --git a/src/compiler/Sema.zig b/src/compiler/Sema.zig index 113cc4adc395ca6e7c3b1cbe3ec4ea4399760397..ad3e3a04565ff117375eb30230bee567e270d3d6 100644 --- a/src/compiler/Sema.zig +++ b/src/compiler/Sema.zig @@ -16,9 +16,10 @@ inst_map: InstMap = undefined, /// scratch memory. can be used in any place you might temporarily allocate an arraylist /// this is most notably used to store block contents before the block is finalized scratch: std.ArrayListUnmanaged(u32) = .{}, -params: []InternPool.Index = undefined, +params: []Type = undefined, // outputs +report: Report.Builder = undefined, air_instructions: std.MultiArrayList(Air.Inst) = .{}, air_extra: dod.ExtraStore(.air, .@"var") = .{}, @@ -27,6 +28,8 @@ pub const Error = error{ AnalysisFail, /// This analysis is waiting on other dependencies to finish Interrupt, + + OutOfMemory, }; /// Instead of using a hash map for this, just pre-allocate a list long enough to hold everything. @@ -74,6 +77,11 @@ pub fn init(s: *Sema) !void { const t = tracer.trace(@src()); defer t.end(); + s.report = .{ + .gpa = s.gpa, + .current_file = s.ip.namespacePtr(s.namespace_index).file_scope, + }; + try s.comp.current_compile_decls.append(s.gpa, s.decl_index); s.inst_map = try InstMap.init( @@ -87,6 +95,10 @@ pub fn deinit(s: *Sema) void { const t = tracer.trace(@src()); defer t.end(); + const report = s.report.finalize() catch @panic("OOM"); + report.dumpToStdErr(s.comp) catch @panic("Error"); + report.deinit(s.gpa); + defer s.* = undefined; s.inst_map.deinit(s.arena); s.scratch.deinit(s.arena); @@ -121,14 +133,14 @@ pub fn analyzeDeclaration(s: *Sema) anyerror!Value { const decl, const fn_decl = s.code.fullFuncDecl(s.inst_map.start); assert(decl.kind == .function); - s.params = try s.gpa.alloc(InternPool.Index, fn_decl.params.len); + s.params = try s.gpa.alloc(Type, fn_decl.params.len); defer s.gpa.free(s.params); for (fn_decl.params.slice(&s.code), 0..) |param, i| { - s.params[i] = (try s.resolveRefComptime(param)).toIntern(); + s.params[i] = (try s.resolveRefComptime(param)).toType(s.ip).?; } - const return_type = try s.resolveRefComptime(fn_decl.return_type); + const return_type = (try s.resolveRefComptime(fn_decl.return_type)).toType(s.ip).?; const root_block = fn_decl.body.toIndex() orelse @panic("TODO: please change this to Index. Ref is not allowed here anymore"); @@ -143,17 +155,16 @@ pub fn analyzeDeclaration(s: *Sema) anyerror!Value { const func_prototype = try s.ip.get(s.gpa, .{ .type_function = .{ .param_types = s.params, - .return_type = return_type.toIntern(), + .return_type = return_type, }, }); - const func_value = try s.ip.get(s.gpa, .{ + const value = try s.ip.get(s.gpa, .{ .func = .{ .ty = func_prototype, .owner_decl = s.decl_index, .teg_body_inst = s.decl.teg_decl_index.unwrap().?, }, }); - const value = Value.fromInterned(func_value); s.decl.val = value; return value; } @@ -221,11 +232,12 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { }, .number => { const number: dod.Float = s.code.instPayload(.number, in); - return s.makeValueAir(.{ .float = number.decode() }); + return s.makeValueAir(.{ .comptime_float = number.decode() }); }, .js => { - const operand = try s.resolveRefComptime(s.code.instData(.js, in).operand); - try s.validateType(operand.type(s.ip), .string_type); + const operand_ref = s.code.instData(.js, in).operand; + const operand = try s.resolveRefComptime(operand_ref); + // const operand_str = try s.coerceValue(operand_ref.toIndex().?, operand, .string); return s.addInstRef(.js_global_ref, operand.asString(s.ip)); }, .field => { @@ -233,12 +245,12 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { const base = try s.resolveRefRuntime(field.lhs); const rhs = try s.resolveRefComptime(field.rhs); - try s.validateType(rhs.type(s.ip), .string_type); + // try s.validateType(rhs.type(s.ip), .string_type); const string = rhs.asString(s.ip); const base_type = s.typeOfRef(base); switch (base_type) { - .opaque_type => { + .@"opaque" => { return s.addInstRef(.field, .{ .base = base, .field = try string.tryToNullTerminatedString(s.ip, s.gpa), @@ -259,12 +271,12 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { return error.AnalysisFail; }; const resolved = try s.resolveDecl(decl_index); - return resolved.toIntern().toAir(); + return resolved.toAir(); }, .param => { const param: Teg.Inst.DataNode = s.code.instData(.param, in); return s.addInstRef(.arg, .{ - .type = Value.fromInterned(s.params[param.data]), + .type = s.params[param.data], .arg_number = param.data, }); }, @@ -325,83 +337,86 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { }); }, .call => { - const call: Teg.Inst.Call = s.code.instPayload(.call, in); - const base = try s.resolveRefRuntime(call.base); - const base_type = s.typeOfRef(base).toKey(s.ip); + @panic("TODO: re-implement .call"); + // const call: Teg.Inst.Call = s.code.instPayload(.call, in); + // const base = try s.resolveRefRuntime(call.base); + // const base_type = s.typeOfRef(base).toKey(s.ip); - const is_opaque = base_type == .simple_type and base_type.simple_type == .@"opaque"; + // const is_opaque = base_type == .simple_type and base_type.simple_type == .@"opaque"; - const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); - defer s.arena.free(scratch); - if (!is_opaque) { - if (base_type != .type_function) { - std.debug.print("Attempt to call non-function {}", .{base_type}); - return error.AnalysisFail; - } + // const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); + // defer s.arena.free(scratch); + // if (!is_opaque) { + // if (base_type != .type_function) { + // std.debug.print("Attempt to call non-function {}", .{base_type}); + // return error.AnalysisFail; + // } - const proto = base_type.type_function; - if (proto.param_types.len != call.args.len) { - std.debug.print("Incorrect args. Expect {d}, Got {d}", .{ - proto.param_types.len, - call.args.len, - }); - return error.AnalysisFail; - } + // const proto = base_type.type_function; + // if (proto.param_types.len != call.args.len) { + // std.debug.print("Incorrect args. Expect {d}, Got {d}", .{ + // proto.param_types.len, + // call.args.len, + // }); + // return error.AnalysisFail; + // } - // validate function args - for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| { - const ref = try s.resolveRefRuntime(arg); - scratch[i] = ref; - try s.validateType( - Value.fromInterned(s.typeOfRef(ref)), - param, - ); - } - } else { - // just copy the args in without checking - for (call.args.slice(&s.code), 0..) |arg, i| { - const ref = try s.resolveRefRuntime(arg); - scratch[i] = ref; - } - } + // // validate function args + // for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| { + // const ref = try s.resolveRefRuntime(arg); + // scratch[i] = ref; + // _ = param; + // // try s.validateType( + // // Value.fromInterned(s.typeOfRef(ref)), + // // param, + // // ); + // } + // } else { + // // just copy the args in without checking + // for (call.args.slice(&s.code), 0..) |arg, i| { + // const ref = try s.resolveRefRuntime(arg); + // scratch[i] = ref; + // } + // } - const start = s.air_extra.list.items.len; - try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch)); + // const start = s.air_extra.list.items.len; + // try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch)); - return s.addInstRef(.call, .{ - .base = base, - .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{ - .args = .{ - .start = @intCast(start), - .len = @intCast(scratch.len), - }, - }), - }); + // return s.addInstRef(.call, .{ + // .base = base, + // .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{ + // .args = .{ + // .start = @intCast(start), + // .len = @intCast(scratch.len), + // }, + // }), + // }); }, .ensure_void => { const operand = try s.resolveRefRuntimeExtra(s.code.instData(.ensure_void, in).operand, false); - const t = Value.fromInterned(s.typeOfRef(operand)); - if (!t.isEnsureVoid(s.ip)) { - std.debug.print("oh no, we are discarding {} on accident\n", .{ - t.fmt(s.ip), - }); - } - return InternPool.Index.void_value.toAir(); + const t = s.typeOfRef(operand); + _ = t; + // if (!t.isEnsureVoid(s.ip)) { + // std.debug.print("oh no, we are discarding {} on accident\n", .{ + // t.fmt(s.ip), + // }); + // } + return InternPool.Value.void.toAir(); }, .discard => { const operand = try s.resolveRefRuntimeExtra(s.code.instData(.discard, in).operand, false); - const t = Value.fromInterned(s.typeOfRef(operand)); - if (t.ip_index == .void_value) { + const t = s.typeOfRef(operand); + if (t == .void) { std.debug.print("oh no, we have a useless _ = void;\n", .{}); } - return InternPool.Index.void_value.toAir(); + return InternPool.Value.void.toAir(); }, .@"if" => { const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code); const cond = try s.resolveRefRuntime(data.cond); - try s.validateType(Value.fromInterned(s.typeOfRef(cond)), .bool_type); + // try s.validateType(s.typeOfRef(cond), .bool_type); // If known at comptime, only analyze the taken path if (cond.kind == .value) { @@ -417,7 +432,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { .data = try s.air_extra.append(s.gpa, Air.If.Data, .{ .yes = yes_block, .no = no_block, - .union_ty = s.unionType(Value.fromInterned(s.typeOfRef(yes_block)), Value.fromInterned(s.typeOfRef(no_block))), + .union_ty = s.unionType(s.typeOfRef(yes_block), s.typeOfRef(no_block)), }), }); }, @@ -434,11 +449,24 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { .memory => { const data = s.code.instData(.memory, in); - const initial_value = try s.resolveRefRuntime(data.operand); + var initial_value = try s.resolveRefRuntime(data.operand); + + switch (initial_value.decode()) { + .value => |v| switch (v.toKey(s.ip)) { + .comptime_float => |val| { + try s.report.warn(.numeric_int_var_ambiguity, .{ + .node = s.nodeLoc(in), + }); + initial_value = (try s.makeValue(.{ .float = val })).toAir(); + }, + else => {}, + }, + else => {}, + } return try s.addInstRef(.memory, .{ .init = initial_value, - .ty = s.typeOfRef(initial_value).toValue(), + .ty = s.typeOfRef(initial_value), }); }, .store => { @@ -448,16 +476,22 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { const location = s.mapTeg(data.memory.toRef()).decode().inst; const operand = try s.resolveRefRuntime(data.operand); - try s.validateType( - s.typeOfRef(operand).toValue(), - s.tempAir().instData(.memory, location).ty.ip_index, - ); + // try s.validateType( + // s.typeOfRef(operand).toValue(), + // s.tempAir().instData(.memory, location).ty.ip_index, + // ); return try s.addInstRef(.store, .{ .memory = location, .operand = operand, }); }, + .as => { + const as: Teg.Inst.As = s.code.instPayload(.as, in); + const coerce_to = try s.resolveRefComptime(as.type); + const operand = try s.resolveRefRuntime(as.operand); + return try s.coerceRef(in, operand, coerce_to.toType(s.ip).?); + }, else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}), } } @@ -498,7 +532,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) anyerror!Air.Inst.Ref { fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) anyerror!Air.Inst.Ref { const index = ref.toIndex() orelse { - return ref.toInterned().?.toAir(); + return ref.toValue().?.toAir(); }; if (s.inst_map.get(index)) |air| { @@ -519,20 +553,21 @@ fn resolveRefRuntime(s: *Sema, ref: Teg.Inst.Ref) anyerror!Air.Inst.Ref { } fn resolveRefComptime(s: *Sema, ref: Teg.Inst.Ref) !Value { - const in = ref.toIndex() orelse { - return Value{ .ip_index = ref.toInterned().? }; + const index = switch (ref.decode()) { + .value => |value| return value, + .index => |index| index, }; - if (s.inst_map.get(in)) |air| { + if (s.inst_map.get(index)) |air| { switch (air.decode()) { .value => |value| return value, else => {}, } } - switch (s.code.instTag(in)) { + switch (s.code.instTag(index)) { .string => { - const string: Teg.SlicedString = s.code.instPayload(.string, in); + const string: Teg.SlicedString = s.code.instPayload(.string, index); return s.makeValue(.{ .string = string.slice(s.code.string_bytes) }); }, @@ -540,6 +575,70 @@ fn resolveRefComptime(s: *Sema, ref: Teg.Inst.Ref) !Value { } } +/// Coerce `value` runtime ref to `coerce_type`, and emit an error at `teg_ref`'s location +fn coerceRef( + s: *Sema, + teg_ref: Teg.Inst.Index, + value: Air.Inst.Ref, + coerce_to: Type, +) !Air.Inst.Ref { + _ = teg_ref; + _ = s; + _ = value; + _ = coerce_to; + // const value_type = s.typeOfRef(value); + // switch (coerce_type.toKey(s.ip)) { + // .simple_type => |simple_type| switch (simple_type) { + // // .@"opaque", + // .int32 => { + // switch (value_type.toKey(s.ip)) { + // .simple_type => switch (simple_type) { + // // .int32 => return value, + // // .numeric_literal => { + // // const val = value.decode().value.toKey(s.ip).comptime_float; + // // std.debug.print("buh {d}\n", .{val}); + // // if (!math.isFinite(val) or + // // val < math.minInt(i32) or + // // val > math.maxInt(i32)) + // // { + // // try s.fail(.comptime_float_to_int, .{ + // // .val = val, + // // .src = s.nodeLoc(teg_ref), + // // }); + // // } + // // return (try s.makeValue(.{ .int32 = @intFromFloat(val) })).toAir(); + // // }, + // else => |tag| std.debug.panic("TODO: coerceTo value_type simple_type .{s}", .{@tagName(tag)}), + // }, + // else => |tag| std.debug.panic("TODO: coerceTo value_type .{s}", .{@tagName(tag)}), + // } + // }, + // // .float, + // // .string, + // // .bool, + // // .noreturn, + // // .type, + // // .numeric_literal, + // else => |tag| std.debug.panic("TODO: coerceTo coerce_type .simple_type = .{s}", .{@tagName(tag)}), + // }, + // else => |tag| std.debug.panic("TODO: coerceTo coerce_type .{s}", .{@tagName(tag)}), + // } + @panic("TODO"); +} + +fn coerceValue( + s: *Sema, + teg_ref: Teg.Inst.Index, + value: Value, + coerce_to: Type, +) !Value { + _ = s; + _ = teg_ref; + _ = value; + _ = coerce_to; + @panic("TODO"); +} + fn resolveDecl(s: *Sema, decl_index: Decl.Index) !Value { const decl = s.ip.declPtr(decl_index); switch (decl.analysis) { @@ -579,27 +678,22 @@ fn resolveDecl(s: *Sema, decl_index: Decl.Index) !Value { } } -fn unionType(s: *Sema, a: Value, b: Value) Value { - if (std.debug.runtime_safety) { - assert(a.type(s.ip).toIntern() == .type_type); - assert(b.type(s.ip).toIntern() == .type_type); - } - - if (a.ip_index == b.ip_index) { +fn unionType(s: *Sema, a: Type, b: Type) Type { + if (a == b) { return a; } - if (a.ip_index == .noreturn_type) + if (a == .noreturn) return b; - if (b.ip_index == .noreturn_type) + if (b == .noreturn) return a; std.debug.panic( "TODO: when two non-equal types implicitly merge (if/match), this should generate a union type.\na: {}\nb: {}", .{ - a.fmt(s.ip), - b.fmt(s.ip), + a.dump(s.ip), + b.dump(s.ip), }, ); // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } }); @@ -650,7 +744,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref { else => comptime unreachable, }; return @as( - InternPool.Index, + InternPool.Value, if (result) .bool_true else .bool_false, ).toAir(); }, @@ -660,7 +754,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref { const r = rhs.toFloat(s.ip) orelse @panic("TODO: typecheck / non-numeric eq"); const result = l == r; return @as( - InternPool.Index, + InternPool.Value, if (result) .bool_true else .bool_false, ).toAir(); }, @@ -709,15 +803,15 @@ fn setInstData( } fn makeValue(s: *Sema, key: InternPool.Key) !Value { - return .{ .ip_index = try s.ip.get(s.gpa, key) }; + return try s.ip.get(s.gpa, key); } fn makeValueAir(s: *Sema, key: InternPool.Key) !Air.Inst.Ref { - return (try s.makeValue(key)).ip_index.toAir(); + return (try s.makeValue(key)).toAir(); } fn mapTeg(s: *Sema, inst: Teg.Inst.Ref) Air.Inst.Ref { - if (inst.toInterned()) |interned| { + if (inst.toValue()) |interned| { return interned.toAir(); } const index = inst.toIndex().?; @@ -729,13 +823,6 @@ fn mapTeg(s: *Sema, inst: Teg.Inst.Ref) Air.Inst.Ref { }; } -fn validateType(s: *Sema, checked_type: Value, wanted_type: InternPool.Index) !void { - _ = s; // autofix - if (checked_type.toIntern() != wanted_type) { - return error.TypeCheckFailed; - } -} - fn scratchToIndexList(s: *Sema, scratch_top: usize) !Air.IndexList { const items = s.scratch.items[scratch_top..]; const extra_top = s.air_extra.list.items.len; @@ -753,7 +840,7 @@ fn tempAir(s: *const Sema) Air { }; } -fn typeOfRef(s: *Sema, ref: Air.Inst.Ref) InternPool.Index { +fn typeOfRef(s: *Sema, ref: Air.Inst.Ref) InternPool.Type { return s.tempAir().typeOf(ref, s.ip); } @@ -769,10 +856,28 @@ fn lookupDecl(s: *Sema, name: NullTerminatedString) ?InternPool.DeclIndex { } } +fn nodeLoc(s: *const Sema, in: Teg.Inst.Index) Report.LazyNode { + return s.code.instSrcLoc(in, s.decl_index); +} + +pub fn addWarn(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void { + try s.report.warn(kind, data); +} + +pub fn addError(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void { + try s.report.err(kind, data); +} + +pub fn fail(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) Error!void { + try s.report.err(kind, data); + return Error.AnalysisFail; +} + const Sema = @This(); const std = @import("std"); const mem = std.mem; +const math = std.math; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const log = std.log.scoped(.sema); @@ -791,14 +896,12 @@ const Decl = Comp.Decl; const Namespace = Comp.Namespace; const Air = @import("Air.zig"); - const Teg = @import("Teg.zig"); - -const Value = @import("Value.zig"); -const InternPool = @import("InternPool.zig"); -const NullTerminatedString = InternPool.NullTerminatedString; -const dod = @import("dod.zig"); - const Report = @import("Report.zig"); - +const dod = @import("dod.zig"); const tracer = @import("tracer"); + +const InternPool = @import("InternPool.zig"); +const Value = InternPool.Value; +const Type = InternPool.Type; +const NullTerminatedString = InternPool.NullTerminatedString; diff --git a/src/compiler/Teg.Dump.zig b/src/compiler/Teg.Dump.zig index 011fbace39b14b6c2c928ca8006144d97342f9a1..eb4e694385fa1647ab4a5bda31435d38a6801b33 100644 --- a/src/compiler/Teg.Dump.zig +++ b/src/compiler/Teg.Dump.zig @@ -22,6 +22,14 @@ const State = struct { const tag = s.teg.instTag(inst); + try tty.setColor(writer, .bright_black); + const node_offset = teg.instSrcLoc(inst, @enumFromInt(0)).node_offset; + try writer.print("{c}{d} ", .{ + @as(u8, if (node_offset >= 0) '+' else '-'), + @abs(node_offset), + }); + try tty.setColor(writer, .reset); + try tty.setColor(writer, .cyan); try writer.print("{s}", .{@tagName(tag)}); try tty.setColor(writer, .reset); diff --git a/src/compiler/Teg.zig b/src/compiler/Teg.zig index 31246d7b0f37f75cac3212d0001c40391a21b863..2e509cd847b8e9a933bd35a29d7e5ac117ac3ebe 100644 --- a/src/compiler/Teg.zig +++ b/src/compiler/Teg.zig @@ -209,11 +209,11 @@ pub const Inst = struct { int32_type, float_type, string_type, - void_type, noreturn_type, type_type, + numeric_literal_type, - void_value, + void, bool_true, bool_false, int32_zero, @@ -227,9 +227,22 @@ pub const Inst = struct { _, - pub fn toIndex(inst: Ref) ?Index { - assert(inst != .none); - const ref_int = @intFromEnum(inst); + pub const Decoded = union(enum) { + value: InternPool.Value, + index: Teg.Inst.Index, + }; + + pub fn decode(ref: Ref) Decoded { + const ref_int = @intFromEnum(ref); + return if (ref_int >= Index.static_len) + .{ .index = @enumFromInt(ref_int - Index.static_len) } + else + .{ .value = @enumFromInt(ref_int) }; + } + + pub fn toIndex(ref: Ref) ?Index { + assert(ref != .none); + const ref_int = @intFromEnum(ref); if (ref_int >= Index.static_len) { return @enumFromInt(ref_int - Index.static_len); } else { @@ -242,7 +255,7 @@ pub const Inst = struct { return toIndex(inst); } - pub fn toInterned(inst: Ref) ?InternPool.Index { + pub fn toValue(inst: Ref) ?InternPool.Value { const ref_int = @intFromEnum(inst); // todo: is this > or >= @@ -383,18 +396,30 @@ pub fn declData(teg: *const Teg, index: Inst.Index) Inst.Decl { return teg.instData(.declaration, index).extra_index.get(teg); } +pub fn instSrcLoc(teg: *const Teg, inst: Inst.Index, decl: Comp.Decl.Index) LazyNode { + switch (teg.instTag(inst)) { + inline else => |tag| { + const data = teg.instData(tag, inst); + return .{ + .node_offset = data.src_node, + .decl = decl, + }; + }, + } +} + pub fn moduleDeclarations(teg: *const Teg, module_index: Inst.Index) []const Inst.Index { const module = teg.instData(.module, module_index); const module_data: IndexList = module.extra_index.get(teg); const slice = module_data.slice(teg); - // if (std.debug.runtime_safety) { - // for (slice) |item| { - // if (teg.instTag(item) != .declaration) { - // std.debug.print("\ngot unknown tag {s}\n", .{@tagName(teg.instTag(item))}); - // unreachable; - // } - // } - // } + if (std.debug.runtime_safety) { + for (slice) |item| { + if (teg.instTag(item) != .declaration) { + std.debug.print("\ngot unknown tag {s}\n", .{@tagName(teg.instTag(item))}); + unreachable; + } + } + } return slice; } @@ -441,4 +466,7 @@ const InternPool = @import("InternPool.zig"); const Air = @import("Air.zig"); +const Comp = @import("Comp.zig"); + const Report = @import("Report.zig"); +const LazyNode = Report.LazyNode; diff --git a/src/compiler/Tokenizer.zig b/src/compiler/Tokenizer.zig index 770d1f980c5a1a8edf73fa557b215ebe7a823f43..687307b791a79fcf5b1c4e89c692e174ec963bb2 100644 --- a/src/compiler/Tokenizer.zig +++ b/src/compiler/Tokenizer.zig @@ -192,7 +192,7 @@ pub const Token = struct { .pipe_pipe => "||", .percent => "%", .plus => "+", - .minus_lt => "<", + .minus_lt => "->", .semi => ";", .slash => "/", .star => "*", diff --git a/src/compiler/Transform.zig b/src/compiler/Transform.zig index 2e07e7d236e213e574bcfdd907c7c1725c654d47..7101e891ec5186e60f6a805bd6977d68aff11a3b 100644 --- a/src/compiler/Transform.zig +++ b/src/compiler/Transform.zig @@ -202,7 +202,7 @@ const BlockContext = struct { const Liveness = enum { alive, dead }; const BuiltInIds = std.ComptimeStringMap(Inst.Ref, .{ - .{ "void", .void_value }, + .{ "void", .void }, .{ "true", .bool_true }, .{ "false", .bool_false }, .{ "int", .int32_type }, @@ -289,7 +289,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I trail.writeStruct(Inst.DeclFn, .{ .params = .{ .start = 0, .len = 0 }, - .return_type = Inst.Ref.void_type, + .return_type = Inst.Ref.void, .body = block.toRef(), }); @@ -378,7 +378,7 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A const return_type = if (fn_decl.return_type) |return_type_node| try t.transformExpressionToBlock(decl, return_type_node, false) else - Inst.Ref.void_type; + Inst.Ref.void; const body = try t.transformExpressionToBlock(decl, fn_decl.body, true); @@ -528,7 +528,7 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc .node = param, .prev = sym.value_ptr.node, }); - return .void_value; + return .void; } sym.value_ptr.* = .{ @@ -558,7 +558,7 @@ const Builtin = enum { if (args.len != 1) { // try t.report.err(.invalid_builtin_args, .{ .id = "js" }); - return .void_value; + return .void; } const source = try t.transformExpression(decl, args[0]) orelse return null; @@ -600,7 +600,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc const lookup = t.lookupId(id) orelse { try t.report.err(.unknown_identifier, .{ .node = node }); - return .void_value; + return .void; }; lookup.used = true; @@ -815,7 +815,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc => |tag| { const statements = t.ast.nodeStatements(node); - var last: Inst.Ref = .void_value; + var last: Inst.Ref = .void; for (statements, 1..) |statement, i| { const is_last = i == statements.len; last = try t.transformBlockStatement( @@ -827,14 +827,14 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc }; } - return if (tag == .block_implicit_return) last else .void_value; + return if (tag == .block_implicit_return) last else .void; }, .builtin_call, => { const builtin = Builtin.map.get(t.ast.basicNodeSlice(node)[1..]) orelse { try t.report.err(.invalid_builtin, .{ .node = node }); - return .void_value; + return .void; }; const base = t.ast.nodeData(node).list; @@ -939,9 +939,9 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc false, ) else - Inst.Ref.void_value; + Inst.Ref.void; - if (yes == .void_value and no == .void_value) { + if (yes == .void and no == .void) { // when both branches do nothing, we can omit the branch, // however we still need to type-check the condition operand _ = try t.addInstRef(.{ @@ -951,7 +951,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .operand = cond, }), }); - return .void_value; + return .void; } return try t.addInstRef(.{ @@ -990,7 +990,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc try t.transformExpression(decl.withReturn(null), expr) orelse return null else - .void_value; + .void; _ = try t.addInstRef(.{ .tag = .@"break", @@ -1022,7 +1022,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc try t.transformExpression(decl.withReturn(null), expr) orelse return null else - .void_value; + .void; _ = try t.addInstRef(.{ .tag = .@"break", @@ -1063,12 +1063,12 @@ fn transformBlockStatement( if (mem.eql(u8, var_decl.id, "_")) { // TODO: relax this restriction into just creating a discard node try t.report.err(.invalid_discard, .{ .node = node }); - return .void_value; + return .void; } if (BuiltInIds.has(var_decl.id)) { try t.report.err(.shadow_builtin_variable, .{ .node = node }); - return .void_value; + return .void; } const type_ref = if (var_decl.type) |data_type| @@ -1132,7 +1132,7 @@ fn transformBlockStatement( }, }; - return .void_value; + return .void; }, .fn_decl, @@ -1188,7 +1188,7 @@ fn transformBlockStatement( const result = try t.transformExpression(decl, node) orelse return null; - if (!is_implicit_return_expression and result != .void_value) { + if (!is_implicit_return_expression and result != .void) { return try t.addInstRef(.{ .tag = .ensure_void, .data = EncodedData.encode(.ensure_void, .{ diff --git a/src/compiler/Value.zig b/src/compiler/Value.zig deleted file mode 100644 index c91366a1fc546c9bf7abf02025e1343754fabc7d..0000000000000000000000000000000000000000 --- a/src/compiler/Value.zig +++ /dev/null @@ -1,96 +0,0 @@ -ip_index: InternPool.Index, - -pub fn fromInterned(i: InternPool.Index) Value { - assert(i != .none); - return .{ .ip_index = i }; -} - -pub fn toIntern(val: Value) InternPool.Index { - assert(val.ip_index != .none); - return val.ip_index; -} - -pub fn toKey(val: Value, ip: *const InternPool) InternPool.Key { - return val.toIntern().toKey(ip); -} - -pub fn asBool(val: Value) i32 { - return switch (val.toIntern()) { - .bool_true => true, - .bool_false => false, - else => unreachable, - }; -} - -pub fn asString(val: Value, ip: *const InternPool) InternPool.String.WithLen { - const index = val.toIntern(); - return ip.items.items(.data)[@intFromEnum(index)].decode( - .string, - ip.items.items(.tag)[@intFromEnum(index)], - ) - .get(ip); -} - -pub fn @"type"(val: Value, ip: *const InternPool) Value { - const v: InternPool.Index = switch (val.toKey(ip)) { - .simple_type, .type_function => .type_type, - - .int32 => .int32_type, - .float => .float_type, - .string => .string_type, - .simple_value => |v| switch (v) { - .true, .false => .bool_type, - .void => .void_type, - }, - .func => |f| f.ty, - }; - return Value.fromInterned(v); -} - -pub fn toFloat(val: Value, ip: *const InternPool) ?f64 { - return switch (val.toKey(ip)) { - .int32 => |i| @floatFromInt(i), - .float => |f| f, - else => null, - }; -} - -/// used for validating ensure_void instructions -pub fn isEnsureVoid(val: Value, ip: *const InternPool) bool { - const intern = val.toIntern(); - const i = @intFromEnum(intern); - - return switch (ip.items.items(.tag)[i]) { - .simple_type => switch (ip.items.items(.data)[i].decode(.simple_type, .simple_type)) { - .@"opaque", .void => true, - else => true, - }, - else => false, - }; -} - -const Value = @This(); - -const std = @import("std"); -const assert = std.debug.assert; - -pub const InternPool = @import("InternPool.zig"); - -pub fn fmt(v: Value, ip: *InternPool) Format { - return .{ .value = v, .ip = ip }; -} - -pub const Format = struct { - value: Value, - ip: *InternPool, - - pub fn format( - self: Format, - comptime unused_fmt_str: []const u8, - _: std.fmt.FormatOptions, - writer: anytype, - ) !void { - comptime assert(unused_fmt_str.len == 0); - try writer.print("{any}", .{self.value.toKey(self.ip)}); - } -}; diff --git a/src/compiler/dod.zig b/src/compiler/dod.zig index e30dbb3fa197d8b8a09acb6860ab141219fb0a92..57ccac606608a1b511c3f0f54871592143edb8a1 100644 --- a/src/compiler/dod.zig +++ b/src/compiler/dod.zig @@ -240,7 +240,15 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil return ip.get(T, trail.index); } - pub fn readInternPoolIndexSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Index.Slice { + pub fn readValueSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Value.Slice { + defer trail.index += len; + return .{ + .start = @intCast(trail.index), + .len = len, + }; + } + + pub fn readTypeSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Type.Slice { defer trail.index += len; return .{ .start = @intCast(trail.index), diff --git a/src/compiler/util.zig b/src/compiler/util.zig index a7210196e0d788228f85ee454e1392dd4dc882fd..f4c51ab4cc6eb35fdcd80465008b9e12672344fb 100644 --- a/src/compiler/util.zig +++ b/src/compiler/util.zig @@ -1,4 +1,3 @@ -/// TODO: move to io utility file pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config { if (@hasField(@TypeOf(writer), "unbuffered_writer")) { return detectTTYFromWriter(writer.unbuffered_writer); @@ -17,4 +16,26 @@ pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config { return .no_color; } +pub fn Ordinal(Int: type) type { + comptime assert(@typeInfo(Int).Int.signedness == .unsigned); + return enum(Int) { + _, + pub fn fromZeroBased(int: Int) @This() { + assert(int != std.math.maxInt(Int)); + return @enumFromInt(int); + } + pub fn fromOneBased(int: Int) @This() { + assert(int > 0); + return @enumFromInt(int - 1); + } + pub fn zeroBased(ord: @This()) Int { + return @intFromEnum(ord); + } + pub fn oneBased(ord: @This()) Int { + return @intFromEnum(ord) + 1; + } + }; +} + const std = @import("std"); +const assert = std.debug.assert; -- 2.54.0