ir: analysis of fn instruction
This commit is contained in:
parent
9a2ea5ca42
commit
22e7ca5613
@ -118,7 +118,7 @@ pub const Allocator = struct {
|
|||||||
ptr[n] = sentinel;
|
ptr[n] = sentinel;
|
||||||
return ptr[0..n :sentinel];
|
return ptr[0..n :sentinel];
|
||||||
} else {
|
} else {
|
||||||
return alignedAlloc(Elem, optional_alignment, n);
|
return self.alignedAlloc(Elem, optional_alignment, n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,12 +68,18 @@ pub const Module = struct {
|
|||||||
exports: []Export,
|
exports: []Export,
|
||||||
errors: []ErrorMsg,
|
errors: []ErrorMsg,
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
|
fns: []Fn,
|
||||||
|
|
||||||
pub const Export = struct {
|
pub const Export = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
typed_value: TypedValue,
|
typed_value: TypedValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Fn = struct {
|
||||||
|
analysis_status: enum { in_progress, failure, success },
|
||||||
|
body: []*Inst,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn deinit(self: *Module, allocator: *Allocator) void {
|
pub fn deinit(self: *Module, allocator: *Allocator) void {
|
||||||
allocator.free(self.exports);
|
allocator.free(self.exports);
|
||||||
allocator.free(self.errors);
|
allocator.free(self.errors);
|
||||||
@ -97,12 +103,14 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module {
|
|||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.old_module = &old_module,
|
.old_module = &old_module,
|
||||||
.errors = std.ArrayList(ErrorMsg).init(allocator),
|
.errors = std.ArrayList(ErrorMsg).init(allocator),
|
||||||
.inst_table = std.AutoHashMap(*text.Inst, Analyze.NewInst).init(allocator),
|
.decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator),
|
||||||
.exports = std.ArrayList(Module.Export).init(allocator),
|
.exports = std.ArrayList(Module.Export).init(allocator),
|
||||||
|
.fns = std.ArrayList(Module.Fn).init(allocator),
|
||||||
};
|
};
|
||||||
defer ctx.errors.deinit();
|
defer ctx.errors.deinit();
|
||||||
defer ctx.inst_table.deinit();
|
defer ctx.decl_table.deinit();
|
||||||
defer ctx.exports.deinit();
|
defer ctx.exports.deinit();
|
||||||
|
defer ctx.fns.deinit();
|
||||||
|
|
||||||
ctx.analyzeRoot() catch |err| switch (err) {
|
ctx.analyzeRoot() catch |err| switch (err) {
|
||||||
error.AnalysisFail => {
|
error.AnalysisFail => {
|
||||||
@ -113,6 +121,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module {
|
|||||||
return Module{
|
return Module{
|
||||||
.exports = ctx.exports.toOwnedSlice(),
|
.exports = ctx.exports.toOwnedSlice(),
|
||||||
.errors = ctx.errors.toOwnedSlice(),
|
.errors = ctx.errors.toOwnedSlice(),
|
||||||
|
.fns = ctx.fns.toOwnedSlice(),
|
||||||
.arena = ctx.arena,
|
.arena = ctx.arena,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -122,42 +131,57 @@ const Analyze = struct {
|
|||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
old_module: *const text.Module,
|
old_module: *const text.Module,
|
||||||
errors: std.ArrayList(ErrorMsg),
|
errors: std.ArrayList(ErrorMsg),
|
||||||
inst_table: std.AutoHashMap(*text.Inst, NewInst),
|
decl_table: std.AutoHashMap(*text.Inst, NewDecl),
|
||||||
exports: std.ArrayList(Module.Export),
|
exports: std.ArrayList(Module.Export),
|
||||||
|
fns: std.ArrayList(Module.Fn),
|
||||||
|
|
||||||
const NewInst = struct {
|
const NewDecl = struct {
|
||||||
/// null means a semantic analysis error happened
|
/// null means a semantic analysis error happened
|
||||||
ptr: ?*Inst,
|
ptr: ?*Inst,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NewInst = struct {
|
||||||
|
ptr: *Inst,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Fn = struct {
|
||||||
|
body: std.ArrayList(*Inst),
|
||||||
|
inst_table: std.AutoHashMap(*text.Inst, NewInst),
|
||||||
|
/// Index into Module fns array
|
||||||
|
fn_index: usize,
|
||||||
|
};
|
||||||
|
|
||||||
const InnerError = error{ OutOfMemory, AnalysisFail };
|
const InnerError = error{ OutOfMemory, AnalysisFail };
|
||||||
|
|
||||||
fn analyzeRoot(self: *Analyze) !void {
|
fn analyzeRoot(self: *Analyze) !void {
|
||||||
for (self.old_module.decls) |decl| {
|
for (self.old_module.decls) |decl| {
|
||||||
if (decl.cast(text.Inst.Export)) |export_inst| {
|
if (decl.cast(text.Inst.Export)) |export_inst| {
|
||||||
try analyzeExport(self, export_inst);
|
try analyzeExport(self, null, export_inst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolveInst(self: *Analyze, old_inst: *text.Inst) InnerError!*Inst {
|
fn resolveInst(self: *Analyze, opt_func: ?*Fn, old_inst: *text.Inst) InnerError!*Inst {
|
||||||
if (self.inst_table.get(old_inst)) |kv| {
|
if (opt_func) |func| {
|
||||||
|
const kv = func.inst_table.get(old_inst) orelse return error.AnalysisFail;
|
||||||
|
return kv.value.ptr;
|
||||||
|
} else if (self.decl_table.get(old_inst)) |kv| {
|
||||||
return kv.value.ptr orelse return error.AnalysisFail;
|
return kv.value.ptr orelse return error.AnalysisFail;
|
||||||
} else {
|
} else {
|
||||||
const new_inst = self.analyzeDecl(old_inst) catch |err| switch (err) {
|
const new_inst = self.analyzeInst(old_inst, null) catch |err| switch (err) {
|
||||||
error.AnalysisFail => {
|
error.AnalysisFail => {
|
||||||
try self.inst_table.putNoClobber(old_inst, .{ .ptr = null });
|
try self.decl_table.putNoClobber(old_inst, .{ .ptr = null });
|
||||||
return error.AnalysisFail;
|
return error.AnalysisFail;
|
||||||
},
|
},
|
||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
try self.inst_table.putNoClobber(old_inst, .{ .ptr = new_inst });
|
try self.decl_table.putNoClobber(old_inst, .{ .ptr = new_inst });
|
||||||
return new_inst;
|
return new_inst;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolveInstConst(self: *Analyze, old_inst: *text.Inst) InnerError!TypedValue {
|
fn resolveInstConst(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) InnerError!TypedValue {
|
||||||
const new_inst = try self.resolveInst(old_inst);
|
const new_inst = try self.resolveInst(func, old_inst);
|
||||||
const val = try self.resolveConstValue(new_inst);
|
const val = try self.resolveConstValue(new_inst);
|
||||||
return TypedValue{
|
return TypedValue{
|
||||||
.ty = new_inst.ty,
|
.ty = new_inst.ty,
|
||||||
@ -169,17 +193,25 @@ const Analyze = struct {
|
|||||||
return base.value() orelse return self.fail(base.src, "unable to resolve comptime value", .{});
|
return base.value() orelse return self.fail(base.src, "unable to resolve comptime value", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolveConstString(self: *Analyze, old_inst: *text.Inst) ![]u8 {
|
fn resolveConstString(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) ![]u8 {
|
||||||
const new_inst = try self.resolveInst(old_inst);
|
const new_inst = try self.resolveInst(func, old_inst);
|
||||||
const wanted_type = Type.initTag(.const_slice_u8);
|
const wanted_type = Type.initTag(.const_slice_u8);
|
||||||
const coerced_inst = try self.coerce(wanted_type, new_inst);
|
const coerced_inst = try self.coerce(wanted_type, new_inst);
|
||||||
const val = try self.resolveConstValue(coerced_inst);
|
const val = try self.resolveConstValue(coerced_inst);
|
||||||
return val.toAllocatedBytes(&self.arena.allocator);
|
return val.toAllocatedBytes(&self.arena.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn analyzeExport(self: *Analyze, export_inst: *text.Inst.Export) !void {
|
fn resolveType(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) !Type {
|
||||||
const symbol_name = try self.resolveConstString(export_inst.positionals.symbol_name);
|
const new_inst = try self.resolveInst(func, old_inst);
|
||||||
const typed_value = try self.resolveInstConst(export_inst.positionals.value);
|
const wanted_type = Type.initTag(.@"type");
|
||||||
|
const coerced_inst = try self.coerce(wanted_type, new_inst);
|
||||||
|
const val = try self.resolveConstValue(coerced_inst);
|
||||||
|
return val.toType();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn analyzeExport(self: *Analyze, func: ?*Fn, export_inst: *text.Inst.Export) !void {
|
||||||
|
const symbol_name = try self.resolveConstString(func, export_inst.positionals.symbol_name);
|
||||||
|
const typed_value = try self.resolveInstConst(func, export_inst.positionals.value);
|
||||||
|
|
||||||
switch (typed_value.ty.zigTypeTag()) {
|
switch (typed_value.ty.zigTypeTag()) {
|
||||||
.Fn => {},
|
.Fn => {},
|
||||||
@ -224,7 +256,7 @@ const Analyze = struct {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn analyzeDecl(self: *Analyze, old_inst: *text.Inst) !*Inst {
|
fn analyzeInst(self: *Analyze, old_inst: *text.Inst, opt_func: ?*Fn) InnerError!*Inst {
|
||||||
switch (old_inst.tag) {
|
switch (old_inst.tag) {
|
||||||
.str => {
|
.str => {
|
||||||
// We can use this reference because Inst.Const's Value is arena-allocated.
|
// We can use this reference because Inst.Const's Value is arena-allocated.
|
||||||
@ -239,7 +271,45 @@ const Analyze = struct {
|
|||||||
.as => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
.as => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
||||||
.@"asm" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
.@"asm" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
||||||
.@"unreachable" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
.@"unreachable" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
||||||
.@"fn" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
.@"fn" => {
|
||||||
|
const fn_inst = old_inst.cast(text.Inst.Fn).?;
|
||||||
|
const fn_type = try self.resolveType(opt_func, fn_inst.positionals.fn_type);
|
||||||
|
|
||||||
|
var new_func: Fn = .{
|
||||||
|
.body = std.ArrayList(*Inst).init(self.allocator),
|
||||||
|
.inst_table = std.AutoHashMap(*text.Inst, NewInst).init(self.allocator),
|
||||||
|
.fn_index = self.fns.items.len,
|
||||||
|
};
|
||||||
|
defer new_func.body.deinit();
|
||||||
|
defer new_func.inst_table.deinit();
|
||||||
|
// Don't hang on to a reference to this when analyzing body instructions, since the memory
|
||||||
|
// could become invalid.
|
||||||
|
(try self.fns.addOne()).* = .{
|
||||||
|
.analysis_status = .in_progress,
|
||||||
|
.body = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (fn_inst.positionals.body.instructions) |src_inst| {
|
||||||
|
const new_inst = self.analyzeInst(src_inst, &new_func) catch |err| {
|
||||||
|
self.fns.items[new_func.fn_index].analysis_status = .failure;
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
try new_func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst });
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fns.items[new_func.fn_index] = .{
|
||||||
|
.analysis_status = .success,
|
||||||
|
.body = new_func.body.toOwnedSlice(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const fn_payload = try self.arena.allocator.create(Value.Payload.Function);
|
||||||
|
fn_payload.* = .{ .index = new_func.fn_index };
|
||||||
|
|
||||||
|
return self.constInst(old_inst.src, .{
|
||||||
|
.ty = fn_type,
|
||||||
|
.val = Value.initPayload(&fn_payload.base),
|
||||||
|
});
|
||||||
|
},
|
||||||
.@"export" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
.@"export" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
||||||
.primitive => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
.primitive => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
||||||
.fntype => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
.fntype => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}),
|
||||||
|
@ -100,6 +100,29 @@ pub const Value = extern union {
|
|||||||
unreachable;
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Asserts that the value is representable as a type.
|
||||||
|
pub fn toType(self: Value) Type {
|
||||||
|
return switch (self.tag()) {
|
||||||
|
.ty => self.cast(Payload.Ty).?.ty,
|
||||||
|
|
||||||
|
.void_type => Type.initTag(.@"void"),
|
||||||
|
.noreturn_type => Type.initTag(.@"noreturn"),
|
||||||
|
.bool_type => Type.initTag(.@"bool"),
|
||||||
|
.usize_type => Type.initTag(.@"usize"),
|
||||||
|
|
||||||
|
.void_value,
|
||||||
|
.noreturn_value,
|
||||||
|
.bool_true,
|
||||||
|
.bool_false,
|
||||||
|
.int_u64,
|
||||||
|
.int_i64,
|
||||||
|
.function,
|
||||||
|
.ref,
|
||||||
|
.bytes,
|
||||||
|
=> unreachable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// This type is not copyable since it may contain pointers to its inner data.
|
/// This type is not copyable since it may contain pointers to its inner data.
|
||||||
pub const Payload = struct {
|
pub const Payload = struct {
|
||||||
tag: Tag,
|
tag: Tag,
|
||||||
@ -116,6 +139,8 @@ pub const Value = extern union {
|
|||||||
|
|
||||||
pub const Function = struct {
|
pub const Function = struct {
|
||||||
base: Payload = Payload{ .tag = .function },
|
base: Payload = Payload{ .tag = .function },
|
||||||
|
/// Index into the `fns` array of the `ir.Module`
|
||||||
|
index: usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ArraySentinel0_u8_Type = struct {
|
pub const ArraySentinel0_u8_Type = struct {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user