From 1fd3bde3b870b71fbcdaefcac952e39e266807b0 Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Mon, 20 May 2024 17:28:35 -0700 Subject: [PATCH] add var decls and comptime math --- CHANGELOG.md | 12 ++- demo.txt | 25 ------ src/compiler/Air.Dump.zig | 24 +++++- src/compiler/Air.zig | 41 ++++++++-- src/compiler/InternPool.zig | 4 + src/compiler/JsLink.zig | 30 ++++++- src/compiler/JsNodeGen.zig | 149 +++++++++++++++++++++++++++++----- src/compiler/JsTree.zig | 14 +++- src/compiler/Sema.zig | 154 ++++++++++++++++++++++++++++++++---- src/compiler/Teg.Dump.zig | 9 ++- src/compiler/Teg.zig | 7 +- src/compiler/Transform.zig | 12 +-- src/compiler/Value.zig | 14 +++- src/compiler/main.zig | 28 ++++++- 14 files changed, 428 insertions(+), 95 deletions(-) delete mode 100644 demo.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index e208b2cba7d5a2b92d7d58de6d1a288c6e969736..c4389e56ab2b4d379f6cbd79c595173967eae089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ -## 0.1.1-alpha +## 0.1.1 -- introduce `Report` and show errors during Tokenize, Parse, and Transform. -- add flag --dump-ir for enabling ir dumps +- feat: `var` declarations / runtime memory +- feat: comptime binary operators +- feat: introduce `Report` and show error reports +- feat: show warnings for unused vars/lets/fns/params +- feat: add flag --dump-ir for enabling ir dumps +- fix: fix AIR generation for a block with a variable name -## 0.1.0-alpha +## 0.1.0 initial demo of compiler pipeline in zig. diff --git a/demo.txt b/demo.txt deleted file mode 100644 index ec22ae0f6084addf11709de75976f2a0fbce62ec..0000000000000000000000000000000000000000 --- a/demo.txt +++ /dev/null @@ -1,25 +0,0 @@ -let print = @js("console").log; - -fn doThing(x: float, y: float) -> float { - let math = if x < y { - x - y - } else b: { - let sum = x + y; - if sum > 100 { - break :b sum; - } - if x == y { - return 5; - } - sum * -sum - }; - if x == y { - return 5; - } - let divisor = if x > 10 { 2 } else { 3 }; - math / divisor -} - -print(doThing(1, 6)); -print(doThing(6, 4)); -print(doThing(100, 52)); diff --git a/src/compiler/Air.Dump.zig b/src/compiler/Air.Dump.zig index f2a3837eddd4782a15e7d862430590917ee9b3d3..4ee482de7cecaab7fb3a4f2c5216d5acd5398a01 100644 --- a/src/compiler/Air.Dump.zig +++ b/src/compiler/Air.Dump.zig @@ -77,6 +77,14 @@ const State = struct { try s.dumpRef(w, brk.value); try w.writeAll(")"); }, + .store => { + const store: Air.Store = air.instData(.store, inst); + try w.writeAll("("); + try s.dumpInstRef(w, store.memory); + try w.writeAll(", "); + try s.dumpRef(w, store.operand); + try w.writeAll(")"); + }, inline .add, .mul, .sub, @@ -92,12 +100,22 @@ const State = struct { try s.dumpRef(w, bin.rhs); try w.writeAll(")"); }, - inline .negate => |comptime_tag| { + inline .negate, + .bool_not, + => |comptime_tag| { const bin: Air.Un = air.instData(comptime_tag, inst); try w.writeAll("("); try s.dumpRef(w, bin.operand); try w.writeAll(")"); }, + .memory => { + const memory: Air.Memory = air.instData(.memory, inst); + try w.writeAll("("); + try s.dumpRef(w, memory.init); + try w.writeAll(", type: "); + try s.dumpValue(w, memory.ty); + try w.writeAll(")"); + }, .field => { const field: Air.Field = air.instData(.field, inst); try w.writeAll("("); @@ -138,14 +156,14 @@ const State = struct { } fn dumpRef(s: *State, w: anytype, ref: Inst.Ref) !void { - switch (ref.unwrap()) { + switch (ref.decode()) { .inst => |i| try s.dumpInstRef(w, i), .value => |v| try s.dumpValue(w, v), } } fn dumpRefRecurse(s: *State, w: anytype, ref: Inst.Ref) !void { - switch (ref.unwrap()) { + switch (ref.decode()) { .inst => |i| try s.dumpInst(w, i), .value => |v| try s.dumpValue(w, v), } diff --git a/src/compiler/Air.zig b/src/compiler/Air.zig index d27c11cd2f5df0b25f593752b0dba34a6fe0e947..2505f7a8a6dc08039f8ba38f9ded1452bfc83dc8 100644 --- a/src/compiler/Air.zig +++ b/src/compiler/Air.zig @@ -21,6 +21,9 @@ pub const Inst = struct { arg, field, + memory, + store, + // bin add, sub, @@ -34,6 +37,7 @@ pub const Inst = struct { // un negate, + bool_not, pub fn Data(comptime tag: Tag) type { return switch (tag) { @@ -43,6 +47,9 @@ pub const Inst = struct { .@"if" => If, .call => Call, .arg => Arg, + .field => Field, + .memory => Memory, + .store => Store, .add, .sub, .mul, @@ -51,10 +58,7 @@ pub const Inst = struct { .gt, .eq, => Bin, - .negate, - => Un, - .field, - => Field, + .negate, .bool_not => Un, }; } }; @@ -71,7 +75,7 @@ pub const Inst = struct { inst: Index, }; - pub fn unwrap(ref: Ref) Decoded { + pub fn decode(ref: Ref) Decoded { return switch (ref.kind) { .value => .{ .value = Value.fromInterned(@enumFromInt(ref.data)) }, .inst => .{ .inst = @enumFromInt(ref.data) }, @@ -90,6 +94,15 @@ pub const Inst = struct { return if (opt != .none) @bitCast(@intFromEnum(opt)) else null; } }; + + pub fn isVoid(ref: Ref) bool { + return ref.kind == .value and + ref.decode().value.toIntern() == .void_value; + } + + pub fn eql(ref: Ref, other: Ref) bool { + return @as(u32, @bitCast(ref)) == @as(u32, @bitCast(other)); + } }; /// reference to another instruction in this air @@ -165,6 +178,11 @@ pub const Bin = struct { rhs: Inst.Ref, }; +pub const Store = struct { + memory: Inst.Index, + operand: Inst.Ref, +}; + pub const Un = struct { operand: Inst.Ref, unused: Unused = .zero, @@ -200,6 +218,11 @@ pub const If = struct { }; }; +pub const Memory = struct { + init: Inst.Ref, + ty: Value, +}; + pub fn deinit(air: *Air, gpa: std.mem.Allocator) void { defer air.* = undefined; air.insts.deinit(gpa); @@ -227,7 +250,7 @@ pub fn instPayload(air: *const Air, comptime tag: Inst.Tag, index: Inst.Index) t } pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *InternPool) InternPool.Index { - return switch (ref.unwrap()) { + return switch (ref.decode()) { .value => |v| v.type(ip).toIntern(), .inst => |i| air.typeOfIndex(i, ip), }; @@ -244,7 +267,7 @@ 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_type, + .lt, .gt, .eq, .bool_not => .bool_type, .arg => air.instData(.arg, index).type.toIntern(), @@ -261,6 +284,10 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *InternPool) InternPo return base_type.toKey(ip).type_function.return_type; }, + .memory => air.instData(.memory, index).ty.toIntern(), + + .store => air.instData(.memory, air.instData(.store, index).memory).ty.toIntern(), + .@"if" => air.instData(.@"if", index).data.get(air).union_ty.toIntern(), }; } diff --git a/src/compiler/InternPool.zig b/src/compiler/InternPool.zig index a2a426385d4ca83d4c1e1ebfa446f9327ddee475..05e602024deb1dfac6c8a5d3fbd6dac5a42dbd74 100644 --- a/src/compiler/InternPool.zig +++ b/src/compiler/InternPool.zig @@ -202,6 +202,10 @@ pub const Index = enum(u32) { return .{ .kind = .value, .data = @intCast(@intFromEnum(index)) }; } + pub fn toValue(index: Index) @import("Value.zig") { + return .{ .ip_index = index }; + } + pub fn toKey(index: Index, ip: *const InternPool) Key { return ip.indexToKey(index); } diff --git a/src/compiler/JsLink.zig b/src/compiler/JsLink.zig index d7c0d57877747498e89f09fcfe5752b6c2e9acde..afb2c0310e78898c83cba0f1fc64c55691089eb6 100644 --- a/src/compiler/JsLink.zig +++ b/src/compiler/JsLink.zig @@ -198,7 +198,6 @@ fn printExpr(link: *JsLink, tree: *const JsTree, node: Node.Index, writer: anyty defer t.end(); switch (tree.nodeTag(node)) { - else => |tag| std.debug.panic("TODO: printExpr .{s}", .{@tagName(tag)}), .root => unreachable, .plain_value => { @@ -302,20 +301,28 @@ fn printExpr(link: *JsLink, tree: *const JsTree, node: Node.Index, writer: anyty .eq, .lt, .gt, + .assign_add, + .assign_sub, + .assign_mul, + .assign_div, => |tag| { const bin: JsTree.Bin = tree.nodeData(tag, node); const op: Operator = @field(Operator, @tagName(tag)); try link.printExpr(tree, bin.lhs, writer); try writer.writeAll(" "); - try writer.writeAll(op.symbol()); + try writer.writeAll(comptime op.symbol()); try writer.writeAll(" "); try link.printExpr(tree, bin.rhs, writer); }, - inline .negate => |tag| { + inline .negate, + .bool_not, + .pre_dec, + .pre_inc, + => |tag| { const un: JsTree.Un = tree.nodeData(tag, node); const op: Operator = @field(Operator, @tagName(tag)); - try writer.writeAll(op.symbol()); + try writer.writeAll(comptime op.symbol()); try link.printExpr(tree, un.operand, writer); }, @@ -402,6 +409,10 @@ const Level = enum { const Operator = enum { assign, + assign_add, + assign_sub, + assign_mul, + assign_div, add, sub, mul, @@ -410,10 +421,17 @@ const Operator = enum { lt, gt, negate, + bool_not, + pre_inc, + pre_dec, pub fn symbol(op: Operator) []const u8 { return switch (op) { .assign => "=", + .assign_add => "+=", + .assign_sub => "-=", + .assign_mul => "*=", + .assign_div => "/=", .add => "+", .sub => "-", .mul => "*", @@ -422,7 +440,11 @@ const Operator = enum { .lt => "<", .gt => ">", + .pre_inc => "++", + .pre_dec => "++", + .negate => "-", + .bool_not => "!", }; } }; diff --git a/src/compiler/JsNodeGen.zig b/src/compiler/JsNodeGen.zig index 6844decf311e81bba302324c23b1e9222a5b7833..2e57ba65862a96b28a570bd49a4e119303c5c7e0 100644 --- a/src/compiler/JsNodeGen.zig +++ b/src/compiler/JsNodeGen.zig @@ -145,6 +145,23 @@ fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void { }; }, + .memory => { + const i = @intFromEnum(inst); + const data: Air.Memory = c.air.instData(.memory, inst); + const value: JsTree.Node.Index.Optional = if (data.init.isVoid()) + .none + else + (try c.fetchExpression(data.init)).toOptional(); + + try c.addStmt(try c.add(.stmt_declare_local, .{ + .identifier_index = c.next_id, + .init = value, + })); + const id = try c.add(.local, .{ .id = c.next_id }); + c.next_id += 1; + c.air_node_map[i] = .{ .node = id }; + }, + inline .gt, .lt, .eq, @@ -161,7 +178,69 @@ fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void { try c.markExpression(inst, node); }, + .store => { + const data: Air.Store = c.air.instData(.store, inst); + + const target = try c.fetchInstExpression(data.memory); + + if (data.operand.kind == .inst) { + const index = data.operand.decode().inst; + switch (c.air.instTag(index)) { + inline .add, + .sub, + .mul, + .div, + => |tag| { + const inner_data: Air.Bin = c.air.instData(tag, index); + if (inner_data.lhs.eql(data.memory.toRef())) { + 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); + + return; + } + } + } + + // convert into a compound assignment + const node = try c.add( + @field(JsTree.Tag, "assign_" ++ @tagName(tag)), + .{ + .lhs = target, + .rhs = try c.fetchExpression(inner_data.rhs), + }, + ); + try c.markExpression(inst, node); + + return; + } + }, + else => {}, + } + } + + const node = try c.add(.assign, .{ + .lhs = target, + .rhs = try c.fetchExpression(data.operand), + }); + try c.markExpression(inst, node); + }, + inline .negate, + .bool_not, => |tag| { const data: Air.Un = c.air.instData(tag, inst); const node = try c.add(@field(JsTree.Tag, @tagName(tag)), .{ @@ -331,27 +410,32 @@ fn markExpression(c: *JsNodeGen, inst: Air.Inst.Index, node: Node.Index) !void { } } -fn fetchExpression(c: *JsNodeGen, inst: Air.Inst.Ref) !Node.Index { +fn fetchExpression(c: *JsNodeGen, ref: Air.Inst.Ref) !Node.Index { const t = tracer.trace(@src()); defer t.end(); - return switch (inst.unwrap()) { + return switch (ref.decode()) { .value => |v| c.valueToExpression(v), - .inst => |index| { - const data = c.air_node_map[@intFromEnum(index)]; - if (std.debug.runtime_safety) { - const flags: Air.Flags = c.air.instFlags(index); - switch (flags.uses) { - .none => unreachable, // incorrectly marked instruction as unused, - .single => {}, - .multi => assert(c.nodes.items(.tag)[@intFromEnum(data.node)] == .local), - } - } - return data.node; - }, + .inst => |index| fetchInstExpression(c, index), }; } +fn fetchInstExpression(c: *JsNodeGen, index: Air.Inst.Index) !Node.Index { + const t = tracer.trace(@src()); + defer t.end(); + + const data = c.air_node_map[@intFromEnum(index)]; + if (std.debug.runtime_safety) { + const flags: Air.Flags = c.air.instFlags(index); + switch (flags.uses) { + .none => unreachable, // incorrectly marked instruction as unused, + .single => {}, + .multi => assert(c.nodes.items(.tag)[@intFromEnum(data.node)] == .local), + } + } + return data.node; +} + fn valueToExpression(c: *JsNodeGen, v: Value) !Node.Index { const t = tracer.trace(@src()); defer t.end(); @@ -405,7 +489,7 @@ fn codeGenBranch(c: *JsNodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Br const this_label = c.next_label; var requires_label = false; - var items = switch (inst.unwrap()) { + var items = switch (inst.decode()) { .value => |v| items: { if (v.ip_index == .void_value) return null; @@ -499,11 +583,15 @@ fn codeGenBlock(c: *JsNodeGen, inst: Air.Inst.Index) !void { })); } +// Ideally, we convert everything to an expression. This allows for much +// more compact code. When that isnt possible (`return`, `while`, etc), +// we detect that here and ensure to lower `if` to actual if statements, +// and so on. fn canLowerToExpression(c: *const JsNodeGen, inst: Air.Inst.Ref) bool { const t = tracer.trace(@src()); defer t.end(); - return switch (inst.unwrap()) { + return switch (inst.decode()) { .value => true, .inst => |index| switch (c.air.instTag(index)) { .js_global_ref, @@ -518,12 +606,33 @@ fn canLowerToExpression(c: *const JsNodeGen, inst: Air.Inst.Ref) bool { .lt, .gt, .negate, + .bool_not, + .store, => true, + + // JavaScript variable declarations require statements. + // Later, this could be made true if we can hoist temporaries up. + .memory => false, + + // break/return must be a js statement .@"break", => false, - // TODO: these depend - .block => false, - .@"if" => false, + + .block => { + // TODO: blocks that are + // - all statements except last are lowerable + // - the last statement is lowerable OR a break of this block + return false; + }, + + .@"if" => { + const call = c.air.instData(.@"if", index); + const data = call.data.get(c.air); + + return c.canLowerToExpression(call.cond) and + c.canLowerToExpression(data.yes) and + c.canLowerToExpression(data.no); + }, }, }; } @@ -533,7 +642,7 @@ fn lowerToExpression(c: *JsNodeGen, ref: Air.Inst.Ref) !Node.Index { defer t.end(); assert(c.canLowerToExpression(ref)); - return switch (ref.unwrap()) { + return switch (ref.decode()) { .value => |v| try c.valueToExpression(v), .inst => |index| { try c.codeGenInst(index); diff --git a/src/compiler/JsTree.zig b/src/compiler/JsTree.zig index dbf57341c79c0bac4a1fa32fdbb55734b27b83ec..a7cd8da9750c0194208a14e526da42a4d876ddd7 100644 --- a/src/compiler/JsTree.zig +++ b/src/compiler/JsTree.zig @@ -26,6 +26,10 @@ pub const Tag = enum { decl, // bin ops assign, + assign_add, + assign_sub, + assign_mul, + assign_div, add, sub, mul, @@ -37,9 +41,11 @@ pub const Tag = enum { call, field, ternary, - // un ops + // prefix ops negate, bool_not, + pre_inc, + pre_dec, // stmts /// `let ... = ...` stmt_declare_local, @@ -60,6 +66,10 @@ pub const Tag = enum { .global => GlobalRef, .decl => DeclRef, .assign, + .assign_add, + .assign_sub, + .assign_mul, + .assign_div, .add, .sub, .mul, @@ -71,6 +81,8 @@ pub const Tag = enum { .ternary => Ternary, .negate, .bool_not, + .pre_inc, + .pre_dec, => Un, .call => Call, .field => Field, diff --git a/src/compiler/Sema.zig b/src/compiler/Sema.zig index badc1be0edaa14ec436c91a4ca2ed1206b55ec5d..937555b7d476d35755b8b262b603d3dc958d0ba7 100644 --- a/src/compiler/Sema.zig +++ b/src/compiler/Sema.zig @@ -50,7 +50,7 @@ const InstMap = struct { map.* = undefined; } - pub fn get(map: *InstMap, teg_code: Teg.Inst.Index) ?Air.Inst.Ref { + pub fn get(map: *const InstMap, teg_code: Teg.Inst.Index) ?Air.Inst.Ref { const source_i = @intFromEnum(teg_code); const start_i = @intFromEnum(map.start); assert(source_i >= start_i and source_i <= (start_i + map.items.len)); @@ -58,7 +58,12 @@ const InstMap = struct { } pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void { - assert(map.get(from) == null); + if (assertWithMessage(map.get(from) == null)) { + std.debug.panic("%{d} is already mapped (to #{d})", .{ + @intFromEnum(from), + @intFromEnum(map.get(from).?.decode().inst), + }); + } const source_i = @intFromEnum(from); const start_i = @intFromEnum(map.start); map.items[source_i - start_i] = to.toOptional(); @@ -128,7 +133,7 @@ pub fn analyzeDeclaration(s: *Sema) anyerror!Value { const root = (try s.analyzeBlock( fn_decl.body.toIndex() orelse @panic("TODO: please change this to Index. Ref is not allowed here anymore"), - )).unwrap().inst; + )).decode().inst; assert(@intFromEnum(root) == 0); const func_prototype = try s.ip.get(s.gpa, .{ @@ -153,13 +158,12 @@ pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) anyerror!Air.Inst.Ref { const t = tracer.trace(@src()); defer t.end(); - const block = try s.addBlock(); + const scratch_top = s.scratch.items.len; + defer s.scratch.shrinkRetainingCapacity(scratch_top); + const block = try s.addBlock(); try s.inst_map.add(in, block.block_index.toRef()); - const scratch_top = s.scratch.items.len; - defer s.scratch.shrinkRetainingCapacity(scratch_top); - var last: Air.Inst.Ref = undefined; const instructions = s.code.blockInstructions(in); assert(instructions.len != 0); @@ -194,8 +198,13 @@ fn analyzeCode(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { defer t.end(); const out = try s.analyzeCodeInner(in); - if (s.code.instTag(in) != .block) + + // Block refs are added automatically + if (s.code.instTag(in) == .block) { + assert(s.inst_map.get(in) != null); + } else { try s.inst_map.add(in, out); + } return out; } @@ -269,7 +278,11 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { const rhs = try s.resolveRefRuntime(bin.rhs); if (lhs.kind == .value and rhs.kind == .value) { - @panic("TODO: comptime math operation"); + return (try s.comptimeBin( + @field(BinaryOp, @tagName(tag)), + lhs.decode().value, + rhs.decode().value, + )); } const left_type = s.typeOfRef(lhs); @@ -284,7 +297,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { .rhs = rhs, }); }, - inline .negate => |tag| { + inline .negate, .bool_not => |tag| { const un: Teg.Inst.RefNode = s.code.instData(tag, in); const operand = try s.resolveRefRuntime(un.operand); @@ -301,7 +314,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { .@"break" => { const brk: Teg.Inst.Break = s.code.instPayload(.@"break", in); const operand = try s.resolveRefRuntime(brk.operand); - const block_ref = s.inst_map.get(brk.block).?.unwrap().inst; // block ref is resolved by teg + const block_ref = s.inst_map.get(brk.block).?.decode().inst; // block ref is resolved by teg return s.addInstRef(.@"break", .{ .block_ref = block_ref, .value = operand, @@ -398,12 +411,41 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { }, .block => { const block = try s.analyzeBlock(in); - switch (block.unwrap()) { - .inst => |t| try s.scratch.append(s.gpa, @intFromEnum(t)), + switch (block.decode()) { + .inst => |t| if (s.tempAir().instTag(t) == .block) { + try s.scratch.append(s.gpa, @intFromEnum(t)); + }, else => {}, } return block; }, + .memory => { + const data = s.code.instData(.memory, in); + + const initial_value = try s.resolveRefRuntime(data.operand); + + return try s.addInstRef(.memory, .{ + .init = initial_value, + .ty = s.typeOfRef(initial_value).toValue(), + }); + }, + .store => { + const data: Teg.Inst.Store = s.code.instPayload(.store, in); + assert(s.code.instTag(data.memory) == .memory); // corrupt store instruction + + 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, + ); + + return try s.addInstRef(.store, .{ + .memory = location, + .operand = operand, + }); + }, else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}), } } @@ -420,19 +462,25 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) anyerror!Air.Inst.Ref { const scratch_top = s.scratch.items.len; defer s.scratch.shrinkRetainingCapacity(scratch_top); + std.debug.print("{d} {d}\n", .{ scratch_top, s.scratch.items.len }); const visited = try s.resolveRefRuntime(in); + std.debug.print("{d} {d}\n", .{ scratch_top, s.scratch.items.len }); - if (scratch_top == s.scratch.items.len) { + if (scratch_top == s.scratch.items.len and visited.kind == .value) { return visited; } if (visited.kind == .inst and - s.air_instructions.items(.tag)[@intFromEnum(visited.unwrap().inst)] == .block) + s.air_instructions.items(.tag)[@intFromEnum(visited.decode().inst)] == .block) { return visited; } const block = try s.addBlock(); + _ = try s.addInstRef(.@"break", .{ + .block_ref = block.block_index, + .value = visited, + }); return (try block.finish(s, scratch_top)).toRef(); } @@ -443,7 +491,7 @@ fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) anyerror!Air. if (s.inst_map.get(index)) |air| { if (mark) { - switch (air.unwrap()) { + switch (air.decode()) { .inst => |i| s.markInst(i), else => {}, } @@ -464,7 +512,7 @@ fn resolveRefComptime(s: *Sema, ref: Teg.Inst.Ref) !Value { }; if (s.inst_map.get(in)) |air| { - switch (air.unwrap()) { + switch (air.decode()) { .value => |value| return value, else => {}, } @@ -545,6 +593,70 @@ fn unionType(s: *Sema, a: Value, b: Value) Value { // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } }); } +const BinaryOp = enum { + add, + sub, + mul, + div, + lt, + gt, + eq, +}; + +fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref { + switch (op) { + .add => { + const l = lhs.toFloat(s.ip) orelse @panic("TODO: typecheck / string add"); + const r = rhs.toFloat(s.ip) orelse @panic("TODO: typecheck / string add"); + return (try s.ip.get(s.gpa, .{ + .float = l + r, + })).toAir(); + }, + inline .sub, .mul, .div => |tag| { + const l = lhs.toFloat(s.ip) orelse @panic("TODO: typecheck"); + const r = rhs.toFloat(s.ip) orelse @panic("TODO: typecheck"); + + const result = switch (tag) { + .sub => l - r, + .mul => l * r, + .div => l / r, + else => comptime unreachable, + }; + + return (try s.ip.get(s.gpa, .{ + .float = result, + })).toAir(); + }, + + inline .lt, + .gt, + => |tag| { + const l = lhs.toFloat(s.ip) orelse @panic("TODO: typecheck"); + const r = rhs.toFloat(s.ip) orelse @panic("TODO: typecheck"); + const result = switch (tag) { + .lt => l < r, + .gt => l > r, + else => comptime unreachable, + }; + return @as( + InternPool.Index, + if (result) .bool_true else .bool_false, + ).toAir(); + }, + + .eq, + => { + const l = lhs.toFloat(s.ip) orelse @panic("TODO: typecheck / non-numeric eq"); + const r = rhs.toFloat(s.ip) orelse @panic("TODO: typecheck / non-numeric eq"); + const result = l == r; + return @as( + InternPool.Index, + if (result) .bool_true else .bool_false, + ).toAir(); + }, + } +} + /// --- helper --- const IncompleteBlock = struct { block_index: Air.Inst.Index, @@ -655,6 +767,14 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const log = std.log.scoped(.sema); +inline fn assertWithMessage(cond: bool) bool { + if (std.debug.runtime_safety) { + return !cond; + } else if (!cond) { + unreachable; // assertion failure + } +} + const Comp = @import("Comp.zig"); const File = Comp.File; const Decl = Comp.Decl; diff --git a/src/compiler/Teg.Dump.zig b/src/compiler/Teg.Dump.zig index 172ca4e9274ffb754d658e5a0ea3dfd638ab4991..b9360febed1a4c8e88050d2ef091230ac658199a 100644 --- a/src/compiler/Teg.Dump.zig +++ b/src/compiler/Teg.Dump.zig @@ -93,7 +93,6 @@ const State = struct { try tty.setColor(writer, .reset); }, inline .field, - .store, .add, .sub, .mul, @@ -129,6 +128,14 @@ const State = struct { try s.dumpRefMarker(writer, data.operand); try writer.writeAll(")"); }, + .store => { + const data: Teg.Inst.Store = teg.instData(.store, inst).extra_index.get(teg); + try writer.writeAll("("); + try s.dumpRefMarker(writer, data.memory.toRef()); + try writer.writeAll(", "); + try s.dumpRefMarker(writer, data.operand); + try writer.writeAll(")"); + }, .string => { const data: Teg.SlicedString = teg.instData(.string, inst).extra_index.get(teg); try tty.setColor(writer, .green); diff --git a/src/compiler/Teg.zig b/src/compiler/Teg.zig index b2444b4221296b0deb11a2417e02447a0eef8f38..31246d7b0f37f75cac3212d0001c40391a21b863 100644 --- a/src/compiler/Teg.zig +++ b/src/compiler/Teg.zig @@ -91,6 +91,7 @@ pub const Inst = struct { .@"if" => PlNode(If), .load_decl => StrNode, .param => DataNode, + .store => PlNode(Store), // Single instruction operand .memory, @@ -108,7 +109,6 @@ pub const Inst = struct { // Two instruction operands .field, - .store, .add, .sub, .mul, @@ -258,6 +258,11 @@ pub const Inst = struct { rhs: Inst.Ref, }; + pub const Store = struct { + memory: Inst.Index, + operand: Inst.Ref, + }; + pub const As = struct { type: Inst.Ref, operand: Inst.Ref, diff --git a/src/compiler/Transform.zig b/src/compiler/Transform.zig index 55d9d703b48cbef8db5db8cbdab9df6705248769..ab3f324cfe5d79e590c1f6154d88fc0585699da0 100644 --- a/src/compiler/Transform.zig +++ b/src/compiler/Transform.zig @@ -656,9 +656,9 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .tag = .store, .data = EncodedData.encode(.store, .{ .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Bin, .{ - .lhs = lhs, - .rhs = rhs, + .extra_index = try t.extra.append(t.gpa, Inst.Store, .{ + .memory = lhs.toIndex() orelse @panic("TODO: convert this into Ref"), + .operand = rhs, }), }), }); @@ -728,9 +728,9 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .tag = .store, .data = EncodedData.encode(.store, .{ .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Bin, .{ - .lhs = lhs, - .rhs = compute_inst, + .extra_index = try t.extra.append(t.gpa, Inst.Store, .{ + .memory = lhs.toIndex() orelse @panic("TODO: convert this into Ref"), + .operand = compute_inst, }), }), }); diff --git a/src/compiler/Value.zig b/src/compiler/Value.zig index 4882152d0b2e1bdca7c543a656bb52cd7aa16f68..c91366a1fc546c9bf7abf02025e1343754fabc7d 100644 --- a/src/compiler/Value.zig +++ b/src/compiler/Value.zig @@ -22,7 +22,7 @@ pub fn asBool(val: Value) i32 { }; } -pub fn asString(val: Value, ip: *InternPool) InternPool.String.WithLen { +pub fn asString(val: Value, ip: *const InternPool) InternPool.String.WithLen { const index = val.toIntern(); return ip.items.items(.data)[@intFromEnum(index)].decode( .string, @@ -31,7 +31,7 @@ pub fn asString(val: Value, ip: *InternPool) InternPool.String.WithLen { .get(ip); } -pub fn @"type"(val: Value, ip: *InternPool) Value { +pub fn @"type"(val: Value, ip: *const InternPool) Value { const v: InternPool.Index = switch (val.toKey(ip)) { .simple_type, .type_function => .type_type, @@ -47,8 +47,16 @@ pub fn @"type"(val: Value, ip: *InternPool) Value { 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: *InternPool) bool { +pub fn isEnsureVoid(val: Value, ip: *const InternPool) bool { const intern = val.toIntern(); const i = @intFromEnum(intern); diff --git a/src/compiler/main.zig b/src/compiler/main.zig index e8991d71f64904475a004d8da1ee92926bb3a182..ee011e37c09e3455562948887dea8351d98a3e40 100644 --- a/src/compiler/main.zig +++ b/src/compiler/main.zig @@ -43,6 +43,7 @@ pub fn main() !void { const cwd = try std.posix.getcwd(&cwd_buf); var exe_name: []const u8 = undefined; + var dump_ir = false; { var args = try std.process.argsWithAllocator(alloc); @@ -52,8 +53,24 @@ pub fn main() !void { errdefer alloc.free(exe_name); while (args.next()) |arg| { - maybe_file_path = try std.fs.path.resolve(alloc, &.{ cwd, arg }); - break; + if (std.mem.startsWith(u8, arg, "-")) { + if (std.mem.eql(u8, arg, "--dump-ir")) { + dump_ir = true; + } else { + std.debug.print("error: unknown flag '{s}'\n", .{arg}); + maybe_file_path = null; + break; + } + continue; + } + + if (maybe_file_path == null) { + maybe_file_path = try std.fs.path.resolve(alloc, &.{ cwd, arg }); + } else { + std.debug.print("error: multiple files specified\n", .{}); + maybe_file_path = null; + break; + } } } defer alloc.free(exe_name); @@ -70,7 +87,12 @@ pub fn main() !void { std.process.exit(1); }; - var comp: Comp = .{ .gpa = tracer.tracingAllocator(&alloc) }; + var comp: Comp = .{ + .gpa = tracer.tracingAllocator(&alloc), + .options = .{ + .dump_ir = dump_ir, + }, + }; try comp.init(); defer comp.deinit(); const main_file = try comp.getFile(file_path); -- 2.54.0