diff --git a/src/AstGen.zig b/src/AstGen.zig index c27ec09..4d7dfdd 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -504,6 +504,7 @@ fn setDeclaration( .extra_index = astgen.addExtraAssumeCapacity(Ir.Inst.Declaration{ .name = args.name, .value = args.value, + .flags = if (args.node.tag == .const_decl) 0x01 else 0x00, }), }; } diff --git a/src/Ir.zig b/src/Ir.zig index e4a62af..5ddadb9 100644 --- a/src/Ir.zig +++ b/src/Ir.zig @@ -230,6 +230,7 @@ pub const Inst = struct { pub const Declaration = struct { name: NullTerminatedString, value: Index, + flags: u32, }; pub const Var = struct { diff --git a/src/Sema.zig b/src/Sema.zig index 19988a0..4de2fb7 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -12,7 +12,7 @@ gpa: std.mem.Allocator, arena: std.mem.Allocator, module: *compile.Module, ir: Ir, -inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, Ref) = .empty, +inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, ValueInfo) = .empty, errors: *std.ArrayListUnmanaged(Module.Error), const InnerError = error{ @@ -22,19 +22,32 @@ const InnerError = error{ InvalidJump, }; -pub const Ref = union(enum) { +pub const ValueInfo = union(enum) { none, - bool_true, - bool_false, - index: u32, - constant: InternPool.Constant.Index, - variable: InternPool.Constant.Index, - temporary: u32, - // FIXME: This is horrible. - knot: struct { - const_index: InternPool.Constant.Index, - namespace: *Module.Namespace, - }, + value: InternPool.Index, + variable: InternPool.Index, + knot: InternPool.Index, + stitch: InternPool.Index, + temp: u32, +}; + +pub const Value = struct { + ip_index: InternPool.Index, + + pub fn unwrap(value: Value, ip: *InternPool) u64 { + const t = ip.values.items[@intFromEnum(value.toInterned())]; + return t.int; + } + + pub fn fromInterned(index: InternPool.Index) Value { + assert(index != .none); + return .{ .ip_index = index }; + } + + pub fn toInterned(value: Value) InternPool.Index { + assert(value.ip_index != .none); + return value.ip_index; + } }; pub const SrcLoc = struct { @@ -60,44 +73,39 @@ fn fail( return error.AnalysisFail; } -/// Retrieve an index into the global constant pool for an integer value. -fn getOrPutInt(sema: *Sema, value: u64) !Ref { - const const_index = try sema.module.intern_pool.getOrPutInt(sema.gpa, value); - return .{ .constant = const_index }; +fn resolveInst(sema: *Sema, ref: Ir.Inst.Ref) ValueInfo { + if (ref.toIndex()) |index| { + return sema.inst_map.get(index).?; + } + return .{ .value = @enumFromInt(@intFromEnum(ref)) }; } -/// Retrieve an index into the global constant pool for a string value. -fn getOrPutStr(sema: *Sema, value: Ir.NullTerminatedString) !Ref { - const const_index = try sema.module.intern_pool.getOrPutStr(sema.gpa, value); - return .{ .constant = const_index }; +fn resolveValue(_: *Sema, info: ValueInfo) ?Value { + switch (info) { + .value => |value| return .fromInterned(value), + else => return null, + } } pub fn lookupIdentifier( sema: *Sema, - chunk: *Chunk, - ident: InternPool.Constant.Index, + builder: *Builder, + ident: InternPool.Index, src: SrcLoc, -) !Ref { - return sema.lookupInNamespace(chunk.namespace, ident, src); +) !Module.Namespace.Decl { + return sema.lookupInNamespace(builder.namespace, ident, src); } pub fn lookupInNamespace( sema: *Sema, namespace: *Module.Namespace, - ident: InternPool.Constant.Index, + ident: InternPool.Index, src: SrcLoc, -) !Ref { +) !Module.Namespace.Decl { var scope: ?*Module.Namespace = namespace; while (scope) |s| : (scope = s.parent) { - if (s.decls.get(ident)) |decl| switch (decl.tag) { - .knot, .stitch => return .{ .knot = .{ - .namespace = decl.namespace.?, - .const_index = ident, - } }, - .variable => return .{ .variable = ident }, - }; + if (s.decls.get(ident)) |decl| return decl; } - // FIXME: This is temporary return sema.fail(src, "unknown identifier", .{}); } @@ -106,11 +114,11 @@ pub fn deinit(sema: *Sema) void { sema.* = undefined; } -pub const Chunk = struct { +pub const Builder = struct { sema: *Sema, namespace: *Module.Namespace, code: *Module.CodeChunk, - constants_map: std.AutoHashMapUnmanaged(InternPool.Constant.Index, u8) = .empty, + constants_map: std.AutoHashMapUnmanaged(InternPool.Index, u8) = .empty, labels: std.ArrayListUnmanaged(Label) = .empty, fixups: std.ArrayListUnmanaged(Fixup) = .empty, @@ -129,116 +137,102 @@ pub const Chunk = struct { const dummy_address = 0xffffffff; - pub fn deinit(chunk: *Chunk, gpa: std.mem.Allocator) void { - chunk.constants_map.deinit(gpa); - chunk.labels.deinit(gpa); - chunk.fixups.deinit(gpa); + pub fn deinit(builder: *Builder, gpa: std.mem.Allocator) void { + builder.constants_map.deinit(gpa); + builder.labels.deinit(gpa); + builder.fixups.deinit(gpa); } /// Reserve a stack slot for temporary variables. - fn addStackSlot(chunk: *Chunk) u8 { - const new_slot = chunk.code.stack_size; - chunk.code.stack_size += 1; + fn addStackSlot(builder: *Builder) u8 { + const new_slot = builder.code.stack_size; + builder.code.stack_size += 1; return @intCast(new_slot); } /// Reserve a stack slot for a parameter. - fn addParameter(chunk: *Chunk) u8 { - chunk.code.args_count += 1; - return chunk.addStackSlot(); + fn addParameter(builder: *Builder) u8 { + builder.code.args_count += 1; + return builder.addStackSlot(); } - fn addByteOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref { - const gpa = chunk.sema.gpa; - const bytecode = &chunk.code.bytecode; - const byte_index = bytecode.items.len; + fn addByteOp(builder: *Builder, op: Story.Opcode) error{OutOfMemory}!void { + const gpa = builder.sema.gpa; + const bytecode = &builder.code.bytecode; + //const byte_index = bytecode.items.len; try bytecode.append(gpa, @intFromEnum(op)); - return .{ .index = @intCast(byte_index) }; + //return .toIndex(); } - fn addConstOp(chunk: *Chunk, op: Story.Opcode, arg: u8) error{OutOfMemory}!Ref { - const gpa = chunk.sema.gpa; - const bytecode = &chunk.code.bytecode; - const byte_index = bytecode.items.len; + fn addConstOp(builder: *Builder, op: Story.Opcode, arg: u8) error{OutOfMemory}!void { + const gpa = builder.sema.gpa; + const bytecode = &builder.code.bytecode; + //const byte_index = bytecode.items.len; try bytecode.ensureUnusedCapacity(gpa, 2); bytecode.appendAssumeCapacity(@intFromEnum(op)); bytecode.appendAssumeCapacity(arg); - return .{ .index = @intCast(byte_index) }; + //return .{ .index = @intCast(byte_index) }; } - fn addJumpOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref { - const gpa = chunk.sema.gpa; - const bytecode = &chunk.code.bytecode; + fn addJumpOp(builder: *Builder, op: Story.Opcode) error{OutOfMemory}!u32 { + const gpa = builder.sema.gpa; + const bytecode = &builder.code.bytecode; try bytecode.ensureUnusedCapacity(gpa, 3); bytecode.appendAssumeCapacity(@intFromEnum(op)); bytecode.appendAssumeCapacity(0xff); bytecode.appendAssumeCapacity(0xff); - return .{ .index = @intCast(bytecode.items.len - 2) }; + return @intCast(bytecode.items.len - 2); } - fn addFixup(chunk: *Chunk, op: Story.Opcode, label: usize) !void { - const code_ref = try chunk.addJumpOp(op); - return chunk.fixups.append(chunk.sema.gpa, .{ + fn addFixup(builder: *Builder, op: Story.Opcode, label: usize) !void { + return builder.fixups.append(builder.sema.gpa, .{ .mode = .relative, .label_index = @intCast(label), - .code_offset = code_ref.index, + .code_offset = try builder.addJumpOp(op), }); } - fn addFixupAbsolute(chunk: *Chunk, op: Story.Opcode, label: usize) !void { - const code_ref = try chunk.addJumpOp(op); - return chunk.fixups.append(chunk.sema.gpa, .{ + fn addFixupAbsolute(builder: *Builder, op: Story.Opcode, label: usize) !void { + return builder.fixups.append(builder.sema.gpa, .{ .mode = .absolute, .label_index = @intCast(label), - .code_offset = code_ref.index, + .code_offset = try builder.addJumpOp(op), }); } - fn addLabel(chunk: *Chunk) error{OutOfMemory}!usize { - const label_index = chunk.labels.items.len; - try chunk.labels.append(chunk.sema.gpa, .{ + fn addLabel(builder: *Builder) error{OutOfMemory}!usize { + const label_index = builder.labels.items.len; + try builder.labels.append(builder.sema.gpa, .{ .code_offset = dummy_address, }); return label_index; } - fn setLabel(chunk: *Chunk, label_index: usize) void { - const bytecode = &chunk.code.bytecode; + fn setLabel(builder: *Builder, label_index: usize) void { + const bytecode = &builder.code.bytecode; const code_offset = bytecode.items.len; - assert(label_index <= chunk.labels.items.len); + assert(label_index <= builder.labels.items.len); - const label_data = &chunk.labels.items[label_index]; + const label_data = &builder.labels.items[label_index]; label_data.code_offset = code_offset; } - /// Intern a reference to a global constant within this chunk. - fn getOrPutConstantIndex(chunk: *Chunk, index: InternPool.Constant.Index) !u8 { - const gpa = chunk.sema.gpa; - const constants = &chunk.code.constants; - if (chunk.constants_map.get(index)) |local_index| return local_index; + fn getOrPutConstantIndex(builder: *Builder, index: InternPool.Index) !u8 { + const gpa = builder.sema.gpa; + const constants = &builder.code.constants; + if (builder.constants_map.get(index)) |local_index| return local_index; const local_index: u8 = @intCast(constants.items.len); try constants.append(gpa, @intCast(@intFromEnum(index))); - try chunk.constants_map.put(gpa, index, local_index); + try builder.constants_map.put(gpa, index, local_index); return local_index; } - fn resolveInst(chunk: *Chunk, ref: Ir.Inst.Ref) Ref { - if (ref.toIndex()) |index| { - return chunk.sema.inst_map.get(index).?; - } - switch (ref) { - .bool_true => return .bool_true, - .bool_false => return .bool_false, - else => unreachable, - } - } + pub fn finalize(builder: *Builder) !void { + const bytecode = &builder.code.bytecode; - pub fn finalize(chunk: *Chunk) !void { - const bytecode = &chunk.code.bytecode; - - for (chunk.fixups.items) |fixup| { - const label = chunk.labels.items[fixup.label_index]; + for (builder.fixups.items) |fixup| { + const label = builder.labels.items[fixup.label_index]; assert(label.code_offset != dummy_address); const target_offset: usize = switch (fixup.mode) { .relative => label.code_offset - fixup.code_offset - 2, @@ -254,116 +248,181 @@ pub const Chunk = struct { } } - fn doLoad(chunk: *Chunk, ref: Ref) InnerError!Ref { - switch (ref) { - .none => return ref, - .bool_true => return chunk.addByteOp(.true), - .bool_false => return chunk.addByteOp(.false), - .constant => |index| { - const local_index = try chunk.getOrPutConstantIndex(index); - return chunk.addConstOp(.load_const, @intCast(local_index)); + fn ensureLoad(self: *Builder, info: ValueInfo) InnerError!void { + switch (info) { + .none => unreachable, // caller should never load .none + .value => |index| { + const local_index = try self.getOrPutConstantIndex(index); + try self.addConstOp(.load_const, @intCast(local_index)); }, - .variable => |index| { - const local_index = try chunk.getOrPutConstantIndex(index); - return chunk.addConstOp(.load_global, @intCast(local_index)); + .variable, .knot, .stitch => |index| { + const local_index = try self.getOrPutConstantIndex(index); + try self.addConstOp(.load_global, @intCast(local_index)); }, - .temporary => |id| return chunk.addConstOp(.load, @intCast(id)), - .index => return ref, - .knot => unreachable, + .temp => |temp| try self.addConstOp(.load, @intCast(temp)), } } }; -fn irInteger(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref { +fn irInt(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo { const value = sema.ir.instructions[@intFromEnum(inst)].data.int; - return sema.getOrPutInt(value); + const ip_index = try sema.module.intern_pool.getOrPutInt(sema.gpa, value); + return .{ .value = ip_index }; } -fn irString(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref { +fn irStr(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo { const data = sema.ir.instructions[@intFromEnum(inst)].data.str; - return sema.getOrPutStr(data.start); + const ip_index = try sema.module.intern_pool.getOrPutStr(sema.gpa, data.start); + return .{ .value = ip_index }; } fn irUnaryOp( sema: *Sema, - chunk: *Chunk, + builder: *Builder, inst: Ir.Inst.Index, op: Story.Opcode, -) InnerError!Ref { +) InnerError!ValueInfo { const data = sema.ir.instructions[@intFromEnum(inst)].data.un; - const lhs = chunk.resolveInst(data.lhs); - try sema.analyzeArithmeticArg(chunk, lhs, .{ .src_offset = 0 }); - return chunk.addByteOp(op); + const ip = &sema.module.intern_pool; + const lhs = sema.resolveInst(data.lhs); + //const lhs_src: SrcLoc = .{ .src_offset = 0 }; + //try sema.analyzeArithmeticArg(builder, lhs, lhs_src); + + if (sema.resolveValue(lhs)) |lhs_value| { + const lhs_unwrapped = lhs_value.unwrap(ip); + _ = lhs_unwrapped; + const new_value = switch (op) { + //.not => !lhs_unwrapped, + //.neg => -lhs_unwrapped, + else => unreachable, + }; + return .{ + .value = try ip.getOrPutInt(sema.gpa, new_value), + }; + } + + try builder.ensureLoad(lhs); + try builder.addByteOp(op); + return .none; } fn irBinaryOp( sema: *Sema, - chunk: *Chunk, + builder: *Builder, inst: Ir.Inst.Index, op: Story.Opcode, -) InnerError!Ref { +) InnerError!ValueInfo { const data = sema.ir.instructions[@intFromEnum(inst)].data.bin; - const lhs = chunk.resolveInst(data.lhs); - const rhs = chunk.resolveInst(data.rhs); + const ip = &sema.module.intern_pool; + const lhs = sema.resolveInst(data.lhs); + const rhs = sema.resolveInst(data.rhs); + //const lhs_src: SrcLoc = .{ .src_offset = 0 }; + //const rhs_src: SrcLoc = .{ .src_offset = 0 }; + //try sema.analyzeArithmeticArg(builder, lhs, lhs_src); + //try sema.analyzeArithmeticArg(builder, rhs, rhs_src); - try sema.analyzeArithmeticArg(chunk, lhs, .{ .src_offset = 0 }); - try sema.analyzeArithmeticArg(chunk, rhs, .{ .src_offset = 0 }); - return chunk.addByteOp(op); + if (sema.resolveValue(lhs)) |lhs_value| { + if (sema.resolveValue(rhs)) |rhs_value| { + const lhs_unwrapped = lhs_value.unwrap(ip); + const rhs_unwrapped = rhs_value.unwrap(ip); + const new_value = switch (op) { + .add => lhs_unwrapped + rhs_unwrapped, + .sub => lhs_unwrapped - rhs_unwrapped, + .mul => lhs_unwrapped * rhs_unwrapped, + .div => lhs_unwrapped / rhs_unwrapped, + else => unreachable, + }; + return .{ + .value = try ip.getOrPutInt(sema.gpa, new_value), + }; + } + } + + try builder.ensureLoad(lhs); + try builder.ensureLoad(rhs); + try builder.addByteOp(op); + return .none; } -fn irAlloc(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref { +fn irDeclRef( + sema: *Sema, + builder: *Builder, + inst: Ir.Inst.Index, + inline_block: bool, +) InnerError!ValueInfo { + const data = sema.ir.instructions[@intFromEnum(inst)].data.str_tok; + const ip_index = try sema.module.intern_pool.getOrPutStr(sema.gpa, data.start); + const src_loc: SrcLoc = .{ .src_offset = data.src_offset }; + const ident = try sema.lookupIdentifier(builder, ip_index, src_loc); + if (inline_block) { + switch (ident.tag) { + .knot, .stitch => unreachable, + .var_const => return sema.resolveGlobalDecl(builder, ip_index, src_loc), + .var_mut => return sema.fail( + src_loc, + "global variable assignments cannot refer to other variables", + .{}, + ), + } + } else { + switch (ident.tag) { + .knot => return .{ .knot = ip_index }, + .stitch => return .{ .stitch = ip_index }, + .var_mut => return .{ .variable = ip_index }, + .var_const => return .{ .variable = ip_index }, + } + } +} + +fn irAlloc(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!ValueInfo { // TODO: Add constraints on how many temporaries we can have. // max(u8) or max(u16) are most likey appropriate. - return .{ .temporary = chunk.addStackSlot() }; + return .{ .temp = builder.addStackSlot() }; } -fn irStore(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { +fn irStore(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void { const data = sema.ir.instructions[@intFromEnum(inst)].data.bin; - const lhs = chunk.resolveInst(data.lhs); - const rhs = chunk.resolveInst(data.rhs); - _ = try chunk.doLoad(rhs); + const lhs = sema.resolveInst(data.lhs); + const rhs = sema.resolveInst(data.rhs); + const src: SrcLoc = .{ .src_offset = 0 }; + + try builder.ensureLoad(rhs); + switch (lhs) { - .bool_true, .bool_false => unreachable, // TODO: "Cannot assign to boolean" - .none => unreachable, - .constant => |_| unreachable, // TODO: "Cannot assign to constant" - .knot => |_| unreachable, // TODO: "Cannot assign to knot" - .variable => |index| { - _ = try chunk.addConstOp(.store_global, @intCast(@intFromEnum(index))); - }, - .temporary => |index| { - _ = try chunk.addConstOp(.store, @intCast(index)); - }, - .index => unreachable, + .value => |_| return sema.fail(src, "could not assign to constant value", .{}), + else => unreachable, } - _ = try chunk.addByteOp(.pop); + + try builder.addByteOp(.pop); } -fn irLoad(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { +fn irLoad(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!ValueInfo { const data = sema.ir.instructions[@intFromEnum(inst)].data.un; - const lhs = chunk.resolveInst(data.lhs); - return chunk.doLoad(lhs); + const lhs = sema.resolveInst(data.lhs); + try builder.ensureLoad(lhs); + return .none; } -fn irCondBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { +fn irCondBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!ValueInfo { const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; const extra = sema.ir.extraData(Ir.Inst.CondBr, data.extra_index); const then_body = sema.ir.bodySlice(extra.end, extra.data.then_body_len); const else_body = sema.ir.bodySlice(extra.end + then_body.len, extra.data.else_body_len); - const else_label = try chunk.addLabel(); - const end_label = try chunk.addLabel(); - const condition = chunk.resolveInst(extra.data.condition); + const else_label = try builder.addLabel(); + const end_label = try builder.addLabel(); + const condition = sema.resolveInst(extra.data.condition); - _ = try chunk.doLoad(condition); - try chunk.addFixup(.jmp_f, else_label); - _ = try chunk.addByteOp(.pop); - try analyzeBodyInner(sema, chunk, then_body); + try builder.ensureLoad(condition); + try builder.addFixup(.jmp_f, else_label); + try builder.addByteOp(.pop); + _ = try analyzeBodyInner(sema, builder, then_body, false); - try chunk.addFixup(.jmp, end_label); - chunk.setLabel(else_label); - _ = try chunk.addByteOp(.pop); + try builder.addFixup(.jmp, end_label); + builder.setLabel(else_label); + try builder.addByteOp(.pop); - try analyzeBodyInner(sema, chunk, else_body); - chunk.setLabel(end_label); + _ = try analyzeBodyInner(sema, builder, else_body, false); + builder.setLabel(end_label); return .none; } @@ -372,14 +431,14 @@ fn irBreak(sema: *Sema, inst: Ir.Inst.Index) InnerError!void { _ = inst; } -fn irBlock(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { +fn irBlock(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void { const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; const extra = sema.ir.extraData(Ir.Inst.Block, data.extra_index); const body = sema.ir.bodySlice(extra.end, extra.data.body_len); - return analyzeBodyInner(sema, chunk, body); + _ = try analyzeBodyInner(sema, builder, body, false); } -fn irSwitchBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { +fn irSwitchBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void { const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; const extra = sema.ir.extraData(Ir.Inst.SwitchBr, data.extra_index); const cases_slice = sema.ir.bodySlice(extra.end, extra.data.cases_len); @@ -389,35 +448,35 @@ fn irSwitchBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { defer case_labels.deinit(sema.gpa); // TODO: Do something with this value? - //const condition = chunk.resolveInst(extra.data.operand); - const exit_label = try chunk.addLabel(); - const cmp_var = chunk.addStackSlot(); - _ = try chunk.addConstOp(.store, cmp_var); + //const condition = builder.resolveInst(extra.data.operand); + const exit_label = try builder.addLabel(); + const cmp_var = builder.addStackSlot(); + try builder.addConstOp(.store, cmp_var); for (cases_slice) |case_index| { const case_extra = sema.ir.extraData(Ir.Inst.SwitchBr.Case, @intFromEnum(case_index)); - const case_expr = chunk.resolveInst(case_extra.data.operand); - const case_label_index = try chunk.addLabel(); + const case_expr = sema.resolveInst(case_extra.data.operand); + const case_label_index = try builder.addLabel(); case_labels.appendAssumeCapacity(case_label_index); - _ = try chunk.addConstOp(.load, @intCast(cmp_var)); - _ = try chunk.doLoad(case_expr); - _ = try chunk.addByteOp(.cmp_eq); - _ = try chunk.addFixup(.jmp_t, case_label_index); - _ = try chunk.addByteOp(.pop); + try builder.addConstOp(.load, @intCast(cmp_var)); + try builder.ensureLoad(case_expr); + try builder.addByteOp(.cmp_eq); + try builder.addFixup(.jmp_t, case_label_index); + try builder.addByteOp(.pop); } - const else_label = try chunk.addLabel(); - try chunk.addFixup(.jmp, else_label); + const else_label = try builder.addLabel(); + try builder.addFixup(.jmp, else_label); for (cases_slice, case_labels.items) |case_index, label_index| { const case_extra = sema.ir.extraData(Ir.Inst.SwitchBr.Case, @intFromEnum(case_index)); const case_body = sema.ir.bodySlice(case_extra.end, case_extra.data.body_len); - chunk.setLabel(label_index); - _ = try chunk.addByteOp(.pop); - try analyzeBodyInner(sema, chunk, case_body); - try chunk.addFixup(.jmp, exit_label); + builder.setLabel(label_index); + try builder.addByteOp(.pop); + _ = try analyzeBodyInner(sema, builder, case_body, false); + try builder.addFixup(.jmp, exit_label); } const else_body = sema.ir.bodySlice( @@ -425,23 +484,25 @@ fn irSwitchBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { extra.data.else_body_len, ); - chunk.setLabel(else_label); - try analyzeBodyInner(sema, chunk, else_body); - chunk.setLabel(exit_label); + builder.setLabel(else_label); + _ = try analyzeBodyInner(sema, builder, else_body, false); + builder.setLabel(exit_label); } -fn irContentPush(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { +fn irContentPush(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void { const data = sema.ir.instructions[@intFromEnum(inst)].data.un; - const lhs = chunk.resolveInst(data.lhs); - _ = try chunk.doLoad(lhs); - return chunk.addByteOp(.stream_push); + const lhs = sema.resolveInst(data.lhs); + if (lhs != .none) { + try builder.ensureLoad(lhs); + } + try builder.addByteOp(.stream_push); } -fn irContentFlush(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref { - return chunk.addByteOp(.stream_flush); +fn irContentFlush(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void { + try builder.addByteOp(.stream_flush); } -fn irChoiceBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { +fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void { const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; const choice_extra = sema.ir.extraData(Ir.Inst.ChoiceBr, data.extra_index); const options_slice = sema.ir.bodySlice(choice_extra.end, choice_extra.data.cases_len); @@ -452,79 +513,72 @@ fn irChoiceBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { for (options_slice) |option_index| { const case_extra = sema.ir.extraData(Ir.Inst.ChoiceBr.Case, @intFromEnum(option_index)); - const case_label = try chunk.addLabel(); + const case_label = try builder.addLabel(); branch_labels.appendAssumeCapacity(case_label); switch (case_extra.data.operand_1) { .none => {}, else => |content| { - const content_inst = chunk.resolveInst(content); - _ = try chunk.doLoad(content_inst); - _ = try chunk.addByteOp(.stream_push); + const content_inst = sema.resolveInst(content); + try builder.ensureLoad(content_inst); + try builder.addByteOp(.stream_push); }, } switch (case_extra.data.operand_2) { .none => {}, else => |content| { - const content_inst = chunk.resolveInst(content); - _ = try chunk.doLoad(content_inst); - _ = try chunk.addByteOp(.stream_push); + const content_inst = sema.resolveInst(content); + try builder.ensureLoad(content_inst); + try builder.addByteOp(.stream_push); }, } - try chunk.addFixupAbsolute(.br_push, case_label); + try builder.addFixupAbsolute(.br_push, case_label); } - _ = try chunk.addByteOp(.br_table); - _ = try chunk.addByteOp(.br_select_index); - _ = try chunk.addByteOp(.br_dispatch); + try builder.addByteOp(.br_table); + try builder.addByteOp(.br_select_index); + try builder.addByteOp(.br_dispatch); for (options_slice, branch_labels.items) |option_index, label| { const case_extra = sema.ir.extraData(Ir.Inst.ChoiceBr.Case, @intFromEnum(option_index)); const body_slice = sema.ir.bodySlice(case_extra.end, case_extra.data.body_len); - chunk.setLabel(label); + builder.setLabel(label); switch (case_extra.data.operand_1) { .none => {}, else => |content| { - const content_inst = chunk.resolveInst(content); - _ = try chunk.doLoad(content_inst); - _ = try chunk.addByteOp(.stream_push); + const content_inst = sema.resolveInst(content); + try builder.ensureLoad(content_inst); + try builder.addByteOp(.stream_push); }, } switch (case_extra.data.operand_3) { .none => {}, else => |content| { - const content_inst = chunk.resolveInst(content); - _ = try chunk.doLoad(content_inst); - _ = try chunk.addByteOp(.stream_push); + const content_inst = sema.resolveInst(content); + try builder.ensureLoad(content_inst); + try builder.addByteOp(.stream_push); }, } - _ = try chunk.addByteOp(.stream_flush); - try analyzeBodyInner(sema, chunk, body_slice); + try builder.addByteOp(.stream_flush); + _ = try analyzeBodyInner(sema, builder, body_slice, false); } } -fn irImplicitRet(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref { - return chunk.addByteOp(.exit); +fn irImplicitRet(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void { + try builder.addByteOp(.exit); } -fn irDeclRef(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { - const data = sema.ir.instructions[@intFromEnum(inst)].data.str_tok; - const src_loc: SrcLoc = .{ .src_offset = data.src_offset }; - const decl_name = try sema.getOrPutStr(data.start); - return sema.lookupIdentifier(chunk, decl_name.constant, src_loc); -} - -fn irCall(_: *Sema, _: *Chunk, _: Ir.Inst.Index) !Ref { +fn irCall(_: *Sema, _: *Builder, _: Ir.Inst.Index) !ValueInfo { return .none; } fn irDivert( sema: *Sema, - chunk: *Chunk, + builder: *Builder, inst: Ir.Inst.Index, comptime kind: enum { direct, field }, ) !void { @@ -538,23 +592,22 @@ fn irDivert( const callee_src: SrcLoc = .{ .src_offset = data.src_offset }; switch (kind) { .direct => { - const callee = chunk.resolveInst(extra.data.callee); - _ = try analyzeDivertTarget(sema, chunk, callee_src, callee); + const callee = sema.resolveInst(extra.data.callee); + _ = try analyzeDivertTarget(sema, builder, callee_src, callee); }, .field => { - const callee = chunk.resolveInst(extra.data.obj_ptr); - const field_name = try sema.getOrPutStr(extra.data.field_name_start); - - _ = try analyzeDivertTarget(sema, chunk, callee_src, callee); - const e = try sema.lookupInNamespace( - callee.knot.namespace, - field_name.constant, - callee_src, + const callee = sema.resolveInst(extra.data.obj_ptr); + const target = try analyzeDivertTarget(sema, builder, callee_src, callee); + const ip_index = try sema.module.intern_pool.getOrPutStr( + sema.gpa, + extra.data.field_name_start, ); - switch (e) { - .knot => |knot| { - const local_index = try chunk.getOrPutConstantIndex(knot.const_index); - _ = try chunk.addConstOp(.load_attr, @intCast(local_index)); + std.debug.print("Target: {any}\n", .{target}); + const e = try sema.lookupInNamespace(target.namespace.?, ip_index, callee_src); + switch (e.tag) { + .knot => { + const local_index = try builder.getOrPutConstantIndex(ip_index); + _ = try builder.addConstOp(.load_attr, @intCast(local_index)); }, else => return sema.fail(callee_src, "invalid divert target", .{}), } @@ -568,150 +621,174 @@ fn irDivert( const arg_end = sema.ir.extra[extra.end + i]; defer arg_start = arg_end; const arg_body = body[arg_start..arg_end]; - try analyzeBodyInner(sema, chunk, @ptrCast(arg_body)); + _ = try analyzeBodyInner(sema, builder, @ptrCast(arg_body), false); // FIXME: hack { const last_inst: Ir.Inst.Index = @enumFromInt(arg_body[arg_body.len - 1]); - const val = chunk.resolveInst(last_inst.toRef()); - _ = try chunk.doLoad(val); + const val = sema.resolveInst(last_inst.toRef()); + try builder.ensureLoad(val); } } - _ = try chunk.addConstOp(.divert, @intCast(args_len)); + try builder.addConstOp(.divert, @intCast(args_len)); } -fn irFieldPtr(_: *Sema, _: *Chunk, _: Ir.Inst.Index) !Ref { +fn irFieldPtr(_: *Sema, _: *Builder, _: Ir.Inst.Index) !ValueInfo { return .none; } // TODO: Check for duplicate parameters. -fn irParam(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) !Ref { +fn irParam(_: *Sema, builder: *Builder, _: Ir.Inst.Index) !ValueInfo { // TODO: Add constraints on how many temporaries we can have. // max(u8) or max(u16) are most likey appropriate. - return .{ .temporary = chunk.addParameter() }; + return .{ .temp = builder.addParameter() }; } fn analyzeArithmeticArg( sema: *Sema, - chunk: *Chunk, - arg: Ref, + builder: *Builder, + arg: ValueInfo, arg_src: SrcLoc, ) !void { switch (arg) { - .variable => |index| { - const local_index = try chunk.getOrPutConstantIndex(index); - _ = try chunk.addConstOp(.load_global, @intCast(local_index)); - }, - .constant => |index| { - const local_index = try chunk.getOrPutConstantIndex(index); - _ = try chunk.addConstOp(.load_const, @intCast(local_index)); + .value => |index| { + const local_index = try builder.getOrPutConstantIndex(index); + try builder.addConstOp(.load_const, @intCast(local_index)); }, + //.temporary => |index| { + // try builder.addConstOp(.load, @intCast(index)); + //}, + //.variable => |index| { + // const local_index = try builder.getOrPutConstantIndex(index); + // try builder.addConstOp(.load_global, @intCast(local_index)); + //}, .knot => return fail(sema, arg_src, "invalid operand to arithmetic expression", .{}), else => unreachable, } } -fn analyzeDivertTarget(sema: *Sema, chunk: *Chunk, src: SrcLoc, callee: Ref) !Ref { +fn analyzeDivertTarget( + sema: *Sema, + builder: *Builder, + src: SrcLoc, + callee: ValueInfo, +) !Module.Namespace.Decl { switch (callee) { - .knot => |knot| { - const local_index = try chunk.getOrPutConstantIndex(knot.const_index); - return chunk.addConstOp(.load_global, @intCast(local_index)); + .knot => |ip_index| { + try builder.ensureLoad(callee); + return sema.lookupIdentifier(builder, ip_index, src); }, else => return sema.fail(src, "invalid divert target", .{}), } } -fn analyzeBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) InnerError!void { +fn analyzeBodyInner( + sema: *Sema, + builder: *Builder, + body: []const Ir.Inst.Index, + inline_block: bool, +) InnerError!ValueInfo { + var result: ValueInfo = .none; for (body) |inst| { const data = sema.ir.instructions[@intFromEnum(inst)]; - const ref: Ref = switch (data.tag) { + result = switch (data.tag) { .file => unreachable, // never present inside block bodies .declaration => unreachable, // never present inside block bodies .decl_var => unreachable, // never present inside block bodies .decl_knot => unreachable, // never present inside block bodies .decl_stitch => unreachable, // never present inside block bodies - .switch_br => { - try irSwitchBr(sema, chunk, inst); - continue; - }, - .alloc => try irAlloc(sema, chunk, inst), + .alloc => try irAlloc(sema, builder, inst), .store => { - try irStore(sema, chunk, inst); + try irStore(sema, builder, inst); continue; }, - .load => try irLoad(sema, chunk, inst), - .add => try irBinaryOp(sema, chunk, inst, .add), - .sub => try irBinaryOp(sema, chunk, inst, .sub), - .mul => try irBinaryOp(sema, chunk, inst, .mul), - .div => try irBinaryOp(sema, chunk, inst, .div), - .mod => try irBinaryOp(sema, chunk, inst, .mod), - .neg => try irUnaryOp(sema, chunk, inst, .neg), - .not => try irUnaryOp(sema, chunk, inst, .not), - .cmp_eq => try irBinaryOp(sema, chunk, inst, .cmp_eq), + .load => try irLoad(sema, builder, inst), + .int => try irInt(sema, inst), + .str => try irStr(sema, inst), + .add => try irBinaryOp(sema, builder, inst, .add), + .sub => try irBinaryOp(sema, builder, inst, .sub), + .mul => try irBinaryOp(sema, builder, inst, .mul), + .div => try irBinaryOp(sema, builder, inst, .div), + .mod => try irBinaryOp(sema, builder, inst, .mod), + .neg => try irUnaryOp(sema, builder, inst, .neg), + .not => try irUnaryOp(sema, builder, inst, .not), + .cmp_eq => try irBinaryOp(sema, builder, inst, .cmp_eq), .cmp_neq => blk: { - _ = try irBinaryOp(sema, chunk, inst, .cmp_eq); - const tmp = try chunk.addByteOp(.not); - break :blk tmp; + const val = try irBinaryOp(sema, builder, inst, .cmp_eq); + try builder.addByteOp(.not); + break :blk val; }, - .cmp_lt => try irBinaryOp(sema, chunk, inst, .cmp_lt), - .cmp_lte => try irBinaryOp(sema, chunk, inst, .cmp_lte), - .cmp_gt => try irBinaryOp(sema, chunk, inst, .cmp_gt), - .cmp_gte => try irBinaryOp(sema, chunk, inst, .cmp_gte), - .decl_ref => try irDeclRef(sema, chunk, inst), - .int => try irInteger(sema, inst), - .str => try irString(sema, inst), - .condbr => try irCondBr(sema, chunk, inst), + .cmp_lt => try irBinaryOp(sema, builder, inst, .cmp_lt), + .cmp_lte => try irBinaryOp(sema, builder, inst, .cmp_lte), + .cmp_gt => try irBinaryOp(sema, builder, inst, .cmp_gt), + .cmp_gte => try irBinaryOp(sema, builder, inst, .cmp_gte), + .decl_ref => try irDeclRef(sema, builder, inst, inline_block), + .implicit_ret => { + try irImplicitRet(sema, builder, inst); + continue; + }, + .condbr => try irCondBr(sema, builder, inst), .@"break" => { try irBreak(sema, inst); continue; }, .block => { - try irBlock(sema, chunk, inst); + try irBlock(sema, builder, inst); + continue; + }, + .content_push => { + try irContentPush(sema, builder, inst); + continue; + }, + .content_flush => { + try irContentFlush(sema, builder, inst); continue; }, - .content_push => try irContentPush(sema, chunk, inst), - .content_flush => try irContentFlush(sema, chunk, inst), .choice_br => { - try irChoiceBr(sema, chunk, inst); + try irChoiceBr(sema, builder, inst); continue; }, - .implicit_ret => try irImplicitRet(sema, chunk, inst), - .call => try irCall(sema, chunk, inst), + .switch_br => { + try irSwitchBr(sema, builder, inst); + continue; + }, + .call => try irCall(sema, builder, inst), .divert => { - try irDivert(sema, chunk, inst, .direct); + try irDivert(sema, builder, inst, .direct); continue; }, - .field_call => try irCall(sema, chunk, inst), + .field_call => try irCall(sema, builder, inst), .field_divert => { - try irDivert(sema, chunk, inst, .field); + try irDivert(sema, builder, inst, .field); continue; }, - .field_ptr => try irFieldPtr(sema, chunk, inst), - .param => try irParam(sema, chunk, inst), + .field_ptr => try irFieldPtr(sema, builder, inst), + .param => try irParam(sema, builder, inst), }; - try sema.inst_map.put(sema.gpa, inst, ref); + try sema.inst_map.put(sema.gpa, inst, result); } + return result; } pub fn analyzeStitch( sema: *Sema, - chunk: *Chunk, + builder: *Builder, inst: Ir.Inst.Index, ) !void { const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; const extra = sema.ir.extraData(Ir.Inst.Stitch, data.extra_index); const body = sema.ir.bodySlice(extra.end, extra.data.body_len); - try analyzeBodyInner(sema, chunk, body); + _ = try analyzeBodyInner(sema, builder, body, false); } pub fn analyzeKnot( sema: *Sema, - chunk: *Chunk, + builder: *Builder, inst: Ir.Inst.Index, ) !void { const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; const extra = sema.ir.extraData(Ir.Inst.Knot, data.extra_index); const body = sema.ir.bodySlice(extra.end, extra.data.body_len); - try analyzeBodyInner(sema, chunk, body); + _ = try analyzeBodyInner(sema, builder, body, false); } fn analyzeNestedDecl( @@ -744,22 +821,7 @@ fn analyzeNestedDecl( } } -pub fn analyzeGlobalBlockInner(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) !void { - const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; - const extra = sema.ir.extraData(Ir.Inst.Var, data.payload.extra_index); - const body = sema.ir.bodySlice(extra.end, extra.data.body_len); - try analyzeBodyInner(sema, chunk, body); - // FIXME: hack - { - const last_inst = body[body.len - 1].toRef(); - const val = chunk.resolveInst(last_inst); - _ = try chunk.doLoad(val); - } - _ = try chunk.addConstOp(.store_global, @intCast(@intFromEnum(0))); - _ = try chunk.addByteOp(.pop); -} - -pub fn analyzeTopLevelDecl( +fn scanTopLevelDecl( sema: *Sema, namespace: *Module.Namespace, inst: Ir.Inst.Index, @@ -777,7 +839,7 @@ pub fn analyzeTopLevelDecl( return sema.fail(src_loc, "duplicate identifier", .{}); } else { gop.value_ptr.* = .{ - .tag = .variable, + .tag = if (extra.flags == 0x01) .var_const else .var_mut, .namespace = null, .decl_inst = extra.value, .args_count = 0, @@ -838,3 +900,67 @@ pub fn analyzeTopLevelDecl( else => unreachable, } } + +fn resolveGlobalDecl( + sema: *Sema, + builder: *Builder, + decl_name: InternPool.Index, + src_loc: SrcLoc, +) InnerError!ValueInfo { + const entry = builder.namespace.decls.getPtr(decl_name).?; + switch (entry.resolution) { + .unresolved => { + const data = sema.ir.instructions[@intFromEnum(entry.decl_inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.Var, data.extra_index); + const body = sema.ir.bodySlice(extra.end, extra.data.body_len); + + entry.resolution = .in_progress; + const val = try sema.analyzeBodyInner(builder, body, true); + entry.resolution = .{ .resolved = val }; + return val; + }, + .in_progress => return sema.fail( + src_loc, + "cycle detected in constant initializer", + .{}, + ), + .resolved => |val| return val, + } +} + +pub fn scanTopLevelDecls( + sema: *Sema, + namespace: *Module.Namespace, + decls: []const Ir.Inst.Index, +) !void { + const gpa = sema.gpa; + for (decls) |decl_index| { + try sema.scanTopLevelDecl(namespace, decl_index); + } + + var builder: Builder = .{ + .sema = sema, + .code = undefined, + .namespace = namespace, + }; + defer builder.deinit(gpa); + + var iter = namespace.decls.iterator(); + while (iter.next()) |entry| { + const key = entry.key_ptr.*; + const value = entry.value_ptr.*; + switch (value.tag) { + .var_mut, .var_const => { + // TODO: Set a proper source offset for this. + const src_loc: SrcLoc = .{ .src_offset = 0 }; + const result = try sema.resolveGlobalDecl(&builder, key, src_loc); + const initial_value = sema.resolveValue(result).?; + try sema.module.globals.append(gpa, .{ + .key = key, + .value = initial_value.toInterned(), + }); + }, + else => {}, + } + } +} diff --git a/src/Story/runtime_tests.zig b/src/Story/runtime_tests.zig index b54ad90..1c8cfb3 100644 --- a/src/Story/runtime_tests.zig +++ b/src/Story/runtime_tests.zig @@ -2,6 +2,22 @@ const std = @import("std"); const fatal = std.process.fatal; const ink = @import("../root.zig"); +test "fixture - hello world" { + try testRuntimeFixture("hello-world"); +} + +test "fixture - monsieur-fogg" { + try testRuntimeFixture("monsieur-fogg"); +} + +test "fixture - variable arithmetic" { + try testRuntimeFixture("variable-arithmetic"); +} + +test "fixture - constant folding" { + try testRuntimeFixture("constant-folding"); +} + const Options = struct { input_reader: *std.Io.Reader, error_writer: *std.Io.Writer, @@ -63,11 +79,3 @@ fn testRuntimeFixture(comptime fixture: []const u8) !void { }); return std.testing.expectEqualSlices(u8, transcript_bytes, io_w.written()); } - -test "fixture - hello world" { - try testRuntimeFixture("hello-world"); -} - -test "fixture - monsieur-fogg" { - try testRuntimeFixture("monsieur-fogg"); -} diff --git a/src/Story/testdata/constant-folding/input.txt b/src/Story/testdata/constant-folding/input.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Story/testdata/constant-folding/story.ink b/src/Story/testdata/constant-folding/story.ink new file mode 100644 index 0000000..88c5773 --- /dev/null +++ b/src/Story/testdata/constant-folding/story.ink @@ -0,0 +1,4 @@ +VAR a = b + 1 +CONST b = c +CONST c = 1 +{a} diff --git a/src/Story/testdata/constant-folding/transcript.txt b/src/Story/testdata/constant-folding/transcript.txt new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/src/Story/testdata/constant-folding/transcript.txt @@ -0,0 +1 @@ +2 diff --git a/src/Story/testdata/variable-arithmetic/input.txt b/src/Story/testdata/variable-arithmetic/input.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Story/testdata/variable-arithmetic/story.ink b/src/Story/testdata/variable-arithmetic/story.ink new file mode 100644 index 0000000..0dccaf1 --- /dev/null +++ b/src/Story/testdata/variable-arithmetic/story.ink @@ -0,0 +1,3 @@ +VAR a = 1 +VAR b = 2 +{a + b} diff --git a/src/Story/testdata/variable-arithmetic/transcript.txt b/src/Story/testdata/variable-arithmetic/transcript.txt new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/src/Story/testdata/variable-arithmetic/transcript.txt @@ -0,0 +1 @@ +3 diff --git a/src/compile.zig b/src/compile.zig index d7849c2..4358919 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -10,7 +10,6 @@ const assert = std.debug.assert; pub fn IntrusiveQueue(comptime T: type) type { return struct { const Self = @This(); - head: ?*T = null, tail: ?*T = null, @@ -80,32 +79,35 @@ test IntrusiveQueue { } pub const InternPool = struct { - constants: std.ArrayListUnmanaged(Constant.Key) = .empty, - constants_map: std.AutoHashMapUnmanaged(Constant.Key, Constant.Index) = .empty, + values: std.ArrayListUnmanaged(Key) = .empty, + values_map: std.AutoHashMapUnmanaged(Key, Index) = .empty, code_chunks: std.ArrayListUnmanaged(*Module.CodeChunk) = .empty, - pub const Constant = struct { - pub const Key = union(enum) { - int: u64, - str: Ir.NullTerminatedString, - }; - - pub const Index = enum(u32) { - _, - }; + pub const Index = enum(u32) { + none, + _, }; - pub fn getOrPutConstant( + pub const Key = union(enum) { + int: u64, + str: Ir.NullTerminatedString, + }; + + pub fn getOrPutValue( ip: *InternPool, gpa: std.mem.Allocator, - key: Constant.Key, - ) error{OutOfMemory}!Constant.Index { - if (ip.constants_map.get(key)) |index| { + key: Key, + ) error{OutOfMemory}!Index { + if (ip.values_map.get(key)) |index| { return index; } else { - const new_index: Constant.Index = @enumFromInt(ip.constants.items.len); - try ip.constants.append(gpa, key); - try ip.constants_map.put(gpa, key, new_index); + const new_index: Index = @enumFromInt(ip.values.items.len); + const new_value: Key = switch (key) { + .int => |int| .{ .int = int }, + .str => |str| .{ .str = str }, + }; + try ip.values.append(gpa, new_value); + try ip.values_map.put(gpa, key, new_index); return new_index; } } @@ -114,8 +116,8 @@ pub const InternPool = struct { ip: *InternPool, gpa: std.mem.Allocator, value: u64, - ) error{OutOfMemory}!Constant.Index { - return ip.getOrPutConstant(gpa, .{ + ) error{OutOfMemory}!Index { + return ip.getOrPutValue(gpa, .{ .int = value, }); } @@ -123,16 +125,16 @@ pub const InternPool = struct { pub fn getOrPutStr( ip: *InternPool, gpa: std.mem.Allocator, - start: Ir.NullTerminatedString, - ) error{OutOfMemory}!Constant.Index { - return ip.getOrPutConstant(gpa, .{ - .str = start, + value: Ir.NullTerminatedString, + ) error{OutOfMemory}!Index { + return ip.getOrPutValue(gpa, .{ + .str = value, }); } - pub fn getStrBytes(ip: *InternPool, ir: Ir, index: Constant.Index) []const u8 { - assert(ip.constants.items.len > @intFromEnum(index)); - const c = ip.constants.items[@intFromEnum(index)]; + pub fn getStrBytes(ip: *InternPool, ir: Ir, index: Index) []const u8 { + assert(ip.values.items.len > @intFromEnum(index)); + const c = ip.values.items[@intFromEnum(index)]; return ir.nullTerminatedString(c.str); } @@ -142,8 +144,8 @@ pub const InternPool = struct { } pub fn deinit(ip: *InternPool, gpa: std.mem.Allocator) void { - ip.constants.deinit(gpa); - ip.constants_map.deinit(gpa); + ip.values.deinit(gpa); + ip.values_map.deinit(gpa); ip.code_chunks.deinit(gpa); } }; @@ -151,7 +153,7 @@ pub const InternPool = struct { pub const WorkItem = struct { tag: Tag, next: ?*WorkItem = null, - decl_name: InternPool.Constant.Index, + decl_name: InternPool.Index, inst_index: Ir.Inst.Index, namespace: *Module.Namespace, @@ -169,13 +171,19 @@ pub const Module = struct { tree: Ast, ir: Ir, intern_pool: InternPool = .{}, + globals: std.ArrayListUnmanaged(Global) = .empty, knots: std.ArrayListUnmanaged(Knot) = .empty, stitches: std.ArrayListUnmanaged(Stitch) = .empty, errors: std.ArrayListUnmanaged(Error) = .empty, work_queue: WorkQueue = .{}, + pub const Global = struct { + key: InternPool.Index, + value: InternPool.Index, + }; + pub const Knot = struct { - name_index: InternPool.Constant.Index, + name_index: InternPool.Index, code_index: CodeChunk.Index, pub const Index = enum(u32) { @@ -186,7 +194,7 @@ pub const Module = struct { pub const Stitch = struct { knot_index: ?Knot.Index, code_index: CodeChunk.Index, - name_index: InternPool.Constant.Index, + name_index: InternPool.Index, pub const Index = enum(u32) { _, @@ -195,18 +203,26 @@ pub const Module = struct { pub const Namespace = struct { parent: ?*Namespace, - decls: std.AutoHashMapUnmanaged(InternPool.Constant.Index, Decl), + decls: std.AutoHashMapUnmanaged(InternPool.Index, Decl), pub const Decl = struct { tag: Tag, namespace: ?*Namespace, decl_inst: Ir.Inst.Index, args_count: u32, + resolution: Resolution = .unresolved, + + pub const Resolution = union(enum) { + unresolved, + in_progress, + resolved: Sema.ValueInfo, + }; pub const Tag = enum { knot, stitch, - variable, + var_mut, + var_const, }; }; }; @@ -237,6 +253,7 @@ pub const Module = struct { const extra = mod.ir.extraData(Ir.Inst.Block, data.extra_index); const top_level_decls = mod.ir.bodySlice(extra.end, extra.data.body_len); + var knot_index: ?Knot.Index = null; var sema: Sema = .{ .module = mod, .gpa = gpa, @@ -247,39 +264,36 @@ pub const Module = struct { defer sema.deinit(); const file_scope = try mod.createNamespace(null); - for (top_level_decls) |decl_index| { - try sema.analyzeTopLevelDecl(file_scope, decl_index); - } + try sema.scanTopLevelDecls(file_scope, top_level_decls); - var knot_index: ?Knot.Index = null; while (mod.work_queue.pop()) |work_unit| { const chunk_index = mod.intern_pool.code_chunks.items.len; - var chunk: Sema.Chunk = .{ + var builder: Sema.Builder = .{ .sema = &sema, .code = try mod.createCodeChunk(), .namespace = work_unit.namespace, }; - defer chunk.deinit(gpa); + defer builder.deinit(gpa); const debug_name_str = mod.intern_pool.getStrBytes(mod.ir, work_unit.decl_name); std.debug.print("Analyzing {s}\n", .{debug_name_str}); switch (work_unit.tag) { .knot => { - try sema.analyzeKnot(&chunk, work_unit.inst_index); - try chunk.finalize(); + try sema.analyzeKnot(&builder, work_unit.inst_index); + try builder.finalize(); knot_index = @enumFromInt(mod.knots.items.len); - try mod.intern_pool.code_chunks.append(gpa, chunk.code); + try mod.intern_pool.code_chunks.append(gpa, builder.code); try mod.knots.append(gpa, .{ .name_index = work_unit.decl_name, .code_index = @enumFromInt(chunk_index), }); }, .stitch => { - try sema.analyzeStitch(&chunk, work_unit.inst_index); - try chunk.finalize(); + try sema.analyzeStitch(&builder, work_unit.inst_index); + try builder.finalize(); - try mod.intern_pool.code_chunks.append(gpa, chunk.code); + try mod.intern_pool.code_chunks.append(gpa, builder.code); try mod.stitches.append(gpa, .{ .knot_index = knot_index, .name_index = work_unit.decl_name, @@ -360,10 +374,10 @@ pub const Module = struct { pub fn setupStoryRuntime(mod: *Module, gpa: std.mem.Allocator, story: *Story) !void { assert(mod.errors.items.len == 0); - const constants_len = mod.intern_pool.constants.items.len; + const constants_len = mod.intern_pool.values.items.len; try story.constants_pool.ensureUnusedCapacity(gpa, constants_len); - for (mod.intern_pool.constants.items) |constant| { + for (mod.intern_pool.values.items) |constant| { switch (constant) { .int => |value| { const obj = try Object.Number.create(story, .{ @@ -380,6 +394,14 @@ pub const Module = struct { }, } } + for (mod.globals.items) |global| { + const key_bytes = mod.intern_pool.getStrBytes(mod.ir, global.key); + const constant_data = mod.intern_pool.values.items[@intFromEnum(global.value)]; + const value_obj = try Object.Number.create(story, .{ + .integer = @intCast(constant_data.int), + }); + try story.globals.put(gpa, key_bytes, @ptrCast(value_obj)); + } for (mod.knots.items) |knot| { const name_bytes = mod.intern_pool.getStrBytes(mod.ir, knot.name_index); const code_chunk = mod.intern_pool.getCodeChunk(knot.code_index); @@ -438,7 +460,7 @@ pub const Module = struct { mod: *Module, options: struct { tag: WorkItem.Tag, - decl_name: InternPool.Constant.Index, + decl_name: InternPool.Index, inst_index: Ir.Inst.Index, namespace: *Namespace, }, @@ -476,6 +498,7 @@ pub const Module = struct { const gpa = mod.gpa; mod.ir.deinit(gpa); mod.intern_pool.deinit(gpa); + mod.globals.deinit(gpa); mod.knots.deinit(gpa); mod.stitches.deinit(gpa); mod.errors.deinit(gpa); diff --git a/src/error_tests.zig b/src/error_tests.zig index bd7aac3..dc6f032 100644 --- a/src/error_tests.zig +++ b/src/error_tests.zig @@ -68,6 +68,29 @@ test "compiler: invalid divert target" { ); } +test "compiler: global variable restrictions" { + try testEqual( + \\VAR a = b + \\VAR b = a + , + \\:1:9: error: global variable assignments cannot refer to other variables + \\1 | VAR a = b + \\ | ^ + \\ + ); +} + +test "compiler: constant cycle detection" { + try testEqual( + \\CONST a = a + , + \\:1:11: error: cycle detected in constant initializer + \\1 | CONST a = a + \\ | ^ + \\ + ); +} + fn testEqual(source_bytes: [:0]const u8, expected_error: []const u8) !void { const gpa = std.testing.allocator; var arena_allocator = std.heap.ArenaAllocator.init(gpa); diff --git a/src/print_ir.zig b/src/print_ir.zig index 663baec..d39db11 100644 --- a/src/print_ir.zig +++ b/src/print_ir.zig @@ -234,6 +234,9 @@ pub const Writer = struct { fn writeDeclarationInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void { const data = self.code.instructions[@intFromEnum(inst)].data.payload; const extra = self.code.extraData(Ir.Inst.Declaration, data.extra_index); + if (extra.data.flags == 0x01) { + try w.writeAll("const, "); + } try w.writeAll("name="); try self.writeStringRef(w, extra.data.name); try w.writeAll(", ");