feat: infrastructure to support qualified lookups

This commit is contained in:
Brett Broadhurst 2026-03-25 03:18:24 -06:00
parent e8ad1cd5b5
commit 440ec68481
Failed to generate hash of commit
6 changed files with 1118 additions and 575 deletions

View file

@ -3,19 +3,16 @@ const Ast = @import("Ast.zig");
const Ir = @import("Ir.zig");
const Story = @import("Story.zig");
const compile = @import("compile.zig");
const Compilation = compile.Compilation;
const InternPool = compile.InternPool;
const Module = compile.Module;
const assert = std.debug.assert;
const Sema = @This();
gpa: std.mem.Allocator,
arena: std.mem.Allocator,
tree: Ast,
module: *compile.Module,
ir: Ir,
constants: std.ArrayListUnmanaged(Compilation.Constant) = .empty,
constants_map: std.AutoHashMapUnmanaged(Compilation.Constant, u32) = .empty,
globals_map: std.AutoHashMapUnmanaged(u32, u32) = .empty,
knots: std.ArrayListUnmanaged(Compilation.Knot) = .empty,
errors: *std.ArrayListUnmanaged(Compilation.Error),
errors: *std.ArrayListUnmanaged(Module.Error),
const InnerError = error{
OutOfMemory,
@ -24,14 +21,19 @@ const InnerError = error{
InvalidJump,
};
const Ref = union(enum) {
pub const Ref = union(enum) {
none,
bool_true,
bool_false,
index: u32,
constant: u32,
global: u32,
local: 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,
},
};
pub const SrcLoc = struct {
@ -45,8 +47,9 @@ fn fail(
args: anytype,
) error{ OutOfMemory, AnalysisFail } {
// TODO: Revisit this
const source_bytes = sema.module.tree.source;
const message = try std.fmt.allocPrint(sema.arena, format, args);
const loc = compile.findLineColumn(sema.tree.source, src.src_offset);
const loc = compile.findLineColumn(source_bytes, src.src_offset);
try sema.errors.append(sema.gpa, .{
.line = loc.line,
.column = loc.column,
@ -56,47 +59,57 @@ fn fail(
return error.AnalysisFail;
}
/// Intern a constant.
fn getOrPutConstant(sema: *Sema, data: Compilation.Constant) error{OutOfMemory}!Ref {
if (sema.constants_map.get(data)) |index| {
return .{ .constant = index };
} else {
const gpa = sema.gpa;
const index = sema.constants.items.len;
try sema.constants.append(gpa, data);
try sema.constants_map.put(gpa, data, @intCast(index));
return .{ .constant = @intCast(index) };
/// 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 };
}
/// 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 };
}
pub fn lookupIdentifier(
sema: *Sema,
chunk: *Chunk,
ident: InternPool.Constant.Index,
) !Ref {
return sema.lookupInNamespace(chunk.namespace, ident);
}
pub fn lookupInNamespace(
sema: *Sema,
namespace: *Module.Namespace,
ident: InternPool.Constant.Index,
) !Ref {
var scope: ?*Module.Namespace = namespace;
while (scope) |s| : (scope = s.parent) {
if (s.decls.get(ident)) |decl| switch (decl.tag) {
.knot => return .{ .knot = .{
.namespace = decl.namespace.?,
.const_index = ident,
} },
.variable => return .{ .variable = ident },
};
}
}
/// Intern an integer as a story constant.
fn getOrPutInt(sema: *Sema, value: u64) error{OutOfMemory}!Ref {
return sema.getOrPutConstant(.{ .integer = value });
}
/// Intern a string as a story constant.
fn getOrPutStr(sema: *Sema, value: Ir.NullTerminatedString) error{OutOfMemory}!Ref {
return sema.getOrPutConstant(.{ .string = value });
// FIXME: This is temporary
return sema.fail(.{ .src_offset = 0 }, "unknown identifier", .{});
}
pub fn deinit(sema: *Sema) void {
const gpa = sema.gpa;
sema.constants.deinit(gpa);
sema.constants_map.deinit(gpa);
sema.globals_map.deinit(gpa);
sema.knots.deinit(gpa);
sema.* = undefined;
}
const Chunk = struct {
pub const Chunk = struct {
sema: *Sema,
knot: *Compilation.Knot,
namespace: *Module.Namespace,
code: *Module.CodeChunk,
inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, Ref) = .empty,
constants_map: std.AutoHashMapUnmanaged(InternPool.Constant.Index, u8) = .empty,
labels: std.ArrayListUnmanaged(Label) = .empty,
fixups: std.ArrayListUnmanaged(Fixup) = .empty,
inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, Ref) = .empty,
constant_map: std.AutoHashMapUnmanaged(u32, u32) = .empty,
const dummy_address = 0xffffffff;
const Label = struct {
code_offset: usize,
@ -111,16 +124,31 @@ const Chunk = struct {
code_offset: u32,
};
fn deinit(chunk: *Chunk, gpa: std.mem.Allocator) void {
chunk.fixups.deinit(gpa);
chunk.labels.deinit(gpa);
const dummy_address = 0xffffffff;
pub fn deinit(chunk: *Chunk, gpa: std.mem.Allocator) void {
chunk.inst_map.deinit(gpa);
chunk.constant_map.deinit(gpa);
chunk.constants_map.deinit(gpa);
chunk.labels.deinit(gpa);
chunk.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;
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 addByteOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref {
const gpa = chunk.sema.gpa;
const bytecode = &chunk.knot.bytecode;
const bytecode = &chunk.code.bytecode;
const byte_index = bytecode.items.len;
try bytecode.append(gpa, @intFromEnum(op));
return .{ .index = @intCast(byte_index) };
@ -128,7 +156,7 @@ const Chunk = struct {
fn addConstOp(chunk: *Chunk, op: Story.Opcode, arg: u8) error{OutOfMemory}!Ref {
const gpa = chunk.sema.gpa;
const bytecode = &chunk.knot.bytecode;
const bytecode = &chunk.code.bytecode;
const byte_index = bytecode.items.len;
try bytecode.ensureUnusedCapacity(gpa, 2);
bytecode.appendAssumeCapacity(@intFromEnum(op));
@ -138,7 +166,7 @@ const Chunk = struct {
fn addJumpOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref {
const gpa = chunk.sema.gpa;
const bytecode = &chunk.knot.bytecode;
const bytecode = &chunk.code.bytecode;
try bytecode.ensureUnusedCapacity(gpa, 3);
bytecode.appendAssumeCapacity(@intFromEnum(op));
bytecode.appendAssumeCapacity(0xff);
@ -173,13 +201,26 @@ const Chunk = struct {
}
fn setLabel(chunk: *Chunk, label_index: usize) void {
const code_offset = chunk.knot.bytecode.items.len;
const bytecode = &chunk.code.bytecode;
const code_offset = bytecode.items.len;
assert(label_index <= chunk.labels.items.len);
const label_data = &chunk.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;
const local_index: u8 = @intCast(constants.items.len);
try constants.append(gpa, @intCast(@intFromEnum(index)));
try chunk.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.inst_map.get(index).?;
@ -187,16 +228,14 @@ const Chunk = struct {
switch (ref) {
.bool_true => return .bool_true,
.bool_false => return .bool_false,
else => return .{ .constant = @intFromEnum(ref) },
else => unreachable,
}
}
fn resolveLabels(chunk: *Chunk) !void {
const start_index = 0;
const end_index = chunk.fixups.items.len;
const bytecode = &chunk.knot.bytecode;
pub fn finalize(chunk: *Chunk) !void {
const bytecode = &chunk.code.bytecode;
for (chunk.fixups.items[start_index..end_index]) |fixup| {
for (chunk.fixups.items) |fixup| {
const label = chunk.labels.items[fixup.label_index];
assert(label.code_offset != dummy_address);
const target_offset: usize = switch (fixup.mode) {
@ -213,82 +252,29 @@ const Chunk = struct {
}
}
/// Intern a reference to a global constant within this chunk.
fn getOrPutConstantIndex(chunk: *Chunk, global_index: u32) !u32 {
const gpa = chunk.sema.gpa;
if (chunk.constant_map.get(global_index)) |local_index| return local_index;
const local_index: u32 = @intCast(chunk.knot.constants.items.len);
try chunk.knot.constants.append(gpa, global_index);
try chunk.constant_map.put(gpa, global_index, local_index);
return local_index;
}
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),
.none => return ref,
.constant => |global_index| {
const local_index = try chunk.getOrPutConstantIndex(global_index);
.constant => |index| {
const local_index = try chunk.getOrPutConstantIndex(index);
return chunk.addConstOp(.load_const, @intCast(local_index));
},
.global => |global_index| {
const local_index = try chunk.getOrPutConstantIndex(global_index);
.variable => |index| {
const local_index = try chunk.getOrPutConstantIndex(index);
return chunk.addConstOp(.load_global, @intCast(local_index));
},
.local => |id| return chunk.addConstOp(.load, @intCast(id)),
.temporary => |id| return chunk.addConstOp(.load, @intCast(id)),
.index => return ref,
.knot => unreachable,
}
}
};
fn analyzeArithmeticArg(
sema: *Sema,
chunk: *Chunk,
arg: Ref,
arg_src: SrcLoc,
) !void {
switch (arg) {
.global => |index| {
const g = sema.ir.globals[index];
switch (g.tag) {
.variable => {
const name = try sema.getOrPutStr(g.name);
const local_index = try chunk.getOrPutConstantIndex(name.constant);
_ = try chunk.addConstOp(.load_global, @intCast(local_index));
},
.knot => return fail(sema, arg_src, "invalid operand to arithmetic expression", .{}),
}
},
.constant => |index| {
const local_index = try chunk.getOrPutConstantIndex(index);
_ = try chunk.addConstOp(.load_const, @intCast(local_index));
},
else => {},
}
}
fn analyzeDivertTarget(sema: *Sema, chunk: *Chunk, src: SrcLoc, callee: Ref) !Ref {
switch (callee) {
.global => |global_index| {
const g = sema.ir.globals[global_index];
switch (g.tag) {
.knot => {
const name = try sema.getOrPutStr(g.name);
const local_index = try chunk.getOrPutConstantIndex(name.constant);
return chunk.addConstOp(.load_global, @intCast(local_index));
},
.variable => return fail(sema, src, "invalid divert target", .{}),
}
},
else => unreachable,
}
}
fn irInteger(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref {
const value = sema.ir.instructions[@intFromEnum(inst)].data.int;
return sema.getOrPutConstant(.{ .integer = value });
return sema.getOrPutInt(value);
}
fn irString(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref {
@ -324,11 +310,9 @@ fn irBinaryOp(
}
fn irAlloc(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref {
const local_index = chunk.knot.stack_size;
// TODO: Add constraints on how many temporaries we can have.
// max(u8) or max(u16) are most likey appropriate.
chunk.knot.stack_size += 1;
return .{ .local = local_index };
return .{ .temporary = chunk.addStackSlot() };
}
fn irStore(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
@ -340,14 +324,18 @@ fn irStore(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
.bool_true, .bool_false => unreachable, // TODO: "Cannot assign to boolean"
.none => unreachable,
.constant => |_| unreachable, // TODO: "Cannot assign to constant"
.global => |id| _ = try chunk.addConstOp(.store_global, @intCast(id)),
.local => |id| _ = try chunk.addConstOp(.store, @intCast(id)),
.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,
}
_ = try chunk.addByteOp(.pop);
}
// TODO: Check what the target is!
fn irLoad(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
const lhs = chunk.resolveInst(data.lhs);
@ -366,13 +354,13 @@ fn irCondBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
_ = try chunk.doLoad(condition);
try chunk.addFixup(.jmp_f, else_label);
_ = try chunk.addByteOp(.pop);
try blockBodyInner(sema, chunk, then_body);
try analyzeBodyInner(sema, chunk, then_body);
try chunk.addFixup(.jmp, end_label);
chunk.setLabel(else_label);
_ = try chunk.addByteOp(.pop);
try blockBodyInner(sema, chunk, else_body);
try analyzeBodyInner(sema, chunk, else_body);
chunk.setLabel(end_label);
return .none;
}
@ -386,7 +374,7 @@ fn irBlock(sema: *Sema, chunk: *Chunk, 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 blockBodyInner(sema, chunk, body);
return analyzeBodyInner(sema, chunk, body);
}
fn irSwitchBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
@ -401,10 +389,8 @@ fn irSwitchBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
// TODO: Do something with this value?
//const condition = chunk.resolveInst(extra.data.operand);
const exit_label = try chunk.addLabel();
const cmp_var = chunk.knot.stack_size;
chunk.knot.stack_size += 1;
_ = try chunk.addConstOp(.store, @intCast(cmp_var));
const cmp_var = chunk.addStackSlot();
_ = try chunk.addConstOp(.store, cmp_var);
for (cases_slice) |case_index| {
const case_extra = sema.ir.extraData(Ir.Inst.SwitchBr.Case, @intFromEnum(case_index));
@ -428,7 +414,7 @@ fn irSwitchBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
chunk.setLabel(label_index);
_ = try chunk.addByteOp(.pop);
try blockBodyInner(sema, chunk, case_body);
try analyzeBodyInner(sema, chunk, case_body);
try chunk.addFixup(.jmp, exit_label);
}
@ -438,7 +424,7 @@ fn irSwitchBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
);
chunk.setLabel(else_label);
try blockBodyInner(sema, chunk, else_body);
try analyzeBodyInner(sema, chunk, else_body);
chunk.setLabel(exit_label);
}
@ -515,7 +501,7 @@ fn irChoiceBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
}
_ = try chunk.addByteOp(.stream_flush);
try blockBodyInner(sema, chunk, body_slice);
try analyzeBodyInner(sema, chunk, body_slice);
}
}
@ -523,72 +509,10 @@ fn irImplicitRet(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref {
return chunk.addByteOp(.exit);
}
fn irDeclRef(sema: *Sema, _: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
fn irDeclRef(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
const data = sema.ir.instructions[@intFromEnum(inst)].data.str_tok;
const str = try sema.getOrPutStr(data.start);
if (sema.globals_map.get(str.constant)) |global_index| {
return .{ .global = global_index };
}
return fail(sema, .{ .src_offset = data.src_offset }, "unknown global variable", .{});
}
fn irDeclVar(
sema: *Sema,
chunk: *Chunk,
name: Ir.NullTerminatedString,
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);
try blockBodyInner(sema, chunk, body);
// FIXME: hack
{
const last_inst = body[body.len - 1].toRef();
const val = chunk.resolveInst(last_inst);
_ = try chunk.doLoad(val);
}
const interned_str = try sema.getOrPutStr(name);
_ = try chunk.addConstOp(.store_global, @intCast(interned_str.constant));
_ = try chunk.addByteOp(.pop);
}
fn irDeclKnot(
sema: *Sema,
name_ref: Ir.NullTerminatedString,
inst: Ir.Inst.Index,
) InnerError!void {
const gpa = sema.gpa;
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
const extra = sema.ir.extraData(Ir.Inst.Knot, data.extra_index);
var knot: Compilation.Knot = .{
.name = name_ref,
.arity = 0,
.stack_size = 0,
};
var chunk: Chunk = .{
.sema = sema,
.knot = &knot,
};
defer chunk.deinit(gpa);
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
try blockBodyInner(sema, &chunk, body);
_ = try chunk.addByteOp(.exit);
try chunk.resolveLabels();
try sema.knots.append(gpa, knot);
}
fn irDeclaration(sema: *Sema, parent_chunk: ?*Chunk, inst: Ir.Inst.Index) !void {
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
const extra = sema.ir.extraData(Ir.Inst.Declaration, data.extra_index).data;
const value_data = sema.ir.instructions[@intFromEnum(extra.value)];
switch (value_data.tag) {
.decl_var => try irDeclVar(sema, parent_chunk.?, extra.name, extra.value),
.decl_knot => try irDeclKnot(sema, extra.name, extra.value),
else => unreachable,
}
const decl_name = try sema.getOrPutStr(data.start);
return sema.lookupIdentifier(chunk, decl_name.constant);
}
fn irCall(_: *Sema, _: *Chunk, _: Ir.Inst.Index) !Ref {
@ -608,12 +532,28 @@ fn irDivert(
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
const extra = sema.ir.extraData(ExtraType, data.extra_index);
const body = sema.ir.extra[extra.end..];
const callee = switch (kind) {
.direct => chunk.resolveInst(extra.data.callee),
.field => chunk.resolveInst(extra.data.obj_ptr),
};
const callee_src: SrcLoc = .{ .src_offset = data.src_offset };
_ = try analyzeDivertTarget(sema, chunk, callee_src, callee);
switch (kind) {
.direct => {
const callee = chunk.resolveInst(extra.data.callee);
const callee_src: SrcLoc = .{ .src_offset = data.src_offset };
_ = try analyzeDivertTarget(sema, chunk, callee_src, callee);
},
.field => {
const callee = chunk.resolveInst(extra.data.obj_ptr);
const callee_src: SrcLoc = .{ .src_offset = data.src_offset };
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);
switch (e) {
.knot => |knot| {
const local_index = try chunk.getOrPutConstantIndex(knot.const_index);
_ = try chunk.addConstOp(.load_attr, @intCast(local_index));
},
else => return sema.fail(callee_src, "invalid divert target", .{}),
}
},
}
const args_len = extra.data.args_len;
var arg_start: u32 = args_len;
@ -622,7 +562,7 @@ 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 blockBodyInner(sema, chunk, @ptrCast(arg_body));
try analyzeBodyInner(sema, chunk, @ptrCast(arg_body));
// FIXME: hack
{
const last_inst: Ir.Inst.Index = @enumFromInt(arg_body[arg_body.len - 1]);
@ -639,28 +579,50 @@ fn irFieldPtr(_: *Sema, _: *Chunk, _: Ir.Inst.Index) !Ref {
// TODO: Check for duplicate parameters.
fn irParam(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) !Ref {
//const data = sema.ir.instructions[@intFromEnum(inst)].data.string;
const local_index = chunk.knot.stack_size;
// TODO: Add constraints on how many temporaries we can have.
// max(u8) or max(u16) are most likey appropriate.
chunk.knot.arity += 1;
chunk.knot.stack_size += 1;
return .{ .local = local_index };
return .{ .temporary = chunk.addParameter() };
}
fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) InnerError!void {
const gpa = sema.gpa;
fn analyzeArithmeticArg(
sema: *Sema,
chunk: *Chunk,
arg: Ref,
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));
},
.knot => return fail(sema, arg_src, "invalid operand to arithmetic expression", .{}),
else => unreachable,
}
}
fn analyzeDivertTarget(sema: *Sema, chunk: *Chunk, src: SrcLoc, callee: Ref) !Ref {
switch (callee) {
.knot => |knot| {
const local_index = try chunk.getOrPutConstantIndex(knot.const_index);
return chunk.addConstOp(.load_global, @intCast(local_index));
},
else => return sema.fail(src, "invalid divert target", .{}),
}
}
fn analyzeBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) InnerError!void {
for (body) |inst| {
const data = sema.ir.instructions[@intFromEnum(inst)];
const ref: Ref = switch (data.tag) {
.file => unreachable,
.declaration => {
try irDeclaration(sema, chunk, inst);
continue;
},
.decl_var => unreachable, // handled in declaration()
.decl_knot => unreachable, // handled in declaration()
.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;
@ -720,29 +682,118 @@ fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) Inner
.field_ptr => try irFieldPtr(sema, chunk, inst),
.param => try irParam(sema, chunk, inst),
};
try chunk.inst_map.put(gpa, inst, ref);
try chunk.inst_map.put(sema.gpa, inst, ref);
}
}
pub fn analyzeFile(sema: *Sema, inst: Ir.Inst.Index) InnerError!void {
pub fn analyzeStitch(
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.Block, data.extra_index);
const extra = sema.ir.extraData(Ir.Inst.Stitch, data.extra_index);
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
// FIXME: We are going to get burned by this if we don't formalize it.
// Adding common constants to the constant pool.
const static_constants = &[_]Compilation.Constant{
.{ .integer = 0 },
.{ .integer = 1 },
};
for (static_constants) |sc| {
_ = try sema.getOrPutConstant(sc);
}
try sema.globals_map.ensureUnusedCapacity(sema.gpa, @intCast(sema.ir.globals.len));
for (sema.ir.globals, 0..) |global, global_index| {
const interned = try sema.getOrPutStr(global.name);
sema.globals_map.putAssumeCapacity(interned.constant, @intCast(global_index));
}
for (body) |body_index| try irDeclaration(sema, null, body_index);
try analyzeBodyInner(sema, chunk, body);
}
pub fn analyzeKnot(
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.Knot, data.extra_index);
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
try analyzeBodyInner(sema, chunk, body);
}
fn analyzeNestedDecl(
sema: *Sema,
namespace: *Module.Namespace,
inst: Ir.Inst.Index,
) !void {
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
const extra = sema.ir.extraData(Ir.Inst.Declaration, data.extra_index).data;
const decl = sema.ir.instructions[@intFromEnum(extra.value)];
const decl_name = try sema.module.intern_pool.getOrPutStr(sema.gpa, extra.name);
switch (decl.tag) {
.decl_stitch => {
const child_namespace = try sema.module.createNamespace(namespace);
try namespace.decls.put(sema.arena, decl_name, .{
.tag = .knot,
.decl_inst = extra.value,
.args_count = 0,
.namespace = child_namespace,
});
try sema.module.queueWorkItem(.{
.tag = .stitch,
.decl_name = decl_name,
.inst_index = extra.value,
.namespace = child_namespace,
});
},
else => unreachable,
}
}
pub fn analyzeTopLevelDecl(
sema: *Sema,
namespace: *Module.Namespace,
inst: Ir.Inst.Index,
) !void {
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
const extra = sema.ir.extraData(Ir.Inst.Declaration, data.extra_index).data;
const decl_inst = sema.ir.instructions[@intFromEnum(extra.value)];
const decl_name = try sema.module.intern_pool.getOrPutStr(sema.gpa, extra.name);
switch (decl_inst.tag) {
.decl_var => {
const decl_extra = sema.ir.extraData(Ir.Inst.Var, decl_inst.data.payload.extra_index);
const body = sema.ir.bodySlice(decl_extra.end, decl_extra.data.body_len);
try namespace.decls.put(sema.gpa, decl_name, .{
.tag = .variable,
.namespace = null,
.decl_inst = extra.value,
.args_count = 0,
});
// FIXME: Broken
var chunk: *Chunk = undefined;
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(decl_name)));
_ = try chunk.addByteOp(.pop);
},
.decl_knot => {
const _data = sema.ir.instructions[@intFromEnum(extra.value)].data.payload;
const _extra = sema.ir.extraData(Ir.Inst.Knot, _data.extra_index);
const _body = sema.ir.bodySlice(_extra.end, _extra.data.body_len);
const _stitches = sema.ir.bodySlice(_extra.end + _body.len, _extra.data.stitches_len);
const child_namespace = try sema.module.createNamespace(namespace);
try namespace.decls.put(sema.arena, decl_name, .{
.tag = .knot,
.decl_inst = extra.value,
.args_count = 0,
.namespace = child_namespace,
});
try sema.module.queueWorkItem(.{
.tag = .knot,
.decl_name = decl_name,
.inst_index = extra.value,
.namespace = child_namespace,
});
for (_stitches) |st| {
try analyzeNestedDecl(sema, child_namespace, st);
}
},
else => unreachable,
}
}