1544 lines
52 KiB
Zig
1544 lines
52 KiB
Zig
const std = @import("std");
|
|
const Ast = @import("Ast.zig");
|
|
const Ir = @import("Ir.zig");
|
|
const Story = @import("Story.zig");
|
|
const InternPool = @import("InternPool.zig");
|
|
const compile = @import("compile.zig");
|
|
const Module = compile.Module;
|
|
const assert = std.debug.assert;
|
|
const Sema = @This();
|
|
|
|
gpa: std.mem.Allocator,
|
|
arena: std.mem.Allocator,
|
|
module: *compile.Module,
|
|
ir: Ir,
|
|
inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, ValueInfo) = .empty,
|
|
errors: *std.ArrayListUnmanaged(Module.Error),
|
|
comptime_break_inst: Ir.Inst.Index = undefined,
|
|
|
|
const InnerError = error{
|
|
OutOfMemory,
|
|
AnalysisFail,
|
|
TooManyConstants,
|
|
InvalidJump,
|
|
} || anyerror;
|
|
|
|
pub const ValueInfo = union(enum) {
|
|
none,
|
|
stack,
|
|
value: InternPool.Index,
|
|
variable: InternPool.Index,
|
|
knot: InternPool.Index,
|
|
stitch: InternPool.Index,
|
|
function: InternPool.Index,
|
|
temp: u32,
|
|
};
|
|
|
|
pub const Value = struct {
|
|
ip_index: InternPool.Index,
|
|
|
|
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 Unwrapped = union(enum) {
|
|
nil,
|
|
bool: bool,
|
|
int: i64,
|
|
float: f64,
|
|
str: InternPool.NullTerminatedString,
|
|
|
|
pub fn toFloat(v: Unwrapped) f64 {
|
|
return switch (v) {
|
|
.nil => 0.0,
|
|
.bool => |boolean| @floatFromInt(@intFromBool(boolean)),
|
|
.int => |int| @floatFromInt(int),
|
|
.float => |float| float,
|
|
.str => unreachable,
|
|
};
|
|
}
|
|
|
|
pub fn isTruthy(v: Unwrapped) bool {
|
|
return switch (v) {
|
|
.nil => false,
|
|
.bool => |b| b,
|
|
.int => |int| int != 0,
|
|
.float => |float| float != 0.0,
|
|
.str => true,
|
|
};
|
|
}
|
|
|
|
pub fn coerce(value: Unwrapped) Unwrapped {
|
|
return switch (value) {
|
|
.bool => |boolean| .{ .int = if (boolean) 1 else 0 },
|
|
.int => value,
|
|
.float => value,
|
|
else => unreachable,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn unwrap(value: Value, ip: *InternPool) Unwrapped {
|
|
switch (ip.values.items[@intFromEnum(value.toInterned())]) {
|
|
.bool => |boolean| return .{ .bool = boolean },
|
|
.int => |int| return .{ .int = int },
|
|
.float => |float| return .{ .float = @bitCast(float) },
|
|
.str => |str| return .{ .str = str },
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const SrcLoc = struct {
|
|
src_offset: u32,
|
|
};
|
|
|
|
fn fail(
|
|
sema: *Sema,
|
|
src: SrcLoc,
|
|
comptime format: []const u8,
|
|
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(source_bytes, src.src_offset);
|
|
try sema.errors.append(sema.gpa, .{
|
|
.line = loc.line,
|
|
.column = loc.column,
|
|
.snippet = loc.source_line,
|
|
.message = message,
|
|
});
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn resolveInst(sema: *Sema, ref: Ir.Inst.Ref) ValueInfo {
|
|
if (ref.toIndex()) |index| {
|
|
return sema.inst_map.get(index).?;
|
|
}
|
|
return .{ .value = @enumFromInt(@intFromEnum(ref)) };
|
|
}
|
|
|
|
fn resolveValue(_: *Sema, info: ValueInfo) ?Value {
|
|
return switch (info) {
|
|
.value => |value| .fromInterned(value),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn lookupIdentifier(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
ident: InternPool.Index,
|
|
src: SrcLoc,
|
|
) !Module.Namespace.Decl {
|
|
return sema.lookupInNamespace(builder.namespace, ident, src);
|
|
}
|
|
|
|
pub fn lookupInNamespace(
|
|
sema: *Sema,
|
|
namespace: *Module.Namespace,
|
|
ident: InternPool.Index,
|
|
src: SrcLoc,
|
|
) !Module.Namespace.Decl {
|
|
var scope: ?*Module.Namespace = namespace;
|
|
while (scope) |s| : (scope = s.parent) {
|
|
if (s.decls.get(ident)) |decl| return decl;
|
|
}
|
|
return sema.fail(src, "unknown identifier", .{});
|
|
}
|
|
|
|
pub fn deinit(sema: *Sema) void {
|
|
sema.inst_map.deinit(sema.gpa);
|
|
sema.* = undefined;
|
|
}
|
|
|
|
pub const Block = struct {
|
|
parent_block: ?*Block,
|
|
block_inst: Ir.Inst.Index,
|
|
exit_label: usize,
|
|
};
|
|
|
|
pub const Builder = struct {
|
|
sema: *Sema,
|
|
namespace: *Module.Namespace,
|
|
code: *InternPool.CodeChunk,
|
|
constants_map: std.AutoHashMapUnmanaged(InternPool.Index, u8) = .empty,
|
|
labels: std.ArrayListUnmanaged(Label) = .empty,
|
|
fixups: std.ArrayListUnmanaged(Fixup) = .empty,
|
|
knot_tag: enum {
|
|
knot,
|
|
function,
|
|
},
|
|
|
|
const Label = struct {
|
|
code_offset: usize,
|
|
};
|
|
|
|
const Fixup = struct {
|
|
mode: enum {
|
|
relative,
|
|
absolute,
|
|
},
|
|
label_index: u32,
|
|
code_offset: u32,
|
|
};
|
|
|
|
const dummy_address = 0xffffffff;
|
|
|
|
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(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(builder: *Builder) u8 {
|
|
builder.code.args_count += 1;
|
|
return builder.addStackSlot();
|
|
}
|
|
|
|
fn addByteOp(builder: *Builder, op: Story.Opcode) error{OutOfMemory}!void {
|
|
const gpa = builder.sema.gpa;
|
|
const bytecode = &builder.code.bytecode;
|
|
try bytecode.append(gpa, @intFromEnum(op));
|
|
}
|
|
|
|
fn addConstOp(builder: *Builder, op: Story.Opcode, arg: u8) error{OutOfMemory}!void {
|
|
const gpa = builder.sema.gpa;
|
|
const bytecode = &builder.code.bytecode;
|
|
try bytecode.ensureUnusedCapacity(gpa, 2);
|
|
bytecode.appendAssumeCapacity(@intFromEnum(op));
|
|
bytecode.appendAssumeCapacity(arg);
|
|
}
|
|
|
|
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 @intCast(bytecode.items.len - 2);
|
|
}
|
|
|
|
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 = try builder.addJumpOp(op),
|
|
});
|
|
}
|
|
|
|
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 = try builder.addJumpOp(op),
|
|
});
|
|
}
|
|
|
|
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(builder: *Builder, label_index: usize) void {
|
|
const bytecode = &builder.code.bytecode;
|
|
const code_offset = bytecode.items.len;
|
|
assert(label_index <= builder.labels.items.len);
|
|
|
|
const label_data = &builder.labels.items[label_index];
|
|
label_data.code_offset = code_offset;
|
|
}
|
|
|
|
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 builder.constants_map.put(gpa, index, local_index);
|
|
return local_index;
|
|
}
|
|
|
|
pub fn finalize(builder: *Builder) !void {
|
|
const bytecode = &builder.code.bytecode;
|
|
|
|
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,
|
|
.absolute => label.code_offset,
|
|
};
|
|
if (target_offset >= std.math.maxInt(u16)) {
|
|
std.debug.print("Too much code to jump over!\n", .{});
|
|
return error.InvalidJump;
|
|
}
|
|
assert(bytecode.capacity >= label.code_offset + 2);
|
|
bytecode.items[fixup.code_offset] = @intCast((target_offset >> 8) & 0xff);
|
|
bytecode.items[fixup.code_offset + 1] = @intCast(target_offset & 0xff);
|
|
}
|
|
}
|
|
|
|
fn materialize(self: *Builder, info: ValueInfo) InnerError!void {
|
|
switch (info) {
|
|
.none => unreachable, // caller should never load .none
|
|
.stack => {},
|
|
.value => |index| {
|
|
const local_index = try self.getOrPutConstantIndex(index);
|
|
try self.addConstOp(.load_const, @intCast(local_index));
|
|
},
|
|
.variable,
|
|
.knot,
|
|
.stitch,
|
|
.function,
|
|
=> |index| {
|
|
const local_index = try self.getOrPutConstantIndex(index);
|
|
try self.addConstOp(.load_global, @intCast(local_index));
|
|
},
|
|
.temp => |temp| try self.addConstOp(.load, @intCast(temp)),
|
|
}
|
|
}
|
|
};
|
|
|
|
fn coerceToString(sema: *Sema, value: Value.Unwrapped) !InternPool.Index {
|
|
const ip = &sema.module.intern_pool;
|
|
var scratch_buffer: [64]u8 = undefined;
|
|
const str_index = switch (value) {
|
|
.nil => blk: {
|
|
const interned = try ip.getOrPutString(sema.gpa, "");
|
|
break :blk interned;
|
|
},
|
|
.bool => |bit| blk: {
|
|
const interned = try ip.getOrPutString(sema.gpa, if (bit) "true" else "false");
|
|
break :blk interned;
|
|
},
|
|
.int => |int| blk: {
|
|
const bytes = std.fmt.bufPrint(&scratch_buffer, "{d}", .{int}) catch |err| switch (err) {
|
|
error.NoSpaceLeft => unreachable,
|
|
};
|
|
const interned = try ip.getOrPutString(sema.gpa, bytes);
|
|
break :blk interned;
|
|
},
|
|
.float => |float| blk: {
|
|
if (std.math.isNan(float))
|
|
break :blk try ip.getOrPutString(sema.gpa, "NaN");
|
|
|
|
if (std.math.isInf(float))
|
|
break :blk try ip.getOrPutString(sema.gpa, if (float > 0) "Inf" else "-Inf");
|
|
|
|
var bytes = std.fmt.bufPrint(&scratch_buffer, "{d:.7}", .{float}) catch |err| switch (err) {
|
|
error.NoSpaceLeft => unreachable,
|
|
else => |e| return e,
|
|
};
|
|
if (std.mem.indexOfScalar(u8, bytes, '.')) |dot| {
|
|
var end = bytes.len;
|
|
while (end > dot + 2 and bytes[end - 1] == '0') end -= 1;
|
|
bytes = bytes[0..end];
|
|
}
|
|
break :blk try ip.getOrPutString(sema.gpa, bytes);
|
|
},
|
|
.str => |str| str,
|
|
};
|
|
return ip.getOrPutValue(sema.gpa, .{ .str = str_index });
|
|
}
|
|
|
|
fn foldArith(lhs: Value.Unwrapped, rhs: Value.Unwrapped, op: Story.Opcode) !Value.Unwrapped {
|
|
const l = lhs.coerce();
|
|
const r = rhs.coerce();
|
|
if (l == .int and r == .int) {
|
|
return switch (op) {
|
|
.add => .{ .int = l.int +% r.int },
|
|
.sub => .{ .int = l.int -% r.int },
|
|
.mul => .{ .int = l.int *% r.int },
|
|
.div => if (r.int == 0)
|
|
error.DivisionByZero
|
|
else
|
|
.{ .int = @divTrunc(l.int, r.int) },
|
|
.mod => if (r.int == 0)
|
|
error.DivisionByZero
|
|
else
|
|
.{ .int = @mod(l.int, r.int) },
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
const lf = l.toFloat();
|
|
const rf = r.toFloat();
|
|
return switch (op) {
|
|
.add => .{ .float = lf + rf },
|
|
.sub => .{ .float = lf - rf },
|
|
.mul => .{ .float = lf * rf },
|
|
.div => if (rf == 0.0)
|
|
error.DivisionByZero
|
|
else
|
|
.{ .float = lf / rf },
|
|
.mod => if (rf == 0.0)
|
|
error.DivisionByZero
|
|
else
|
|
.{ .float = @mod(lf, rf) },
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
fn foldCmp(lhs: Value.Unwrapped, rhs: Value.Unwrapped, op: Story.Opcode) !Value.Unwrapped {
|
|
switch (op) {
|
|
.cmp_eq => return .{ .bool = std.meta.eql(lhs, rhs) },
|
|
.cmp_neq => return .{ .bool = !std.meta.eql(lhs, rhs) },
|
|
else => {},
|
|
}
|
|
|
|
const lf = lhs.coerce().toFloat();
|
|
const rf = rhs.coerce().toFloat();
|
|
const result = switch (op) {
|
|
.cmp_lt => lf < rf,
|
|
.cmp_gt => lf > rf,
|
|
.cmp_lte => lf <= rf,
|
|
.cmp_gte => lf >= rf,
|
|
else => unreachable,
|
|
};
|
|
return .{ .bool = result };
|
|
}
|
|
|
|
fn foldConstant(lhs: Value.Unwrapped, rhs: Value.Unwrapped, op: Story.Opcode) !Value.Unwrapped {
|
|
return switch (op) {
|
|
.add,
|
|
.sub,
|
|
.mul,
|
|
.div,
|
|
.mod,
|
|
=> foldArith(lhs, rhs, op),
|
|
.cmp_eq,
|
|
.cmp_neq,
|
|
.cmp_lt,
|
|
.cmp_gt,
|
|
.cmp_lte,
|
|
.cmp_gte,
|
|
=> foldCmp(lhs, rhs, op),
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
fn irInt(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo {
|
|
const value = sema.ir.instructions[@intFromEnum(inst)].data.int;
|
|
const ip_index = try sema.module.intern_pool.getOrPutInt(sema.gpa, value);
|
|
return .{ .value = ip_index };
|
|
}
|
|
|
|
fn irFloat(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo {
|
|
const value = sema.ir.instructions[@intFromEnum(inst)].data.float;
|
|
const ip_index = try sema.module.intern_pool.getOrPutFloat(sema.gpa, value);
|
|
return .{ .value = ip_index };
|
|
}
|
|
|
|
fn irStr(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo {
|
|
const bytes = sema.ir.instructions[@intFromEnum(inst)].data.str.get(sema.ir);
|
|
const ip_index = try sema.addStr(bytes);
|
|
return .{ .value = ip_index };
|
|
}
|
|
|
|
fn addStr(sema: *Sema, bytes: []const u8) InnerError!InternPool.Index {
|
|
const str_value = try sema.module.intern_pool.getOrPutString(sema.gpa, bytes);
|
|
return sema.module.intern_pool.getOrPutValue(sema.gpa, .{
|
|
.str = str_value,
|
|
});
|
|
}
|
|
|
|
fn irStrFormat(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
_: *Block,
|
|
inst: Ir.Inst.Index,
|
|
) InnerError!ValueInfo {
|
|
const ip = &sema.module.intern_pool;
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
|
const extra = sema.ir.extraData(Ir.Inst.MultiOp, data.extra_index);
|
|
const args_slice = sema.ir.refSlice(extra.end, extra.data.operands_len);
|
|
|
|
const StrFragment = union(enum) {
|
|
interned: InternPool.Index,
|
|
unknown: ValueInfo,
|
|
};
|
|
|
|
var all_const = true;
|
|
var scratch: std.ArrayList(StrFragment) = .empty;
|
|
defer scratch.deinit(sema.gpa);
|
|
|
|
for (args_slice) |arg| {
|
|
const arg_inst = sema.resolveInst(arg);
|
|
if (sema.resolveValue(arg_inst)) |arg_info| {
|
|
const str = try sema.coerceToString(arg_info.unwrap(ip));
|
|
try scratch.append(sema.gpa, .{ .interned = str });
|
|
} else {
|
|
all_const = false;
|
|
try scratch.append(sema.gpa, .{ .unknown = arg_inst });
|
|
}
|
|
}
|
|
if (all_const) {
|
|
var buffer: std.ArrayList(u8) = .empty;
|
|
defer buffer.deinit(sema.gpa);
|
|
for (scratch.items) |frag| {
|
|
const t = ip.indexToKey(frag.interned);
|
|
try buffer.appendSlice(sema.gpa, ip.nullTerminatedString(t.str));
|
|
}
|
|
return .{ .value = try sema.addStr(buffer.items) };
|
|
}
|
|
|
|
try builder.addByteOp(.string_builder);
|
|
|
|
var i: usize = 0;
|
|
while (i < scratch.items.len) {
|
|
var buffer: std.ArrayList(u8) = .empty;
|
|
defer buffer.deinit(sema.gpa);
|
|
|
|
while (i < scratch.items.len) : (i += 1) {
|
|
const frag = scratch.items[i];
|
|
if (frag != .interned) break;
|
|
const t = ip.indexToKey(frag.interned);
|
|
try buffer.appendSlice(sema.gpa, ip.nullTerminatedString(t.str));
|
|
}
|
|
if (buffer.items.len > 0) {
|
|
const val = try sema.addStr(buffer.items);
|
|
try builder.materialize(.{ .value = val });
|
|
try builder.addByteOp(.string_append);
|
|
}
|
|
if (i < scratch.items.len) {
|
|
const val = scratch.items[i].unknown;
|
|
try builder.materialize(val);
|
|
try builder.addByteOp(.string_append);
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
try builder.addByteOp(.string_freeze);
|
|
return .stack;
|
|
}
|
|
|
|
fn irUnaryOp(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
inst: Ir.Inst.Index,
|
|
op: Story.Opcode,
|
|
) InnerError!ValueInfo {
|
|
const gpa = sema.gpa;
|
|
const ip = &sema.module.intern_pool;
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
|
|
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_info| {
|
|
switch (lhs_info.unwrap(ip)) {
|
|
.nil => unreachable,
|
|
.bool => |boolean| {
|
|
const new_value = switch (op) {
|
|
.not => !boolean,
|
|
.neg => return error.TypeError,
|
|
else => unreachable,
|
|
};
|
|
return .{ .value = ip.getOrPutBool(new_value) };
|
|
},
|
|
.int => |int| {
|
|
const new_value: i64 = switch (op) {
|
|
.not => if (int > 0) 0 else 1,
|
|
.neg => -int,
|
|
else => unreachable,
|
|
};
|
|
return .{ .value = try ip.getOrPutInt(gpa, new_value) };
|
|
},
|
|
.float => |float| {
|
|
const new_value: f64 = switch (op) {
|
|
.not => if (float > 0.0) 0.0 else 1.0,
|
|
.neg => -float,
|
|
else => unreachable,
|
|
};
|
|
return .{ .value = try ip.getOrPutFloat(gpa, new_value) };
|
|
},
|
|
.str => unreachable,
|
|
}
|
|
}
|
|
|
|
try builder.materialize(lhs);
|
|
try builder.addByteOp(op);
|
|
return .stack;
|
|
}
|
|
|
|
fn irBinaryOp(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
inst: Ir.Inst.Index,
|
|
op: Story.Opcode,
|
|
) InnerError!ValueInfo {
|
|
const gpa = sema.gpa;
|
|
const ip = &sema.module.intern_pool;
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
|
|
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);
|
|
|
|
if (sema.resolveValue(lhs)) |lhs_value| {
|
|
if (sema.resolveValue(rhs)) |rhs_value| {
|
|
const lhs_coerced = lhs_value.unwrap(ip).coerce();
|
|
const rhs_coerced = rhs_value.unwrap(ip).coerce();
|
|
return switch (try foldConstant(lhs_coerced, rhs_coerced, op)) {
|
|
.nil => unreachable,
|
|
.bool => |boolean| .{ .value = ip.getOrPutBool(boolean) },
|
|
.int => |int| .{ .value = try ip.getOrPutInt(gpa, int) },
|
|
.float => |float| .{ .value = try ip.getOrPutFloat(gpa, float) },
|
|
.str => unreachable,
|
|
};
|
|
}
|
|
}
|
|
|
|
try builder.materialize(lhs);
|
|
try builder.materialize(rhs);
|
|
try builder.addByteOp(op);
|
|
return .stack;
|
|
}
|
|
|
|
fn analyzeInlineBody(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
block: *Block,
|
|
body: []const Ir.Inst.Index,
|
|
) !ValueInfo {
|
|
if (sema.analyzeBodyInner(builder, block, body, true)) |_| {
|
|
// TODO: Do something.
|
|
} else |err| switch (err) {
|
|
error.ComptimeBreak => {},
|
|
else => |e| return e,
|
|
}
|
|
const break_inst = sema.ir.instructions[@intFromEnum(sema.comptime_break_inst)];
|
|
const extra = sema.ir.extraData(Ir.Inst.Break, break_inst.data.payload.extra_index).data;
|
|
return sema.resolveInst(extra.operand);
|
|
}
|
|
|
|
fn irLogicalOp(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
parent_block: *Block,
|
|
inst: Ir.Inst.Index,
|
|
is_logical_or: bool,
|
|
) InnerError!ValueInfo {
|
|
const ip = &sema.module.intern_pool;
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
|
const extra = sema.ir.extraData(Ir.Inst.BoolBr, data.extra_index);
|
|
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
|
const lhs = sema.resolveInst(extra.data.lhs);
|
|
|
|
if (sema.resolveValue(lhs)) |lhs_info| {
|
|
const lhs_value = lhs_info.unwrap(ip);
|
|
if (is_logical_or and lhs_value.bool) {
|
|
const value = ip.getOrPutBool(true);
|
|
return .{ .value = value };
|
|
} else if (!is_logical_or and !lhs_value.bool) {
|
|
const value = ip.getOrPutBool(false);
|
|
return .{ .value = value };
|
|
}
|
|
|
|
var block: Block = .{
|
|
.parent_block = parent_block,
|
|
.exit_label = try builder.addLabel(),
|
|
.block_inst = inst,
|
|
};
|
|
|
|
return sema.analyzeInlineBody(builder, &block, body);
|
|
}
|
|
|
|
const else_label = try builder.addLabel();
|
|
try builder.materialize(lhs);
|
|
try builder.addFixup(if (is_logical_or) .jmp_t else .jmp_f, else_label);
|
|
try builder.addByteOp(.pop);
|
|
|
|
var block: Block = .{
|
|
.parent_block = parent_block,
|
|
.exit_label = try builder.addLabel(),
|
|
.block_inst = inst,
|
|
};
|
|
|
|
const rhs = try sema.analyzeInlineBody(builder, &block, body);
|
|
try builder.materialize(rhs);
|
|
builder.setLabel(else_label);
|
|
return .none;
|
|
}
|
|
|
|
fn irDeclRef(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
block: *Block,
|
|
inst: Ir.Inst.Index,
|
|
inline_block: bool,
|
|
) InnerError!ValueInfo {
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.str_tok;
|
|
const decl_name = try sema.addStr(sema.ir.nullTerminatedString(data.start));
|
|
const src_loc: SrcLoc = .{ .src_offset = data.src_offset };
|
|
const ident = try sema.lookupIdentifier(builder, decl_name, src_loc);
|
|
|
|
if (inline_block) {
|
|
switch (ident.tag) {
|
|
.knot, .stitch, .function => unreachable,
|
|
.var_const => return sema.resolveGlobalDecl(builder, block, decl_name, src_loc),
|
|
.var_mut => return sema.fail(
|
|
src_loc,
|
|
"global variable assignments cannot refer to other variables",
|
|
.{},
|
|
),
|
|
}
|
|
} else {
|
|
switch (ident.tag) {
|
|
.knot => return .{ .knot = decl_name },
|
|
.stitch => return .{ .stitch = decl_name },
|
|
.function => return .{ .function = decl_name },
|
|
.var_mut => return .{ .variable = decl_name },
|
|
.var_const => return .{ .variable = decl_name },
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
builder.code.locals_count += 1;
|
|
return .{ .temp = builder.addStackSlot() };
|
|
}
|
|
|
|
fn irStore(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void {
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
|
|
const lhs = sema.resolveInst(data.lhs);
|
|
const rhs = sema.resolveInst(data.rhs);
|
|
const src: SrcLoc = .{ .src_offset = 0 };
|
|
|
|
try builder.materialize(rhs);
|
|
|
|
switch (lhs) {
|
|
.none => unreachable,
|
|
.stack => {},
|
|
.value => |_| return sema.fail(src, "could not assign to constant value", .{}),
|
|
.variable => |index| {
|
|
const local_index = try builder.getOrPutConstantIndex(index);
|
|
try builder.addConstOp(.store_global, @intCast(local_index));
|
|
},
|
|
.temp => |temp| try builder.addConstOp(.store, @intCast(temp)),
|
|
.knot => |_| return sema.fail(src, "could not assign to knot", .{}),
|
|
.stitch => |_| return sema.fail(src, "could not assign to stitch", .{}),
|
|
.function => |_| return sema.fail(src, "could not assign to function", .{}),
|
|
}
|
|
|
|
try builder.addByteOp(.pop);
|
|
}
|
|
|
|
fn irLoad(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!ValueInfo {
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
|
|
const lhs = sema.resolveInst(data.lhs);
|
|
if (lhs == .value) return lhs;
|
|
|
|
try builder.materialize(lhs);
|
|
return .stack;
|
|
}
|
|
|
|
fn irCondBr(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
block: *Block,
|
|
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 builder.addLabel();
|
|
const end_label = try builder.addLabel();
|
|
const condition = sema.resolveInst(extra.data.condition);
|
|
if (condition != .none) try builder.materialize(condition);
|
|
|
|
try builder.addFixup(.jmp_f, else_label);
|
|
try builder.addByteOp(.pop);
|
|
|
|
_ = try analyzeBodyInner(sema, builder, block, then_body, false);
|
|
|
|
try builder.addFixup(.jmp, end_label);
|
|
builder.setLabel(else_label);
|
|
try builder.addByteOp(.pop);
|
|
|
|
_ = try analyzeBodyInner(sema, builder, block, else_body, false);
|
|
builder.setLabel(end_label);
|
|
return .none;
|
|
}
|
|
|
|
fn irBlock(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
parent_block: *Block,
|
|
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);
|
|
|
|
var block: Block = .{
|
|
.parent_block = parent_block,
|
|
.exit_label = try builder.addLabel(),
|
|
.block_inst = inst,
|
|
};
|
|
|
|
_ = try analyzeBodyInner(sema, builder, &block, body, false);
|
|
builder.setLabel(block.exit_label);
|
|
}
|
|
|
|
fn irSwitchBlock(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
parent_block: *Block,
|
|
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.SwitchBr, data.extra_index);
|
|
const cases_slice = sema.ir.bodySlice(extra.end, extra.data.cases_len);
|
|
|
|
var case_labels: std.ArrayListUnmanaged(usize) = .empty;
|
|
try case_labels.ensureUnusedCapacity(gpa, cases_slice.len + 1);
|
|
defer case_labels.deinit(gpa);
|
|
|
|
const condition = sema.resolveInst(extra.data.operand);
|
|
if (condition != .none) try builder.materialize(condition);
|
|
|
|
var switch_block: Block = .{
|
|
.parent_block = parent_block,
|
|
.exit_label = try builder.addLabel(),
|
|
.block_inst = inst,
|
|
};
|
|
|
|
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 = sema.resolveInst(case_extra.data.operand);
|
|
const case_label_index = try builder.addLabel();
|
|
case_labels.appendAssumeCapacity(case_label_index);
|
|
|
|
try builder.addConstOp(.load, @intCast(cmp_var));
|
|
try builder.materialize(case_expr);
|
|
try builder.addByteOp(.cmp_eq);
|
|
try builder.addFixup(.jmp_t, case_label_index);
|
|
try builder.addByteOp(.pop);
|
|
}
|
|
|
|
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);
|
|
|
|
builder.setLabel(label_index);
|
|
try builder.addByteOp(.pop);
|
|
_ = try analyzeBodyInner(sema, builder, &switch_block, case_body, false);
|
|
try builder.addFixup(.jmp, switch_block.exit_label);
|
|
}
|
|
|
|
const else_body = sema.ir.bodySlice(
|
|
extra.end + extra.data.cases_len,
|
|
extra.data.else_body_len,
|
|
);
|
|
|
|
builder.setLabel(else_label);
|
|
_ = try analyzeBodyInner(sema, builder, &switch_block, else_body, false);
|
|
builder.setLabel(switch_block.exit_label);
|
|
}
|
|
|
|
fn irBreak(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
parent_block: *Block,
|
|
inst: Ir.Inst.Index,
|
|
) InnerError!void {
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
|
const extra = sema.ir.extraData(Ir.Inst.Break, data.extra_index).data;
|
|
const target = extra.block_inst;
|
|
|
|
var current_block: ?*Block = parent_block;
|
|
while (current_block) |block| : (current_block = block.parent_block) {
|
|
if (block.block_inst == target) {
|
|
try builder.addFixup(.jmp, block.exit_label);
|
|
return;
|
|
}
|
|
}
|
|
return error.InvalidBreakTarget;
|
|
}
|
|
|
|
fn irContentPush(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void {
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
|
|
const lhs = sema.resolveInst(data.lhs);
|
|
if (lhs == .none) return error.AnalysisFail;
|
|
if (lhs != .stack) try builder.materialize(lhs);
|
|
try builder.addByteOp(.stream_push);
|
|
}
|
|
|
|
fn irChoiceBr(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
block: *Block,
|
|
inst: Ir.Inst.Index,
|
|
) InnerError!void {
|
|
const gpa = sema.gpa;
|
|
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);
|
|
|
|
var branch_labels: std.ArrayListUnmanaged(usize) = .empty;
|
|
try branch_labels.ensureUnusedCapacity(gpa, options_slice.len + 1);
|
|
defer branch_labels.deinit(gpa);
|
|
|
|
for (options_slice) |option_index| {
|
|
const case_extra = sema.ir.extraData(Ir.Inst.ChoiceBr.Case, @intFromEnum(option_index));
|
|
const lhs_slice = sema.ir.bodySlice(
|
|
case_extra.end,
|
|
case_extra.data.lhs_len,
|
|
);
|
|
const mhs_slice = sema.ir.bodySlice(
|
|
case_extra.end + case_extra.data.lhs_len,
|
|
case_extra.data.mhs_len,
|
|
);
|
|
|
|
const case_label = try builder.addLabel();
|
|
branch_labels.appendAssumeCapacity(case_label);
|
|
|
|
try builder.addByteOp(.stream_mark);
|
|
_ = try analyzeBodyInner(sema, builder, block, lhs_slice, false);
|
|
_ = try analyzeBodyInner(sema, builder, block, mhs_slice, false);
|
|
try builder.addFixupAbsolute(.br_push, case_label);
|
|
}
|
|
|
|
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 lhs_slice = sema.ir.bodySlice(
|
|
case_extra.end,
|
|
case_extra.data.lhs_len,
|
|
);
|
|
const rhs_slice = sema.ir.bodySlice(
|
|
case_extra.end +
|
|
case_extra.data.lhs_len +
|
|
case_extra.data.mhs_len,
|
|
case_extra.data.rhs_len,
|
|
);
|
|
const body_slice = sema.ir.bodySlice(
|
|
case_extra.end +
|
|
case_extra.data.lhs_len +
|
|
case_extra.data.mhs_len +
|
|
case_extra.data.rhs_len,
|
|
case_extra.data.body_len,
|
|
);
|
|
|
|
builder.setLabel(label);
|
|
|
|
_ = try analyzeBodyInner(sema, builder, block, lhs_slice, false);
|
|
_ = try analyzeBodyInner(sema, builder, block, rhs_slice, false);
|
|
try builder.addByteOp(.stream_line);
|
|
_ = try analyzeBodyInner(sema, builder, block, body_slice, false);
|
|
}
|
|
}
|
|
|
|
fn irRet(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void {
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
|
|
const lhs = sema.resolveInst(data.lhs);
|
|
if (lhs != .none) {
|
|
try builder.materialize(lhs);
|
|
} else {
|
|
try builder.addByteOp(.stream_glue);
|
|
}
|
|
try builder.addByteOp(.ret);
|
|
}
|
|
|
|
fn irImplicitRet(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void {
|
|
switch (builder.knot_tag) {
|
|
.knot => {
|
|
try builder.addByteOp(.exit);
|
|
},
|
|
.function => {
|
|
try builder.addByteOp(.stream_glue);
|
|
try builder.addByteOp(.nil);
|
|
try builder.addByteOp(.ret);
|
|
},
|
|
}
|
|
}
|
|
|
|
fn irCall(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
block: *Block,
|
|
inst: Ir.Inst.Index,
|
|
comptime kind: enum { direct, field },
|
|
) !ValueInfo {
|
|
const ExtraType = switch (kind) {
|
|
.direct => Ir.Inst.Call,
|
|
.field => Ir.Inst.FieldCall,
|
|
};
|
|
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_src: SrcLoc = .{ .src_offset = data.src_offset };
|
|
switch (kind) {
|
|
.direct => {
|
|
const callee = sema.resolveInst(extra.data.callee);
|
|
_ = try analyzeCallTarget(sema, builder, callee_src, callee);
|
|
},
|
|
.field => {
|
|
const callee = sema.resolveInst(extra.data.obj_ptr);
|
|
const target = try analyzeCallTarget(sema, builder, callee_src, callee);
|
|
const str = try sema.module.intern_pool.getOrPutString(
|
|
sema.gpa,
|
|
extra.data.field_name_start.bytes(sema.ir),
|
|
);
|
|
const ip_index = try sema.module.intern_pool.getOrPutValue(sema.gpa, .{
|
|
.str = str,
|
|
});
|
|
const e = try sema.lookupInNamespace(target.namespace.?, ip_index, callee_src);
|
|
switch (e.tag) {
|
|
.function => {
|
|
const local_index = try builder.getOrPutConstantIndex(ip_index);
|
|
_ = try builder.addConstOp(.load_attr, @intCast(local_index));
|
|
},
|
|
else => return sema.fail(callee_src, "invalid call target", .{}),
|
|
}
|
|
},
|
|
}
|
|
|
|
const args_len = extra.data.args_len;
|
|
var arg_start: u32 = args_len;
|
|
var i: u32 = 0;
|
|
while (i < args_len) : (i += 1) {
|
|
const arg_end = sema.ir.extra[extra.end + i];
|
|
defer arg_start = arg_end;
|
|
const arg_body = body[arg_start..arg_end];
|
|
const arg_value = try sema.analyzeInlineBody(builder, block, @ptrCast(arg_body));
|
|
if (arg_value != .none) try builder.materialize(arg_value);
|
|
}
|
|
try builder.addConstOp(.call, @intCast(args_len));
|
|
return .stack;
|
|
}
|
|
|
|
fn irDivert(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
block: *Block,
|
|
inst: Ir.Inst.Index,
|
|
comptime kind: enum { direct, field },
|
|
) !void {
|
|
const ExtraType = switch (kind) {
|
|
.direct => Ir.Inst.Call,
|
|
.field => Ir.Inst.FieldCall,
|
|
};
|
|
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_src: SrcLoc = .{ .src_offset = data.src_offset };
|
|
|
|
switch (kind) {
|
|
.direct => {
|
|
const callee = sema.resolveInst(extra.data.callee);
|
|
_ = try analyzeDivertTarget(sema, builder, callee_src, callee);
|
|
},
|
|
.field => {
|
|
const callee = sema.resolveInst(extra.data.obj_ptr);
|
|
const target = try analyzeDivertTarget(sema, builder, callee_src, callee);
|
|
const str = try sema.module.intern_pool.getOrPutString(
|
|
sema.gpa,
|
|
extra.data.field_name_start.bytes(sema.ir),
|
|
);
|
|
const ip_index = try sema.module.intern_pool.getOrPutValue(sema.gpa, .{
|
|
.str = str,
|
|
});
|
|
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", .{}),
|
|
}
|
|
},
|
|
}
|
|
|
|
const args_len = extra.data.args_len;
|
|
var arg_start: u32 = args_len;
|
|
var i: u32 = 0;
|
|
while (i < args_len) : (i += 1) {
|
|
const arg_end = sema.ir.extra[extra.end + i];
|
|
defer arg_start = arg_end;
|
|
const arg_body = body[arg_start..arg_end];
|
|
const arg_value = try analyzeInlineBody(sema, builder, block, @ptrCast(arg_body));
|
|
if (arg_value != .none) try builder.materialize(arg_value);
|
|
}
|
|
try builder.addConstOp(.divert, @intCast(args_len));
|
|
}
|
|
|
|
fn irFieldPtr(_: *Sema, _: *Builder, _: Ir.Inst.Index) !ValueInfo {
|
|
return .none;
|
|
}
|
|
|
|
// TODO: Check for duplicate parameters.
|
|
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 .{ .temp = builder.addParameter() };
|
|
}
|
|
|
|
fn irDone(_: *Sema, builder: *Builder, _: Ir.Inst.Index) !ValueInfo {
|
|
try builder.addByteOp(.done);
|
|
return .none;
|
|
}
|
|
|
|
fn irExit(_: *Sema, builder: *Builder, _: Ir.Inst.Index) !ValueInfo {
|
|
try builder.addByteOp(.exit);
|
|
return .none;
|
|
}
|
|
|
|
fn analyzeArithmeticArg(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
arg: ValueInfo,
|
|
arg_src: SrcLoc,
|
|
) !void {
|
|
switch (arg) {
|
|
.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 analyzeCallTarget(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
src: SrcLoc,
|
|
callee: ValueInfo,
|
|
) !Module.Namespace.Decl {
|
|
switch (callee) {
|
|
.function => |ip_index| {
|
|
try builder.materialize(callee);
|
|
return sema.lookupIdentifier(builder, ip_index, src);
|
|
},
|
|
else => return sema.fail(src, "invalid call target", .{}),
|
|
}
|
|
}
|
|
|
|
fn analyzeDivertTarget(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
src: SrcLoc,
|
|
callee: ValueInfo,
|
|
) !Module.Namespace.Decl {
|
|
switch (callee) {
|
|
.knot => |ip_index| {
|
|
try builder.materialize(callee);
|
|
return sema.lookupIdentifier(builder, ip_index, src);
|
|
},
|
|
else => return sema.fail(src, "invalid divert target", .{}),
|
|
}
|
|
}
|
|
|
|
fn analyzeBodyInner(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
block: *Block,
|
|
body: []const Ir.Inst.Index,
|
|
inline_block: bool,
|
|
) InnerError!ValueInfo {
|
|
var result: ValueInfo = .none;
|
|
for (body) |inst| {
|
|
const data = sema.ir.instructions[@intFromEnum(inst)];
|
|
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
|
|
.decl_function => unreachable, // never present inside block bodies
|
|
.alloc => try irAlloc(sema, builder, inst),
|
|
.store => {
|
|
try irStore(sema, builder, inst);
|
|
continue;
|
|
},
|
|
.load => try irLoad(sema, builder, inst),
|
|
.int => try irInt(sema, inst),
|
|
.float => try irFloat(sema, inst),
|
|
.str => try irStr(sema, inst),
|
|
.str_format => try irStrFormat(sema, builder, block, 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),
|
|
.bool_br_and => try irLogicalOp(sema, builder, block, inst, false),
|
|
.bool_br_or => try irLogicalOp(sema, builder, block, inst, true),
|
|
.cmp_eq => try irBinaryOp(sema, builder, inst, .cmp_eq),
|
|
.cmp_neq => try irBinaryOp(sema, builder, inst, .cmp_neq),
|
|
.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, block, inst, inline_block),
|
|
.ret => {
|
|
try irRet(sema, builder, inst);
|
|
continue;
|
|
},
|
|
.implicit_ret => {
|
|
try irImplicitRet(sema, builder, inst);
|
|
continue;
|
|
},
|
|
.condbr => try irCondBr(sema, builder, block, inst),
|
|
.@"break" => {
|
|
try irBreak(sema, builder, block, inst);
|
|
continue;
|
|
},
|
|
.break_inline => {
|
|
sema.comptime_break_inst = inst;
|
|
return error.ComptimeBreak;
|
|
},
|
|
.block => {
|
|
try irBlock(sema, builder, block, inst);
|
|
continue;
|
|
},
|
|
.content_push => {
|
|
try irContentPush(sema, builder, inst);
|
|
continue;
|
|
},
|
|
.content_line => {
|
|
try builder.addByteOp(.stream_line);
|
|
continue;
|
|
},
|
|
.content_glue => {
|
|
try builder.addByteOp(.stream_glue);
|
|
continue;
|
|
},
|
|
.choice_br => {
|
|
try irChoiceBr(sema, builder, block, inst);
|
|
continue;
|
|
},
|
|
.switch_br => {
|
|
try irSwitchBlock(sema, builder, block, inst);
|
|
continue;
|
|
},
|
|
.call => try irCall(sema, builder, block, inst, .direct),
|
|
.field_call => try irCall(sema, builder, block, inst, .field),
|
|
.divert => {
|
|
try irDivert(sema, builder, block, inst, .direct);
|
|
continue;
|
|
},
|
|
.field_divert => {
|
|
try irDivert(sema, builder, block, inst, .field);
|
|
continue;
|
|
},
|
|
.field_ptr => try irFieldPtr(sema, builder, inst),
|
|
.param => try irParam(sema, builder, inst),
|
|
.done => try irDone(sema, builder, inst),
|
|
.exit => try irExit(sema, builder, inst),
|
|
};
|
|
try sema.inst_map.put(sema.gpa, inst, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// TODO: No return allowed.
|
|
pub fn analyzeStitch(
|
|
sema: *Sema,
|
|
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);
|
|
|
|
var block: Block = .{
|
|
.parent_block = null,
|
|
.exit_label = try builder.addLabel(),
|
|
.block_inst = inst,
|
|
};
|
|
_ = try analyzeBodyInner(sema, builder, &block, body, false);
|
|
}
|
|
|
|
// TODO: No diverts allowed.
|
|
pub fn analyzeFunction(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
inst: Ir.Inst.Index,
|
|
) !void {
|
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
|
const extra = sema.ir.extraData(Ir.Inst.Function, data.extra_index);
|
|
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
|
|
|
var block: Block = .{
|
|
.parent_block = null,
|
|
.exit_label = try builder.addLabel(),
|
|
.block_inst = inst,
|
|
};
|
|
_ = try analyzeBodyInner(sema, builder, &block, body, false);
|
|
}
|
|
|
|
// TODO: No return allowed.
|
|
pub fn analyzeKnot(
|
|
sema: *Sema,
|
|
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);
|
|
|
|
var block: Block = .{
|
|
.parent_block = null,
|
|
.exit_label = try builder.addLabel(),
|
|
.block_inst = inst,
|
|
};
|
|
_ = try analyzeBodyInner(sema, builder, &block, body, false);
|
|
}
|
|
|
|
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.getOrPutString(
|
|
sema.gpa,
|
|
sema.ir.nullTerminatedString(extra.name),
|
|
);
|
|
const ip_index = try sema.module.intern_pool.getOrPutValue(sema.gpa, .{ .str = decl_name });
|
|
|
|
switch (decl.tag) {
|
|
.decl_stitch => {
|
|
const child_namespace = try sema.module.createNamespace(namespace);
|
|
try namespace.decls.put(sema.arena, ip_index, .{
|
|
.tag = .knot,
|
|
.decl_inst = extra.value,
|
|
.args_count = 0,
|
|
.namespace = child_namespace,
|
|
});
|
|
try sema.module.queueWorkItem(.{
|
|
.tag = .stitch,
|
|
.decl_name = ip_index,
|
|
.inst_index = extra.value,
|
|
.namespace = child_namespace,
|
|
});
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn scanTopLevelDecl(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_str = try sema.module.intern_pool.getOrPutString(
|
|
sema.gpa,
|
|
sema.ir.nullTerminatedString(extra.name),
|
|
);
|
|
const decl_name = try sema.module.intern_pool.getOrPutValue(sema.gpa, .{ .str = decl_str });
|
|
const src_loc: SrcLoc = .{ .src_offset = data.src_offset };
|
|
|
|
switch (decl_inst.tag) {
|
|
.decl_var => {
|
|
const gop = try namespace.decls.getOrPut(sema.arena, decl_name);
|
|
if (gop.found_existing) {
|
|
return sema.fail(src_loc, "duplicate identifier", .{});
|
|
} else {
|
|
gop.value_ptr.* = .{
|
|
.tag = if (extra.flags == 0x01) .var_const else .var_mut,
|
|
.namespace = null,
|
|
.decl_inst = extra.value,
|
|
.args_count = 0,
|
|
};
|
|
}
|
|
},
|
|
.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);
|
|
const gop = try namespace.decls.getOrPut(sema.arena, decl_name);
|
|
if (gop.found_existing) {
|
|
return sema.fail(src_loc, "duplicate identifier", .{});
|
|
} else {
|
|
gop.value_ptr.* = .{
|
|
.tag = .knot,
|
|
.decl_inst = extra.value,
|
|
// FIXME: This will be necessary for argument count checks.
|
|
.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);
|
|
}
|
|
},
|
|
.decl_stitch => {
|
|
const child_namespace = try sema.module.createNamespace(namespace);
|
|
const gop = try namespace.decls.getOrPut(sema.arena, decl_name);
|
|
if (gop.found_existing) {
|
|
return sema.fail(src_loc, "duplicate identifier", .{});
|
|
} else {
|
|
gop.value_ptr.* = .{
|
|
.tag = .stitch,
|
|
.decl_inst = extra.value,
|
|
// FIXME: This will be necessary for argument count checks.
|
|
.args_count = 0,
|
|
.namespace = child_namespace,
|
|
};
|
|
}
|
|
try sema.module.queueWorkItem(.{
|
|
.tag = .stitch,
|
|
.decl_name = decl_name,
|
|
.inst_index = extra.value,
|
|
.namespace = child_namespace,
|
|
});
|
|
},
|
|
.decl_function => {
|
|
const child_namespace = try sema.module.createNamespace(namespace);
|
|
const gop = try namespace.decls.getOrPut(sema.arena, decl_name);
|
|
if (gop.found_existing) {
|
|
return sema.fail(src_loc, "duplicate identifier", .{});
|
|
} else {
|
|
gop.value_ptr.* = .{
|
|
.tag = .function,
|
|
.decl_inst = extra.value,
|
|
// FIXME: This will be necessary for argument count checks.
|
|
.args_count = 0,
|
|
.namespace = child_namespace,
|
|
};
|
|
}
|
|
try sema.module.queueWorkItem(.{
|
|
.tag = .function,
|
|
.decl_name = decl_name,
|
|
.inst_index = extra.value,
|
|
.namespace = child_namespace,
|
|
});
|
|
},
|
|
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn resolveGlobalDecl(
|
|
sema: *Sema,
|
|
builder: *Builder,
|
|
block: *Block,
|
|
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.analyzeInlineBody(builder, block, body);
|
|
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,
|
|
.knot_tag = .knot,
|
|
};
|
|
defer builder.deinit(gpa);
|
|
|
|
var iter = namespace.decls.iterator();
|
|
while (iter.next()) |entry| {
|
|
const key = entry.key_ptr.*;
|
|
const value = entry.value_ptr.*;
|
|
|
|
var block: Block = .{
|
|
.parent_block = null,
|
|
.block_inst = @enumFromInt(0),
|
|
.exit_label = 0,
|
|
};
|
|
|
|
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, &block, key, src_loc);
|
|
const initial_value = sema.resolveValue(result).?;
|
|
try sema.module.globals.append(gpa, .{
|
|
.key = key,
|
|
.value = initial_value.toInterned(),
|
|
});
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|